Java教程

JavaScript 相关面试题目

本文主要是介绍JavaScript 相关面试题目,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JavaScript

为什么javascript是单线程?

如果js是多线程的,在运行时多个线程同时对DOM元素进行操作,那具体以哪个线程为主就是个问题了

HTML5新的标准中允许使用new Worker的方式来开启一个新的线程,去运行一段单独的js文件脚本,但是在这个新线程中严格的要求了可以使用的功能,比如说他只能使用ECMAScript, 不能访问DOM和BOM。这也就限制死了多个线程同时操作DOM元素的可能

数据类型

数据类型有几种?

Number、String、Boolean、Null、undefined、object、symbol(ES6),bigInt(ES11).

原始数据类型:
-  布尔类型:布尔表示一个逻辑实体,可以有两个值:true 和 false。
-  Null 类型:Null 类型只有一个值: null。
-  Undefined 类型:一个没有被赋值的变量会有个默认值 undefined。
-  数字类型:根据 ECMAScript 标准,JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(253 -1) 到 253 -1)。它并没有为整数给出一种特定的类型。除了能够表示浮点数外,还有一些带符号的值:+Infinity,-Infinity 和 NaN (非数值,Not-a-Number)。
-  BigInt 类型:BigInt类型是 JavaScript 中的一个基础的数值类型,可以用任意精度表示整数。使用 BigInt,您可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。
-  String字符串类型:JavaScript的字符串类型用于表示文本数据。它是一组16位的无符号整数值的“元素”。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为0,下一个是索引1,依此类推。字符串的长度是它的元素的数量。
- Symbols符号类型:符号(Symbols)是ECMAScript 第6版新定义的。符号类型是唯一的并且是不可修改的, 并且也可以用来作为Object的key的值(如下). 在某些语言当中也有类似的原子类型(Atoms). 你也可以认为为它们是C里面的枚举类型。
引用数据类型:
- Object对象: 在计算机科学中, 对象是指内存中的可以被标识符引用的一块区域。

栈和对的区别

栈的特性:先进后出,主要为一个线程独享,为这个线程的函数的调用服务的。用于存放返回地址,临时变量而用。栈的内存一般都是由编译器自己来分配释放的,编译器所分配的内存是连续的,当定义一个变量的时候,在当前栈区的尾部来分配心的变量的内存。在windows系统里面栈的大小是2M,在linux系统里面栈的大小是8M,可以使用ulimit-s来设置栈的大小。栈的空间的分配是由高地址向低地址分配的。

堆的分配和释放是由程序员来分配和释放。在windows系统里面一般是小于2G的。因为系统是用链表来实现空闲地址空间的,所以堆的内存空间不是连续的,链表的遍历也是由低地址到高地址的,所以分配内存也是又低地址向高地址分配。

https://blog.csdn.net/choudan8888/article/details/88538704

Object包含了哪几种类型?

其中包含了Date、function、Array等。这三种是常规用的。

JS中typeof 输出分别是什么

{ } 、[ ] 输出 object。

console.log 输出 function。

console.log() 输出undefined

Number(‘as’) == NaN?

虽然 Number(‘as’) 输出 NaN。

但是这的输出是false

因为js规定NaN不等于任何值 包括NaN

需要用isNaN() 去判断是不是NaN

null 和undefined 有什么区别

Null 只有一个值,是 null。不存在的对象。

Undefined 只有一个值,是undefined。没有初始化。undefined 是从 null 中派生出来的。

简单理解就是:undefined 是没有定义的,null 是定义了但是为空。

null不存在的原因是什么?如何解决?

不存在的原因

  1. 方法不存在
  2. 对象不存在
  3. 字符串变量不存在
  4. 接口类型对象没初始化

解决方法: 做判断处理的时候,放在设定值的最前面

如何判断数据类型

  1. typeof

    typeof null  // object
    typeof undefined // undefined
    typeof [] //object
    typeof console.log // function
    typeof console.log() //undefined
    typeof 1 // number
    typeof "1" //string
    
  2. instanceof

    原理 因为A instanceof B 可以判断A是不是B的实例,返回一个布尔值,由构造类型判断出数据类型

    console.log(arr instanceof Array ); // true
    console.log(date instanceof Date ); // true
    console.log(fn instanceof Function ); // true
    //注意: instanceof 后面一定要是对象类型,大小写不能写错,该方法试用一些条件选择或分支
    
  3. .通过Object下的toString.call()方法来判断

    Object.prototype.toString.call();
    console.log(toString.call(123)); //[object Number]
    console.log(toString.call('123')); //[object String]
    console.log(toString.call(undefined)); //[object Undefined]
    console.log(toString.call(true)); //[object Boolean]
    console.log(toString.call({})); //[object Object]
    console.log(toString.call([])); //[object Array]
    console.log(toString.call(function(){})); //[object Function]
    
  4. 根绝对象的constructor判断

    console.log('数据类型判断' -  constructor);
    console.log(arr.constructor === Array); //true
    console.log(date.constructor === Date); //true
    console.log(fn.constructor === Function); //true
    
  5. jQuery的方法 $.isArray()等…

