ECMAScript新特性

一、JavaScript vs ECMAScript

JavaScript 是一种基于 ECMAScript 规范的脚本语言,并在此基础上进行了自己的封装。ECMAScript 不是一种编程语言,仅仅是一种脚本语言规范,由欧洲计算机协会制定和发布,任何基于此规范实现的脚本语言都要遵守它的约定。
ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现。

二、块级作用域、模板字符串

块级作用域:ES6新增块级作用域。
用let命令新增了块级作用域,外层作用域无法获取到内层作用域,非常安全明了。即使外层和内层都使用相同变量名,也都互不干扰。

模板字符串使用反引号 ( ) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。可以使用多行字符串和字符串插值功能。

三、对象与数组的解构、rest操作符

对象的解构:const {a, b} = obj;
数组的解构:const [first, second] = arr;

ES6引入了rest参数(形式为“…变量名”),用于获取函数的多余参数。

1
const {name, ...others}=a;

rest运算符不能用于箭头函数中;
rest运算符后不能再跟其他参数。

四、函数进阶(箭头函数、默认参数)

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。
箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
箭头函数没有prototype属性。
yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。因此,箭头函数不能用作函数生成器。

五、对象和数组的扩展用法

1.数组的扩展

  • Array.from()
    用于将两类对象转化为真正的数组 1.类似数组的对象(只要据有length属性, 都可以被转化为数组) 2.可遍历(iterable)的对象。
  • Array.of()
    将一组值转化为数组。
  • find和findIndex
    find方法,用于找出第一个符合条件的数组成员,没有找到会返回undefiend,findIndex方法返回的是第一个符合条件成员的位置, 如果找不到返回-1, find和findIndex都可以接受第二个参数,用来绑定回调函数的this。
  • 数组实例的fill()
    fill方法使用给定值,填充一个数组。用于初始化数组值,接受第二个和第三个参数,用于指定开始和结束位置。
  • 数组实例的方法entries() keys()和values()
    这三个方法提供新的方法,用于遍历数组, 都返回一个遍历器对象,可以用for..of循环进行遍历 keys是对键名的遍历,values是对键值的遍历 entries是对键值对的遍历。
  • 数组实例的includes()
    表示某个数组是否包含给定的值, 返回true,否则返回false。

2.对象的扩展

  • 属性的简洁表示
    es6允许直接写入变量和函数,最为对象的属性和方法 主要使用: 1.函数返回值, 2. module.exports导出 3. 属性的setter和getter。

    1
    2
    3
    4
    5
    var obj = {
    foo, // 属性
    methods() { // 方法
    }
    }
  • 属性的遍历
    for..in: 循环遍历对象自身和继承的可枚举变量 (不含Symbol属性)
    Object.keys(obj): 返回一个数组,包含对象自身所有可枚举属性的键名
    Object.getOwnPropertyNames(obj): 返回一个数组,包含对象自身的所有属性的键名
    Object.getOwnPropertySymbols(obj): 返回一个数组,包含自身所有的symbol属性的键名
    Reflect.ownKeys(): 返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举

  • super关键字
    js中this的关键字总是指向函数所在的当前对象,es6新增了另一个类似的关键字super,指向当前对象的原型对象,super只能用在对象的方法中,而且是方法的简写属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const proto = {
    foo: 'hello'
    }
    const obj = {
    foo: 'world',
    find() {
    return super.foo // 访问obj的原型属性的foo属性
    }
    }
    Object.setPrototypeOf(obj, proto) // 设置obj的原型为proto
    obj.find() // 'hello'
  • 对象的新增方法
    Object.is()
    同值相等的算法, 用来比较两个值严格相等 Object.is(‘foo’, ‘foo’) // true Object.is({}, {}) // false
    Object.assign(target, source)
    对象的合并,将原对象所有的可枚举属性,复制到目标对象上

  • __proto__属性,Object.setPrototypeOf() Object.getPrototypeOf()
    js语言对象继承是通过原型链实现的,ES6提供了更多的原型链方法 __proto__属性,Object.getPrototypeOf() 获取原型prototype属性 Object.setPrototypeOf(obj, proto), Object.create() 设置原型属性。

  • Object.keys() Object.Values() Object.entries()
    返回对象属性组成的数组。

六、Proxy、Reflect、Map、Set、Symbol

Proxy

proxy在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截。

1.Proxy简介

Proxy构造函数能够让我们轻松的使用代理模式:

1
var proxy = new Proxy(target, handler);

Proxy构造函数中有两个参数:
target是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。

2.Proxy的处理方法

  • get 获取某个key的值
  • set 设置某个key的值
  • has 使用in操作符判断某个key是否存在
  • apply 函数调用,仅在代理对象为function时有效
  • ownKeys 获取目标对象所有的key
  • construct 函数通过实例化调用,仅在代理对象为function时有效
  • isExtensible 判断对象是否可扩展,Object.isExtensible的代理
  • deleteProperty 删除一个property
  • defineProperty 定义一个新的property
  • getPrototypeOf 获取原型对象
  • setPrototypeOf 设置原型对象
  • preventExtensions 设置对象不可扩展
  • getOwnPropertyDescriptor 获取一个自由属性(不会去原型链查找)的属性描述

3.Proxy相比Object.defineProperty的优势

