迭代器(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所有失败