1.let

  • let 语句声明一个块级作用域的本地变量,并且可选的将其初始化为一个值。
  • let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,let声明的变量只能是全局或者整个函数块的。
  • let声明的变量只在其声明的块或子块中可用
  • let与var主要的区别在于var声明的变量的作用域是整个封闭函数。

1.语法

1
let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];

2.参数

1
2
3
4
5
var1, var2, …, varN
变量名。必须是合法的标识符。

value1, value2, …, valueN
变量的初始值。可以是任意合法的表达式。

3.示例

1.重复声明

  • 在同一个函数或块作用域中重复声明同一个变量会引起SyntaxError。

    1
    2
    3
    4
    if (x) {
    let foo;
    let foo; // SyntaxError thrown.
    }

    2.switch

  • 因为switch只有一个作用块。所以在一个switch中不可声明同一命名的变量。

  • 可以通过对case后面创建新的块作用域,形成新的词法环境。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // error

    switch(x){
    case 0:
    let foo;
    break;
    case 1:
    let foo; // SyntaxError for redeclaration
    break;
    }

    // success

    switch(x){
    case 0:{
    let foo;
    break;
    }
    case 1:{
    let foo;
    break;
    }
    }

3.暂存死区(暂时性死区(TDZ))

  • let 被创建在包含该声明的(块)作用域顶部,一般被称为“提升”。

  • 通过 var 声明的有初始化值 undefined 的变量不同,通过 let 声明的变量直到它们的定义被执行时才初始化。

  • 在变量初始化前访问该变量会导致ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。

    1
    2
    3
    4
    5
    6
    function do_something() {
    console.log(bar); // undefined
    console.log(foo); // ReferenceError
    var bar = 1;
    let foo = 2;
    }
  • let后跟一个函数传递的参数时将导致循环内部报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 因为let n of n.a 是一个作用域 未声明调用
    function go(n){
    for (let n of n.a) { // ReferenceError: n is not defined
    console.log(n);
    }
    }

    go({a:[1,2,3]});

    // 与之var对比
    function go(n){
    for (var n of n.a) {
    console.log(n); // 1 2 3
    }
    }

    go({a:[1,2,3]});

  • 循环定义中的let作用域

    1
    2
    3
    4
    5
    6
    7
    var i=0;
    for(let i=i;i<10;i++){ // i is not defined
    console.log(i);
    }

    for(let expr1;expr2;expr3) statement
    这个例子中expr1、expr2、expr3、statement都包含再一个隐含域块中。

4.块级作用域

  • 利用块级作用域声明同样名称的变量名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let a='1';
    function a(){
    let a='2';
    console.log(a);//2
    if(true){
    let a='3';
    console.log(a);//3
    }
    }
  • 利用块级作用域声明函数

    • 声明函数相当于let声明方法,所以有块级作用域,可以重复声明,只有在有大括号的情况下可以声明。
    • 在处理构造函数时就可以直接使用let而不是用闭包来创建私有变量
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function func(){
      if(true){
      function a(){
      console.log(1);//1
      }
      a();
      }
      }

4.总结

  • let只在他所在代码块有效。
  • 不存在变量提升,必须先声明后使用
  • 暂时性死区。TDZ 不受外部影响。(函数外与函数内可以声明统一命名的变量)
  • do表达式

2.const

  • 一旦声明变量不允许改变。
  • 并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
  • 对于简单类型的数据,值就是指向的那个内存地址,因此等于常量。
  • 只能保证指针是固定的,至于他指向的数据结构是不是可变的,就不能完全控制了,所以可以用作统计count之类的东西。是没有变量提升的。
  • let的暂存死区同样适用于const
  • 一个常量不能和它所在作用域内的其他变量或函数拥有相同的名称。
    1
    2
    3
    4
    5
    6
    7
    8
    const a={
    count:0
    }
    function b(){
    a.count=a.count+1;
    }

    作用域也是块级作用域。

1.声明变量常用的六种方法

  • ES5

    1
    var function // 两种
  • ES6

    1
    let const import class // 4种

2.顶层的对象的属性

1
2
3
4
浏览器: window对象
Node:global
es5,全局变量就是顶层对象。
es6,全局变量逐步与顶层对象的属性脱钩。

3.Set

  • Set 可以去从但是适用于数组或者类似数组的对象作为参数,去从限制在同类型之间。
  • Set加入值得时候,不会发生类型转换,所以5和’5’是两个不同的值,使用===来判断。
  • 两个对象总是不相等的,因为地址不同。地址为唯一标识。
  • NaN和undefined都可以被存储在Set 中, NaN之间被视为相同的值(尽管 NaN !== NaN)。
  • 由于5和’5’两个不同的值,内部使用Same-value-zero equality算法来实现判断是否是相同的值
1
const s = new Set([1,2,3,4,4]);// 1,2,3,4

1.参数

  • iterable,如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set中。如果不指定此参数或其值为null,则新的 Set为空

2.描述

  • Set对象是值的集合,你可以按照插入的顺序迭代它的元素。 Set中的元素只会出现一次,即 Set 中的元素是唯一的。

3.属性

  • size 成员总数
  • constructor 构造函数

4.方法

  • add 添加某个值,返回Set结构本身。
  • delete 删除某个值,返回bloonlean 是否删除成功
  • has 返回布尔值,表示是否是set的成员。
  • clear 清除所有,无返回值。
  • entries 返回新的迭代器,包含按顺序插入排列的所有元素的[value,value]的值
  • forEach 按照插入插入顺序,为Set对象每个值调用callback
  • keys 返回新的迭代器对象,包含Set对象中的按照插入顺序排列的所有元素的值
  • values 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
  • Set.prototype@@iterator 返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。

5.使用示例

去重

1
Array.from(new Set([1,1,2])); // [1,2]

遍历操作、遍历运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
new Set([1,2]).keys(); // 返回键名的遍历器。
new Set([1,2]).values(); // 返回键名的遍历器。
new Set([1,2]).entries(); // 返回键值对的遍历器
new Set([1,2]).forEach();// 遍历每个成员 Set的遍历顺序就是插入顺序。
// 可以直接使用 for of 循环遍历Set
// ... 运算符可以用于展开set,扩展运算符和Set结构结合,可以去从
let arry=[1,2,3,4,5,5];
let unique=[...new Set(arr)];// [1,2,3,4,5]
[...set].map(x=>x*2); // 返回的数组为每一个都*2 x=>x*2 相当于 (x)=>{return x*2} 以及 (x)=>(x*2) 括号隐式return
[...set].filter(x=>(x % 2)==0);// [1,2,3,4]后面写条件 比如has=>// [2,4]

// 因为是集合所以对于交集、去重、并集、差集很好的去处理
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

6.时间复杂度

  • 数组用来搜索元素的时间复杂度为O(N)。当元素越多时间就越长。
  • Set一般是使用红黑树实现的,实际上V8是使用哈希实现的,查找复杂度为O(1),所以总体时间为O(N);
  • 所以通过多使用Set,可以提高代码的性能

4.Map

1.参数