判断数据类型的方式

判断是不是数组

typeof 不可以用来判断数组

typeof 数组 返回object

  1. Array.isArray(arr)
  2. arr.constructor === Array
  3. toString.call(arr).slice(8,-1) === ‘array’ 或toString.call(arr) === ‘[object Array]’
  4. arr instanceof Array
  5. arr.__proto__ === Array.prototype 和instanceof原理上是一样的
  6. jQuery的方法 $.isArray()等…

== 和 ===

==先转换类型再比较,===先判断类型,如果不是同一类型直接为false。
===表示恒等于,比较的两边要绝对的相同

alert(0 == ""); // true
alert(0 == false); // true
alert("" == false); // true

alert(0 === ""); // false
alert(0 === false); // false
alert("" === false); // false

先说 === 严格等于,这个比较简单,具体比较规则如下:

1、如果类型不同,就[不相等]
2、如果两个都是数值,并且是同一个值,那么[相等];(!例外)的是,如果其中至少一个是NaN,那么[不相等]。(判断一个值是否是NaN,只能用isNaN()来判断)
3、如果两个都是字符串,每个位置的字符都一样,那么[相等];否则[不相等]。
4、如果两个值都是true,或者都是false,那么[相等]。
5、如果两个值都引用同一个对象或函数,那么[相等];否则[不相等]。
6、如果两个值都是null,或者都是undefined,那么[相等]。

再说 ==,具体比较规则如下:

1、如果两个值类型相同,进行 === 比较,比较规则同上
2、如果两个值类型不同,他们可能相等。根据下面规则进行类型转换再比较:
a、如果一个是null、一个是undefined,那么[相等]。
b、如果一个是字符串,一个是数值,把字符串转换成数值再进行比较。
c、如果任一值是 true,把它转换成 1 再比较;如果任一值是 false,把它转换成 0 再比较。
d、如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。js核心内置类,会尝试valueOf先于toString;例外的是Date,Date利用的是toString转换。非js核心的对象,令说(比较麻烦,我也不大懂)
e、任何其他组合(array数组等),都[不相等]。

双等号比较规则

如果类型不相同则进行类型转换

  1. 如果一个运算数是Boolean值,在检查相等性之前,把它转换成数字值。false转换成0,true转换成1。
  2. 如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。
  3. 如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串(调用toString()方法)。
  4. 如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。

在进行比较时,该运算符还遵循以下规则

  1. 值null和undefined相等。
  2. 在检查相等性时,不能把null和undefined转换成其他值。
  3. 如果某个运算数是NaN,等号将返回false,非等号将返回true。重要提示:即使两个运算数都是NaN,等号仍然返回false,因为根据规则,NaN不等于NaN。
  4. 如果两个运算数都是对象,那么比较的是它们的引用值。如果两个运算数指向同一个对象,那么等号返回true,否则两个运算数不等。

if语句判断规则

一个值为 true 或者 false 的表达式。如果需要,非 Boolean 表达式也可以被转换为 Boolean 值,但是要遵循下列规则:
所有的对象都被当作 true。
当且仅当字符串为空时,该字符串被当作 false。
null 和 undefined 被当作 false。
当且仅当数字为零时,该数字被当作 false。

undefine和null的区别

null表示没有对象,即该处不应该有值

作为函数的参数,表示该函数的参数不是对象

作为对象原型链的终点

undefined表示缺少值,即此处应该有值,但没有定义

定义了形参,没有传实参,显示undefined

对象属性名不存在时,显示undefined

函数没有写返回值,即没有写return,拿到的是undefined

写了return,但没有赋值,拿到的是undefined

null和undefined转换成number数据类型

null默认转为0

undefined 默认转为 NaN

number 最大范围

绝对值小于 2的53次方

原型链和原型对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3T7QzQm9-1641822732979)(image/20190311194017886.png)]

prototype 和 __proto__ 和 constructor

https://blog.csdn.net/cc18868876837/article/details/81211729 这篇文章讲述的很清除了

原型对象 prototype

所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法。

什么原型链

所有 JavaScript 中的对象都是位于JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。原型链顶端的 Object 的实例。

instanceof

a instanceof A

instanceof 就是可以用来判断一个变量是否是属于某个对象的实例,instanceof检测的是这个变量的原型属于谁。

  • instanceof的判断规则是沿着 Func 的 prototype 属性向上找,如果能找到同一个引用,就返回true,否则返回false。
  • 每个函数都有 prototype 属性(存储的是原型对象),每个原型对象都有 proto (隐式原型)指向它构造函数的原型对象(prototype)。对象没有 prototype 属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZNF7V5J-1641822732980)(image/20210107170912701.jpg)]

  • func._ proto_ 指向 Func.prototype
  • Func._ proto_ 指向 Object.prototype
  • 最终指向Object,也是验证了万物皆对象的思想。因此,func instanceof Func 和 func instanceof Object 的返回结果都是 true,func instanceof Function 的返回结果是 false。

