ES6 核心知识点

925 阅读31分钟

1、let、const

letvarconst之间的变量

声明方式变量提升暂时性死区重复声明初始值作用域
var存在不存在允许不需要除块级
let存在存在不允许不需要块级
const存在存在不允许需要块级

1.1、变量提升

变量提升指的是将变量和函数声明提升到它们所在作用域的顶部。这意味着在执行代码之前,JavaScript 引擎会先处理变量和函数的声明,使它们在声明之前就可以被访问。

// 变量提升
console.log(a); //undefined
var a = 1;

console.log(b); //ReferenceError: b is not defined
let b = 1;

console.log(c); //ReferenceError: c is not defined
const c = 1;


letconst 不能在声明之前被访问是由于它们引入了暂时性死区的概念。在这个区域内,变量虽然已经被提升到作用域的顶部,但在初始化之前访问它们会导致运行时错误。

在声明阶段,letconst 声明的变量被提升到作用域的顶部,但它们在初始化之前处于暂时性死区。在这个区域内,尝试访问变量会导致 ReferenceError,即使在声明点之前有变量名的提升。

1.2、暂时性死区

如果在代码块中存在 letconst 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

var tmp = 123;

if (true) {
	tmp = 'abc';//报错,ReferenceError: tmp is not defined
	let tmp;
}

这段代码的原意是在 if 内定义外部的 tmp 为 'abc'。

但现实是,存在全局变量 tmp ,但是块级作用域内 let 又声明了一个 tmp变量,导致后者被绑定在这个块级作用域中,所以在 let 声明变量前,对 tmp 赋值就报错了。

1.3、重复声明

指在相同作用域内,重复声明同一个变量。

letconst 命令声明的变量不允许重复声明:

function func(){
    let a = 10;
    const PI = 3.1415;
    
    var a = 1;// 报错,Uncaught SyntaxError: Identifier 'a' has already been declared
    var PI = 3;// 报错,Uncaught SyntaxError: Identifier 'PI' has already been declared
  }


  // 当调用func()时报错,Uncaught SyntaxError: Identifier 'a' has already been declared
  function func(){
    let a = 10;
    const PI = 3.1415;
    
    let a = 1;// 报错,Uncaught SyntaxError: Identifier 'a' has already been declared
    const PI = 3;// 报错,Uncaught SyntaxError: Identifier 'PI' has already been declared
  }

var 是允许重复定义的,而这又会给我们带来什么麻烦呢?

var i = 10;
for(var i = 0;i < 5;i++){
  console.log(i);
}
console.log(i);// 输出 5

对于学习过静态(类型)语言的人知道,这段代码要是替换成 c 语言或其他静态语言,输出的结果应该是 10。然而对于 javascript 来说,这段代码的输出结果是 5。因为 var 命令没有块级作用域,所以 for 循环括号内的变量 i 会覆盖外层 i,而且 var 允许重复声明,所以这段代码中 i 被声明了两次,i 的最终结果就被 for 循环的 i 给覆盖了。

1.4、初始值