iterable

  • Iterable 可以是一个数组或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, ‘one’ ],[ 2, ‘two’ ]])
  • 每个键值对都会添加到新的 Map。
  • null 会被当做 undefined。

2.属性

  • Map.length
    1
    属性 length 的值为 0
  • get Map[@@species]
    1
    本构造函数用于创建派生对象。
  • Map.prototype
    1
    表示 Map 构造器的原型。 允许添加属性从而应用于所有的 Map 对象

3.描述

  • 一种更完善Hash结构实现,本质上键值对的集合,时间复杂度则为O(1)。
  • 键的比较是基于 “SameValueZero” 算法:NaN 是与 NaN 相等的(虽然 NaN !== NaN),剩下所有其它的值是根据=== 运算符的结果判断是否相等。
    1
    2
    map.set(NaN,123);
    map.get(NaN);//123
  • 在目前的ECMAScript规范中 +0 -0 会被Map认为同一个键
    1
    2
    map.set(-0,123);
    map.get(+0); // 123
  • 对于同一个值得操作,set两遍会替换之前的值。

Objects和Maps的比较

  • Objects和Maps类似的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值
  • 一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。
  • Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。
  • 你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算
  • Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。虽然 ES5 开始可以用 map = Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见。
  • Map 在涉及频繁增删键值对的场景下会有些性能优势。

4.Map实例

  • 所有的Map对象实例都会继承 Map.prototype

属性

  • Map.prototype.constructor

    1
    返回一个函数,它创建了实例的原型。默认是Map函数
  • Map.prototype.size

    1
    返回Map对象的键/值对的数量。

    5.方法

  • Map.prototype.clear()

    1
    移除Map对象的所有键/值对 。
  • Map.prototype.delete(key)

    1
    如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 false
  • Map.prototype.entries()

    1
    返回一个新的 Iterator 对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。
  • Map.prototype.forEach(callbackFn[, thisArg])

    1
    按插入顺序,为 Map对象里的每一键值对调用一次callbackFn函数。如果为forEach提供了thisArg,它将在每次回调中作为this值。
  • Map.prototype.get(key)

    1
    返回键对应的值,如果不存在,则返回undefined。
  • Map.prototype.has(key)

    1
    返回一个布尔值,表示Map实例是否包含键对应的值。
  • Map.prototype.keys()

    1
    返回一个新的 Iterator对象, 它按插入顺序包含了Map对象中每个元素的键 。
  • Map.prototype.set(key, value)

    1
    设置Map对象中键的值。返回该Map对象。
  • Map.prototype.values()

    1
    返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的值 。
  • Map.prototype@@iterator

    1
    返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。

    6.使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    let m = new Map();
    let o = { p: 'hello world' };
    m.set(o, 'content');
    m.get(o); // "content"
    m.has(o); // true
    m.delete(o); // true
    m.clear(); // 清除所有值
    m.has(o);
  • 当Object作为Key时,有时候因为内存地址的不同导致无法取到数据。只有对同一个对象的引用,Map结构才将其视为同一个键。

1
2
map.set(['a'],'1');
map.get(['a']); // undefined

Map转换数组

1
2
let a = new Map([[1,2],[3,4]])
[...a] // [ [ 1, 2 ], [ 3, 4 ] ]

Map转为对象

1
2
3
4
5
6
let a = new Map([[1,2],[3,4]])
let obj = {};
for (let [k, v] of a) {
obj[k] = v;
}
// { '1': 2, '3': 4 }

对象转为Map

1
2
3
4
5
6
let obj={a:1,b:2}
let a=new Map();
for(let key of Object.keys(obj)){
a.set(key,obj[key])
}
// Map { 'a' => 1, 'b' => 2 }

循环迭代for of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let myMap = new Map([['a', 1], ['b', 2]]);
for (let [key, value] of myMap) {
console.log(`${key}---${value}`);
}
// a---1
// b---2

for (let key of myMap.keys()) {
console.log(key);
}
// a
// b

for (let value of myMap.values()) {
console.log(value);
}
// 1
// 2