instanceof 操作符其实就是检查左侧的元素的__proto__链上有没有右侧类或对象的prototype存在。
同理 当某某某是某某某的实例时 其实也是证明左侧的__proto__链上有右侧类或对象的prototype存在。

instanceof 的局限性

(1)instanceof无法直接判断原始数据类型

var str1 = 'hello word';
var str2 = new String('hello world');
console.log(str1 instanceof String);		//false
console.log(str2 instanceof String);		//true

我们可以这样来解决:

class PrimitiveString{
	static [Symbol.hasInstance](x){
		return typeof x === 'string'
	}
}
console.log('hello world' instanceof PrimitiveString);		//true

Symbol.hasInstance 可以理解为一个自定义 instanceof 行为的方法,上面代码的功能等于typeof (‘hello world’ === ‘string’)。

(2)instanceof 对于特殊类型无法判断

console.log(new Date() instanceof Date);		//true
console.log(new Date() instanceof Object);		//true
console.log([] instanceof Array);		//true
console.log([] instanceof Object);		//true

instanceof只能用来判断两个对象是否属于实例关系,而不能判断一个是里究竟属于哪种类型。

高能!typeof Function.prototype 引发的先有 Function 还是先有 Object 的探讨

function F(){};
var o = {};
                
typeof F;                          //==> function
typeof o;                          //==> object
typeof F.prototype;                //==> object
typeof o.prototype;                //==> undefinded
typeof new F;                      //==> object
typeof (new F).prototype;          //==> undefined
typeof (new F).__proto__;          //==> object
typeof F.__proto__;                //==> function
typeof o.__proto__;                //==> object
typeof Object;                     //==> function
typeof Function;                   //==> function
typeof (new Function).prototype;   //==> object
typeof (new Function).__proto__;   //==> function
typeof (new Object).prototype;     //==> undefined
typeof (new Object).__proto__;     //==> object
typeof Object.prototype;           //==> object
typeof Object.__proto__;           //==> function
typeof Function.prototype;         //==> function
typeof Function.__proto__;         //==> function	

原文链接

写一下一个空数组的原型链

console.log(typeof [].__proto__)
console.log([].__proto__.__proto__)
console.log([].__proto__.__proto__.__proto__)

Object(0) []
[Object: null prototype] {} 
null

所以空数组的原型链  首先指向 Array.prototype 然后指向 Object.prototype 然后指向null

Promise

js 是单线程的.

Promise是什么

  1. 主要用于异步计算
  2. 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  3. 可以在对象之间传递和操作promise,帮助我们处理队列

Promise

  • promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
  • 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
  • 代码风格,容易理解,便于维护
  • 多个异步等待合并便于解决
new Promise(
  function (resolve, reject) {
    // 一段耗时的异步操作
    resolve('成功') // 数据处理完成
    // reject('失败') // 数据处理出错
  }
).then(
  (res) => {console.log(res)},  // 成功
  (err) => {console.log(err)} // 失败
)
  • promise有三个状态:
    1、pending[待定]初始状态
    2、fulfilled[实现]操作成功
    3、rejected[被否决]操作失败
    当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
    promise状态一经改变,不会再变。
  • resolve作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    reject作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
  • Promise对象的状态改变,只有两种可能:
    从pending变为fulfilled
    从pending变为rejected。
    这两种情况只要发生,状态就凝固了,不会再变了。

结论:promise作为队列最为重要的特性,我们在任何一个地方生成了一个promise队列之后,我们可以把他作为一个变量传递到其他地方。

假如在.then()的函数里面不返回新的promise,会怎样?

then()

1、接收两个函数作为参数,分别代表fulfilled(成功)和rejected(失败)
2、.then()返回一个新的Promise实例,所以它可以链式调用
3、当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
4、状态响应函数可以返回新的promise,或其他值,不返回值也可以我们可以认为它返回了一个null;
5、如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行
6、如果返回其他任何值,则会立即执行下一级.then()

常见用法:

异步操作和定时器放在一起,,如果定时器先触发,就认为超时,告知用户;
例如我们要从远程的服务家在资源如果5000ms还没有加载过来我们就告知用户加载失败

现实中的用法
回调包装成Promise,他有两个显而易见的好处:
1、可读性好
2、返回 的结果可以加入任何Promise队列

https://www.jianshu.com/p/1b63a13c2701

手撕Promise.all

递归实现

