ES6知识点总结

43 阅读12分钟

迭代器(Iterator)

迭代器(Iterator)是ES6的一种新的数据结构,它提供了一种统一的遍历机制,可以用来遍历不同类型的数据。迭代器是一个对象,它提供了一种顺序访问集合中每个元素的方式。通过调用迭代器的next()方法,可以依次获取集合中的每个元素,并返回一个包含value和done属性的对象,value表示当前元素的值,done表示是否已经遍历完所有元素。

作用:迭代器提供了一种统一的访问机制,使得我们可以使用相同的方式来访问不同的类型的数据结构,无论是数组,字符串,Set,Map,还是自定义对象,只要实现了迭代器接口,就可以使用for of或者手动调用next()来进行遍历

可迭代对象(Iterable):自身或原型链上存在Symbol.iterator方法的对象被称为可迭代对象,自身或原型链本就内置Symbol.iterator方法的对象被称为内置可迭代对象

迭代协议:可迭代协议(Iterable protocol) 迭代器协议(Iteartor protocol)

可迭代协议(Iterable protocol): 可以自定义自己的迭代行为的协议,一个对象本身或原型链上存在一个可以通过Symbol.iterator调用的iterator属性,那么这个对象就是可迭代对象。

迭代器协议(Iteartor protocol):定义了迭代器的迭代方式,调用next()方法,返回对应的{value, done}结构。

自定义一个迭代器

const bears = ['ice', 'panda', 'grizzly']

function createArrIterator(arr) {
  let index = 0

  return {
    next() {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      }

      return { done: true, value: undefined }
    }
  }
}

let iter = createArrIterator(bears)

console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

查看内置的Symbol.iterator方法

const bears = ['ice', 'panda', 'grizzly']
//数组的Symbol.iterator方法
const iter = bears[Symbol.iterator]()

console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

const nickName = 'ice'
//字符串的Symbol.iterator方法
const strIter = nickName[Symbol.iterator]()

console.log(strIter.next())
console.log(strIter.next())
console.log(strIter.next())
console.log(strIter.next())

调用某些可迭代对象的Symbol.iterator方法,我们发现返回了一个对象,这个对象具有一个next方法。这就证明了上面的观点,只要符合可迭代协议的,那么就一定是可迭代对象。

实现一个可迭代对象

const info = {
  bears: ['ice', 'panda', 'grizzly'],
  [Symbol.iterator]: function() {
    let index = 0
    let _iterator = {
       //这里一定要箭头函数,或者手动保存上层作用域的this
       next: () => {
        if (index < this.bears.length) {
          return { done: false, value: this.bears[index++] }
        }
  
        return { done: true, value: undefined }
      }
    }

    return _iterator
  }
}

let iter = info[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())

//符合可迭代对象协议 就可以利用 for of 遍历
for (let bear of info) {
  console.log(bear)
}
//ice panda grizzly

info是一个对象,对象是不能用for of方法遍历的,但是部署了Symbol.iterator后就能被for of方法遍历了,说明info已经是一个可迭代对象了

生成器(Generator)

生成器Generator是ES6的一种特殊的函数,它使用function* 语法进行定义,在内部使用yield关键字来暂停函数的执行,并返回一个包含value和done属性的对象。生成器函数返回一个生成器,具有next方法,生成器是一种特殊的迭代器。

function* generatorFunc() {
  yield 'Hello';
  yield 'World';
}
let generator = generatorFunc();
console.log(generator.next()); // { value: 'Hello', done: false }
console.log(generator.next()); // { value: 'World', done: false }
console.log(generator.next()); // { value: undefined, done: true }

只有当调用next()时,代码才会开始执行,遇到yield就会暂停,直到再次调用next()。

next方法可以传参,传的参数会作为上一个yield的返回值,第一次调用next时不能传参或者传了也没用,因为第一次next前面没有yield。

生成器可以return方法,会返回其携带的参数,并结束Generator。

生成器内部也可以直接return,return的值会作为第一次done为true的值

快速把一个对象变为可迭代对象

const obj = {};

obj[Symbol.iterator] = function* () {
    yield "a";
    yield "b";
    yield "c";
};
for (const value of obj) {
    //a
    //b
    //c
    console.log(value);
}