for (let [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
// a = 1
// b = 2

forEach 方法迭代

1
2
3
4
5
6
7
let myMap = new Map([['a', 1], ['b', 2]]);
myMap.forEach((value, key) => {
console.log(key + ' = ' + value);
}, myMap);

// a = 1
// b = 2

复制或合并Maps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 复制
let myMap = new Map([['a',1]]);
let cloneMyMap = new Map(myMap);

console.log(myMap===cloneMyMap) // false

// 多个Map可以合并,但是会保持键的唯一性,回和Object一样后面的覆盖前面的

// Maps
let myMap = new Map([[1, 1], [2, 2], [3, 3]]);
let myMap2 = new Map([[1, 2], [4, 5]]);
let mergeMap = new Map([...myMap, ...myMap2]);
// Map { 1 => 2, 2 => 2, 3 => 3, 4 => 5 }
// 也可以和数组合并
mergeMap = new Map([...myMap, ...myMap2,[5,'123']]);
// Map { 1 => 2, 2 => 2, 3 => 3, 4 => 5, 5 => '123' }

// Object
let obj = { a: 1 };
let obj2 = { a: 2, b: 3 };
let mergeObj = { ...obj, ...obj2 };
// { a: 2, b: 3 }

7.Object和Map的应用场景

  • Map适用于存储经常发生增减键值对或经常遍历数据集,而Object适用于存储静态数据集。
  • Map适用于key的类型比较多的情况,而Object的key必须是String或者Symbol。
  • Object适用于需要报错独有的逻辑和属性场景。
  • Map适用于需要保持元素顺序的逻辑。

5.WeakMap

  • WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

1.语法

1
new WeakMap([iterable])

参数

  • iterable,是一个数组(二元数组)或者其他可迭代的且其元素是键值对的对象。每个键值对会被加到新的 WeakMap 里。null 会被当做 undefined。

2.为什么在有了Map的情况下还要加WeakMap

  • 在 JavaScript 里,map API 可以通过使其四个 API 方法共用两个数组(一个存放键,一个存放值)来实现。
  • 给这种 map设置值时会同时将键和值添加到这两个数组的末尾。从而使得键和值的索引在两个数组中相对应。
  • 当从该 map取值的时候,需要遍历所有的键,然后使用索引从存储值的数组中检索出相应的值。
  • 但这样的实现会有两个很大的缺点,首先赋值和搜索操作都是 O(n) 的时间复杂度( n是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。
  • 另外一个缺点是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。
  • 相比之下,原生的 WeakMap 持有的是每个键或值对象的“弱引用”,这意味着在没有其他引用存在时垃圾回收能正确进行。
  • 原生 WeakMap的结构是特殊且有效的,其用于映射的 key 只有在其没有被回收时才是有效的。
  • 正由于这样的弱引用,WeakMap 的 key 是不可枚举的 (没有方法能给出所有的 key)。
  • 如果key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。因此,如果你想要这种类型对象的 key 值的列表,你应该使用 Map。

3.方法

  • WeakMap.prototype.delete(key)
    1
    移除key的关联对象。执行后 WeakMap.prototype.has(key)返回false。
  • WeakMap.prototype.get(key)
    1
    返回key关联对象, 或者 undefined(没有key关联对象时)。
  • WeakMap.prototype.has(key)
    1
    根据是否有key关联对象返回一个Boolean值。
  • WeakMap.prototype.set(key, value)
    1
    在WeakMap中设置一组key关联对象,返回这个 WeakMap对象。
  • WeakMap.prototype.clear()
    1
    2
    3
    4
    5
    从WeakMap中移除所有的 key/value 。 注意,该方法已弃用,但可以通过创建一个空的WeakMap并替换原对象来实现 (参看 WeakMap的后半部分)

    let obj={}
    let myWeakMap= new WeakMap([[obj,1234]]);
    myWeakMap=new WeakMap(); // 这个即可清空

    6.解构赋值

    1.语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let [a, b] = [10, 20];
    console.log(a); // 10
    console.log(b); // 20

    let [a, b, ...rest] = [10, 20, 30, 40, 50];
    console.log(a); // 10
    console.log(b); // 20
    console.log(rest); // [30, 40, 50]

    let { a, b } = { a: 10, b: 20 };
    console.log(a); // 10
    console.log(b); // 20


    let { a, b, ...rest } = { a: 10, b: 20, c: 30, d: 40 };
    console.log(a); // 10
    console.log(b); // 20
    console.log(rest); // {c: 30, d: 40}

    2.描述

  • 对象和数组逐个对应表达式,或称对象字面量和数组字面量,提供了一种简单的定义一个特定的数据组的方法。
  • 解构赋值使用了相同的语法,不同的是在表达式左边定义了要从原变量中取出什么变量。

3.使用方法

1.解构数组

声明变量并赋值时的解构

1
2
3
4
5
6
let foo = ['one','two','three'];

let [one,two,three]=foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"

默认值

  • 为了防止从数组中取出一个值为undefined的对象,可以在表达式左边的数组中为任意对象预设默认值。
1
2
3
let [a = 10, b = 20] = [1];
console.log(a); // 1
console.log(b); // 20

交换变量

  • 在没有解构赋值的情况下一般交换两个变量需要一个中间变量
1
2
3
4
5
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a);// 2
console.log(b);// 1

解析方法的返回值

1
2
3
4
5
6
function f(){
return [1,2]
}
let [a,b]=f();
console.log(a);// 1
console.log(b);// 2

忽略某些返回值

1
2
3
4
5
6
function f(){
return [1,2,3]
}
let [a,,b]=f();
console.log(a); // 1
console.log(b); // 3

剩余的值赋值一个变量

1
2
3
let [a,...b]=[1,2,3]
console.log(a); // 1
console.log(b) // [2,3]

用正则表达式取值

1
2
3
4
5
6
7
8
9
10
11
12
13
function parseProtocol (url) {
let parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
if (!parsedURL) {
return false;
}
console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"]

let [, protocol, fullhost, fullpath] = parsedURL;
return protocol;
}

console.log(parseProtocol('https://developer.mozilla.org/en-US/Web/JavaScript')); // "https"

2.解构对象

基本赋值

1
2
3
let {p, q} = {p: 42, q: true};
console.log(p); // 42
console.log(q); // true

给新的变量名赋值

1
2
3
4
let o = { a: 1, b: 2 };
let { a: aa, b: bb } = o;
console.log('aa:', aa); // 1
console.log('bb:', bb); // 2

默认值

  • 判断条件为undefined
1
2
3
let {a=10,b=5}={a:3}
console.log(a) // 3
console.log(b) // 5

给新的变量命名并提供默认值

1
2
3
let {a:aa=1,b:bb=2}={c:3};
console.log(aa) // 1
console.log(bb)// 2

解构嵌套对象和数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const metadata = {
title: 'Scratchpad',
translations: [
{
title: 'JavaScript-Umgebung',
},
],
url: '/en-US/docs/Tools/Scratchpad',
};

let {
title: englishTitle, // rename
translations: [
{
title: localeTitle, // rename
},
],
} = metadata;

console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"

For of 迭代和解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let people = [
{
name: 'Mike Smith',
family: {
mother: 'Jane Smith',
father: 'Harry Smith',
sister: 'Samantha Smith'
},
age: 35
},
{
name: 'Tom Jones',
family: {
mother: 'Norah Jones',
father: 'Richard Jones',
brother: 'Howard Jones'
},
age: 25
}
];

for (var {name: n, family: {father: f}} of people) {
console.log('Name: ' + n + ', Father: ' + f);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

For of 迭代和解构结合默认使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let people = [
{
name: 'Mike Smith',
family: {
mother: 'Jane Smith',
// father: 'Harry Smith',
sister: 'Samantha Smith',
},
age: 35,
},
{
name: 'Tom Jones',
/* family: {
mother: 'Norah Jones',
father: 'Richard Jones',
brother: 'Howard Jones',
},*/
age: 25,
},
];

for (let { name: n, family: { father: f = null } = {} } of people) {
console.log('Name: ' + n + ', Father: ' + f);
}

// "Name: Mike Smith, Father: Harry Smith"
// "Name: Tom Jones, Father: Richard Jones"

函数实参提取数据

1
2
3
4
5
6
7
8
9
let params = { a: 1, b: 2, c: 2 };

const executeParams = ({ a, b, c }) => {
console.log('a:', a);
console.log('b:', b);
console.log('c:', c);
};
executeParams(params);

对象属性名解构

1
2
3
let key = 'z';
let {[key]:foo}={z:'bar'}
console.log(foo); // bar

对象解构中的Rest

1
2
3
4
let {a,b,...rest}={a:1,b:2,c:3,d:4}
console.log(a); // 1
console.log(b); // 2
console.log(rest); // {c:3,d:4}

7.Symbol

1.概述

  • Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法
  • 静态属性会暴露几个内建的成员对象
  • 不支持new Symbol() (只有不完整的构造函数)
  • Symbol是一种基本类型

2.语法

1
Symbol([description]) // 可选的字符串。symbol的描述,可用于调试但不能访问symbol本身。

3.属性

  • Symbol.length
1
长度属性为0
  • Symbol.prototype
1
描述symbol构造函数的原型

内部方法,代表了内部语言行为

  • 迭代 symbols
1
2
3
Symbol.iterator // 一个返回一个对象默认迭代器的方法。被 for...of 使用。

Symbol.asyncIterator // 一个返回对象默认的异步迭代器的方法。被 for await of 使用
  • 正则表达式 symbols
1
2
3
4
Symbol.match // 一个用于对字符串进行匹配的方法,也用于确定一个对象是否可以作为正则表达式使用。被 String.prototype.match() 使用。
Symbol.replace // 一个替换匹配字符串的子串的方法. 被 String.prototype.replace() 使用。
Symbol.search // 一个返回一个字符串中与正则表达式相匹配的索引的方法。被String.prototype.search() 使用。
Symbol.split // 一个在匹配正则表达式的索引处拆分一个字符串的方法.。被 String.prototype.split() 使用。
  • 其他 symbols
1
2
3
4
5
6
7
Symbol.hasInstance // 一个确定一个构造器对象识别的对象是否为它的实例的方法。被 instanceof 使用。
Symbol.isConcatSpreadable // 一个布尔值,表明一个对象是否应该flattened为它的数组元素。被 Array.prototype.concat() 使用。
Symbol.unscopables // 拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外。
Symbol.species // 一个用于创建派生对象的构造器函数。
Symbol.toPrimitive // 一个将对象转化为基本数据类型的方法。
Symbol.toStringTag // 用于对象的默认描述的字符串值。被 Object.prototype.toString() 使用。
Symbol.description // 获取Symbol([description]) 里面的描述 用于替换toString

4.基本方法

全局共享的Symbol

  • Symbol() 方法创建的symbol类型 不会使用全局的symbol类型
  • 使用Symbol.for() 或者Symbol.keyFor()方法创建跨文件、跨域的全局symbol类型

1.直接使用Symbol() 创建新的symbol类型

1
2
3
4
5
6
const sym1 = Symbol();
const sym2 = Symbol('foo');
const sym3 = Symbol('foo');
// Symbol('foo')不会强制字符串foo成为一个相同的symbol类型,每次都会创建新的。

sym2 === sym3; // false
  • 使用new运算符会报TypeError错误
1
const sym = new Symbol(); // TypeError
  • 这会阻止创建一个显式的 Symbol 包装器对象而不是一个 Symbol 值。
  • 绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持
  • 现有的原始包装器对象,如 new Boolean、new String以及new Number因为遗留原因仍可被创建

2.Symbol.for(key)

  • 使用给定的key(一个字符串,为Symbol的一个描述)搜索现有的symbol(全局注册表),如果找到则返回该symbol。
  • 否则将使用给定的key在全局symbol注册表中创建一个新的symbol。
  • 和 Symbol() 不同的是,用 Symbol.for() 方法创建的的 symbol 会被放入一个全局 symbol 注册表中
  • Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。
  • 如果存在则返回上次存储的,否则创建新的。
1
2
3
4
5
6
7
8
9
10
// 示例代码
Symbol.for('123') === Symbol.for('123') // true
Symbol('123') === Symbol('123'); // false
Symbol.for('123') === Symbol('123'); // false
console.log(Symbol.for('123')) // Symbol(123)

// 为了防止冲突可以对symbol的键加上前缀

Symbol.for('test.demo')
Symbol.for('test.getDemo')

3.Symbol.keyFor(sym)

  • 从全局symbol注册表中,为给定的symbol检索一个共享的 symbol key。
  • 如果全局注册表中查找到该symbol,则返回该symbol的key值,形式为string。如果symbol未在注册表中,返回undefined
1
2
3
4
5
6
7
// 示例代码
let sym = Symbol.for('sym');
Symbol.keyFor(sym); // sym

// 由于没有在全局Symbol注册表里面 所以是undefined
let sym2= Symbol('sym2');
Symbol.keyFor(sym2) // undefined

5.Symbol 原型

  • 所有的Symbol 继承自Symbol.prototype

属性

1
Symbol.prototype.constructor // 返回创建实例原型的函数. 默认为 Symbol 函数。

方法

1
2
3
4
Symbol.prototype.toSource()  // 返回包含Symbol 对象源码的字符串。覆盖Object.prototype.toSource() 方法。
Symbol.prototype.toString() // 返回包含Symbol描述符的字符串。 覆盖Object.prototype.toString() 方法。
Symbol.prototype.valueOf() // 返回 Symbol 对象的初始值.。覆盖 Object.prototype.valueOf() 方法。
Symbol.prototype[@@toPrimitive] // 返回Symbol对象的初始值。

示例

  • 对 symbol 使用 typeof 运算符
1
2
3
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'
  • Symbol类型转换
    • Object(sym) == sym returns true
1
2
const sym = Symbol('sym');
console.log(Object(sym) == sym); // true
    • 其余的转换均报错

Symbols 与 for…in 迭代

  • Symbols 在 for…in 迭代中不可枚举。
  • Object.getOwnPropertyNames() 不会返回 symbol 对象的属性,但是你能使用 Object.getOwnPropertySymbols() 得到它们
1
2
3
4
5
6
7
8
9
10
let obj = {};

obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";

for (var i in obj) {
console.log(i); // logs "c" and "d"
}

Symbols 与 JSON.stringify()

  • 当使用 JSON.strIngify() 时以 symbol 值作为键的属性会被完全忽略
1
JSON.stringify({[Symbol("foo")]: "foo"}); // '{}'

Symbol 包装器对象作为属性的键

  • 当一个 Symbol 包装器对象作为一个属性的键时,这个对象将被强制转换为它包装过的 symbol 值
1
2
3
4
var sym = Symbol("foo");
var obj = {[sym]: 1};
obj[sym]; // 1
obj[Object(sym)]; // still 1

8.class

涉及到原型、原型链问题,请点击此处查看

设计到this相关,请点击此处查看

概述

  • ECMAScript6引入的JavaScript类实质上是JavaScript现有的基于原型的继承的语法糖。类语法不会为JavaScript引入新的面向对象的继承模型。

1.定义类

  • 类实际上是个特殊的函数,就像能够定义的函数表达式和函数声明一养,类语法有两个组成部分:类表达式和类声明。

1.类声明

  • 定义一个类的一种方法是使用一个类声明
  • 类声明不存在变量提升,即先声明后使用。

示例

1
2
3
4
5
class myClass{
constructor(num){
this.num=num;
}
}

constructor方法

  • 一个类必须有constructor方法,new命令生成对象实例时,自动调用该方法。
  • 如果没有显式声明,会被添加默认的constructor方法。
  • 默认会返回实例对象this(即空对象 {})

2.类表达式

  • 类表达式是定义一个类的另一种方式。类表达式可以是被命名和匿名的。
1
2
3
4
5
6
7
8
9
10
11
12
/* 匿名类 */
let myClass = class {
constructor(num){
this.num=num;
}
}
/* 命名的类 */
let myClass2 = class myClass2{
constructor(num){
this.num=num;
}
}

3.类体和方法定义

  • 一个类体是一对花括号/打括号{}中的部分。

1.严格模式

  • 类声明和类表达式的主体都执行在严格模式下。构造函数、静态方法、原型方法、getter、setter都在严格模式瞎执行。

2.构造函数

  • constructor方法是一个特殊的方法,这种方法用于创建和初始化一个由class创建的对象。
  • 一个类只能拥有一个名为constructor的特殊方法。
  • 如果类包含多个constructor的方法,则会抛出SystaxError
  • 一个构造函数可以使用Super关键字来调用一个父类的构造函数。

3.原型方法

  • static关键字用来定义一个类的一个静态方法
  • 调用静态方法不需要实例化该类,但不能通过一个类实例调用静态方法
  • 静态方法通常用于为一个应用程序创建工具函数。

static

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point {
constructor (x, y) {
this.x = x;
this.y = y;
}

static distance (a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2));

4.用原型和静态方法包装

  • 如果没有指定this的值,则是undefined,不会指向全局(遵循严格模式)。

class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Animal {
speak () {
return this;
}

static eat () {
return this;
}
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined

Animal.eat(); // class Animal
let eat = Animal.eat;
eat(); // undefined

传统方式

  • 如果使用传统的基于函数的方式来编写,this会指向到global。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Animal () { }

Animal.prototype.speak = function () {
return this;
};

Animal.eat = function () {
return this;
};

let obj = new Animal();
let speak = obj.speak;
speak(); // global object

let eat = Animal.eat;
eat(); // global object

4.使用extends创建子类、使用super调用超类、静态方法

  • extends关键字在类声明或类表达式中用于创建一个类作为另一个类的子类。
  • 如果想要调用方法父类的方法需要使用super方法(超类)
  • 也可以继承原生构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class A {
constructor (numA, numB) {
this.a = numA;
this.b = numB;
}

speak () {
console.log(this.a, this.b);
}
}

class B extends A {
speak () {
super.speak();
console.log('this.a:', this.a);
console.log('this.b:', this.b);
}
}

(new B(1, 2)).speak();
// 1 2
// this.a:1
// this.a:2

类不能继承常规(非可构造)对象。如果要继承可以使用Object.setPrototypeOf();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let Animal = {
speak () {
console.log(this.name + ' makes a noise.');
},
};

class Dog {
constructor (name) {
this.name = name;
}
}

Object.setPrototypeOf(Dog.prototype, Animal);// If you do not do this you will get a TypeError when you invoke speak

let d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

静态方法也可继承(但是不可以被实例调用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const bar = Symbol('bar');

class A {
constructor () {
this.num = 1;
}

static staticEx () {
console.log('static1');
}

get [bar] () {
return this.getBar();
}

setBar = () => {
return this.getBar();
};

getBar () {
return 123;
}
}

class B extends A {
getBar () {
this.num = 2;
console.log('this.num:', this.num);
return super.getBar();
}
}

B.staticEx(); // static1

如果子类也需要构造函数,需要使用super方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class B extends A {
constructor () {
super();
this.x = 'x';
}

getBar () {
return super.getBar();
}

getX () {
return this.x;
}
}

console.log((new B()).getX()); // x

类的静态属性和实例属性(可不用在构造函数中定义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 实例属性类似于在构造函数中声明,即可继承
// 静态属性也可继承

const bar = Symbol('bar');

class A {
static num3 = 16;

constructor () {
this.num = 1;
}

static staticEx () {
console.log('static1');
}
}

class B extends A {
num2 = 12;
static num2 = 13;

constructor (num) {
super();
}

getNum2 () {
return this.num2;
}

}

console.log(B.num3); // 16
console.log(B.num2); // 13
console.log((new B()).getNum2()); // 12

5.Species

  • 你可能希望在派生数组类 MyArray 中返回 Array对象。这种 species 方式允许你覆盖默认的构造函数。

例如,当使用像map()返回默认构造函数的方法时,您希望这些方法返回一个父Array对象,而不是MyArray对象。Symbol.species 符号可以让你这样做:

1
2
3
4
5
6
7
8
9
10
11
class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get [Symbol.species] () { return Array; }
}

let a = new MyArray(1, 2, 3);
let mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true

6.Mix-ins(多重继承)

  • 抽象子类或者 mix-ins 是类的模板。
  • 一个 ECMAScript 类只能有一个单超类,所以想要从工具类来多重继承的行为是不可能的。
  • 子类继承的只能是父类提供的功能性。因此,例如,从工具类的多重继承是不可能的。该功能必须由超类提供。
  • 以超类作为输入的函数和一个继承该超类的子类作为输出可以用于在ECMAScript中实现混合
1
2
3
4
5
6
7
8
9
10
11
var calculatorMixin = Base => class extends Base {
calc() { }
};

var randomizerMixin = Base => class extends Base {
randomize() { }
};

class Foo {}

class Bar extends calculatorMixin(randomizerMixin(Foo)) {}

7.this的指向

  • 类的方法内部如果含有this,它默认指向类的实例。如果单独使用就出现报错

bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const bar = Symbol('bar');

class A {
constructor () {

}

get [bar] () {
return this.getBar();
}

setBar () {
return this.getBar();
}

getBar () {
return 123;
}
}

let newA = new A();
let { setBar } = newA;
console.log('setBar:', setBar());
// 可以在构造函数bind this
constructor () {
this.setBar = this.setBar.bind(this);
}

箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const bar = Symbol('bar');

class A {
constructor () {
// this.setBar = this.setBar.bind(this);
}

get [bar] () {
return this.getBar();
}

setBar = () => {
return this.getBar();
};

getBar () {
return 123;
}
}

let newA = new A();
let { setBar } = newA;
console.log('setBar:', setBar());

proxy

  • todo 利用proxy实现

8.私有方法、私有变量

利用Symbol值的唯一性,创建私有变量和私有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const bar = Symbol('bar');
const barNum = Symbol('barNum');

class A {
[barNum] = 123;

constructor () {

}

// 私有方法
[bar] () {
console.log('bar:', 123);
}

getBar () {
this[bar]();
}
}

console.log(new A());

利用新特性 #

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
#num = 123;
static #num2 = 1234;

#privateFun = () => {
return this.#num;
};

getBar = () => {
return this.#privateFun();
};

getPrivateField () {
return A.#num2;
}
}

console.log((new A()).getPrivateField()); // 1234
console.log((new A()).getBar()); // 123

利用WeakMap、Map

  • 键值对格式
  • WeakMap的key必须为引用。
  • Map也可以,但是必须也使用引用类型的。
  • 利用内存地址的唯一性来实现私有方法
  • 只要不暴露唯一引用即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const wm = new WeakMap();

class A {
constructor () {
wm.set(this, '这里是私有变量');
}

getPrivateField () {
return wm.get(this);
}
}

console.log(new A()); // A {}
console.log((new A()).getPrivateField());// 这里是私有变量

9.async await

1.async function

涉及到微任务、宏任务以及执行顺序,请点此查看

1.概述

  • async function 声明用于定义一个返回AsyncFunction对象的异步行数。
  • 当调用一个 async 函数时,会返回一个 Promise 对象(从代码执行的角度来说,其实是先返回了AsyncFunction,然后里面返回一个Promise,可以结合上面那个链接里面的面试题理解)
  • 当这个 async 函数返回一个值时,Promise 的 resolve 方法会负责传递这个值
  • 当 async 函数抛出异常时,Promise 的 reject 方法也会传递这个异常值。
  • async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复async函数的执行并返回解析值(resolved)。
  • await 关键字仅仅在 async function中有效。如果在 async function函数体外使用 await ,会抛出错误(SyntaxError)。
  • async/await的目的是简化使用多个 promise 时的同步行为,并对一组 Promises执行某些操作。
  • Promises类似于结构化回调,async/await类似于组合生成器和 promises。
  • async function 是Generator函数的语法糖,内置了执行器。

2.语法

1
async function name([param[,param[,...]]])

3.参数

  • name 函数名称
  • param 要传递给函数的参数的名称
  • statements 函数体语句

返回值

  • 一个返回的Promise对象会以async function的返回值进行解析(resolved),或者以该函数抛出异常(rejected)

4.代码示例

  • async函数可以使用箭头函数和普通的funciton。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const returnNum1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
});
});
};