function diyPromiseAll(arr) {
    // 这个是保存返回值的数组
    let res_arr = [];
    // 这个是当前遍历到的promise的下标
    let currentPromiseIndx = 0;
    return new Promise((resolve, reject) => {
        // 这里差不多是个递归函数,如果没有遍历到最后一个promise,那么将一致递归到最后一个为止
        function dealPromise(){
            if (currentPromiseIndx === arr.length - 1) {
                arr[currentPromiseIndx].then(res => {
                    res_arr.push(res);
                    resolve(res_arr);
                })
            } else {
                arr[currentPromiseIndx].then(res => {
                    res_arr.push(res);
                    currentPromiseIndx++;
                    dealPromise();
                })
            }
        }
        dealPromise();
    })
}

async和await

async和awati对 reduce和map的影响

https://blog.csdn.net/weixin_33725239/article/details/91390618

事件循环 EventLoop

宏任务和微任务

练习题

  • 题目:

    • 输出以下运行结果
    • 正确输出为:3 3 4 4 5 2 2 7
  • 思路

    • 第一步、输出同步 Promise实例为同步输出
    • 第二部、输出Promise then
    • 第三部、输出setTimeout
  let int = 1;
  setTimeout(function() {
    console.log(int) // 5 第5个输出
    int = 2
    new Promise((resolve, reject) => {
      resolve()
    }).then(function() {
      console.log(int) // 2 第7个输出
      int = 7
    })
    console.log(int) // 2 第6个输出
  })
  int = 3
  console.log(int) // 3 第1个输出
  new Promise((resolve, reject) => {
    console.log(int) // 3 第2个输出
    return resolve(int = 4)
  }).then(function(res) {
    console.log(int) // 4 第4个输出
    int = 5
    setTimeout(function() {
      console.log(int) // 7 第8个输出
      int = 8
    })
    return false
  })

  console.log(int) // 4 第3个输出

ES6

ES6有哪些新特性?

  1. let,const
  2. 箭头函数
  3. 增加了数据类型 symbol
  4. 解构赋值
  5. rest…
  6. 数组添加了新方法 isArray, Array.from
  7. Object.assign
  8. class 关键字
  9. Promise 异步操作
  10. iterable 迭代器
  11. generator 生成器
  12. for…of
  13. Map,Set

var、let、const的区别

  1. var声明的变量会挂载在window上,而let和const声明的变量不会;
var a = 100;
console.log(a, window.a);    // 100 100

let b = 10;
console.log(b, window.b);    // 10 undefined

const c = 1;
console.log(c, window.c);    // 1 undefined
  1. var声明变量存在变量提升,let和const不存在变量提升
console.log(a);    // undefined 变量提升,已声明未赋值
var a = 100;

console.log(b);    // 报错 Uncaught ReferenceError: Cannot access 'a' before initialization
let b = 10;

console.log(c);    // 报错 Uncaught ReferenceError: Cannot access 'a' before initialization
const c = 1;
  1. var没有块级作用域,而let、const拥有块级作用域
if(true) {
    var a = 100;
    let b = 200; 
}
console.log(a); // 100
console.log(b); // 报错 Uncaught ReferenceError: b is not defined
  1. 同一作用域下let和const不能声明同名变量,而var可以
var a = 20;
var a = 30;
console.log(a); // 30
let b = 10;
let b = 5;
console.log(b); // 报错 Uncaught SyntaxError: Identifier 'b' has already been declared
  1. let、const会有暂时性死区,而var不会
var a = 10;
if(true) {
   console.log(a); // 10
   var a = 50;
}
if(true) {
   console.log(a); // 报错 Uncaught ReferenceError: Cannot access 'a' before initialization
   let a = 500;
}
  1. const声明的是常量
    • 一旦声明必须赋值,不能使用null占位。
    • 声明后不能再修改。
    • 如果声明的是引用类型数据,可以修改其属性,但是不可以修改引用。

Array对象

属性

  • constructor 返回创建数组对象的原型函数。
  • length 设置或返回数组元素的个数。
  • prototype 允许你向数组对象添加属性或方法。

1.concat() 连接两个或者更多的数组,并返回结果

concat() 方法用于连接两个或多个数组。

该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2epE6DmO-1641822732981)(image/image-20210331234635891.png)]

2.every 检测数值元素的每个元素是否都符合条件

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。

every() 方法使用指定函数检测数组中的所有元素:

  • 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
  • 如果所有元素都满足条件,则返回 true。

every() 不会改变原始数组。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z0EQF6WX-1641822732983)(image/image-20210331234855910.png)]

3.fill

使用固定值填充数组: 改变原数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gm1zhGjY-1641822732984)(image/image-20210331234930357.png)]

4.filter

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

5.find

6.findIndex

7.forEach()

8.from

9.includes

10.indexOf

11.isArray

12.join

13.map

14.reduce

15.reduceRight

16.push

17.pop

18.reverse

19.slice

20.some