生成器和迭代器的结合

迭代器写法
function createArrayIterator(arr) {
  let index = 0
  return {
    next: function() {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      } else {
        return { done: true, value: undefined } 
      }
    }
  }
}

// 1.生成器来替代迭代器
function* createArrayIterator(arr) {

  // 3.第三种写法 yield*
  // yield* arr

  // 2.第二种写法
  // for (const item of arr) {
  //   yield item
  // }
  
  // 1.第一种写法
  for(let i = 0; i < arr.length; i++) {
    yield arr[i];
  }
}

const names = ["zh", "llm", "zhllm"]
const namesIterator = createArrayIterator(names)

console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())

可迭代对象的终极封装

class myInfo {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }

  *[Symbol.iterator]() {
    yield* this.friends
  }
}

const info = new myInfo('ice', 22, ['panda','grizzly'])

for (let bear of info) {
  console.log(bear)
}

//panda
//grizzly

精髓:迭代器里面或者Symbol.iterator方法里面写yield,是快速生成迭代器的最好方法。

Class

一、什么是类

类是用于创建对象的模板,类只是让对象原型的写法更加清晰、更像面向对象编程的语法。

看个例子

class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 方法
  say() {
    console.log("我能说话");
  }
}
// 实例化
let zs = new Person("张三", 24);
// 实例化
let ls = new Person("李四", 24);
console.log(zs); //Person {name: '张三', age: 24}
console.log(ls); //Person {name: '张三', age: 24}

是不是跟构造函数很像?

下面我们会讲类与构造函数之间区别

我们先了解下类的基本用法。

二、类的基本用法

2.1、定义类

类是“特殊的函数”

就像定义的函数表达式和函数声明一样

类语法有两个组成部分:类表达式和类声明。

// 类声明
class Point {
  constructor() {}
}

// 类表达式
let Point = {
  constructor() {}
};
2.2、类不会变量提升

函数声明和类声明之间的一个重要区别

函数声明会提升,类声明不会。

需要先声明类,然后再访问它。

// 构造函数会变量提升
let son = new Person("zs", 24);
// Person {name: 'zs', age: 24}

// 类不会变量提升,导致引用异常
let classSon = new ClassPerson("classZs", 48);
// Uncaught ReferenceError: Cannot access 'ClassPerson' before initialization

// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 类
class ClassPerson {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
2.3、constructor() 方法

一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

class Point {}

// 等同于
class Point {
  constructor() {}
}

constructor()方法什么时候被执行呢?在实例化的时候会自动调用该方法。constructor()方法默认返回实例对象(this)

class Point {
  constructor() {
    // 通过new命令生成对象实例时,会执行constructor方法
    console.log("我执行了");
    // 返回的this是实例对象
    console.log(this);
  }
}

let p = new Point();

类的实例化一定要使用new,否则会报错。这也是跟构造函数的一个主要区别。

// 构造函数
function Point1() {}

// 可以不使用new,当成普通函数执行
let p1 = Point1();

// 类
class Point {
  constructor() {
    console.log("我执行了");
    console.log(this);
  }
}

// 类不使用new会报错
// Uncaught TypeError: Class constructor Point cannot be invoked without 'new'
let p = Point();
2.4、静态方法(属性)

类相当于实例的原型,所有在类中定义的方法(属性),都会被实例继承。

如果在一个方法(属性)前,加上static关键字,就表示该方法(属性)不会被实例继承,而是直接通过类来调用。

class Person {
  static personAge = 28;

  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  static getAge(age) {
    return this.personAge + age;
  }
}

let zs = new Person("zs", 28);

// 静态属性只能通过类来访问
console.log(Person.personAge); // 28
// 静态属性实例不能使用
console.log(zs.personAge); // undefined

// 静态方法只能通过类来访问
Person.getAge(28);
// 静态方法实例不能使用
// zs.getAge();
// Uncaught TypeError: zs.getAge is not a function

// 执行会报错,因为this在严格模式下是underfined
// 这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到getAge方法而报错。
let getAge = Person.getAge;
getAge(18);
// Uncaught TypeError: Cannot read properties of undefined (reading 'personAge')

尽管静态方法(属性)不能被实例使用,但是父类的静态方法,可以被子类继承(继承那边会介绍)。

2.5、私有方法(属性)

私有方法(属性),是只能在类的内部访问的方法和属性,外部不能访问

这也是比较常见的需求,有利于代码的封装。 然而私有方法(属性)的定义之前一直不是很友好,在ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示。

class Person {
  // 私有属性
  #name = "我能说话了";