const returnNum2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
});
});
};

const getNum = async () => {
let num1 = await returnNum1();
let num2 = await returnNum2();
return { num1, num2 };
};
getNum().then(v => {
console.log('v:', v); // v: { num1: 1, num2: 2 }
}).catch(e => {
console.log('e:', e);
});

  • class中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function foo () {

}

class A {
constructor () {
this.b = 1;
}

async get () {
return await this.b;
}
}

(new A()).get().then(v => {
console.log(v);
})
  • 如果在async方法中使用catch则外层catch中不会捕获到错误。并且程序会继续往下执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const get = async () => {
await getResolve().catch(e => {
console.log('e:', e); // e:123
});
return await 456;
};
const getResolve = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(123);
}, 1000);
});
};
get().then(v => {
console.log('v:', v); // v: 456
}).catch(e => {
console.log('e2:', e);
});
// e: 123
// v: 456
  • async function可以让try catch捕获
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const get = async () => {
await (new Promise((resolve, reject) => {
setTimeout(() => {
reject('error');
});
}));
};
(async function () {
try {
await get();
} catch (e) {
console.log('e:', e);
}
}());

2.await

1.概述

  • await 操作符用于等待一个Promise对象
  • 它只能在async function中使用
  • await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成
  • 若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。
  • 若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
  • 如果await后面不是一个Promise对象,则返回本身。