21.splice

22.unshift

23.shift

24.valueOf

https://www.runoob.com/jsref/jsref-obj-array.html

会改变原数组的 (变异方法)

添加元素类:(返回新的长度)

  • push() 把元素添加到数组尾部
  • unshift() 在数组头部添加元素

删除元素类:(返回的是被删除的元素)

  • pop() 移除数组最后一个元素
  • shift() 删除数组第一个元素

颠倒顺序:

  • reverse() 在原数组中颠倒元素的顺序

插入、删除、替换数组元素:(返回被删除的数组)

  • splice(index, howmany, item1…intemx)
    index代表要操作数组位置的索引值,必填
    howmany 代表要删除元素的个数,必须是数字,可以是0,如果没填就是删除从index到数组的结尾
    item1…intemx 代表要添加到数组中的新值

排序

  • sort() 对数组元素进行排序

不改变原数组的

  • concat() 连接两个或更多数组,返回结果
  • every() 检测数组中每个元素是否都符合要求
  • some() 检测数组中是否有元素符合要求
  • filter() 挑选数组中符合条件的并返回符合要求的数组
  • join() 把数组的所有元素放到一个字符串
  • toString() 把数组转成字符串
  • slice() 截取一段数组,返回新数组
  • indexOf 搜索数组中的元素,并返回他所在的位置
  • map
  • reduce

高阶函数

map

map() 方法:原数组中的每个元素调用一个指定方法后,返回返回值组成的新数组

array.map(function(currentValue,index,arr), thisValue)

例子:

有一个数组x=[1, 2, 3, 4, 5, 6, 7, 8, 9],求x^2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qgr4LBU2-1641822732985)(image/image-20210319175300515.png)]

function pow(x){  //定义一个平方函数
    return x*x;
}

var arr=[1,2,3,4,5,6,7,8,9];
var result = arr.map(pow);  //map()传入的是函数对象本身
console.log(result);       //结果:[1,4,9,16,25,36,49,64,81];

reduce

reduce()方法: 为数组中的每一个元素依次执行回调函数(不包括数组中被删除或从未被赋值的元素),返回一个具体的结果。

[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

语法

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • callback (执行数组中每个值的函数,包含四个参数)
    • previousValue (第一项的值或者上一次叠加的结果值,或者是提供的初始值(initialValue))
    • currentValue (数组中当前被处理的元素)
    • index (当前元素在数组中的索引)
    • array (数组本身)
    • initialValue (作为第一次调用 callback 的第一个参数,可以控制返回值的格式)

例子1:数组求和

var  arr = [1, 2, 3, 4, 5];
sum = arr.reduce(function(prev, cur, index, arr) {
    console.log(prev, cur, index);   //输出的是第一项的值或上一次叠加的结果,正在被处理的元素,正在被处理的元素的索引值
    return prev + cur;
})
console.log(arr, sum); //输入数组本身和最后的结果

// 简写
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {  //callback函数只传入了previousValue、currentValue两个参数
    return x + y;
});               // 结果:25,initialValue默认为0

// 传入一个初始值
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
    return x + y;
},10);               // 结果:35,变成了初始值和数组的和

例子2:

不要使用JavaScript内置的parseInt()函数,利用map和reduce操作实现一个string2int()函数
思路:1.先把字符串13579先变成Array——[1, 3, 5, 7, 9]
2.再利用reduce()就可以写出一个把字符串转换为Number的函数。

function string2int(s) {
    var arr = s.split('').map(function(x){
        return +x;
    })
    return arr.reduce(function(prev,res){
       return prev*10+res;
    })
}
// 测试:
if (string2int('0') === 0 && string2int('12345') === 12345 && string2int('12300') === 12300) {
    if (string2int.toString().indexOf('parseInt') !== -1) {
        console.log('请勿使用parseInt()!');
    } else if (string2int.toString().indexOf('Number') !== -1) {
        console.log('请勿使用Number()!');
    } else {
        console.log('测试通过!');
    }
}
else {
    console.log('测试失败!');
}

tips: 字符串前面加+号可快速转为number类型

四种字符串转number的方法

  • 例子3 求一串字符串中每个字母出现的次数

    var arrString = 'abcdaabc';
    arrString.split('').reduce(function(res, cur) {
        res[cur] ? res[cur] ++ : res[cur] = 1
        return res;
    }, {})
    
  • 例子4

    请把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
    输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]。

    function normalize(arr) {
        return arr.map(function(x){
           x=x.toLowerCase();   //注意将x转化成小写之后要重新赋值给x,否则x没有变化
           x=x[0].toUpperCase()+ x.substr(1); //substr(start,length)字符串分割,从start开始,截取length长
           return x;
        })
    }
    // 测试:
    if (normalize(['adam', 'LISA', 'barT']).toString() === ['Adam', 'Lisa', 'Bart'].toString()) {
        console.log('测试通过!');
    }
    else {
        console.log('测试失败!');
    }
    