3.1 支持数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let arr = [1,2,3]
let proxy = new Proxy(arr, {
get (target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
})
proxy.push(4)
// 能够打印出很多内容
// get push (寻找 proxy.push 方法)
// get length (获取当前的 length)
// set 3 4 (设置 proxy[3] = 4)
// set length 4 (设置 proxy.length = 4)

Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。

3.2 针对对象
在数据劫持这个问题上,Proxy 可以被认为是 Object.defineProperty() 的升级版。外界对某个对象的访问,都必须经过这层拦截。因此它是针对 整个对象,而不是 对象的某个属性,所以也就不需要对 keys 进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = {
name: 'Eason',
age: 30
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
proxy.name = 'Zoe' // set name Zoe
proxy.age = 18 // set age 18

3.3 嵌套支持
本质上,Proxy 也是不支持嵌套的,这点和 Object.defineProperty() 是一样的。因此也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let obj = {
info: {
name: 'eason',
blogs: ['webpack', 'babel', 'cache']
}
}
let handler = {
get (target, key, receiver) {
console.log('get', key)
// 递归创建并返回
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
console.log('set', key, value)
return Reflect.set(target, key, value, receiver)
}
}
let proxy = new Proxy(obj, handler)
// 以下两句都能够进入 set
proxy.info.name = 'Zoe'
proxy.info.blogs.push('proxy')

4.应用实例

使用Proxy实现表单校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let person = {
name: 'xiaoming',
age: 30
}
let handler = {
set (target, key, value, receiver) {
if (key === 'name' && typeof value !== 'string') {
throw new Error('用户姓名必须是字符串类型')
}
if (key === 'age' && typeof value !== 'number') {
throw new Error('用户年龄必须是数字类型')
}
return Reflect.set(target, key, value, receiver)
}
}
let boy = new Proxy(person, handler)
boy.name = 'xiaohong' // OK
boy.age = '18' // 报错 用户年龄必须是数字类型

Reflect

Reflect对象和Proxy对象一样,也是es6为了操作对象提供的新api。

Reflect的设计目的:
(1)将Object对象的属于语言内部的方法放到Reflect对象上,即从Reflect对象上拿Object对象内部方法。

(2)将用 老Object方法 报错的情况,改为返回false。
老写法

1
2
3
4
5
6
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}

新写法

1
2
3
4
5
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}

(3)让Object操作变成函数行为
老写法(命令式写法)

1
'name' in Object //true

新写法

1
Reflect.has(Object,'name') //true

(4)Reflect与Proxy是相辅相成的,在Proxy上有的方法,在Reflect就一定有

1
2
3
4
5
6
7
8
9
10
11
12
let target={}
let handler={
set(target,proName,proValue,receiver){
//确认对象的属性赋值成功
let isSuccess=Reflect.set(target,proName,proValue,receiver)
if(isSuccess){
console.log("成功")
}
return isSuccess
}
}
let proxy=new Proxy(target,handler)

Reflect的API

(1)Reflect.get(target,property,receiver)
查找并返回target对象的property属性
例1

1
2
3
4
5
let obj={
name:"chen",
}
let result=Reflect.get(obj,"name")
console.log(result) //chen

例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj={
//属性yu部署了getter读取函数
get yu(){
//this返回的是Reflect.get的receiver参数对象
return this.name+this.age
}
}
let receiver={
name:"shen",
age:"18",
}

let result=Reflect.get(obj,"yu",receiver)
console.log(result) //shen18

(2)Reflect.set(target,propName,propValue,receiver)
设置target对象的propName属性为propValue
例1

1
2
3
4
5
6
let obj={
name:"chen"
}
let result=Reflect.set(obj,"name","shi")
console.log(result) //true
console.log(obj.name) //shi

例2

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj={
age:38,
set setAge(value){
return this.age=value
}
}
let receiver={
age:28
}
let result=Reflect.set(obj,"setAge",18,receiver)
console.log(result) //true
console.log(obj.age) //38
console.log(receiver.age) //18

(3)Reflect.set与Proxy.set联合使用,并且传入receiver,则会进行定义属性操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let obj={
name:"chen"
}
let handler={
set(target,key,value,receiver){
console.log("Proxy拦截赋值操作")
//Reflect完成赋值操作
Reflect.set(target,key,value,receiver)
},
defineProperty(target,key,attribute){
console.log("Proxy拦截定义属性操作")
//Reflect完成定义属性操作
Reflect.defineProperty(target,key,attribute)
}
}
let proxy=new Proxy(obj,handler)
proxy.name="ya"
//Proxy拦截赋值操作
//Proxy拦截定义属性操作

为什么Reflect.set()传入receiver参数,就会触发定义属性的操作?
因为Proxy.set()中的receiver是Proxy的实例,即obj,而Reflect.set一旦传入receiver,就会将属性赋值到receiver上面,也是obj,所以就会触发defineProperty拦截。

(4)Reflect.has(obj,name)
返回target对象是否含有name属性

(5)Reflect.deleteProperty(obj, name)
删除对象的属性

(6)Reflect.construct(target, args)

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
}

//new的写法
let person= new Person('chen')

//construct写法
let person = Reflect.construct(Person, ['chen']);

(7)Reflect.getPrototypeOf(obj)
用于读取对象的proto属性,对应Object.getPrototypeOf(obj)

(8)Reflect.setPrototypeOf(obj, newProto)
设置目标对象的原型(prototype),对应Object.setPrototypeOf(obj, newProto)方法

(9)Reflect.apply(func, thisArg, args)
继承目标对象的特定方法

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

//老写法
let small= Math.min.apply(Math, array) //1
let big = Math.max.apply(Math, array) //6
let type = Object.prototype.toString.call(small) //"[object Number]"

//新写法
const small= Reflect.apply(Math.min, Math, array)
const big = Reflect.apply(Math.max, Math, array)
//第三个参数是Object类型的就好,因为调用的是Object的原型方法toString
const type = Reflect.apply(Object.prototype.toString, small, [])

(10)Reflect.defineProperty(target, propertyKey, attributes)

1
2
3
4
5
6
7
8
9
let proxy = new Proxy({}, {
defineProperty(target, prop, descriptor) {
console.log(descriptor);
return Reflect.defineProperty(target, prop, descriptor);
}
});
proxy .name= 'chen';
// {value: "chen", writable: true, enumerable: true, configurable: true}
p.name // "chen"