2.语法

1
[return_value] = await expression;
  • 表达式,一个Promise对象或者任何要等待的值
  • 返回值,返回Promise对象的处理结果。如果返回值不是Promise对象,则返回本身。

Promise实现的原理(手写promise)

Promise A+规范

10.Promise

1.Promise 构造函数

1.语法

1
new Promise( function(resolve, reject) {...} /* executor */  );
  • 参数 executor
  • executor是带有 resolve 和 reject 两个参数的函数
  • Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)
  • resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)
  • executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected
  • 如果在executor函数中抛出一个错误,那么该promise 状态为rejected
  • executor函数的返回值被忽略。

2.描述

  • Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。
  • 允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)
  • 一个 Promise有以下几种状态:
    • pending: 初始状态,既不是成功,也不是失败状态
    • fulfilled: 意味着操作成功完成。
    • rejected: 意味着操作失败。
  • pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。
  • 当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。
  • 当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。

3.属性

  • Promise.length
1
length属性,其值总是为 1 (构造器参数的数目).
  • Promise.prototype
1
表示 Promise 构造器的原型.

4.方法

1.Promise.all(iterable)

iterable 一个可迭代对象,如Array或String

  • 返回一个新的promise对象
  • 当iterable参数对象里所有的promise对象都成功时才会触发成功。
  • 如果其中一个发生错误,则直接触发promise对象的失败状态,并返回触发错误的promise的错误信息,而不管其他promise是否完成。
  • 成功后,会返回一个包含iterable里所有的promise返回值的数组。
  • 顺序跟iterable的顺序保持一致。
  • 如果传入空,则直接同步返回已完成(already resolved)状态
  • 如果传入非promise,则返回异步完成(asynchronously resolved)(还是一个pending状态)
  • 其余情况贼返回一个处理中(pending)状态。
  • 任何情况下返回的promise的完成状态的结果都是一个数组