filter

filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。

map()类似,Arrayfilter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。

  • 例子1: 选出偶数

    var arr = [1, 2, 4, 5, 6, 9, 10, 15];
    var r = arr.filter(function (x) {
        return x % 2 === 0;
    });
    r; // [1, 5, 9, 15]
    

回调函数

filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:

var arr = ['A', 'B', 'C'];
var r = arr.filter(function (element, index, self) {
    console.log(element); // 依次打印'A', 'B', 'C'
    console.log(index); // 依次打印0, 1, 2
    console.log(self); // self就是变量arr
    return true;
});

利用filter,可以巧妙地去除Array的重复元素:

var arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];
var r = arr.filter(function (element, index, self) {
    return self.indexOf(element) === index;
});
console.log(r);

去除重复元素依靠的是indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了。

sort

JavaScript的Arraysort()方法就是用于排序的,但是排序结果可能让你大吃一惊:

// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];

// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']

// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]

第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。

第三个排序结果是什么鬼?简单的数字排序都能错?

这是因为Arraysort()方法默认把所有元素先转换为String再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的ASCII码小。

如果不知道sort()方法的默认排序规则,直接对数字排序,绝对栽进坑里!

幸运的是,sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。

要按数字大小排序,我们可以这么写:

'use strict';

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x < y) {
        return -1;
    }
    if (x > y) {
        return 1;
    }
    return 0;
});
console.log(arr); // [1, 2, 10, 20]

// 倒序
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x < y) {
        return 1;
    }
    if (x > y) {
        return -1;
    }
    return 0;
}); // [20, 10, 2, 1]

//number类型排序重新写
console.log(arr.sort((x, y) => x - y)); // [1, 2, 10, 20]

最后,sort()方法会直接对Array进行修改,它返回的结果仍是当前Array

var a1 = ['B', 'A', 'C'];
var a2 = a1.sort();
a1; // ['A', 'B', 'C']
a2; // ['A', 'B', 'C']
a1 === a2; // true, a1和a2是同一对象

对于数组,除了map()reducefilter()sort()这些方法可以传入一个函数外,Array对象还提供了很多非常实用的高阶函数。

every

every()方法可以判断数组的所有元素是否满足测试条件。

var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
    return s.length > 0;
})); // true, 因为每个元素都满足s.length>0

console.log(arr.every(function (s) {
    return s.toLowerCase() === s;
})); // false, 因为不是每个元素都全部是小写

find

find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined

var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
    return s.toLowerCase() === s;
})); // 'pear', 因为pear全部是小写

console.log(arr.find(function (s) {
    return s.toUpperCase() === s;
})); // undefined, 因为没有全部是大写的元素

findIndex

findIndex()find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1

var arr = ['Apple', 'pear', 'orange'];
console.log(arr.findIndex(function (s) {
    return s.toLowerCase() === s;
})); // 1, 因为'pear'的索引是1

console.log(arr.findIndex(function (s) {
    return s.toUpperCase() === s;
})); // -1

forEach

forEach()map()类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()常用于遍历数组,因此,传入的函数不需要返回值:

var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // 依次打印每个元素

其他

  • JavaScript中findIndex和indexOf的区别

    • indexOf 的第一个参数 expect a value,可以用于原始类型的数组.

      丢进去的是要找的元素,直接找元素。

    • findIndex 的一个参数 expect a callback,可以用于复杂数据类型的数组或者查找条件比一个值要复杂的情况.

      丢进去的是一个函数,找满足函数关系的元素。

函数补充

call,apply

apply()把参数打包成Array再传入;
call()把参数按顺序传入。

Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

利用apply(),我们还可以动态改变函数的行为。

var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // 调用原函数
};

// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3

模拟call实现

思路:

给object对象添加方法fn 运行object.fn时this便指向了object,从而实现了改变this指向

通过arguments获取函数参数

var a = 100
let object = {
  a: 1,
  b: 2
}
function foo (n1, n2) {
  console.log(n1)
  console.log(n2)
  console.log(this.a)
}
Function.prototype.call1 = function (o) {
  let object = o || window
  object.fn = this
  // 获取参数
  let params = []
  for (let i = 1; i < arguments.length; i++) {
 
    params.push(arguments[i])
    // params.push('arguments[' + i + ']')
  }
  object.fn(...params)
  
  // 运行函数
  // eval('object.fn(' + params +')')
}
foo.call1(object,'lisi', 'zhangsan')

arguments

JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array

function foo(x) {
    console.log('x = ' + x); // 10
    for (var i=0; i<arguments.length; i++) {
        console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
    }
}
foo(10, 20, 30);

arguments可以拿到调用者传入的所有参数

callee caller

rest参数 ES6

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo(1);
// 结果:
// a = 1
// b = undefined
// Array []