如上,Proxy.defineProperty对属性赋值设置拦截,然后使用Reflect.defineProperty完成赋值。

(11)Reflect.getOwnPropertyDescriptor(target, propertyKey)
基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象

(12)Reflect.isExtensible (target)
对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展

(13)Reflect.preventExtensions(target)
对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功

(14)Reflect.ownKeys (target)
用于返回对象的所有属性

使用Proxy和Reflect实现观察者模式

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
class Friend{
constructor(age){
this.age=age
}
}
let friend=new Friend("chen")
//proxy 代理
let changeValueProxy=new Proxy(friend,{
// set(target,property,value,receiver){
set(target,property,value,receiver){
if(property==="age"){
console.log(`age从${target[property]}转变成${value}`)
}
// Reflect.set方法设置target对象的name属性等于value。
//如本例是 age,则设置 friend(target) 的 age 属性(property)
//为 value
//如果不写这个,则 friend 的 age 值依然是 chen
Reflect.set(target,property,value,receiver)
//这样写也可以
// Reflect.set(friend,"age",value,receiver)
}
})

changeValueProxy.age="jin"
console.log(friend)

Map

Map 是 ES6 中新增的数据结构,Map 类似于对象,但普通对象的 key 必须是字符串或者数字,而 Map 的 key 可以是任何数据类型…

Map 实例的属性和方法如下:

  • size:获取成员的数量
  • set:设置成员 key 和 value
  • get:获取成员属性值
  • has:判断成员是否存在
  • delete:删除成员
  • clear:清空所有
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 的所有成员。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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
    //...

Set

类似于数组,但它的一大特性就是所有元素都是唯一的,没有重复。
单一数组的去重。

1
2
let set6 = new Set([1, 2, 2, 3, 4, 3, 5])
console.log('distinct 1:', set6)

多数组的合并去重

1
2
3
4
let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]
let set7 = new Set([...arr1, ...arr2])
console.log('distinct 2:', set7)

操作
1.向Set中添加元素。

1
2
3
4
5
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
console.log('added:', set1)

2.从Set中删除元素。

1
2
3
4
5
6
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
set1.delete(1)
console.log('deleted:', set1)

3.判断某元素是否存在。

1
2
3
4
5
6
7
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
set1.delete(1)
console.log('has(1):', set1.has(1))
console.log('has(2):', set1.has(2))

4.清除所有元素。

1
2
3
4
5
6
let set1 = new Set()
set1.add(1)
set1.add(2)
set1.add(3)
set1.clear()
console.log('cleared:', set1)

Set和Array互转
1.数组转Set

1
2
3
4
5
let set2 = new Set([4,5,6])
console.log('array to set 1:', set2)

let set3 = new Set(new Array(7, 8, 9))
console.log('array to set 2:', set3)

2.Set转数组

1
2
3
let set4 = new Set([4, 5, 6])
console.log('set to array 1:', [...set4])
console.log('set to array 2:', Array.from(set4))

遍历
可以使用Set实例对象的keys(),values(),entries()方法进行遍历。
由于Set的键名和键值是同一个值,它的每一个元素的key和value是相同的,所有keys()和values()的返回值是相同的,entries()返回的元素中的key和value是相同的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let set5 = new Set([4, 5, 'hello'])
console.log('iterate useing Set.keys()')
for(let item of set5.keys()) {
console.log(item)
}

console.log('iterate useing Set.values()')
for(let item of set5.values()) {
console.log(item)
}

console.log('iterate useing Set.entries()')
for(let item of set5.entries()) {
console.log(item)
}

其他特性
在向Set加入值时,Set不会转换数据类型,内部在判断元素是否存在时用的类似于精确等于(===)的方法,“2”和2是不同的,NaN等于其自身。

Symbol

ES6新加入了一种原始数据类型Symbol,表示独一无二的值,这是js的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
因此,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Symbol值通过Symbol函数生成。

1
2
3
let s = Symbol();
typeof s
// "symbol"

Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象,所以不能添加属性。基本上,它是一种类似于字符串的原始数据类型。

Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。这个参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不同的Symbol实例。

1
2
3
4
var s1 = Symbol("foo");
var s2 = Symbol("foo");
console.log(s1 == s2); // false
console.log(s1 === s2); // false

作为属性名的Symbol
由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
console.log(a[mySymbol]); // "Hello!"

Symbol值作为对象属性名时,不能用点运算符。
在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。

1
2
3
4
let s = Symbol();
let obj = {
[s](arg) { console.log("a function called!") }
};

属性名遍历
Symbol作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。

Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。

1
2
3
4
5
6
7
8
9
var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

Symbol.for()
使用给定的key搜索现有符号,如果找到则返回符号。否则将得到一个新的使用给定的key在全局符号注册表中创建的符号。

有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