示例

  • 等待所有完成,如果是非promise的值则忽略,当仍然会被返回到数组中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const p1 = new Promise((resolve) => {
    setTimeout(() => {
    resolve(0);
    }, 1000);
    });
    const p2 = 1234;
    const p3 = Promise.resolve(5);
    console.time('time');
    Promise.all([p1, p2, p3]).then(v => {
    console.timeEnd('time');
    console.log('v:', v);
    });
    // time: 1000.971ms
    // v: [ 0, 1234, 5 ]
  • 只有当可迭代对象为空时是同步

    1
    2
    3
    4
    5
    6
    const p = Promise.all([]); // will be immediately resolved
    const p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
    console.log(p);
    console.log(p2)
    // Promise { [] }
    // Promise { <pending> }
  • Promise.all在任意一个传入的promise失败时立即返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'one');
    });
    const p2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000, 'two');
    });
    const p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 3000, 'three');
    });
    const p4 = new Promise((resolve, reject) => {
    setTimeout(resolve, 4000, 'four');
    });
    const p5 = new Promise((resolve, reject) => {
    reject('reject');
    });

    Promise.all([p1, p2, p3, p4, p5]).then(values => {
    console.log(values);
    }, reason => {
    console.log(reason);
    });
    // reject

    2.Promise.race(iterable)

  • 返回iterable里面执行最快的一个 (不论成功还是错误)

  • 如果传递的是空,则会一直等待。

  • 如果同时返回多个,则解析为iterable里面的第一个值。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1');
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2');
}, 990);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('p3');
}, 980);
});
Promise.race([p1, p2, p3]).then(v => {
console.log('v:', v);
}).catch(e => {
console.log('e:', e);
});
// e:p3

3.Promise.reject(reason)

  • reason 表示被拒绝的原因
  • 静态函数Promise.reject返回一个被拒绝的Promise对象。
1
2
3
4
5
6
Promise.reject(123).then(v => {
console.log('v:', v);
}).catch(e => {
console.log('e:', e);
});
// e: 123

4.Promise.resolve(value);

  • value 将被Promise对象解析的参数。可以是一个Promise对象或者是一个变量
  • 返回的状态由value的内容决定

示例

  • Promise.resolve一个变量
1
2
3
4
5
6
Promise.resolve(true).then(v => {
console.log('v:', v);
}).catch(e => {
console.log('e:', e);
});
// v: true
  • Resolve另一个promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 普通变量
let original = Promise.resolve(33);
let cast = Promise.resolve(original);
console.log('original:', original);
console.log('cast:', cast);
cast.then(function (value) {
console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));

// original: Promise { 33 }
// cast: Promise { 33 }
// original === cast ? true
// value: 33


// 异步函数
let original = Promise.resolve(new Promise((resolve) => {
setTimeout(() => {
resolve('setTimeout');
}, 1000);
}));
let cast = Promise.resolve(original);
console.log('original:', original);
console.log('cast:', cast);
cast.then(function (value) {
console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));

// original: Promise { 33 }
// cast: Promise { 33 }
// original === cast ? true
// value: 33

5.Promise原型

1.属性

  • Promise.prototype.constructor
    1
    返回被创建的实例行数,默认为Promise函数。

2.方法

1.Promise.prototype.then(onFulfilled, onRejected)

  • 返回一个Promise
  • 两个参数,成功和错误的回调函数
  • 由于then和Promise.prototype.catch() 方法都会返回promise,可以被链式调用(复合操作)
  • 当一个Promise完成(fullfilled)或者失败(rejected),返回函数将被异步调用
    • 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
    • 如果then中的回调函数没有返回值,那么then返回的Promise将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
    • 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
    • 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
    • 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
    • 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