  // 私有方法
  #say() {
    // 引用私有属性
    console.log(this.#name);
  }

  // 可能这样间接调用私有方法
  indirectSay() {
    this.#say();
  }
}

let p = new Person();

// p.#name
// 报错 Uncaught SyntaxError: Private field '#name' must be declared in an enclosing class
// p.#say()
// 报错 Uncaught SyntaxError: Private field '#say' must be declared in an enclosing class
// 间接调用
p.indirectSay();
// 我能说话了

当然,如果在私有方法(属性)前面加上static关键字,表示这是一个静态的私有方法(属性)。

2.6、类的继承

类可以通过extends关键字实现继承,让子类继承父类的属性和方法。

ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。

这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。

如果不调用super()方法,子类就得不到自己的this对象

2.7、super关键字

super这个关键字,既可以当作函数使用,也可以当作对象使用。

2.7.1、当作函数使用

当作函数使用super代表父类的构造函数

子类的构造函数必须执行一次super()函数。

作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错。

class Person {
  constructor() {
    console.log("person");
  }
}

class Son extends Person {
  constructor() {
    // 必须执行一次
    super();
  }

  say() {
    super();
    // Uncaught SyntaxError: 'super' keyword unexpected here (a
  }
}
2.7.2、当作对象使用。

super作为对象时,在普通方法中,指向父类的原型对象

class Person {
  constructor() {
    console.log("person");
  }

  say() {
    console.log("我能说话");
  }
}

class Son extends Person {
  constructor() {
    super();
  }

  say() {
    // 在普通方法中,指向父类的原型对象
    super.say();
  }
}

//
let son = new Son();
son.say();
// 我能说话

由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

class Person {
  constructor(name) {
    this.name = name;
    console.log("person");
  }

  say() {
    console.log("我能说话");
  }
}

class Son extends Person {
  constructor(name) {
    super(name);
  }

  say() {
    console.log(super.name);
  }
}

//
let son = new Son("zs");
// 由于name是父类的实例属性,不是原型属性,使用super获取不到
son.say();
// undefined
2.7、静态方法(属性)继承

通过super()方法,子类可以继承父类的静态方法(属性)

class Person {
  static count = 100;
  static obj = { name: "zs obj" };

  constructor() {
    // 实例化时被调用
    console.log("person");
  }

  say() {
    console.log("我能说话了");
  }
}

class Son extends Person {
  constructor() {
    super();
  }
}

// 子类继承了父类的静态属性
Son.count--;
Son.obj.name = "son obj";
// 子类继承了父类的静态属性是浅拷贝,如果父类的静态属性的值是一个对象,那么子类的静态属性也会指向这个对象,因为浅拷贝只会拷贝对象的内存地址。
console.log("Son.count:%s", Son.count);
// Son.count:99
console.log("Son.obj:%o", Son.obj);
// Son.obj:{name: 'son obj'}
console.log("Person.count:%s", Person.count);
// Person.count:100
console.log("Person.obj:%o", Person.obj);
// Person5.obj:{name: 'son obj'}
//  Object.getPrototypeOf()方法可以用来从子类上获取父类。
Object.getPrototypeOf(Son) === Person;
2.8、私有方法(属性)继承

子类无法通过super()继承父类的私有方法(属性)

class Foo {
  #p = 1;
  #m() {
    console.log("hello");
  }
}