1
2
3
4
5
6
7
8
9
10
11
12
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s1 === s2); // true
```

Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat") 30次,每次都会返回同一个Symbol值,但是调用Symbol("cat") 30次,会返回30个不同的Symbol值。



**Symbol.keyFor()**
为给定符号从全局符号注册表中检索一个共享符号键。
Symbol.keyFor方法返回一个已登记的Symbol类型值的key。

var s1 = Symbol.for(“foo”);
Symbol.keyFor(s1) // “foo”

var s2 = Symbol(“foo”);
Symbol.keyFor(s2) // undefined

1
2
3
4
5
6
7
8
9
10




# 七、for...of,迭代器模式,生成器函数
## 1.for...of
for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议。for...of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。


for...of 循环仅适用于迭代。 而普通对象不可迭代。

const obj = { fname: ‘foo’, lname: ‘bar’ };

for (const value of obj) { // TypeError: obj[Symbol.iterator] is not a function
console.log(value);
}

1
2

我们可以通过将类数组(array-like)对象转换为数组来绕过它。该对象将具有一个 length 属性,其元素必须可以被索引。

const obj = { length: 3, 0: ‘foo’, 1: ‘bar’, 2: ‘baz’ };

const array = Array.from(obj);
for (const value of array) {
console.log(value);
}
// Output:
// foo
// bar
// baz

1
2
3
4
5


for...of 遍历普通对象的解决方法:
  1.使用 Objet.keys 将对象键名生成一个数组,然后遍历该数组;
  2.Generator 函数重新包装对象

let person = {
name: ‘Ken’,
sex: ‘Male’
}

// Object.keys
for (let key of Object.keys(person)) {
console.log(${key}: ${person[key]});
}

// Generator 包装对象
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(person)) {
console.log(${key}: ${value});
}

// 输出:
// name: Ken
// sex: Male

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

**for...of 对比 for / for...in / forEach**
for 循环 :需定义索引变量,指定循环终结条件。
forEach: 无法中途跳出循环,break/return。
for...in:
  1.只能获取键名,不能获取键值
  2.以字符串为键名(但数组的键名为数值类型索引)
  3.任意顺序遍历键名(???)
  4.会遍历手动添加的其它键(原型链上的键)
  5.为遍历对象设计,不适用数组

for...of 较其它三者优点:
  1.和 for...in 一样简洁,但没有 for...in 的缺点;
  2.不同于 forEach, 可使用 break/return/continue 退出循环;
  3.提供了遍历所有数据的统一接口
缺点:遍历普通对象时,不能直接使用。


## 2.迭代器模式
迭代器模式(Iterator):提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
**特点:**
* 访问一个聚合对象的内容而无需暴露它的内部表示。
* 为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
* 遍历的同时更改迭代器所在的集合结构可能会导致问题(比如C#的foreach里不允许修改item)。
**实现:**

// 统一遍历接口实现
var each = function(arr, callBack) {
for (let i = 0, len = arr.length; i < len; i++) {
// 将值,索引返回给回调函数callBack处理
if (callBack(i, arr[i]) === false) {
break; // 中止迭代器,跳出循环
}
}
}

// 外部调用
each([1, 2, 3, 4, 5], function(index, value) {
if (value > 3) {
return false; // 返回false中止each
}
console.log([index, value]);
})

// 输出:[0, 1] [1, 2] [2, 3]

1
2
3
4
5
6
7
8
9
10
**“迭代器模式的核心,就是实现统一遍历接口。”**


**模式细分**
&nbsp;&nbsp;1.内部迭代器 (jQuery 的 $.each / for...of)
&nbsp;&nbsp;2.外部迭代器 (ES6 的 yield)



内部迭代器: 内部定义迭代规则,控制整个迭代过程,外部只需一次初始调用。

// jQuery 的 $.each(跟上文each函数实现原理类似)
$.each([‘Angular’, ‘React’, ‘Vue’], function(index, value) {
console.log([index, value]);
});

// 输出:[0, Angular] [1, React] [2, Vue]

1
2
3
4
5
优点:调用方式简单,外部仅需一次调用  
缺点:迭代规则预先设置,欠缺灵活性。无法实现复杂遍历需求(如: 同时迭代比对两个数组)


外部迭代器: 外部显示(手动)地控制迭代下一个数据项

// ES6 的 yield 实现外部迭代器
function* generatorEach(arr) {
for (let [index, value] of arr.entries()) {
yield console.log([index, value]);
}
}

let each = generatorEach([‘Angular’, ‘React’, ‘Vue’]);
each.next();
each.next();
each.next();

// 输出:[0, ‘Angular’] [1, ‘React’] [2, ‘Vue’]

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
优点:灵活性更佳,适用面广,能应对更加复杂的迭代需求  
缺点:需显示调用迭代进行(手动控制迭代过程),外部调用方式较复杂



**ES6 的 Iterator 迭代器**
ES6 中迭代器 Iterator 作为一个接口,作用就是为各种不同数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作。


Iterator 作用:
&nbsp;&nbsp;1.为各种数据结构,提供一个统一的、简便的访问接口;
&nbsp;&nbsp;2.使得数据结构的成员能够按某种次序排列;
&nbsp;&nbsp;3.为新的遍历语法 for...of 实现循环遍历

S6 默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性上,该属性本身是一个函数,代表当前数据结构默认的遍历器生成函数。执行该函数 [Symbol.iterator](),会返回一个遍历器对象。只要数据结构拥有 Symbol.iterator 属性,那么它就是 “可遍历的” 。


遍历器对象的特征:
&nbsp;&nbsp;1.拥有 next 属性方法;
&nbsp;&nbsp;2.执行next(),会返回一个包含value和done属性的对象
* value: 当前数据结构成员的值
* done: 布尔值,表示遍历是否结束


原生具备 Iterator 接口的数据结构:
&nbsp;&nbsp;1.Array
&nbsp;&nbsp;2.Map
&nbsp;&nbsp;3.Set
&nbsp;&nbsp;4.String
&nbsp;&nbsp;5.TypedArray
&nbsp;&nbsp;6.函数的 arguments 对象
&nbsp;&nbsp;7.NodeList 对象

let arr = [‘a’, ‘b’, ‘c’];
let iterator = arrSymbol.iterator;

iterator.next(); // { value: ‘a’, done: false }
iterator.next(); // { value: ‘b’, done: false }
iterator.next(); // { value: ‘c’, done: false }
iterator.next(); // { value: undefined, done: false }

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
原生部署 Iterator 接口的数据结构,无需手动执行遍历器生成函数,可使用 for...of 自动循环遍历。   


**ES6 的 Iterator 应用场景**
&nbsp;&nbsp;1.解构赋值
&nbsp;&nbsp;2.扩展运算符
&nbsp;&nbsp;3.yield*
&nbsp;&nbsp;4.任何以数组为参数的遍历的场景:
* for...of
* Array.from()
* Map()/Set()/WeakMap()/WeakSet()
* Promise.all()/Promise.race()



# 八、ES Modules模块系统
ES Modules 是用于处理模块的 ECMAScript 标准。 虽然 Node.js 长期使用 CommonJS 标准,但浏览器从未有过模块系统。 每个主要决策(如模块系统)必须首先由 ECMAScript 标准化,然后由浏览器实施。


## 当前的JavaScript模块系统
JavaScript 目前没有内置支持模块化,但社区创造了令人印象深刻的解决方案。两个最重要的(不幸的是互不兼容)标准是:
* CommonJS 模块:这个标准的主要实现在Node.js中(Node.js模块有一些超越 CommonJS 的功能)。特点:
* 语法简洁
* 为同步加载设计
* 主要用途:服务器端
* 异步模块定义(AMD):这个标准最流行的实现是RequireJS。 特点:
* 稍微复杂的语法,使AMD能够在没有 eval()(或编译步骤)的情况下工作。
* 专为异步加载而设计
* 主要用途:浏览器



## ES6模块
ECMAScript 6模块的目标是创建一个格式,使 CommonJS 和 AMD 的用户都满意:
* 与 CommonJS 类似,简洁的语法,倾向于单一的接口并且支持循环依赖。
* 与AMD类似,直接支持异步加载和可配置的模块加载。
内置语言允许ES6模块超越 CommonJS 和 AMD(细节将在后面解释):
* 他们的语法比 CommonJS 更简洁。
* 他们的结构可以静态分析(用于静态检查,优化等)。
* 他们支持的循环依赖性优于 CommonJS。
ES6模块标准有两个部分:
* 声明语法(用于导入和导出)
* 编程式加载器(loader)API:配置如何加载模块以及有条件地加载模块



**ES6模块语法**
有两种导出方式:命名的导出(每个模块可以导出多个)和 默认的导出(每个模块仅导出一个)。


**命名的导出(每个模块多个)**
模块可以通过使用前缀关键词 export 声明来导出多个东西。这些导出由其名称进行区分,并称为命名的导出。

//—— lib.js ——
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}

//—— main.js ——
import { square, diag } from ‘lib’;
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

1
2

如果你需要,您还可以 导入(import) 整个模块,并通过属性符号引用其命名的导出(export) :

//—— main.js ——
import * as lib from ‘lib’;
console.log(lib.square(11)); // 121
console.log(lib.diag(4, 3)); // 5

1
2

**默认的导出(每个模块一个)**

//—— myFunc.js ——
export default function () { … };

//—— main1.js ——
import myFunc from ‘myFunc’;
myFunc();

1
2
3


默认导出一个类的 ECMAScript 6 模块如下所示:

//—— MyClass.js ——
export default class { … };

//—— main2.js ——
import MyClass from ‘MyClass’;
let inst = new MyClass();

1
2
3


**在一个模块中同时具有命名的导出和默认的导出**

//—— underscore.js ——
export default function (obj) {

};
export function each(obj, iterator, context) {

}
export { each as forEach };

//—— main.js ——
import _, { each } from ‘underscore’;

1
2
3

**默认导出只是另一个命名导出**
默认导出实际上只是一个具有特殊名称default的命名导出。也就是说,以下两个语句是是等价的:

import { default as foo } from ‘lib’;
import foo from ‘lib’;

1
2

类似地,以下两个模块也是等价的默认导出:

//—— module1.js ——
export default 123;

//—— module2.js ——
const D = 123;
export { D as default };

1
2
3
4


**导入**
ECMAScript 6 提供了以下的导入方式

// 默认导出和命名导出
import theDefault, { named1, named2 } from ‘src/mylib’;
import theDefault from ‘src/mylib’;
import { named1, named2 } from ‘src/mylib’;

// 重命名: 导入 named1 作为 myNamed1
import { named1 as myNamed1, named2 } from ‘src/mylib’;

// 导入模块作为一个对象
// (每个命名导出都作为一个属性)
import * as mylib from ‘src/mylib’;

// 只加载模块,不导入任何东西
import ‘src/mylib’;

1
2
3
4


**导出**
有两种方法可以导出当前模块中的内容。 第一种是,您可以使用关键字 export 来声明。

export var myVar1 = …;
export let myVar2 = …;
export const MY_CONST = …;

export function myFunc() {

}
export function* myGeneratorFunc() {

}
export class MyClass {

}

1
2

默认导出(注:通过关键字default声明)的运算对象是一个表达式(包括函数表达式和类表达式)。 例如:

export default 123;
export default function (x) {
return x
};
export default x => x;
export default class {
constructor(x, y) {
this.x = x;
this.y = y;
}
};

1
2

第二种是,您可以在模块的末尾列出要导出的所有内容(风格上与模块模式类似)。

const MY_CONST = …;
function myFunc() {

}

export { MY_CONST, myFunc };

1
2

也可以使用不同的名称导出

export { MY_CONST as THE_CONST, myFunc as theFunc };

1
2
3

**重新导出**
重新导出意味着将另一个模块的导出添加到当前模块的导出。 你可以添加所有其他模块的导出:

export * from ‘src/other_module’;

1
2

或者你可以有更多选择性(随意地重命名):

export { foo, bar } from ‘src/other_module’;

// 导出其他模块的 foo 作为 myFoo
export { foo as myFoo, bar } from ‘src/other_module’;

1
2
3
4
5
6
7
8
9
10
11


## ECMAScript 6 模块加载器 API
除了使用模块的声明性的语法外,还有一个编程式的API。 它允许您:
* 以编程方式使用模块和脚本
* 配置模块加载
加载器处理解析 模块说明符(在 import...from 后面的字符串 ID)加载模块,等。他们的构造函数是Reflect.Loader。每个平台在全局变量 System 中保留自定义实例(系统加载器),实现其平台特定的模块加载方式。


### 导入模块并加载脚本
您可以通过基于 ES6 promises的 API 以编程方式导入模块:

System.import(‘some_module’)
.then(some_module => {
// Use some_module
})
.catch(error => {

});

1
2
3
4
5
System.import() 使你可以:  
* 在&lt;script&gt;元素中使用模块。
* 有条件地加载模块。

System.import() 检索单个模块,您可以使用Promise.all() 来导入多个模块:

Promise.all(
[‘module1’, ‘module2’, ‘module3’]
.map(x => System.import(x)))
.then(([module1, module2, module3]) => {
// Use module1, module2, module3
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
更多加载器方法:  
* System.module(source, options?) 计算source中的JavaScript代码到模块(通过promise异步传递)。
* System.set(name, module)用于注册一个模块(例如,您通过 System.module() 创建的一个模块)。
* System.define(name, source, options?) 都会评估source中的模块代码并注册结果。



# 九、ES2016-ES2020(ES7-ES11)特性一览
## ES7新特性
### 1.Array.prototype.includes()
includes的用法和indexOf用法相似,都可以用来判断数组中是否包含一个元素,唯一的区别在于includes可以识别NaN。


### 2.幂运算符
我们如何求一个数的幂运算呢?
在es5中我们可以通过以下两种方式来实现:

// 通过Math.pow()
console.log(Math.pow(2,53))

// 自定义pow函数
function pow(base, exponent) {
let sum = 1;
for (let i = 0; i <exponent; i += 1) {
sum *= base
}
return sum
}
console.log(pow(2, 53));

1
2

es7提供了一种 ** 运算符,可以更简单实现

console.log(2**53)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* 幂运算符的两个*号之间不能出现空格,前后有无空格都可以。  
* 注意最大安全数:Number.MAX_SAFE_INTEGER = (2**53)-1



## ES8新特性
### 1.async/await
async/await是继es6中promise、generator后又一种更加优雅的异步编程的解决方案
async函数是generator函数的语法糖
async/await可以使异步任务处理起来像是同步任务。


### 2.Object.values()/Object.entries()
Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。

const obj = {
name: “张三”,
age: 18,
sex: “male”,
}
console.log(Object.values(obj)) // [“张三”, 18, “male”]

1
2
3
 
Object.entries
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组。

const obj = {
name: “张三”,
age: 18,
sex: “male”,
}
console.log(Object.entries(obj))
// [[“name”, “张三”],[“age”, “18”], [“sex”, “male”]]

1
2
3
4
5
6
7
8
9
10
for in可以遍历出原型链上的可枚举属性,而Object.keys()/Object.values()/Object.entries()只能遍历自身的可枚举属性。  



### 3.Object.getOwnPropertyDescriptors()
Object.defineProperty()可以通过对描述符的设置进行更精准的控制对象属性,所谓描述符:
* value [属性的值]
* writable [属性的值是否可被改变]
* enumerable [属性的值是否可被枚举]
* configurable [描述符本身是否可被修改,属性是否可被删除]

let test = {
name: ‘测试’,
value: 5
}
console.log(Object.getOwnPropertyDescriptors(test))
// {
// name: {value: “测试”, writable: true, enumerable: true, configurable: true}
// value: {value: 5, writable: true, enumerable: true, configurable: true}
// }

1
2
3
4
5
6

### 4.String.prototype.padStart()/String.prototype.padEnd()
es8 中 String 新增了两个实例函数 String.prototype.padStart() 和 String.prototype.padEnd(),允许将空字符串或其他字符串添加到原始字符串的开头或结尾。

padStart

function getTime() {
const date = new Date();
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, “0”)
const day = (date.getDate()).toString().padStart(2, “0”)
return ${year}-${month}-${day}
}

console.log(getTime())
// 2020-07-09

1
2
3

padEnd()
在正式项目中后台返回的数据中时间一般会转为时间戳格式,处理时间戳的时候单位都是ms毫秒(13位),但有时候有可能是s秒做单位(10位),这个时候我们需要做一个13位的补全,保证单位是毫秒。

time = String(time).padEnd(13, ‘0’)

1
2
3

### 5.尾逗号
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号,es8 允许函数的最后一个参数有尾逗号。

// es8以前
function foo(a, b, c, d) {
console.log(a, b, c, d)
}

// es8
function foo(a, b, c, d,) {
console.log(a, b, c, d)
}

1
2
3
4
5
6
7
8




## ES9新特性
### 1.for await of/Symbol.asyncIterator
for…await…of遍历异步任务。
await需要在async 函数或者 async 生成器里面使用。

function getPromise(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
value: time,
done: false,
});
}, time);
});
}

const asyncArr = [getPromise(1000), getPromise(200), getPromise(3000)];
asyncArr[Symbol.asyncIterator] = function () {
let nextIndex = 0;
return {
next() {
return nextIndex < asyncArr.length
? asyncArr[nextIndex++]
: Promise.resolve({
value: undefined,
done: true,
});
},
};
};

async function test() {
for await (let item of asyncArr) {
console.log(Date.now(), item);
}
}

test();

// 1594374685156 1000
// 1594374685157 200
// 1594374687157 3000

1
2
3
4
5
6
7
8
9
10
11
12
13


同步迭代器/异步迭代器
|类别|同步迭代器|异步迭代器|
|-|-|-|
|迭代器协议|Symbol.iterator|Symbol.asyncIteartor|
|遍历|for…of|for…await…of|



### 2.正则的扩展
dotAll/s
在正则中使用(.)字符时使用s修饰符可以解决(.)字符不能匹配行终止符的例外。

console.log(/./.test(1));
console.log(/./.test(“1”));
console.log(/./.test(“\n”));
console.log(/./.test(“\r”));
console.log(/./.test(“\u{2028}”));
// true
// true
// false
// false
// false

// 使用s修饰符
console.log(/./s.test(1));
console.log(/./s.test(“1”));
console.log(/./s.test(“\n”));
console.log(/./s.test(“\r”));
console.log(/./s.test(“\u{2028}”));
// true
// true
// true
// true
// true

1
2
3
4
5
* (.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符。  
* 正则中可以使用的修饰符有i,g,m,y,u,s


具名组匹配

console.log(“2020-07-10”.match(/(\d{4})-(\d{2})-(\d{2})/));
// [“2020-07-10”, “2020”, “07”, “10”, index: 0, input: “2020-07-10”, groups: undefined]

1
2
3
4
5
按照 match 的语法,没有使用 g 修饰符,所以返回值第一个数值是正则表达式的完整匹配,接下来的第二个值到第四个值是分组匹配(2020, 07, 10),我们想要获取年月日的时候不得不通过数组的下标去获取,这样显得不灵活。仔细观察 match 返回值还有几个属性,分别是 index、input、groups。  
* index [匹配的结果的开始位置]
* input [匹配的字符串]
* groups [捕获组]
所谓的具名组匹配就是命名捕获分组:

console.log(“2020-07-10”.match(/(?\d{4})-(?\d{2})-(?\d{2})/));

// groups的值
groups: {year: “2020”, month: “07”, day: “10”}

1
2
3
4
这样我们就可以通过groups及命名分组获取对应的年月日的值了。  


后行断言

let test = ‘world hello’
console.log(test.match(/(?<=world\s)hello/))

1
2
3
4
5
(?<)是后行断言的符号配合= 、!等使用。  



### 3.对象的Rest和Spread语法

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 数组合并
const arr = […arr1, …arr2];
console.log(arr);

const obj1 = { a: 1 };
const obj2 = { b: 2 };

// 对象合并
const obj = { …obj1, …obj2 };
console.log(obj);

// [1, 2, 3, 4, 5, 6]
// {a: 1, b: 2}

1
2
3
4
5
6
7
一句话总结就是(…)运算符在数组中可以怎样使用,在对象就可以怎样使用。  



### 4.Promise.prototype.finally()
不管promise状态如何都会执行的回调函数

new Promise((resolve, reject) => {
resolve(1);
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log(“finally”);
});
// 1
// promise

1
2
3
4


### 5.带标签的模板字符串扩展
es9 新特性中移除了对 ECMAScript带标签的模板字符串中转义序列的语法限制。 遇到不合法的字符串转义返回undefined,并且从raw上可获取原字符串。

function foo(str) {
console.log(str);
}

foo\undfdfdf;
// es9以前报错
// es9:[undefined, raw:[“\undfdfdf”]]

1
2
3
4
5


## ES10新特性
### 1.Object.fromEntries()
es8中对象添加了一个entries()静态方法,这个方法返回一个给定对象自身可枚举属性的键值对数组 ,Object.fromEntries()方法与 Object.entries() 正好相对,可以将键值对列表转换为一个对象 。

const obj = {
x: 1,
y: 2,
};

const entries = Object.entries(obj);

console.log(entries);
console.log(Object.fromEntries(entries));

// [[“x”,1],[“y”:2]]
// {x:1,y:2}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
只要符合entries结构的都可以使用Object.fromEntries(entries)将键值对列表转换为一个对象,比如Map。  



### 2.String.prototype.trimStart()/String.prototype.trimEnd()
trimStart() /trimLeft()
trimLeft是trimStart的别名,作用是去掉字符串左边的空格。
trimEnd() / trimRight()
trimEnd是trimRight的别名,作用是去掉字符串右边的空格。



### 3.Array.prototype.flat()/Array.prototype.flatMap()
Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,默认深度是1,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回 。

const arr = [1, [2, [3, [4, [5, [6, 7], 8], 9]]]];
console.log(arr.flat(1));
console.log(arr.flat(5));
console.log(arr.flat(Infinity));

// [1,2,[3, [4, [5, [6, 7], 8], 9]]]
// [1,2,3,4,5,6,7,8,9]
// [1,2,3,4,5,6,7,8,9]

1
2
Array.prototype.flatMap()  
flatMap实质上包含两部分功能,一是map,二是flat。

const numbers = [1, 2, 3];

console.log(numbers.map((x) => [x ** 2]).flat());
console.log(numbers.flatMap((x) => [x ** 2]));

// [1,4,9]
// [1,4,9]

1
2
3

### 4.Symbol.description
可以通过 description 获取 Symbol 的描述

const symbol = Symbol(“symbol”);
console.log(symbol.description); // symbol
console.log(symbol.description === “symbol”); // true

1
2

在es10以前,我们只能通过调用 Symbol 的 toString() 时才可以读取这个属性

console.log(symbol.toString() === “Symbol(symbol)”);

1
2
3
4


### 5.Function.prototype.toString()
Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串

function test(a) {
// es10以前不返回注释部分
console.log(a);
}
console.log(test.toString());

// function test(a) {
// // es10以前不返回注释部分
// console.log(a);
// }

1
2
3
4
5



### 6.catch Building
es10允许我们在捕获异常时省略catch的参数

// es10以前
try {
throw new Error();
} catch (error) {
console.log(“fail”);
}

// es10
try {
throw new Error();
} catch {
console.log(“fail”);
}

1
2
3
4
5


### 7.JSON扩展
* JSON 内容可以支持包含 U+2028行分隔符 与 U+2029段分隔符
* 在 ES10 JSON.stringify 会用转义字符的方式来处理 超出范围的 Unicode 展示错误的问题 而非编码的方式

console.log(JSON.stringify(‘\uD83D\uDE0E’)) // 笑脸

// 单独的\uD83D其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify(‘\uD83D’)) // “\ud83d”

1
2
3
4
5
6
7




## ES11新特性
### 1.BigInt
es11为我们提供了第七种新的原始数据类型,对于js来说,他的最大取值范围是2的53次方

console.log(2 ** 53);
console.log(2 ** 53 + 1);
console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740992
// 9007199254740992
// 9007199254740991

1
BigInt,表示一个任意精度的整数,可以表示超长数据,可以超出2的53次方 。  

// 方式一
console.log(9007199254740993);
console.log(9007199254740993n);

// 9007199254740992
// 9007199254740993n

// 方式二
console.log(9007199254740993);
console.log(BigInt(9007199254740993n));

// 9007199254740992
// 9007199254740993n

1
2
3
4
5
6
7
8
* 1==1n // true  
* 1 === 1n // false
* typeof 1n // bigint
* BigInt(9007199254740993n).toString() // 9007199254740993


### 2.可选链
可选链可以使我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。

const a = {
b: {
c: {
d: {
e: “111”,
},
},
},
};

// es11前
const value = a && a.b && a.b.c && a.b.c.d && a.b.c.d.e;
console.log(value);

// es11:可选链
const value2 = a?.b?.c?.d?.e;
console.log(value2);

1
2
3
4


### 3.空值合并运算符
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。

const a = 0;
const b = a || 1;
console.log(b);

// 1

1
我们在使用||运算符时, 变量值为 0 就是 false ,所以我们会看到上述结果会输出1,但是很多时候我们希望b的输出结果就是a的值0,es11提出了空值合并运算符(??),当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。  

const a = 0;
const b = a ?? 1;
console.log(b);

// 0

1
2
3
4
5


### 4.Promise.allSettled()
es6中 Promise.all方法接受一个数组元素都是由 Promise.resolve 包装的数组, 生成并返回一个新的 Promise 对象, 如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。
Promise.all只要有一个任务返回reject,整个任务都会失败, 我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态 ,这就是Promise.allSettled()的作用。

function getPromise(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(time);
}, time);
});
}

function getReject(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(time);
}, time);
});
}

Promise.allSettled([getPromise(1000), getReject(2000), getPromise(3000)])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

// [{status: “fulfilled”, value: 1000},
// {status: “rejected”, reason: 2000},
// {status: // “fulfilled”, value: 3000}]

1
2
3
4
5
6



### 5.import
按需加载
现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。

(async () => {
if (somethingIsTrue) {
// import module for side effects
await import(‘xxx/xxx.js’);
}
})();

1
2
3
4
5
6
7
8



### 6.String.prototype.matchAll()
* matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器 ;
* 使用: str.matchAll(regexp) ;
字符串处理的一个常见场景是想要匹配出字符串中的所有目标子串,例如:
match()

const str =
“es2015/es6 es2016/es7 es2017/es8 es2018/es9 es2019/es10 es2020/es10”;
console.log(str.match(/(es\d+)/es(\d+)/g));
// [“es2015/es6”, “es2016/es7”, “es2017/es8”, “es2018/es9”, “es2019/es10”, //“es2020/es10”]

1
2
3
4
match()方法中,正则表达式所匹配到的多个结果会被打包成数组返回,但无法得知每个匹配除结果之外的相关信息,比如捕获到的子串,匹配到的index位置等 。  


exec ()

const str =
“es2015/es6 es2016/es7 es2017/es8 es2018/es9 es2019/es10 es2020/es10”;
const reg = /(es\d+)/es(\d+)/g;
let matched;
let formatted = [];
while ((matched = reg.exec(str))) {
formatted.push(${matched[1]}-es${matched[2]});
}
console.log(formatted);

//[“es2015-es6”,”es2016-es7”,”es2017-es8”,”es2018-es9”,”es2019-es10”,
//“es2020-es10”]

1
2

matchAll()

const str =
“es2015/es6 es2016/es7 es2017/es8 es2018/es9 es2019/es10 es2020/es10”;
const reg = /(es\d+)/es(\d+)/g;

const matchs = [];
for (let match of str.matchAll(reg)) {
matchs.push(${match[1]}-es${match[2]});
}
console.log(matchs);
// [“es2015-es6”, “es2016-es7”, “es2017-es8”, “es2018-es9”, “es2019-es10”, “es2020-es10”]

matchAll() 是返回一个迭代器,对大数据量的场景更友好 。   


### 7.globalThis  
Javascript 在不同的环境获取全局对象有不通的方式:  
* node 中通过 global,  
* web 中通过 window, self 。  
es11提出的globalThis一句话总结就是: 无论是在node环境还是web中,全局作用域中的 this 可以通过globalThis访问, 不必担心它的运行环境 。  


### 8.for…in遍历机制  
JavaScript 中通过for-in遍历对象时 key 的顺序是不确定的,因为规范没有明确定义,并且能够遍历原型属性让for-in的实现机制变得相当复杂,不同 JavaScript 引擎有各自根深蒂固的不同实现,很难统一  
* 所以 es11不要求统一属性遍历顺序,而是对遍历过程中的一些特殊 Case 明确定义了一些规则:  
* 遍历不到 Symbol 类型的属性  
* 遍历过程中,目标对象的属性能被删除,忽略掉尚未遍历到却已经被删掉的属性  
* 遍历过程中,如果有新增属性,不保证新的属性能被当次遍历处理到  
* 属性名不会重复出现(一个属性名最多出现一次)  
* 目标对象整条原型链上的属性都能遍历到





























请我喝杯咖啡吧~

支付宝
微信