参数

  • onFulfilled
    • 当Promise变成接受状态(fulfillment)时,该参数作为回调函数被调用
    • 只接受一个参数,即接受的最后的最终结果
    • 如果传入的参数不是function,则会在内部替换晨(x)=>x,即原样返回promise最终结果的函数。
  • onRejected
    • 当Promise变成拒绝状态时(rejection)时,调用该函数。
    • 只接受一个参数,即拒绝的原因(reason)
    • 如果不是function,则原样返回promise的最终reason

语法

1
2
3
4
5
6
7
p.then(onFulfilled, onRejected);

p.then(function(value) {
// fulfillment
}, function(reason) {
// rejection
});

示例

  • 使用then方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let p1 = new Promise(function(resolve, reject) {
    resolve("Success!");
    // or
    // reject ("Error!");
    });

    p1.then(function(value) {
    console.log(value); // Success!
    }, function(reason) {
    console.log(reason); // Error!
    });
  • 链式调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // then函数返回Promise

    Promise.resolve('foo')
    .then(function (string) {
    return new Promise(function (resolve, reject) {
    setTimeout(function () {
    string += 'bar';
    resolve(string);
    }, 1);
    });
    })
    .then(function (string) {
    setTimeout(function () {
    string += 'baz';
    console.log(string);
    }, 1);
    return string;
    })
    .then(function (string) {
    console.log('fin:', string);
    });

    // fin: foobar
    // foobarbaz
  • 函数抛出错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Promise.resolve().then(() => {
    throw 'reason log';
    }).then((v) => {
    console.log('v:', v);
    }, reason => {
    console.log('reason:', reason);
    });

    // reason: reason log
  • 返回一个非Promise

    1
    2
    3
    4
    5
    Promise.reject()
    .then(() => 99, () => 42)
    .then(solution => console.log('Resolved with ' + solution));

    // Resolved with 42
  • 返回的接收状态中不传入funciton

    1
    2
    3
    4
    Promise.reject('error')
    .then(() => 99)
    .then(solution => console.log('Resolved with ' + solution), reason => console.log('reason:', reason));
    // reason: error
  • 使用catch捕获,不使用then的两种状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Promise.resolve().then(() => {
    throw 'Oh no!';
    }).catch(reason => {
    console.log('onRejected function called: ', reason);
    }).then(() => {
    console.log('I am always called even if the prior then\'s promise rejects');
    }).then(v => console.log(v));

    // onRejected function called: Oh no!
    // I am always called even if the prior then's promise rejects
    // undefined

    2.Promise.prototype.catch(onRejected)

  • 语法

    1
    2
    3
    4
    5
    p.catch(onRejected);

    p.catch(function(reason) {
    // 拒绝
    });
  • 返回一个Promise

  • catch方法可以用于promise组合中的错误处理

示例

  • 链式调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let p1 = new Promise((resolve, reject) => {
    reject('error');
    });
    p1.then(v => {
    console.log('v:', v);
    }).catch(e => {
    console.log('e:', e);
    }).then(v2 => {
    console.log('v2:', v2);
    }).catch(e2 => {
    console.log('e2:', e2);
    });

  • 捕获抛出的错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // 抛出一个错误,大多数时候将调用catch方法
    let p1 = new Promise(function (resolve, reject) {
    throw 'Uh-oh!';
    });

    p1.catch(function (e) {
    console.log(e); // "Uh-oh!"
    });

    // 在异步函数中抛出的错误不会被catch捕获到
    let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
    throw 'Uncaught Exception!';
    }, 1000);
    });

    p2.catch(function (e) {
    console.log(e); // 不会执行
    });

    // 在resolve()后面抛出的错误会被忽略
    let p3 = new Promise(function (resolve, reject) {
    resolve();
    throw 'Silenced Exception!';
    });

    p3.catch(function (e) {
    console.log(e); // 不会执行
    });

    3.Promise.prototype.finally(onFinally)

  • 接受一个funciton的参数

  • 返回一个Promise

  • Promise结束时不论是fulfilled或者rejected,都会执行指定的回调函数。

  • 如果你想在promise执行完毕后无论结果如何都要做一些处理,可以使用该方法

  • 语法

    1
    2
    3
    4
    5
    p.finally(onFinally);

    p.finally(function() {
    // 返回状态为(resolved 或 rejected)
    });

示例

1
2
3
4
5
6
7
let bool = false;
Promise.resolve(2).then(v => {
console.log('v:', v);
}).finally(() => {
bool = true;
});

6.创建Promise

  • 使用关键字new以及构造函数创建
  • 该构造函数会把一个叫处理器函数(executor function)的函数作为参数。
  • executor function 接收两个参数 resolve reject
  • 成功时会调用resolve函数,失败则调用reject函数。

示例

  • 简单例子
1
2
3
4
5
6
7
8
9
let newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
});
});
newPromise.then(v => {
console.log('v:', v);
});
// v: 123

11.箭头函数

1.概述

  • 箭头函数表达式语法更加简洁
  • 没有this、arguments(可以使用Rest参数代替)、super和new.target
  • 更适用于需要匿名函数的地方
  • 不能用作构造函数(new)
  • 没有prototype属性
  • 不可以使用yield,箭头函数不能用作Generator函数
  • 箭头函数在参数和箭头之间不能换行

2.语法

  • 基础语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    (参数1, 参数2, …, 参数N) => { 函数声明 }
    // (x,y)={return x+y}
    //相当于:(参数1, 参数2, …, 参数N) =>{ return 表达式; }

    (参数1, 参数2, …, 参数N) => 表达式(单一)
    // (x,y)=>x+y

    // 当只有一个参数时,圆括号是可选的:
    (单一参数) => {函数声明}
    单一参数 => {函数声明}

    // 没有参数的函数应该写成一对圆括号。
    () => {函数声明}
  • 高级语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //加括号的函数体返回对象字面表达式:
    参数=> ({foo: bar})

    //支持剩余参数和默认参数
    (参数1, 参数2, ...rest) => {函数声明}
    (参数1 = 默认值1,参数2, …, 参数N = 默认值N) => {函数声明}

    //同样支持参数列表解构
    let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
    f(); // 6

