引子:ES新特性已经是时代发展所必须掌握的知识,主流浏览器几乎全部支持,不支持还有Babel,怕啥?
此文涉及的知识倾向于面试总结,而非零基础学习,其中的ES6-ES12的叫法更准确的应该叫ES2015、ES2016....
let声明变量,const声明常量,且声明时必须初始化,后续常量可以修改(例如对象属性),不能重新赋值
推荐尽量使用const
共同特性:
- 块级作用域({}、for、while、do while、if、switch)
- 不允许同一作用域重复声明
- 不存在变量提升
- 暂时性死区
- 不会成为window上的属性和方法
使用反引号``,在其里面使用${expression}动态嵌入内容
模板字符串标签函数:
includes()
startsWith()
endsWith()
const message = 'Error: foo is not undefined.'
console.log(
message.startsWith('Error'),
message.endsWith('undefined.'),
message.includes('foo')
) // true true true
复制代码
箭头函数格式:
const/let 函数名 = 参数 => 函数体
一般函数写成箭头函数:
function add() {} ===> const add = function () {} ==> const add = () => {}
箭头函数其内部this指向永远指向上层作用域的this,
箭头函数不适应场景:
作为构造函数
需要 this 指向调用对象的时候
需要arguments (可用剩余参数...替代解决)
数组:
// 数组解构 通过索引按顺序匹配解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 数组本质是特殊的对象,因此可以对数组进行对象属性的解构
const arr = [1, 2, 3];
const { 0: first, [arr.length - 1]: last } = arr;
console.log(first, last); // 1 3
复制代码
// 默认值
const [a, b, c = "defaultVal"] = [1, 2];
console.log(a, b, c); // 1 2 defaultVal
复制代码
对象:
// 对象解构赋值 键名进行配对
const { n: name } = { n: "yunmu", age: 18 };
console.log(name); // yunmu
复制代码
// 连续解构赋值 + 重命名 + 默认值
const {
name: handSome,
username = "linduidui",
friend: { n: n1 },
} = { name: "yunmu", age: 18, friend: { n: "baojiejie" } };
console.log(handSome, username, n1); // yunmu linduidui baojiejie
复制代码
字符串:
// 既可以使用对象解构也可以使用数组解构
const [a, b, , , c] = "hello";
console.log(a, b, c); // h e o
const { 0: a, 1: b, length } = "hello";
console.log(a, b, length); // h e 5
复制代码
其他类型数据例如数值布尔值会先转换为对应包装对象然后可以使用对象解构的方式解构出一些内置方法,例如toString:s
// 剩余参数与解构赋值结合,...解构只能用于最后一个位置
const [a, ...c] = [1, 2, 3];
console.log(c); // [ 2, 3]
复制代码
// 剩余参数与解构赋值结合,...解构只能用于最后一个位置
const { x, y, ...z } = { a: 3, x: 1, y: 2, b: 4 };
console.log(x, y, z); // 1 2 { a: 3, b: 4 }
复制代码
当没有剩余参数函数如果想接受传递的所有实参,可以使用 arguments
但是箭头函数没有 arguments ,剩余参数横空出世,只能在形参最后一位使用一次
const foo = (...args) => {
console.log(args)
}
foo(1, 2, 3) // [ 1, 2, 3 ]
复制代码
数组:
const arr = ["foo", "bar", "baz"];
console.log(...arr); // foo bar baz
console.log([...arr]); // 浅克隆 ['foo', 'bar', 'baz']
console.log({ ...arr }); // {0: 'foo', 1: 'bar', 2: 'baz'}
复制代码
对象:
const yunmu = {
name: "云牧",
age: 18,
};
console.log({ ...yunmu }); // 对象展开必须放在 {} 中,浅克隆
复制代码
const yunmu = {
name: "云牧",
age: 18,
};
const xiyan = {
name: "林怼怼",
age: 16,
school: "中心小学",
};
// 新对象拥有全部属性,相同属性,后者覆盖前者
console.log({ ...yunmu, ...xiyan }); // { name: "林怼怼", age: 16, school: "中心小学" }
复制代码
注:如果展开不是对象,则自动转换为对象再罗列
字符串:
console.log({ ...'alex' }); // { '0': 'a', '1': 'l', '2': 'e', '3': 'x' }
console.log([...'alex']); // [ 'a', 'l', 'e', 'x' ]
console.log(...'alex'); // a l e x
复制代码
不传参数,或者明确的传递 undefined 作为参数,只有这两种情况下,默认值才会生效,参数列表最好从右往左设置默认值
const multiply = (x, y = 1) => x * y;
console.log(multiply(2)); // 2
复制代码
const bar = 666;
const func = () => "age2";
const obj = {
// bar: bar,
bar, // 同上一行效果
// method1: function () {
// console.log(`method1: ${this}`)
// },
// 同上面的冒号属性函数写法
method2() {
console.log(`method2: ${this}`);
},
[func()]: 123, // 计算属性名
["s" + "ex"]: "male", // 计算属性名
};
console.log(obj); // {bar: 666, age2: 123, sex: 'male', method2: ƒ}
复制代码
for...of
循环使用// Object.assign
const target = {
a: 11,
b: 22,
};
const source1 = {
a: 123,
b: 456,
};
const source2 = {
a: 333,
c: 33,
};
// 合并到第一个对象返回
const result = Object.assign(target, source1, source2);
console.log(result); // { a: 333, b: 456, c: 33 }
console.log(result === target); // true
复制代码
// Object.keys()、Object.values() 和 Object.entries()
const person = {
name: "云牧",
age: 18,
};
console.log(Object.keys(person)); //得到对象键组成的数组 ['name', 'age']
console.log(Object.values(person)); //得到对象值组成的数组 ['云牧', 18]
console.log(Object.entries(person)); //得到对象键值组成的二维数组 [['name', '云牧'], ['age', 18]]
// 解构遍历的键值对
for (const [key, value] of Object.entries(person)) {
console.log(key, value); // name yunmu age 18
}
// 数组有实例方法跟上面类似,只不过返回Iterator,但同样能用for...of遍历
console.log( [1, 2].keys());
console.log( [1, 2].values());
console.log( [1, 2].entries());
复制代码
// Object.is
console.log(
0 === false, // false
0 == false, // true
+0 ===-0, // true
NaN === NaN, // false
Object.is(+0, -0), // false
Object.is(NaN, NaN) // true
)
复制代码
const p1 = {
firstName: 'yun',
lastName: 'mu',
get fullName() {
return this.firstName + ' '+ this.lastName
}
}
const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
/*
{
firstName: { value: 'yun', writable: true, enumerable: true, configurable: true },
lastName: { value: 'mu', writable: true, enumerable: true, configurable: true },
fullName: {
get: [Function: get fullName],
set: undefined,
enumerable: true,
configurable: true
}
}
*/
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'lin'
console.log(p3.fullName) // lin mu
复制代码
const arr = ["yun", "mu", "dsb"];
// keys() 得到的是索引的可遍历对象,可以遍历出索引值
for (const key of arr.keys()) {
console.log(key); // 0 1 2
}
//values() 得到的是值的可遍历对象,可以遍历出值,这里不用values一样的效果
for (const item of arr) {
cconsole.log(item) // // item为每个实例,相当于数组的forEach
}
//搭配解构赋值
for (const [index, value] of arr.entries()) {
console.log(index, value); // 0 yun 1 mu 2 dsb
}
复制代码
只要有 Symbol.iterator 方法,并且这个方法可以生成可遍历对象,就是原生可遍历的,就可以使用for-of循环遍历,例如:
使用了iterator接口的场景:
迭代器的价值在于自定义暴露给外面统一遍历的接口,让外部不用关心内部的数据结构是如何:
// todos 实现可迭代接口 Iterable
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '英语'],
work: ['喝茶'],
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback (item)
}
},
// 实现迭代器接口
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
// 必须返回迭代器对象
return {
// 必须要有next方法
next: function () {
// 迭代结果接口 IterationResult
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
todos.each(function (item) {
console.log(item)
})
console.log('---------')
for(const item of todos) {
console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶
// ---------
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶
复制代码
Set是一系列无序、没有重复值的数据集合(类似没有重复成员的数组)
可以通过传入一个数组初始化一个 Set 实例或者通过 add 添加元素,重复元素会被忽略
// 例1
const set = new Set([1, 2, 3, 4, 4]);
console.log(set) // Set(4) {1, 2, 3, 4}
// 例2
const set = new Set();
[2, 3, 5, 4, 5, 8, 8].forEach(item => set.add(item));
for (let item of set) {
console.log(item);
}
// 2 3 5 4 8
复制代码
Set 实例的属性和方法有:
size
:获取元素数量add(value)
:添加元素,返回 Set 实例本身delete(value)
:删除元素,返回一个布尔值,表示删除是否成功has(value)
:返回一个布尔值,表示该值是否是 Set 实例的元素clear()
:清除所有元素,没有返回值const s = new Set();
s.add(1).add(2).add(2); // 添加元素
s.size // 2
s.has(1) // true
ss.has(3) // false
s.delete(1);
s.has(2) // false
s.clear();
console.log(s);
复制代码
Set 实例的遍历,可使用如下方法:
keys()
:返回键名的遍历器values()
:返回键值的遍历器。不过由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys()
和values()
返回结果一致entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员let set = new Set(['aaa', 'bbb', 'ccc']);
for (let item of set.keys()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.values()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.entries()) {
console.log(item);
}
// ["aaa", "aaa"]
// ["bbb", "bbb"]
// ["ccc", "ccc"]
set.forEach((value, key) => console.log(key + ' : ' + value))
// aaa : aaa
// bbb : bbb
// ccc : ccc
复制代码
Map 和 对象 都是键值对的集合,但是对象一般用字符串或者 Symbol 当作键名,但是 Map 任意基本都能当键名:
const map = new Map();
const obj = {p: 'Hello World'};
map.set(obj, 'OK')
map.get(obj) // "OK"
map.has(obj) // true
map.delete(obj) // true
map.has(obj) // false
复制代码
Map 实例的属性和方法如下:
size
:获取成员的数量set
:设置成员 key 和 valueget
:获取成员属性值has
:判断成员是否存在delete
:删除成员clear
:清空所有const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);
map.size // 2
map.get('aaa') // 100
map.has('aaa') // true
map.delete('aaa')
map.has('aaa') // false
map.clear()
复制代码
Map 实例的遍历方法有:
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回所有成员的遍历器forEach()
:遍历 Map 的所有成员const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);
for (let key of map.keys()) {
console.log(key);
}
// "aaa"
// "bbb"
for (let value of map.values()) {
console.log(value);
}
// 100
// 200
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// aaa 100
// bbb 200
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// aaa 100
// bbb 200
复制代码
Set/Map构造函数的参数:
除此之外还有 WeakSet、WeakMap,
主要作用就是为对象添加独一无二的属性名:
// 创建Symbol
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol
// 独一无二
console.log(Symbol() === Symbol()) // false
// 后面字符串为 Symbol 实例添加描述,主要方便区分,用处不大
console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
console.log(Symbol('baz')) // Symbol(baz)
// Symbol当对象的键名
const obj = {}
obj[Symbol()] = 123
obj[Symbol()] = 456
console.log(obj) // { [Symbol()]: 123, [Symbol()]: 456 }
// // 作为私有成员防止被访问
const name = Symbol()
const person = {
[name]: 'yunmu',
say(){
console.log(this[name])
}
}
person.say()// yunmu
console.log(person[Symbol()])
// Symbol.for 新建对象,如果之前使用字符串创建过则复用之前的
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(
s1 === s2, // true
// Symbol.for的参数会被转化为字符串
Symbol.for(true) === Symbol.for('true'), // true
)
const obj2 = {
// 为对象实现迭代器时会用到
[Symbol.toStringTag]: 'XObject'
}
console.log(obj2.toString()) // [object Object] [object XObject]
// 遍历
const obj3 = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
// 只能遍历普通属性
for(const key in obj3) {
console.log(key) // foo
}
console.log(Object.getOwnPropertySymbols(obj3)) // [ Symbol() ]
//只能遍历Symbol属性名
for(let key of Object.getOwnPropertySymbols(person)){
console.log(key);
}
//既能取到普通属性又有Symbol属性
for(let key of Reflect.ownKeys(person)){
console.log(key);
}
console.log(Object.keys(obj3)) // [ 'foo' ]
console.log(JSON.stringify(obj3)) // {"foo": "normal value"}
复制代码
// Proxy
const person = {
name: 'yunmu',
age: 18
}
const personProxy = new Proxy(person, {
// 参数为目标对象、属性名
get (target, property) {
console.log(target, property) // { name: 'yunmu', age: 18 } name
return property in target ? target[property]: 'default'
},
// 参数为目标对象、属性名、属性值
set (target, property, value) {
console.log(target, property, value) // { name: 'yunmu', age: 18 } gender true
if(property === 'age') {
// 判断给定的参数是否为整数
if(!Number.isInteger(value)) {
throw new TypeError(`${value} is not an int`)
}
}
}
})
personProxy.gender = true
// personProxy.age = '11' // TypeError: 11 is not an int
personProxy.age = 11
console.log(personProxy.name) // yunmu
console.log(personProxy.xxx) // default
复制代码
Proxy对比Object.defineProperty:
const person = {
name: 'yunmu',
age: 18
}
const personProxy = new Proxy(person, {
deleteProperty(target, property) {
console.log('delete', property) // delete age
delete target[property]
}
})
delete personProxy.age
复制代码
const list = []
const listProxy = new Proxy(list, {
set(target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
// set 0 100
// set length 1
复制代码
Reflect属于静态类(如 Math ),不能 new,只能调用静态方法:Reflect.get()
Reflect 内部封装了一系列对对象的底层操作,Reflect 成员方法就是 Proxy 处理对象的默认实现
const proxy = new Proxy(obj, {
get(target, property) {
// 不写get逻辑,相当于调用Reflect.get(target, property)。
return Reflect.get(target, property)
}
})
复制代码
Reflect统一提供一套用于操作对象的API:
const obj = {
foo: '111',
bar: 'rrr',
age: 18
}
// console.log("age" in obj)
// console.log(delete obj['bar'])
// console.log(Object.keys(obj))
console.log(Reflect.has(obj, 'name')) // false
console.log(Reflect.deleteProperty(obj, 'bar')) // true
console.log(Reflect.ownKeys(obj)) // [ 'foo', 'age' ]
复制代码
Promise
可以将回调变成链式调用写法三个状态:pending
、fulfilled
、rejected
两个过程:
三个方法:then、catch、finally
Promise静态方法:
Array.from()
:将伪数组或可遍历对象转换为真数组
console.log(Array.isArray(document.getElementsByTagName('button')));
复制代码
Array.prototype.includes()
:判断一个数组是否包含一个指定的值,包含true,否则返回 falseconst arr = ['foo', 1, false, NaN]
// 以前使用indexOf, 存在则返回下标,不存在则返回-1, 缺点是无法判断NaN
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf(1)) // 1
console.log(arr.indexOf(false)) // 2
console.log(arr.indexOf(NaN)) // -1
console.log(arr.includes('foo')) // true
console.log(arr.includes(1)) // true
console.log(arr.includes(false)) // true
console.log(arr.includes(NaN)) // true
复制代码
Array.prototype.fill
:一个固定值填充一个数组中从起始索引到终止索引内的全部元素,不包括终止索引const arr = [1, 2, 3, 4];
// 三个参数: 填充数组元素的值,起始索引(默认0),终止索引(默认this.length)
console.log(arr.fill(0, 2, 4)) // [1, 2, 0, 0]
console.log(arr.fill(5, 1)); // [1, 5, 5, 5]
console.log(arr.fill(6)); // [6, 6, 6, 6]
复制代码
后面的forEach、map 、filter、some、every、find、findIndex的参数都一样,举个例子:
// callback的三个参数:遍历项 索引 数组本身,thisArg则可以指定callback里的this变量
arr.filter(callback(element[, index[, array]])[, thisArg])
复制代码
Array.prototype.forEach
:新加的数组遍历方法const arr = [1, 2, 3, 4, 5]
arr.forEach((item, index, arr) => {
console.log(item, index, arr)
})
/*
1 0 [ 1, 2, 3, 4, 5 ]
2 1 [ 1, 2, 3, 4, 5 ]
3 2 [ 1, 2, 3, 4, 5 ]
4 3 [ 1, 2, 3, 4, 5 ]
5 4 [ 1, 2, 3, 4, 5 ]
*/
// 绑定个 this 瞧瞧, 注意如果回调函数是箭头函数则绑定失败
const out = [];
[1, 2, 3].forEach(function(elem) {
// this是out哦
this.push(elem * elem);
}, out);
复制代码
Array.prototype.map()
:其结果是数组中每个元素调用一次提供的 callback 函数后的返回值const mapArr = [1, 2, 3, 4, 5]
// 对每一个元素数值进行翻倍
const newArr = mapArr.map((num, index, arr) => 2 * num)
console.log(newArr) // [ 2, 4, 6, 8, 10 ]
复制代码
Array.prototype.filter()
:过滤数组的元素const filterArr = [1, 2, 3, 4, 5]
// 返回大于3的集合
const newArr = filterArr.filter((num, index, arr) => num > 3)
console.log(newArr) // [ 4, 5 ]
复制代码
Array.prototype.some()
:遍历数组元素,一个返回 true,整体就返回 true,否则就 falseconst someArr = [false, true, false, true, false]
const someArr2 = someArr.some((item, index, arr) => item)
console.log(someArr2) // true
复制代码
Array.prototype.every()
: 遍历数组元素,所有返回 true , 整体才返回 true,不然就 falseconst everyArr = [false, true, false, true, false]
// 三个参数:遍历项 索引 数组本身
const newArr = everyArr.every((item, index, arr) => item)
console.log(newArr) // false
复制代码
Array.prototype.find()
:返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefinedlet arr = [2, 3, 2, 5, 7, 6];
let result = arr.find((item, index, arr) => {
return item > 4; //遍历数组arr,一旦发现有第一个元素大于4,就把这个元素返回
});
console.log(result); // 5
复制代码
Array.prototype.findIndex()
:返回数组中满足提供的测试函数的第一个元素的索引,若没有找到对应元素则返回 -1let arr = [2, 3, 2, 5, 7, 6];
let result = arr.findIndex((item, index, arr) => {
return item > 4; // 遍历数组arr,一旦发现有第一个元素大于4,就把这个元素的index返回
});
console.log(result); // 3
复制代码
Array.prototype.reduce()
:对数组中的每个元素执行一个自己提供的 reducer 函数,将结果汇总成一个值// arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
// accumulator 上一次调用回调时返回的累积值,
// currentValue 数组中正在处理的元素
// index可选 正在处理的当前元素的索引
// array可选 调用reduce()的数组
// initialValue可选 第一次调用 callback函数时的第一个参数的值,没有提供则使用数组第一个元素
const arr = [1, 2, 3, 4];
const initialValue = 0;
const sumWithInitial = arr.reduce(
// 0 + 1 + 2 + 3 + 4
(previousValue, currentValue) => previousValue + currentValue,
initialValue
);
console.log(sumWithInitial); // 10
复制代码
const arr = [1, 2, [3, 4, [3.1, 3.2]], 5]
console.log(arr.flat(1)) // [1, 2, 3, 4, Array(2), 5]
console.log(arr.flat(Infinity)) // [1, 2, 3, 4, 3.1, 3.2, 5]
复制代码
let str = "a";
console.log(str.padStart(5,"cd")); // cdcda
console.log(str.padEnd(5,"cd")); // acdcd
复制代码
const message = " Hello Yun "
console.log(message.trim()) // "Hello Yun"
console.log(message.trimStart()) // "Hello Yun "
console.log(message.trimEnd()) // "Hello Yun"
复制代码
console.log(2 ** 5) // 32
复制代码
基本写法:
class Name {...}
这种形式,和函数的写法完全不一样constructor
函数中,constructor
即构造器,初始化实例时默认执行add() {...}
这种形式,并没有function
关键字class Person {
constructor(name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('yunmu')
p.say()
// 可以改造为ES5构造函数的形式
// function Person(name) {
// this.name = name
// }
// Person.prototype.say = function() {
// console.log(`hi, my name is ${this.name}`)
// }
// const p = new Person('yunmu')
// p.say() // hi, my name is jal
复制代码
静态方法, this 指向当前类型,而不是实例:
class Person {
constructor(name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
static create(name) {
// this 指向当前类型,而不是实例
console.log(this) // [Function: Person]
return new Person(name)
}
}
const tom = Person.create('tom')
tom.say() // hi, my name is tom
复制代码
继承使用 extends 关键词,子类的constructor
一定要执行super()
,以调用父类的constructor
class Student extends Person {
constructor(name, number){
super(name)
this.number = number
}
hello () {
// super 代表父类的原型对象 Person.prototype
// 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例
super.say()
console.log(`my school number is ${this.number}`)
}
}
const s = new Student('jack', 100)
s.hello()
// hi, my name is jack
// my school number is 100
复制代码
如果只是输出一个唯一的对象,使用export default
即可,代码如下:
// 创建 util1.js 文件,内容如
export default {
a: 100
}
// 创建 index.js 文件,内容如
import obj from './util1.js'
console.log(obj)
复制代码
如果想要输出许多个对象,就不能用default
了,且import
时候要加{...}
,代码如下:
// 创建 util2.js 文件,内容如
export function fn1() {
alert('fn1')
}
export function fn2() {
alert('fn2')
}
// 创建 index.js 文件,内容如
import { fn1, fn2 } from './util2.js'
fn1()
fn2()
复制代码
避免异步编程中回调函数嵌套过深,提供更好的异步编程解决方案
function * foo () {
console.log('yun')
return 100
}
// 这个foo就是一个Generator函数
const result = foo()
console.log(result) // Object [Generator] {}
console.log(result.next())
// yun
// { value: 100, done: true }
// 可以看出生成器对象实现了Iterator接口
复制代码
配合yield关键词使用:
生成器函数会返回一个生成器对象,调用这个生成器对象的 next 方法,才会让函数体执行
一旦遇到了 yield 关键词,函数的执行则会暂停下来,next 函数的参数作为 yield 结果返回
如果继续调用函数的 next 函数,则会再上一次暂停的位置继续执行,直到函数体执行完毕,next 返回的对象的 done 就变成了 true
function * fn () {
console.log(111)
yield 100
console.log(222)
yield 200
console.log(333)
yield 300
}
const generator = fn()
console.log(generator.next())
// 111
// { value: 100, done: false }
console.log(generator.next())
// 222
// { value: 200, done: false }
console.log(generator.next())
// 333
// { value: 300, done: false }
复制代码
案例1:发号器:
function * createIdMaker () {
let id = 1
while(true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
console.log(idMaker.next())
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// { value: 5, done: false }
复制代码
案例2:Generator函数实现迭代器Iterator:
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '英语'],
work: ['喝茶'],
// 实现迭代器接口
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for (const item of all) {
yield item
}
}
}
for(const item of todos) {
console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 英语
// 喝茶
复制代码
Generator实现异步:
// 注意:generator.next(value)中,next传入的参数会作为上一次yield的返回值
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open("GET", url)
xhr.responseType = 'json'
xhr.onload = function () {
if(this.status === 200)
resolve(this.response)
else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
// 生成器函数
function * main () {
const users = yield ajax('/api/users.json')
console.log(users)
const posts = yield ajax('/api/posts.json')
console.log(posts)
const urls = yield ajax('/api/urls.json')
console.log(urls)
}
// 调用生成器函数得到一个生成器对象
const generator = main()
// 递归实现generator.next()的调用,直到done为true终止
function dfs(value) {
const result = generator.next(value)
if(result.done) return
result.value.then(data=>{
console.log(data)
dfs(data)
})
}
dfs()
// 打印结果
// Generator实现异步.js:35 {username: "yibo"}
// Generator实现异步.js:19 {username: "yibo"}
// Generator实现异步.js:35 {posts: "jiailing"}
// Generator实现异步.js:22 {posts: "jiailing"}
// Generator实现异步.js:35 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator实现异步.js:25 {posts: "/api/posts.json", users: "/api/users.json"}
复制代码
封装生成器函数执行器co:
function ajax(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "json";
xhr.onload = function () {
if (this.status === 200) resolve(this.response);
else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
}
// 生成器函数
function* main() {
try {
const users = yield ajax("/api/users.json");
console.log(users);
const posts = yield ajax("/api/posts.json");
console.log(posts);
const urls = yield ajax("/api/urls.json");
console.log(urls);
} catch (e) {
// 如果生成器函数中,发生了异常,会被生成器对象的throw方法捕获
console.log(e);
}
}
// 封装了一个生成器函数执行器
function co(main) {
// 调用生成器函数得到一个生成器对象
const generator = main();
// 递归实现generator.next()的调用,直到done为true终止
function handleResult(result) {
if (result.done) return;
result.value.then(
(data) => {
handleResult(generator.next(data));
},
(error) => {
generator.throw(error);
}
);
}
handleResult(generator.next());
}
co(main);
// Generator实现异步.js:42 {username: "yunmu"}
// Generator实现异步.js:20 {username: "yunmu"}
// Generator实现异步.js:42 {posts: "linduidui"}
// Generator实现异步.js:23 {posts: "linduidui"}
// Generator实现异步.js:42 {posts: "/api/posts.json", users: "/api/users.json"}
// Generator实现异步.js:26 {posts: "/api/posts.json", users: "/api/users.json"}
复制代码
const bigInt = 1929191292091n
复制代码
空值合并操作符(??
)是一个逻辑操作符,当左侧的操作数为 null
或者 undefined
时,返回其右侧操作数,否则返回左侧操作数
const foo = "";
const result1 = foo || "默认值";
const result2 = foo ?? "默认值";
console.log(result1); // 默认值
console.log(result2); // ""
复制代码
可选链作符( ?.
)允许读取位于连接对象链深处的属性的值,在遇到取值为空的情况下返回 undefined
const obj = {
friend: {
boyFriend: {
name: "yunmu",
},
},
};
if (obj.friend && obj.friend.boyFriend) {
console.log(obj.friend.boyFriend.name);
}
// 可选链
console.log(obj.friend?.boyFriend?.name);
复制代码
有时需要获取 JS 环境下全局对象值,但是不同环境不同,浏览器环境相当于window
,Node环境下 global Object
后面对全局对象进行了统一的规范:globalThis
console.log(globalThis)
作者:云牧