class Bar extends Foo {
  constructor() {
    super();
    console.log(this.#p); // 报错
    this.#m(); // 报错
  }
}

但是子类可以通过父类的方法间接访问父类的私有方法(属性),说白了还是不能直接访问呗,只能通过定义它的类来使用。

class Foo {
  #p = 1;
  getP() {
    return this.#p;
  }
}

class Bar extends Foo {
  constructor() {
    super();
    console.log(this.getP()); // 1
  }
}

Promise

Promise 是异步编程的一种解决方案: 从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。 promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。

一个Promise必然会处于以下三个状态之一:

  • pending:初始状态,既没有被兑现,也没有被拒绝;
  • fullfilled:意味着操作成功完成;
  • rejected:意味着操作失败;
let p = new Promise((resolve, reject) => {
    //做一些异步操作
    setTimeout(() => {
        console.log('执行完成');
        resolve('我是成功!!');
    }, 2000);
});

Promise的状态一经改变就不能再改变。

const promise = new Promise((resolve, reject) => {
  resolve("success1");
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  })
  
 // "then: success1"

.then.catch都会返回一个新的Promise

Promise.resolve(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    return 3;
  })
  .then(res => {
    console.log(res);
  });
  
1
2


Promise.reject(1)
  .then(res => {
    console.log(res);
    return 2;
  })
  .catch(err => {
    console.log(err);
    return 3
  })
  .then(res => {
    console.log(res);
  });
  
1
3

catch不管被连接到哪里,都能捕获上层未捕捉过的错误

const promise = new Promise((resolve, reject) => {
  reject("error");
  resolve("success2");
});
promise
.then(res => {
    console.log("then1: ", res);
  }).then(res => {
    console.log("then2: ", res);
  }).catch(err => {
    console.log("catch: ", err);
  }).then(res => {
    console.log("then3: ", res);
  })

"catch: " "error"
"then3: " undefined

Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)

.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获

Promise.resolve().then(() => {
  return new Error('error!!!')
}).then(res => {
  console.log("then: ", res)
}).catch(err => {
  console.log("catch: ", err)
})

"then: " "Error: error!!!"

如果你抛出一个错误的话,可以用下面👇两的任意一种:

return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

1

.then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch.then第二个参数的简便写法

Promise.reject('err!!!')
  .then((res) => {
    console.log('success', res)
  }, (err) => {
    console.log('error', err)
  }).catch(err => {
    console.log('catch', err)
  })
'error' 'error!!!'


Promise.reject('error!!!')
  .then((res) => {
    console.log('success', res)
  }).catch(err => {
    console.log('catch', err)
  })
'catch' 'error!!!'
function promise1 () {
  let p = new Promise((resolve) => {
    console.log('promise1');
    resolve('1')
  })
  return p;
}
function promise2 () {
  return new Promise((resolve, reject) => {
    reject('error')
  })
}
promise1()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally1'))

promise2()
  .then(res => console.log(res))
  .catch(err => console.log(err))
  .finally(() => console.log('finally2'))
  
'promise1'
'1'
'error'
'finally1'
'finally2'

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
async1();
console.log('start')

'async1 start'
'async2'
'start'
'async1 end'

紧跟着await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  setTimeout(() => {
    console.log('timer')
  }, 0)
  console.log("async2");
}
async1();
console.log("start")

'async1 start'
'async2'
'start'
'async1 end'
'timer'
async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
  setTimeout(() => {
    console.log('timer1')
  }, 0)
}
async function async2() {
  setTimeout(() => {
    console.log('timer2')
  }, 0)
  console.log("async2");
}
async1();
setTimeout(() => {
  console.log('timer3')
}, 0)
console.log("start")


'async1 start'
'async2'
'start'
'async1 end'
'timer2'
'timer3'
'timer1'
const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6);
            console.log(p)
        }, 0)
        resolve(1);
    });
    resolve(2);
    p.then((arg) => {
        console.log(arg);
    });
}));
first().then((arg) => {
    console.log(arg);
});
console.log(4);

3
7
4
1
2
5
Promise{<resolved>: 1}

Promise.all 全部成功返回全部成功的值,一个失败返回失败的值

Promise.race 任一一个成功或者失败就返回成功或失败的值

Promise.allSettled 全部都有了结果,不管是成功还是失败,全部的结果都resolve出去,allSettled永远不会reject

Promise.any 一个成功就resolve成功,全部失败才reject所有失败