3.示例

  • 更短的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 不使用箭头函数
    let elements = ['jon', 'mike', 'steve', 'matt.meng', 'hydrogen'];
    elements.map(function (value) {
    return value.length;
    });// 返回数组; [ 3, 4, 5, 9, 8 ]


    // 箭头函数优化
    elements = elements.map( (value) =>{
    return value.length;
    });// 返回数组: [ 3, 4, 5, 9, 8 ]

    // 当箭头函数的参数只有一个时,可以省略括号
    elements = elements.map(value =>{
    return value.length;
    });// 返回数组: [ 3, 4, 5, 9, 8 ]

    // 当箭头函数的函数体只有一个return语句时,可以省略return关键字和方法体的花括号
    elements = elements.map(value =>element.length);// 返回数组: [ 3, 4, 5, 9, 8 ]

    // 可以使用解构获取属性值
    elements = elements.map(({ length: len }) => len);
  • 不绑定this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // 1.不使用箭头函数
    function Person () {
    this.a = 1;
    setInterval(function () {
    this.a++;
    // 这里的this不是Person实例的this
    // 绑定了全局的this
    // 浏览器为window
    // Nodejs环境则指向了Timeout
    console.log('this.a:', this.a);
    }, 1000);// this.a: NaN
    }

    let p = new Person();

    // 2.ECMAScript 3/5,通过将this值分配给封闭的变量
    // 解决this问题
    function Person () {
    let that = this;
    that.a = 1;

    setInterval(function growUp () {
    that.a++;
    console.log('this.a:', that);
    }, 1000);// 正常相加
    }

    let p = new Person();

    // 3.使用箭头函数,箭头函数不会创建自己的this
    // 只会从自己作用域链的上一层继承this。
    function Person () {
    this.a = 1;

    setInterval(() => {
    this.a++;
    console.log('this.a:', this.a);
    }, 1000); // 正常相加
    }

    let p = new Person();

    // 4.箭头函数通过call或者apply调用、箭头没有会用上一级的this所以只会用b=1
    const getValue = {
    b: 1,
    getB: function (a) {
    let f = v => {
    return v + this.b;
    };
    let base = { b: 2 };
    return f.call(base, a);
    },
    };
    console.log(getValue.getB(2)); // 3 1+2
    console.log(getValue.getB(3)); // 4 1+3
  • 不绑定arguments

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 1.直接调用
    const f = (a, b, c) => {
    console.log('arguments:', arguments);
    };
    f(1, 2, 3); // arguments is not defined

    // 2.闭包调用
    function foo (n) {
    console.log('arguments:', arguments);
    let f = () => {
    console.log('arguments:', arguments);
    return arguments[0] + n;
    }; // 隐式绑定 foo 函数的 arguments 对象. arguments[0] 是 n
    return f();
    }
    foo(1) // 2

    // 3.如果非要用arguments,可以使用Rest参数代替
    function foo (arg) {
    var f = (...args) => {
    console.log('args:',args);
    };
    return f(arg);
    }

    foo(1); // 1
  • 返回对象字面量

    1
    2
    3
    // 要使用括号包住
    const f = () => ({ a: 1 });
    console.log('f:', f());
  • 箭头函数解析顺序

    1
    2
    3
    4
    5
    6
    7
    8
    // 常规方法
    let callback = null || function () {};

    // 箭头函数
    let callback2 = null || (() => {});

    // 错误写法
    let callback2 = null || () => {};
  • 箭头函数使用三元运算符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let simple = (a) => a ? true : false;
    simple(undefined);
    simple(null);
    simple(0);
    simple(1);
    // false
    // false
    // false
    // true
  • 箭头函数递归

    1
    2
    var fact = (x) => ( x==0 ?  1 : x*fact(x-1) );
    fact(5); // 120

    12.其余新增方法

    1.Array.prototype.flat()

  • 按照指定的深度递归遍历数组,并把所有元素与遍历的子数组中的元素合并为一个新数组返回。

  • 会移除数组中的空项

  • 和Map一样

1.语法

1
let newArray = arr.flot([depth])

参数

  • depth [可选] 指定要提取嵌套数组的结构深度,默认值为1

返回值

  • 一个包含将数组与子数组中所有元素的新数组。

2.示例

  • 一层(默认)

    1
    2
    let arrDepth1 = [1, 2, 3, [4]];
    arrDepth1.flat();// [ 1, 2, 3, 4 ]
  • 二层

    1
    2
    let arrDepth2 = [1, 2, 3, [4, [5]]];
    arrDepth2.flat(2); // [ 1, 2, 3, 4, 5 ]
  • N层

    1
    2
    3
    // 可以使用Infinity,来展开任意深度的嵌套数组
    let arrDepthN = [1, [2, [3, [4, [5]]]]];
    arrDepthN.flat(Infinity) // [ 1, 2, 3, 4, 5 ]
  • 移除空项

    1
    2
    let arr = [1, 2, , 4, 5];
    arr.flat(); // [ 1, 2, 4, 5 ]

2.Array.prototype.flatMap()

  • 结构深度只能为1

1.语法

1
2
3
let new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
// 返回新数组的元素
}[, thisArg])

参数

  • callback 三个参数
    • currentValue 当前正在数组中处理的元素
    • index[可选] 当前正在处理的元素的索引
    • array[可选] 被调用的map数组
  • thisArg[可选]
    • 执行callback函数时使用的this值。

返回值

  • 一个新的数组,其中每个元素都是回调函数的结果,并且结构深度 depth 值为1。

2.示例

  • Map 与 flatMap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let arr1 = [1, 2, 3, 4];

    arr1.map(x => [x * 2]);
    // [[2], [4], [6], [8]]

    arr1.flatMap(x => [x * 2]);
    // [2, 4, 6, 8]

    // 只会将 flatMap 中的函数返回的数组 “压平” 一层
    arr1.flatMap(x => [[x * 2]]);
    // [[2], [4], [6], [8]]
  • 清晰展示flatMap用法

    1
    2
    3
    4
    5
    6
    7
    let arr = ["今天天气不错", "", "早上好"]

    arr.map(s => s.split(""))
    // [["今", "天", "天", "气", "不", "错"],[],["早", "上", "好"]]

    arr.flatMap(s => s.split(''));
    // ["今", "天", "天", "气", "不", "错", "早", "上", "好"]

    3.Object.fromEntries()

  • 与Object.entries()相对,可以把类似于Object.entries()转出的结果进行重新生成Object

1.示例

  • Map 转化为 Object
    1
    2
    3
    const map = new Map([ ['foo', 'bar'], ['baz', 42] ]);
    const obj = Object.fromEntries(map);
    console.log(obj); // { foo: "bar", baz: 42 }
  • Array 转化为 Object
    1
    2
    3
    const arr = [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ];
    const obj = Object.fromEntries(arr);
    console.log(obj); // { 0: "a", 1: "b", 2: "c" }
  • 对象转换
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const object1 = { a: 1, b: 2, c: 3 };

    const object2 = Object.fromEntries(
    Object.entries(object1)
    .map(([ key, val ]) => [ key, val * 2 ])
    );

    console.log(object2);
    // { a: 2, b: 4, c: 6 }

部分内容引用自MDN

如有侵权行为,请点击这里联系我删除

如发现疑问或者错误点击反馈

备注

2019年7月18日

  • 增加let、const
  • 增加const TDZ说明

2019年7月30日

  • 增加Set基本用法

2019年7月31日

  • 增加Map、WeakMap基本用法
  • 修改Set相关用法

2019年8月01日

  • 完善Set用法
  • 增加Set时间复杂度相关
  • 增加Map的适用场景

2019年8月02日

  • 增加文章来源备注
  • 增加解构相关内容

2019年8月05日

  • 增加class

2019年8月06日

  • 完善class相关内容

2019年8月07日

  • 增加基本类型 Symbol基本用法

2019年8月09日

  • 增加async await基本用法

2019年8月12日

  • 增加Promise基本用法

2019年8月13日

  • 增加Promise原型方法的使用

2019年8月14日

  • 增加箭头函数的基本用法

2019年9月5日

  • 增加其余新增函数用法