由于 const 声明的是只读的常量,一旦声明,就必须立即初始化,声明之后值不能改变。(注意:不可改变指的是在内存中的地址不可改变

const PI = 3.1415;
PI = 3;// 报错,Uncaught TypeError: Assignment to constant variable.

//const 的本质: const 定义的变量并非常量,并非不可变,
// 它定义了一个常量引用一个值。
// 不可变的本质是:在内存中的地址是不能改变的
// 使用 const 定义的对象或者数组,其实是可变的。因为内存中地址不变

const b ={}
b.m=123
console.log(b.m) //123
b.m=456
console.log(b.m) //456
b.o={
  k:789
}
b.o={
  k:125
}
console.log(b.o) //{ k: 125 }

b ={} //Assignment to constant variable.  引用值不能改变

1.5、作用域

在 ES5 中只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

第一种场景,内层变量可能会覆盖外层变量。

var tmp = new Date();//处于全局作用域

function f() {
  console.log(tmp);//处于函数作用域
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined


上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。 然而现实是在这段代码中,function 内部的2个 tmp 变量处在同一函数作用域,由于变量提升,导致函数作用域中的 tmp 覆盖全局作用域中的 tmp,所以,f()输出结果为undefined

第二种场景,用来计数的循环变量泄露为全局变量(前面在重复声明中提到的):

var i = 10;
for(var i = 0;i < 5;i++){
  console.log(i);
}
console.log(i);// 输出 5

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

2、解构赋值

他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值

在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。

var person = {
  name:'jerry',
  age:22
}

const {name ,age } = person 

console.log(name) //jerry
console.log(age) //22

console.log(a) //undefined
console.log(b) //undefined

3、symbol

一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名

Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。

let sy = Symbol("KK");
console.log(sy);   // Symbol(KK)
typeof(sy);        // "symbol"
 
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk"); 
sy === sy1;       // false

3.1、使用场景

作为属性名

由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。

写法:

let sy = Symbol("key1");
 
// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);    // {Symbol(key1): "kk"}
 
// 写法2
let syObject = {
  [sy]: "kk"
};
console.log(syObject);    // {Symbol(key1): "kk"}
 
// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject);   // {Symbol(key1): "kk"}

Symbol 作为对象属性名时不能用.运算符,要用方括号。因为.运算符后面是字符串,所以取到的是字符串 sy 属性,而不是 Symbol 值 sy 属性。

Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

定义常量

使用 Symbol 定义常量,这样就可以保证这一组常量的值都不相等。

const RED = Symbol('Red');
const BLUE = Symbol('Blue');

function getColor(color) {
  switch (color) {
    case RED:
      return 'This is red';
    case BLUE:
      return 'This is blue';
    default:
      return 'Unknown color';
  }
}

console.log(getColor(RED)); // 'This is red'

私有属性和方法的模拟

使用 Symbol 可以模拟实现私有属性和方法,因为在外部无法访问使用 Symbol 创建的属性。

const privateMethod = Symbol('privateMethod');

class MyClass {
  constructor() {
    this[privateMethod]();
  }

  [privateMethod]() {
    console.log('This is a private method');
  }
}

const instance = new MyClass(); // 输出 'This is a private method'
// instance[privateMethod](); // 无法直接访问私有方法

3.2、Symbol.for()

Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。

let yellow = Symbol("Yellow");
let yellow1 = Symbol.for("Yellow");
yellow === yellow1;      // false
 
let yellow2 = Symbol.for("Yellow");
yellow1 === yellow2;     // true

3.3、Symbol.keyFor()

Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。

let yellow1 = Symbol.for("Yellow");
Symbol.keyFor(yellow1);    // "Yellow"

4、Map

Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

4.1、Map 和 Object 的区别

  • 一个 Object 的键只能是字符串或者 Symbols(使用其他类型的数据会转化成字符串),但一个 Map 的键可以是任意值。
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 有原型链,可能会包含从原型链继承来的属性。Map 没有原型链,不包含从原型链继承来的属性。
var myMap = new Map();
var keyString = "a string"; 
 
myMap.set(keyString, "和键'a string'关联的值");
 
myMap.get(keyString);    // "和键'a string'关联的值"
myMap.get("a string");   // "和键'a string'关联的值"
                         // 因为 keyString === 'a string'

4.2、Map 的迭代

for...of

var myMap = new Map();
myMap.set('0key', "zero");
myMap.set('1key', "one");

for (let [key, value] of myMap) {
  console.log(key + " = " + value);
}
/**
 * 0key = zero
 * 1key = one
 */

for (let item of myMap) {
  console.log('item', item);
}
/**
 * item [ '0key', 'zero' ]
 * item [ '1key', 'one' ]
 */

forEach()

var myMap = new Map();
myMap.set('0key', "zero");
myMap.set('1key', "one");

myMap.forEach(function(value, key) {
  console.log(key + " = " + value);
})
// 0key = zero
// 1key = one

4.3、Map 对象的操作

Map 与 Array的转换

    var kvArray = [["key1", "value1"], ["key2", "value2"]];
     
    // Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
    var myMap = new Map(kvArray);
     
    // 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
    var outArray = Array.from(myMap);

    console.log(outArray) //[ [ 'key1', 'value1' ], [ 'key2', 'value2' ] ]
    console.log(kvArray) //[ [ 'key1', 'value1' ], [ 'key2', 'value2' ] ]
    console.log(myMap) //Map { 'key1' => 'value1', 'key2' => 'value2' }

Map 的克隆

    var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
    var myMap2 = new Map(myMap1);
     
    console.log(myMap1 === myMap2);  //  false。 Map 对象构造函数生成实例,迭代出新的对象。

Map 的合并

var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var second = new Map([[1, 'uno'], [2, 'dos']]);
 
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var merged = new Map([...first, ...second]);
console.log(first) //Map { 1 => 'one', 2 => 'two', 3 => 'three' }
console.log(second) //Map { 1 => 'uno', 2 => 'dos' }
console.log(merged) //Map { 1 => 'uno', 2 => 'dos', 3 => 'three' }

4.4、WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合。 不同之处在于:

  • 键:

    • Map 的键可以是任意数据类型,键的引用是强引用,即使键没有其他引用,它们仍然会存在于 Map 中。
    • WeakMap 的键必须是对象,而且,键是弱引用,当键对象没有其他引用时,垃圾回收器可以回收它们,这可能导致键值对从 WeakMap 中移除。
  • 遍历和迭代:

    • Map 提供了丰富的遍历方法,包括 forEachentrieskeysvalues
    • WeakMap 不提供直接的遍历方法,因为键是弱引用,遍历可能会导致一些不确定的结果。
  • 性能影响:

    • Map 的键是强引用,键值对不会被轻易回收,因此在某些场景下,可能对内存占用有一些影响。
    • WeakMap 的键是弱引用,当键对象没有其他引用时,垃圾回收器可以回收它们,有助于减小内存占用。

5、Set

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

5.1、Set 中的特殊值

Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  • +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
  • undefined 与 undefined 是恒等的,所以不重复;
  • NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
    let mySet = new Set();
     
    console.log(mySet.add(1)); // Set {1}
    console.log(mySet.add(5)); // Set {1, 5}
    console.log(mySet.add(5)); // Set {1, 5} 体现了值的唯一性
    console.log(mySet.add("some text"));  // Set {1, 5, "some text"} 体现了类型的多样性

    var o = {a: 1, b: 2}; 
    console.log(mySet.add(o)); //Set { 1, 5, 'some text', { a: 1, b: 2 } }
    console.log(mySet.add({a: 1, b: 2}));  //Set { 1, 5, 'some text', { a: 1, b: 2 }, { a: 1, b: 2 } }
    // 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储

5.2、类型转换

    // Array 转 Set
    var mySet = new Set(["value1", "value2", "value3"]);
    console.log(mySet) // Set { 'value1', 'value2', 'value3' }

    // 用...操作符,将 Set 转 Array
    var myArray = [...mySet];
    console.log(myArray) //[ 'value1', 'value2', 'value3' ]

    // String 转 Set
    var mySet = new Set('hello');  
    console.log(mySet) //Set { 'h', 'e', 'l', 'o' }
    // 注:Set 中 toString 方法是不能将 Set 转换成 String

5.3、set作用

Set 是 JavaScript 中的一种数据结构,它是一种集合,用于存储唯一的值,即集合中的每个元素都必须是唯一的。Set 的主要作用包括以下几个方面:

  1. 存储唯一值:

    • Set 中的元素是唯一的,不允许重复值。当你向 Set 中添加重复的值时,它会被自动忽略。

      const uniqueSet = new Set();
      uniqueSet.add(1);
      uniqueSet.add(2);
      uniqueSet.add(1); // 重复值被忽略
      
      console.log(uniqueSet); // Set { 1, 2 }
      
  2. 检查值是否存在:

    • 通过 has 方法可以轻松检查某个值是否存在于 Set 中。

      const mySet = new Set([1, 2, 3]);
      
      console.log(mySet.has(2)); // true
      console.log(mySet.has(4)); // false
      
  3. 删除特定值:

    • 使用 delete 方法可以从 Set 中删除特定的值。

      const mySet = new Set([1, 2, 3]);
      
      mySet.delete(2);
      console.log(mySet); // Set { 1, 3 }
      
  4. 迭代集合元素:

    • Set 提供了多种迭代集合元素的方法,包括 forEachfor...of 等。

      const mySet = new Set([1, 2, 3]);
      
      mySet.forEach(value => {
        console.log(value);
      });
      
      // 或者使用 for...of 迭代
      for (const value of mySet) {
        console.log(value);
      }
      
  5. 获取集合大小:

    • 使用 size 属性可以获取 Set 的大小,即集合中唯一元素的个数。

      const mySet = new Set([1, 2, 3]);
      
      console.log(mySet.size); // 3
      

Set 的主要优势在于它提供了一种简单且高效的方式来存储唯一值,并且可以方便地进行集合操作,如并集、交集和差集。它对于需要存储不重复值的场景非常有用。

5.4、WeakSet

WeakSet 是 JavaScript 中的一种数据结构,它是 Set 的变体,用于存储对象的弱引用。与 Set 不同的是,WeakSet 中的元素必须是对象,并且这些对象是弱引用的,即它们不会阻止垃圾回收器回收它们,即便这些对象存在于 WeakSet 中。

以下是 WeakSet 的主要特点和用法:

  1. 只能存储对象:

    • WeakSet 中的元素必须是对象。如果尝试添加非对象值,将引发 TypeError

      const weakSet = new WeakSet();
      const obj = { key: 'value' };
      const nonObj = 'not an object';
      
      weakSet.add(obj);
      // weakSet.add(nonObj); // TypeError: Invalid value used in weak set
      
  2. 弱引用:

    • WeakSet 中的对象是弱引用的,这意味着它们不会阻止对象被垃圾回收。如果在其他地方不存在对这些对象的引用,它们可能会被回收,并且相应的键值对会从 WeakSet 中自动移除。

      const weakSet = new WeakSet();
      let obj = { key: 'value' };
      
      weakSet.add(obj);
      console.log(weakSet.has(obj)); // true
      
      obj = null; // 对象失去引用
      // 在某个时间点,垃圾回收器可能回收了对象,并且从 WeakSet 中移除
      console.log(weakSet.has(obj)); // false
      
  3. 不可迭代:

    • 由于 WeakSet 中的元素是弱引用的,它没有提供类似于 forEachfor...of 这样的方法来迭代元素。因此,无法直接获取 WeakSet 中的所有元素。
  4. 没有size属性:

    • Set 不同,WeakSet 没有 size 属性,也不能直接获取其包含的元素个数。

      const weakSet = new WeakSet();
      console.log(weakSet.size); // undefined
      

WeakSet 主要用于需要存储一组对象,但这些对象的生命周期是由其他部分代码控制的情况。例如,它可以用于存储对象的附加信息,而不会阻止这些对象在其他地方被垃圾回收。需要注意的是,由于 WeakSet 中的对象是弱引用的,不可遍历,因此它具有一些特定的使用场景,并且不适用于所有情况。

Set 与 Map 的异同

SetMap 是 JavaScript 中两种常见的集合类型,它们有一些相似之处,但也有明显的区别。以下是它们的异同点:

相同点:

  1. 存储唯一值:SetMap 都用于存储唯一的值(或者说键),即集合中的每个元素都必须是唯一的。

  2. 提供迭代方法: 两者都提供了迭代集合元素的方法,包括 forEachfor...of 等。

不同点:

  1. 存储的内容:
    • Set: 存储的是一组唯一的值,不分键值对,每个值在 Set 中都是唯一的。

    • Map: 存储的是键值对,其中键和值可以是任意类型。

选择使用 Set 还是 Map 取决于需求。如果只需要存储一组唯一值,而不需要与这些值关联其他信息,可以选择使用 Set。如果需要键值对存储,或者需要与每个值关联其他信息,那么选择 Map 更合适。

6、Reflect和Proxy

Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。

Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

proxy

一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。

let target = {
  name: 'Tom',
  age: 24
}
let handler = {
  get: function(target, key) {
      console.log('getting '+key);
      return target[key]; // 不是target.key
  },
  set: function(target, key, value) {
      console.log('setting '+key);
      target[key] = value;
  }
}
let proxy = new Proxy(target, handler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age

// proxy 是对target的浅拷贝
console.log(target.age) //25

reflect

ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。

Reflect.get(target, name, receiver)

查找并返回 target 对象的 name 属性

let exam = {
    name: "Tom",
    age: 24,
    get info(){
        return this.name + this.age;
    }
}
Reflect.get(exam, 'name'); // "Tom"
 
// 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
let receiver = {
    name: "Jerry",
    age: 20
}
Reflect.get(exam, 'info', receiver); // Jerry20
 
// 当 name 为不存在于 target 对象的属性时,返回 undefined
Reflect.get(exam, 'birth'); // undefined
 
// 当 target 不是对象时,会报错
Reflect.get(1, 'name'); // TypeError

Reflect.set(target, name, value, receiver)

将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。

let exam = {
    name: "Tom",
    age: 24,
    set info(value){
        return this.age = value;
    }
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
 
// value 为空时会将 name 属性清除
Reflect.set(exam, 'age', ); // true
exam.age; // undefined
 
// 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
let receiver = {
    age: 18
}
Reflect.set(exam, 'info', 1, receiver); // true
receiver.age; // 1
 
let receiver1 = {
    name: 'oppps'
}
Reflect.set(exam, 'info', 1, receiver1);
receiver1.age; // 1

Reflect.has(obj, name)

是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。

let exam = {
    name: "Tom",
    age: 24
}
Reflect.has(exam, 'name'); // true

Reflect.deleteProperty(obj, property)

let exam = {
    name: "Tom",
    age: 24
}
Reflect.deleteProperty(exam , 'name'); // true
exam // {age: 24} 
// property 不存在时,也会返回 true
Reflect.deleteProperty(exam , 'name'); // true

Reflect.ownKeys(target)

用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。

var exam = {
  name: 1,
  [Symbol.for('age')]: 4
}
Reflect.ownKeys(exam) // ["name", Symbol(age)]

组合使用

Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。

let exam = {
    name: "Tom",
    age: 24
}
let handler = {
    get: function(target, key){
        console.log("getting "+key);
        return Reflect.get(target,key);
    },
    set: function(target, key, value){
        console.log("setting "+key+" to "+value)
        Reflect.set(target, key, value);
    }
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"

7、模板字符串

使用大量的“”(双引号)和 + 来拼接才能得到我们需要的模版。模板字符串允许更优雅的方式去拼接字符串,不容易出错

`string text ${expression} string text`

8、对象

Object.assign(target, source_1, ···)

用于将源对象的所有可枚举属性复制到目标对象中。

如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。

Object.is(value1, value2)

用来比较两个值是否严格相等,与(===)基本类似。

区别:

//一是+0不等于-0
Object.is(+0,-0);  //false
+0 === -0  //true
//二是NaN等于本身
Object.is(NaN,NaN); //true
NaN === NaN  //false

9、数组

Array.of()

将参数中所有值作为元素形成数组。

console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
 
// 参数值可为不同类型
console.log(Array.of(1, '2', true)); // [1, '2', true]
 
// 参数为空时返回空数组
console.log(Array.of()); // []

Array.from()

将类数组对象或可迭代对象转化为数组

// 参数为数组,返回与原数组一样的数组
console.log(Array.from([1, 2])); // [1, 2]
 
// 参数含空位
console.log(Array.from([1, , 3])); // [1, undefined, 3]

10.扩展运算符

扩展运算符(spread)(形如:...),有点像 rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

var arr = [1,2,3,4,5,6]
console.log(...arr) //1 2 3 4 5 6

应用场景:

  1. 解构赋值
const [a,b,...c] = [1,2,3,4,5,6]
console.log(a) //1
console.log(b) //2
console.log(c) // [ 3, 4, 5, 6 ]



const {name,age,...f} = {name:'jerry',age:22,some:true,another:'sda'}
console.log(name) //jerry 必须是name
console.log(age) //22  必须是age
console.log(f) //{ some: true, another: 'sda' }

2. 合并数组,对象

var arr1=[1,2,3]
var arr2 = [3,4,5]

var arr = [...arr1,...arr2]
console.log(arr) //[ 1, 2, 3, 3, 4, 5 ]

var obj1 = {
  name:'jerry',
  age:22
}

var obj2={
  school:'hebeigc',
  class:3
}

var obj = {...obj1,...obj2}
console.log(obj) //{ name: 'jerry', age: 22, school: 'hebeigc', class: 3 }

3. 帮助完成vuex中函数的映射

export default new Vuex.Store({
  state: {
    list: [],
    inputValue: ''
  },
  mutations: {
    setInput(state, inpVal) {
      state.inputValue = inpVal
    }
  },
  actions: {
    async getList(context) {
      const { data: res } = await axios.get('/list.json')
      context.commit('initList', res)
    }
  },
  getters: {
    unDone(state) {
      const unDoneList = state.list.filter(item => {
        return item.done === false
      })
      return '剩余' + unDoneList.length + '条'
    }
})

// 导入函数
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
  name: 'app',
  data() {
    return {}
  },
  methods: {
    ...mapMutations(['setInput']),
    ...mapActions(['getList']),
  },
  computed: {
    ...mapState(['inputValue']),
    ...mapGetters(['unDone'])
  }
}

mapMutations函数和mapActions函数将mutations和actions中指定的函数映射为当前组件的methods函数。

mapState函数和mapGetters函数将state中全局数据映射为当前组件的计算属性。

  1. 浅拷贝
var obj = {
  o:{
    a:1
  },
  arr:[1,2]
}

var clonObj = {...obj}
console.log(clonObj) //{ o: { a: 1 }, arr: [ 1, 2 ] }

clonObj.o.a=5;
clonObj.arr[0]=5
console.log(obj) //{ o: { a: 5 }, arr: [ 5, 2 ] }
console.log(clonObj) //{ o: { a: 5 }, arr: [ 5, 2 ] }

11.rest参数

rest参数搭配的变量是一个数组,将函数多余的且未知个数的参数收纳到一个数组中(不限长度)

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

用于解决箭头函数没有 arguments 问题。

function arg(){
  console.log(arguments)
  console.log(arguments[0]) //1
}

arg(1,2,3,4,5,6) //[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }

const args=(...rest)=>{
  console.log('argument',arguments) //得不到传入的参数
  console.log('rest',rest) //[ 1, 2, 3, 4, 5 ]
}

args(1,2,3,4,5)

arguments和...rest:

function cb(a,b){}

在上述函数中,我们如果想获得除了 a,b,之外的参数,
必须for从2开始循环arguments,或者挨个给传入的参数一个形参

如果用...rest 的话就可以简单:
function cb(a,b,...rest){} 

剩下的参数都会在rest数组中

10、箭头函数

(function(){
  console.log([
    (()=>this.x).bind({x:'in'})(),
    (()=>this.x)()
  ])
}).call({x:'ourter'})
// [ 'ourter', 'ourter' ]
        var id = 1
        const o = {
            id: 2,
            m() {
                console.log('m--->', this.id)
            },
            n: () => {
                console.log('n--->', this.id)
            }
        }


        o.m() //  2 指向o
        o.n() // 1 指向window

        let p = o.m
        p() // 1 指向window 

        p = o.n
        p() // 1 指向window
        var id = 1
        const o = {
            id: 2,
            m() {
                console.log('m--->', this.id)
            },
            n: function () {
                return () => {
                    console.log('n--->', this.id)
                }
            }
        }


        o.m() //  2 指向o
        o.n()() // 2 指向o

        let p = o.m
        p() // 1 指向window 

        p = o.n()
        p() // 2 指向o
        
        p = o.n
        p()() // 1 指向o

由上总结:箭头函数this取决于定义它时的作用域,别的函数this取决于运行时作用域

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this

11、class 类

12、模块module

13、promise

promise对象是一个构造函数,用于生成Promise实例。对象状态不受外界影响,只有异步操作的结果才能决定当前是哪一种状态。三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。实例生成以后,用then方法分别指定resolved状态和rejected状态的回调函数

promise常用的API

在 JavaScript 中,有四个常用的 Promise 组合方法,它们分别是 Promise.allPromise.allSettledPromise.anyPromise.race。这些方法用于处理多个 Promise 对象,每个方法都有不同的行为和适用场景。

1. Promise.all

  • 语法:

    Promise.all(iterable).then(
      (values) => {
        // 处理所有 Promise 解决时的值
      },
      (reason) => {
        // 处理第一个被拒绝的 Promise 的原因
      }
    );
    
  • 行为:

    • 当所有的 Promise 都解决时,返回一个新的 Promise,解决值是包含所有解决值的数组。
    • 如果任何一个 Promise 被拒绝,整个 Promise.all 就会被拒绝,且返回第一个被拒绝的 Promise 的原因。

2. Promise.allSettled

  • 语法:

    Promise.allSettled(iterable).then((results) => {
      // 处理所有 Promise 解决或拒绝时的结果
    });
    
  • 行为:

    • 返回一个新的 Promise,解决值是包含所有 Promise 结果的数组。
    • 不关心 Promise 是解决还是被拒绝,只要有结果就会放入结果数组中。

3. Promise.any

  • 语法:

    Promise.any(iterable).then((value) => {
      // 处理第一个完成的 Promise 的值
    });
    
  • 行为:

    • 返回一个新的 Promise,解决值是第一个完成的 Promise 的值。
    • 只要有一个 Promise 解决,即使其他 Promise 被拒绝,整个 Promise.any 也会解决。

4. Promise.race

  • 语法:

    Promise.race(iterable).then(
      (value) => {
        // 处理第一个完成的 Promise 的值
      },
      (reason) => {
        // 处理第一个被拒绝的 Promise 的原因
      }
    );
    
  • 行为:

    • 返回一个新的 Promise,解决值或拒绝原因与第一个完成或被拒绝的 Promise 相关。
    • 如果第一个完成,整个 Promise.race 解决;如果第一个被拒绝,整个 Promise.race 被拒绝。

总结

  • 使用 Promise.all 当你希望等待所有 Promise 完成,只有当所有 Promise 解决时才解决。
  • 使用 Promise.allSettled 当你希望等待所有 Promise 完成,不管是解决还是被拒绝,都收集结果。
  • 使用 Promise.any 当你希望等待第一个 Promise 完成,不关心其他 Promise 是否被拒绝。
  • 使用 Promise.race 当你希望等待第一个 Promise 完成或被拒绝,整个 race 会与第一个完成或被拒绝的 Promise 相关。

Promise的链式调用

可以在then中继续写Promise对象并返回,然后继续调用then来进行回调操作

先看一段代码:

new Promise((resolve, reject) => {
  console.log("log: 外部promise");
  resolve();
})
  .then(() => {
    console.log("log: 外部第一个then");
    new Promise((resolve, reject) => {
      console.log("log: 内部promise");
      resolve();
    })
      .then(() => {
        console.log("log: 内部第一个then");
      })
      .then(() => {
        console.log("log: 内部第二个then");
      });
  })
  .then(() => {
    console.log("log: 外部第二个then");
  });
  
// log: 外部promise
// log: 外部第一个then
// log: 内部promise
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then

结论

  1. 当执行 then 方法时,如果前面的 promise 已经是 resolved 状态,则直接将回调放入微任务队列中

    在同步执行 then 方法时,会进行判断:

    • 如果前面的 promise 已经是 resolved 状态,则会立即将回调推入微任务队列(但是执行回调还是要等到所有同步任务都结束后)

    • 如果前面的 promise 是 pending 状态则会将回调存储在 promise 的内部,一直等到 promise 被 resolve 才将回调推入微任务队列

  2. 当一个 promise 被 resolve 时,会遍历之前通过 then 给这个 promise 注册的所有回调,将它们依次放入微任务队列中

    如何理解通过 then 给这个 promise 注册的所有回调,考虑以下案例

    let p = new Promise((resolve, reject) => {
      setTimeout(resolve, 1000);
    });
    p.then(() => {
      console.log("log: 外部第一个then");
    });
    p.then(() => {
      console.log("log: 外部第二个then");
    });
    p.then(() => {
      console.log("log: 外部第三个then");
    });
    

    1 秒后变量 p 才会被 resolve,但是在 resolve 前通过 then 方法给它注册了 3 个回调,此时这 3 个回调不会被执行,也不会被放入微任务队列中,它们会被 p 内部储存起来(在手写 promise 时,这些回调会放在 promise 内部保存的数组中),等到 p 被 resolve 后,依次将这 3 个回调推入微任务队列,此时如果没有同步任务就会逐个取出再执行。

    • then必须返回的是promise实例(才能通过resolve将下一个then注册的回调函数推入微任务队列中)

      对于 then 方法返回的 promise 它是没有 resolve 函数的,取而代之只要 then 中回调的代码执行完毕并获得同步返回值,这个 then 返回的 promise 就算被 resolve

      同步返回值的意思换句话说,如果 then 中的回调返回了一个 promise,那么 then 返回的 promise 会等待这个 promise 被 resolve 后再 resolve

      new Promise((resolve, reject) => {
        resolve();
      })
        .then(() =>                                 //1.返会promise  3.这个then被resolve
          new Promise((resolve, reject) => {
            resolve();
          }).then(() => {
            console.log("log: 内部第一个then");                //2.返回的promise被 resolve
          })
        )
        .then(() => {      //4.外部第一个thenresolve,将外部第二个then注册的回调函数推入微任务队列
          console.log("log: 外部第二个then");
        });
      
        // log: 内部第一个then
        // log: 外部第二个then
      
    • resolve:

      resolve 函数就是在实例化 Promise 时,传入函数的第一个参数

      new Promise(resolve => {
        resolve();
      });
      

      它的作用除了将当前的 promise 由 pending 变为 resolved,还会遍历之前通过 then 给这个 promise 注册的所有回调,将它们依次放入微任务队列中,很多人以为是由 then 方法来触发它保存回调,而事实上 then 方法即不会触发回调,也不会将它放到微任务,then 只负责注册回调,由 resolve 将注册的回调放入微任务队列,由事件循环将其取出并执行

回过头看最开始的例子:

new Promise((resolve, reject) => {
  console.log("log: 外部promise");
  resolve();
})
  .then(() => {
    console.log("log: 外部第一个then");
    new Promise((resolve, reject) => {
      console.log("log: 内部promise");
      resolve();
    })
      .then(() => {
        console.log("log: 内部第一个then");
      })
      .then(() => {
        console.log("log: 内部第二个then");
      });
  })
  .then(() => {
    console.log("log: 外部第二个then");
  });
  
// log: 外部promise
// log: 外部第一个then
// log: 内部promise
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then
  1. 最开始promise 实例化,log: 外部promise

  2. 执行 resolve 函数,最开始promise的状态变成resolved

  3. 执行外部第一个 then函数,由于前边的promise已经resolved,所以直接将then中的回调函数推入微任务队列

    • 主线程:外部第二个then函数
    • 微任务:外部第一个then函数
  4. 执行主线程外部第二个then函数,由于前边的promise(第一个then返回的promise)仍然是pending状态,所以什么都不做。

    • 主线程:
    • 微任务:外部第一个then函数
  5. 执行微任务队列中外部第一个then函数,log: 外部第一个then,初始化promise,log: 内部promise,resolve函数执行,内部promise状态变为resolved

  6. 执行内部第一个then函数,由于之前的promise已经resolved,所以直接将内部第一个then函数的回调推入微任务队列

    • 主线程:内部第二个then
    • 微任务:内部第一个then
  7. 执行主线程上内部第二个then函数,由于前边的promise(内部第一个then)还处于pending状态,所以什么都不做(注册,不推进,不执行)。

  8. 此时外部第一个then函数执行完毕,状态变为resolved,将用then(外部第二个then函数)为他注册的回调函数,推入微任务队列。

    • 主线程:
    • 微任务:内部第一个then函数,外部第二个then函数
  9. 执行微任务中内部第一个then函数,log: 内部第一个then,此时内部第一个then函数执行完毕,状态变为resolved,将他的回调函数(内部第二个then)推入微任务队列

    • 主线程:
    • 微任务:外部第二个then函数,内部第二个then函数
  10. 之后依次执行,log: 外部第二个thenlog: 内部第二个then

promise常用方法

function func(){
	return new Promise((resolve,reject)=>{ 
		// resolve(data)或者reject(err)
	})
}
  1. promise.then(成功回调函数,失败回调函数)

    func().then(
    	(data) => { //resolve },
    	(err) => { //reject }
    )
    
  2. promise.then(成功回调函数).catch(失败回调函数)

    func().then(
    	(data) => {//resolved}
    ).catch(
    	(err) => { // reject}
    )
    
  3. promise.then(成功回调函数).catch(失败回调函数).finally(成功失败都执行的函数)

    func().then(
    	(data) => {//resolved}
    ).catch(
    	(err) => { // reject}
    ).finally(
    	(over) => {//all}
    )
    
  4. promise.all([a1,a2,a3])

    Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

    Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

    需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

    let p1 = new Promise((resolve, reject) => {
      resolve("成功了");
    });
    
    let p2 = new Promise((resolve, reject) => {
      resolve("success");
    });
    
    let p3 = Promise.reject("失败");
    
    Promise.all([p1, p2])
      .then((result) => {
        console.log(result); //['成功了', 'success']
      })
      .catch((error) => {
        console.log(error);
      });
    
    Promise.all([p1, p3, p2])
      .then((result) => {
        console.log(result);
      })
      .catch((error) => {
        console.log(error); // 失败了,打出 '失败'
      });
    
    
  5. promise.race([a1,a2,a3])

    顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("success");
      }, 1000);
    });
    
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject("failed");
      }, 500);
    });
    
    Promise.race([p1, p2])
      .then((result) => {
        console.log(result);
      })
      .catch((error) => {
        console.log('err-',error); // err- failed
      });
    
    
  6. promise.resolve()

    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))
    
  7. promise.reject()

    // Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected
    const p = Promise.reject('出错了');
    // 等同于
    const p = new Promise((resolve, reject) => reject('出错了'))
    
    p.then(null, function (s) {
      console.log(s)      // 出错了
    });
    

async/await

async返回什么

先看看看以下代码的输出值:

async function asyncReturn() {
     return 'async返回的是什么?'
}

var result = asyncReturn();
console.log(result); //Promise { 'async返回的是什么?' }

image

从结果中可以看到async函数返回的是一个promise对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

如果async函数没有返回值:

async function testAsync1() {
    console.log("hello async");
}
let result1 = testAsync1();
console.log(result1);

image

await做了什么处理

从字面意思上看await就是等待,await 等待的是一个表达式,这个表达式的返回值可以是一个promise对象也可以是其他值。

很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async函数中等待await 后面表达式的返回值,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入promise队列(Promise的Job Queue)。

async/await执行顺序

function testSometing() {
    console.log("2-执行testSometing");  //3--> 打印2-执行testSometing
    return "5-testSometing";            //4--> 返回值,await让出线程,向下执行
}

async function testAsync() {
    console.log("6-执行testAsync");     //11-->打印6-执行testAsync
    return Promise.resolve("8-hello async"); //12-->返回值,await让出线程,执行别的代码(promise)
}

async function test() {                 //整体从调用test开始
    console.log("1-test start...");     //1-->打印1-test start...
    const v1 = await testSometing();    //2-->执行到这去执行testSometing()
    console.log(v1);                    //9-->打印v1:5-testSometing
    const v2 = await testAsync();       //10-->执行testAsync()
    console.log(v2);                    //14-->打印8-hello async
    console.log(v1, v2);                //15-->打印5-testSometing,8-hello async,结束
}


test();

var promise = new Promise((resolve)=> { //5-->执行promise
console.log("3-promise start..");       //6--> 打印3-promise start..
resolve("7-promise");});//关键点2       //7-->将promise放入promise的队列
promise.then((val)=> console.log(val)); //13-->打印7-promise,返回test()

console.log("4-test end...")            //8-->打印4-test end... 本轮事件循环执行结束,跳回到async函数test()中。

加上seTimeout看看结果如何:

    	async function async1() {
            console.log("async1 start");
            await  async2();
            console.log("async1 end");
 
        };
        async  function async2() {
           console.log( 'async2');
        };
        
        console.log("script start");
        
        setTimeout(function () {
            console.log("settimeout");
        },0);
        
        async1();
        
        new Promise(function (resolve) {
            console.log("promise1");
            resolve();
        }).then(function () {
            console.log("promise2");
        });
        console.log('script end');

image

await 让出线程后边的未执行代码执行完后,在执行微任务。

再看一个例子:

var a = 0
var b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
  a = (await 10) + a
  console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1

输出结果:

1 1
2 10
3 20

解析:

  • 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为在 await 内部实现了 generatorsgenerators 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
  • 因为 await 是异步操作,遇到await就会立即返回一个pending状态的Promise对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行 console.log('1', a)
  • 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 10
  • 然后后面就是常规执行代码了