rest参数只能写在最后,前面用...标识,从运行结果可知,传入的参数先绑定ab,多余的参数以数组形式交给变量rest,所以,不再需要arguments我们就获取了全部参数。

变量提升

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:

function foo() {
    var x = 'Hello, ' + y;
    console.log(x);
    var y = 'Bob';
}

foo();

// 提升后
function foo() {
    var y; // 提升变量y的申明,此时y为undefined
    var x = 'Hello, ' + y;
    console.log(x);
    y = 'Bob';
}

命名空间

全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:

// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

jQuery 就是这么实现的

解构赋值 ES6

var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分别被赋值为数组对应元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);

解构赋值可以忽略某些元素

let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前两个元素,只对z赋值第三个元素
z; // 'ES6'

对象解构赋值可以快速获取对象指定的属性

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, school,age, passport} = person;
// name, age, passport分别被赋值为对应属性:
console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport+ ',school='+ school);

可以对嵌套对象属性进行赋值,要保证对应的层次是一致的:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined

解构赋值还可以使用默认值, 避免不存在属性返回undefined

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

如果一个函数接收一个对象作为参数,那么,可以使用解构直接把对象的属性绑定到变量中。例如,下面的函数可以快速创建一个Date对象:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
    return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

它的方便之处在于传入的对象只需要yearmonthday这三个属性:

buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

也可以传入hourminutesecond属性:

buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

箭头函数 ES6

ES6 新增的一种函数

x => x*x
等价于
function (x) {
	return x*x;
}

箭头函数就相当于是匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }return

x => {
    if (x > 0) {
        return x * x;
    }
    else {
        return - x * x;
    }
}

如果参数不是一个,就需要用括号()括起来:

/ 两个参数:
(x, y) => x * x + y * y

// 无参数:
() => 3.14

// 可变参数:
(x, y, ...rest) => {
    var i, sum = x + y;
    for (i=0; i<rest.length; i++) {
        sum += rest[i];
    }
    return sum;
}

this

箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。

回顾前面的例子,由于JavaScript函数对this绑定的错误处理,下面的例子无法得到预期结果:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
        return fn();
    }
};

现在,箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 25

如果使用箭头函数,以前的那种hack写法:

var that = this; 

就不再需要了

由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:

var obj = {
    birth: 1990,
    getAge: function (year) {
        var b = this.birth; // 1990
        var fn = (y) => y - this.birth; // this.birth仍是1990
        return fn.call({birth:2000}, year);
    }
};
obj.getAge(2015); // 25

generator ES 生成器

这里的生成器和python的差不多

unction foo(x) {
    return x + x;
}

var r = foo(1); // 调用foo函数

函数在执行过程中,如果没有遇到return语句(函数末尾如果没有return,就是隐含的return undefined;),控制权无法交回被调用的代码。

generator跟函数很像,定义如下:

function* foo(x) {
    yield x + 1;
    yield x + 2;
    return x + 3;
}

generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

直接调用一个generator和调用函数不一样,fib(5)仅仅是创建了一个generator对象,还没有去执行它

调用generator对象有两个方法,一是不断地调用generator对象的next()方法:

var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果donetrue,则value就是return的返回值。

当执行到donetrue时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。

第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断done

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

for (var x of fib(10)) {
    console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}

generator和普通函数相比,有什么用?

因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。例如,用一个对象来保存状态,得这么写

var fib = {
    a: 0,
    b: 1,
    n: 0,
    max: 5,
    next: function () {
        var
            r = this.a,
            t = this.a + this.b;
        this.a = this.b;
        this.b = t;
        if (this.n < this.max) {
            this.n ++;
            return r;
        } else {
            return undefined;
        }
    }
};

用对象的属性来保存状态,相当繁琐。

generator还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个好处要等到后面学了AJAX以后才能体会到。

没有generator之前的黑暗时代,用AJAX时需要这么写代码:

ajax('http://url-1', data1, function (err, result) {
    if (err) {
        return handle(err);
    }
    ajax('http://url-2', data2, function (err, result) {
        if (err) {
            return handle(err);
        }
        ajax('http://url-3', data3, function (err, result) {
            if (err) {
                return handle(err);
            }
            return success(result);
        });
    });
});

回调越多,代码越难看。

有了generator的美好时代,用AJAX时可以这么写:

try {
    r1 = yield ajax('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

闭包

只要在某个内部作用域内访问在当前作用域之外定义的变量,就会创建闭包。 它允许你从内部函数访问外部函数的作用域。 在JS中,每次创建函数时都会创建闭包。 要使用闭包,只需在另一个函数内定义一个函数并暴露它。

闭包:是指有权访问另一个函数作用域中的变量的函数。

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

我们来实现一个对Array的求和。通常情况下,求和的函数是这样定义的:

function sum(arr) {
    return arr.reduce(function (x, y) {
        return x + y;
    });
}

sum([1, 2, 3, 4, 5]); // 15

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!

function lazy_sum(arr) {
    var sum = function () {
        return arr.reduce(function (x, y) {
            return x + y;
        });
    }
    return sum;
}

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()

调用函数f时,才真正计算求和的结果:

f(); // 15

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

f1()f2()的调用结果互不影响。

闭包

注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) { // 使用let就好了啊
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。

你可能认为调用f1()f2()f3()结果应该是149,但实际结果是:

f1(); // 16
f2(); // 16
f3(); // 16

全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

注意这里用了一个“创建一个匿名函数并立刻执行”的语法:

(function (x) {
    return x * x;
})(3); // 9

理论上讲,创建一个匿名函数并立刻执行可以这么写:

function (x) { return x * x } (3);

但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:

(function (x) { return x * x }) (3);

通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:

(function (x) {
    return x * x;
})(3);

说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?

当然不是!闭包有非常强大的功能。举个栗子:

在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。

在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:

'use strict';

function create_counter(initial) {
    var x = initial || 0;
    return {
        inc: function () {
            x += 1;
            return x;
        }
    }
}

它用起来像这样:

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。

闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2pow3

'use strict';

function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}
// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

闭包的缺点: 内存泄漏 需要手动释放内存

函数节流和函数防抖

js基础知识补充

  • isNaN(NaN) 来判断类型是不是NaN

  • 浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:

    Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
    
  • null和undefined

  • 字符串 \ 转义字符

  • 多行字符串用反引号 `

  • 模板字符串

    var name = '小明';
    var age = 20;
    console.log(`你好, ${name}, 你今年${age}岁了!`);
    
  • 字符串操作 s=“Hello,world!”

    • 访问问字符串某个指定位置的字符 s[0] 字符串是不可变的,通过s[0] = 1 修改字符串没有用
    • toUpperCase
    • toLowerCase
    • indexOf
    • substring
    • split
  • 数组

    • indexOf

    • slice

    • push

    • pop

    • unshift

    • shift

    • sort 排序有坑

    • reverse 反转

    • splice 修改数组的万能方法

      var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
      // 从索引2开始删除3个元素,然后再添加两个元素:
      arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite']
      arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
      // 只删除,不添加:
      arr.splice(2, 2); // ['Google', 'Facebook']
      arr; // ['Microsoft', 'Apple', 'Oracle']
      // 只添加,不删除:
      arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素
      arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
      
    • concat 连接两个数组放回一个新数组 不修改原数组

    • join 用指定的字符串连接

  • 对象

    • 要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:

      var xiaoming = {
          name: '小明'
      };
      xiaoming.hasOwnProperty('name'); // true
      xiaoming.hasOwnProperty('toString'); // false
      
  • Map和Set ES6
    JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。

    • Map是一组键值对的结构,具有极快的查找速度。

      var m = new Map(); // 空Map
      m.set('Adam', 67); // 添加新的key-value
      m.set('Bob', 59);
      m.has('Adam'); // 是否存在key 'Adam': true
      m.get('Adam'); // 67
      m.delete('Adam'); // 删除key 'Adam'
      m.get('Adam'); // undefined
      
    • set

      var s = new Set([1, 2, 3, 3, '3']);
      s; // Set {1, 2, 3, "3"}
      

      通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:

      通过delete(key)方法可以删除元素:

  • iterable ES6

    ES6标准引入了新的iterable类型,ArrayMapSet都属于iterable类型。

    • 具有iterable类型的集合可以通过新的for ... of循环来遍历。

    • for…in 取的是key

      问题
      var a = ['A', 'B', 'C'];
      a.name = 'Hello';
      for (var x in a) {
          console.log(x); // '0', '1', '2', 'name'
      }
      
    • for…of 遍历自身

    • foreach

      var s = new Set(['A', 'B', 'C']);
      s.forEach(function (element, sameElement, set) {
          console.log(element);
      });	
      

js中监听一个对象中属性的方法

使用构造函数中的方法definePrpperty
obj = {}
Object.defineProperty(obj,‘name’,{
set:function(){
console.log(‘属性发生改变’)
}
})

obj.name=‘值’ //执行set方法进行监听属性

todo 阅读vue源码 diff patch

diff算法

https://blog.csdn.net/qq_43958325/article/details/112315992

https://blog.csdn.net/qq_39414417/article/details/104763824

https://blog.csdn.net/qq2276031/article/details/106407647

https://juejin.cn/post/6844904067370598413#heading-0

https://zhuanlan.zhihu.com/p/248248674

数据绑定

https://segmentfault.com/a/1190000006599500

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0IOnAgJ-1641822732986)(image/bVBQYu)]

https://blog.csdn.net/sinat_27346451/article/details/78315075

这篇关于JavaScript 相关面试题目的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!