乐趣区

关于前端:ECMAScript20152021全特性学习宝典

前言

ES 全称 ECMAScript,ECMAScript 是 ECMA 制订的标准化脚本语言。目前 JavaScript 应用的 ECMAScript 版本为 ECMA-417。对于 ECMA 的最新资讯能够浏览 ECMA news 查看。

2015 年正式公布的 ECMAScript6(2015)曾经成为了 JavaScript 这门语言的下一代规范,随着 ES2015 的公布,规范委员会决定在每年都会公布一个 ES 的新版本。本文汇合了 ES6 至 ES11 罕用到的个性,包含还在布局的 ES12,只列举大略应用,应用新个性须要应用最新版的 bable 就行本义。

ECMAScript2015(ES6)

Let

应用 let 申明的变量:1. 不属于顶层对象 window, 2. 不容许反复申明,3. 不存在变量晋升,4. 暂时性死区,5. 块级作用域

1. let 申明的全局变量不是全局对象 window 的属性

let a = 5
console.log(window.a) // undefined

2. 用 let 定义变量不容许反复申明

let a = 5
let a = 6
// VM131:1 Uncaught SyntaxError: Identifier 'a' has already been declared
//   at <anonymous>:1:1

3. let 申明的变量不存在变量晋升

function foo() {console.log(a)
    let a = 5
}

foo()
// Uncaught ReferenceError: Cannot access 'a' before initialization

4. let 申明的变量具备暂时性死区

var a = 5
if (true) {
    a = 6
    let a
}
// Uncaught ReferenceError: Cannot access 'a' before initialization

下面代码中,存在全局变量 a,然而块级作用域内 let 又申明了一个局部变量 a,导致后者绑定这个块级作用域,所以在 let 申明变量前,对 a 赋值会报错。

有时“暂时性死区”比拟荫蔽,比方:

function foo(b = a, a = 2) {console.log(a, b)
}
foo()
// Uncaught ReferenceError: Cannot access 'a' before initialization

5. let 申明的变量领有块级作用域

let 实际上为 JavaScript 新增了块级作用域

{let a = 5}
console.log(a) // undefined

Const

应用 const 申明的常量:1. 不属于顶层对象 window,2. 不容许反复申明,3. 不存在变量晋升,4. 暂时性死区,5. 块级作用域

1.const 定义变量后,不能批改它了,对变量的批改会抛出异样。

const PI = 3.1415

console.log(PI)

PI = 5

console.log(PI)
// Uncaught TypeError: Assignment to constant variable.

2.const 申明变量不能扭转,如果申明的是一个援用类型,则不能扭转它的内存地址,能够扭转它的内容。

const obj = {
    name: 'wawa',
    age: 34
}
obj.school = 'imooc'
console.log(obj)
// {name: "wawa", age: 34, school: "imooc"}

obj = {name: 'xx'}
// VM109:9 Uncaught TypeError: Assignment to constant variable.

3. const 申明的时候必须初始化值,否则会报错

const PI

PI = 3.1415
// Uncaught SyntaxError: Missing initializer in const declaration

解构赋值

在 ES6 中新增了变量赋值的形式:解构赋值。容许依照肯定模式,从数组和对象中提取值,对变量进行赋值。

1. 数组解构赋值

  • 赋值元素能够是任意可遍历的对象

    let [a, b, c] = "abc" // ["a", "b", "c"]
    let [one, two, three] = new Set([1, 2, 3])
  • 被赋值的变量还能够是对象的属性,不局限于单纯的变量。

    let user = {}
    [user.firstName, user.secondName] = 'Kobe Bryant'.split(' ')
    
    console.log(user.firstName, user.secondName) // Kobe Bryant
  • 解构赋值在循环体中的利用,能够配合 entries 应用。

    let user = {
    name: 'John',
    age: 30
    }
    
    // loop over keys-and-values
    for (let [key, value] of Object.entries(user)) {console.log(`${key}:${value}`) // name:John, then age:30
    }
  • 能够跳过赋值元素, 如果想疏忽数组的某个元素对变量进行赋值,能够应用逗号来解决。

    // second element is not needed
    let [name, , title] = ['John', 'Jim', 'Sun', 'Moon']
    
    console.log(title) // Sun
  • rest 参数

    let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]
    
    console.log(name1) // Julius
    console.log(name2) // Caesar
    
    // Note that type of `rest` is Array.
    console.log(rest[0]) // Consul
    console.log(rest[1]) // of the Roman Republic
    console.log(rest.length) // 2
  • 如果数组的内容少于变量的个数,并不会报错,没有调配到内容的变量会是 undefined。

    let [firstName, surname] = []
    
    console.log(firstName) // undefined
    console.log(surname) // undefined

    当然你也能够给变量赋予默认值,避免 undefined 的状况呈现:

    // default values
    let [name = "Guest", surname = "Anonymous"] = ["Julius"]
    
    console.log(name)    // Julius (from array)
    console.log(surname) // Anonymous (default used)

    2. 对象解构赋值

    解构赋值除了能够利用在 Array,也能够利用在 Object。根本的语法如下:let {var1, var2} = {var1:…, var2…}

    let options = {
    title: "Menu",
    width: 100,
    height: 200
    }
    
    let {title, width, height} = options
    
    console.log(title)  // Menu
    console.log(width)  // 100
    console.log(height) // 200
  • 赋值的过程中能够指定默认值的:

    let options = {title: "Menu"}
    
    let {width = 100, height = 200, title} = options
    
    console.log(title)  // Menu
    console.log(width)  // 100
    console.log(height) // 200
  • rest 运算符

    let options = {
    title: "Menu",
    height: 200,
    width: 100
    }
    
    let {title, ...rest} = options
    
    // now title="Menu", rest={height: 200, width: 100}
    console.log(rest.height)  // 200
    console.log(rest.width)   // 100
  • 嵌套对象

    如果一个 Array 或者 Object 比较复杂,它嵌套了 Array 或者 Object,那只有被赋值的构造和右侧赋值的元素统一就好了

    let options = {
    size: {
      width: 100,
      height: 200
    },
    items: ["Cake", "Donut"],
    extra: true    // something extra that we will not destruct
    }
    
    // destructuring assignment on multiple lines for clarity
    let {
    size: { // put size here
      width,
      height
    },
    items: [item1, item2], // assign items here
    title = 'Menu' // not present in the object (default value is used)
    } = options
    
    console.log(title)  // Menu
    console.log(width)  // 100
    console.log(height) // 200
    console.log(item1)  // Cake
    console.log(item2)  // Donut

    3. 字符串解构赋值

能够当做是数组的解构:

let str = 'imooc'

let [a, b, c, d, e] = str

console.log(a, b, c, d, e)

Array

在 ES6 中新增了很多实用的原生 API,不便开发者对 Array 的操控性更强,如 for…of、from、of、fill、find、findIndex 等。

1. ES6 中数组遍历形式 for…of

for (let val of [1, 2, 3]) {console.log(val);
}
// 1,2,3

for…of 是反对 break、continue、return 的,所以在性能上十分贴近原生的 for。

2. Array.from()将为数组转换为数组

let arrLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3
}
let arr = Array.from(arrLike);
// ["a", "b", "c"]

3.Array.of()

Array.of() 办法创立一个具备可变数量参数的新数组实例,而不思考参数的数量或类型。

Array.of() 和 Array 构造函数之间的区别在于解决整数参数:Array.of(7) 创立一个具备单个元素 7 的数组,而 Array(7) 创立一个长度为 7 的空数组(留神:这是指一个有 7 个空位 (empty) 的数组,而不是由 7 个 undefined 组成的数组)。

Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]

Array(7); // [, , , , , ,]
Array(1, 2, 3); // [1, 2, 3]

4.Array.prototype.fill()

fill() 办法用一个固定值填充一个数组中从起始索引到终止索引内的全副元素。不包含终止索引。

let array = [1, 2, 3, 4]
array.fill(0, 1, 2)
// [1,0,3,4]

5. Array.prototype.find()

find() 办法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。

let array = [5, 12, 8, 130, 44];

let found = array.find(function(element) {return element > 10;});

console.log(found);
// 12

6.Array.prototype.findIndex()

findIndex()办法返回数组中满足提供的测试函数的第一个元素的索引。否则返回 -1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。

let array = [5, 12, 8, 130, 44];

let found = array.find(function(element) {return element > 10;});

console.log(found);
// 1

7. Array.prototype.copyWithin()

在以后数组外部,将指定地位的成员复制到其余地位(会笼罩原有成员),而后返回以后数组。也就是说,应用这个办法,会批改以后数组。

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

Function

1. 默认参数

function foo(x, y = 'world') {console.log(x, y)
}
foo('hello', 0)

2.Rest 参数

function sum(...nums) {
    let num = 0
    nums.forEach(function(item) {num += item * 1})
    return num
}

console.log(sum(1, 2, 3)) // 6
console.log(sum(1, 2, 3, 4)) // 10

3. 扩大运算符

Spread Operator 和 Rest Parameter 是形似但相同意义的操作符,简略的来说 Rest Parameter 是把不定的参数“收敛”到数组,而 Spread Operator 是把固定的数组内容“打散”到对应的参数。示例如下:

function sum(x = 1, y = 2, z = 3) {return x + y + z}

console.log(sum(...[4])) // 9
console.log(sum(...[4, 5])) // 12
console.log(sum(...[4, 5, 6])) // 15

4.length 属性

函数指定了默认值当前,函数的 length 属性,将返回没有指定默认值的参数个数。

function foo(x = 1, y = 2, z = 3) {console.log(x, y)
}
console.log(foo.length)
// 0

5.name 属性

function foo() {}

foo.name // "foo"

6. 箭头函数

1、箭头函数中 this 指向定义时所在的对象,而不是调用时所在的对象,
2、箭头函数不能够当作构造函数,
3、箭头函数不能够应用 arguments 对象

  • 如果只有一个参数,能够省略括号,如果大于一个参数肯定要记得带括号.

    let hello = (name) => {console.log('say hello', name)
    }
    // 或者
    
    let hello = name => {console.log('say hello', name)
    }
  • 如果返回值是表达式, 如果返回值是表达式能够省略 return 和 {}

    let pow = x => x * x
  • 如果返回值是字面量对象,肯定要用小括号包起来

    let person = (name) => ({
        age: 20,
        addr: 'Beijing City'
    })

    7. 拓展

    let foo = {
      name: 'es',
      say: () => {console.log(this.name, this)
      }
    }
    console.log(foo.say()) // undefined

    因为箭头函数中对 this 的解决是定义时,this 的指向也就是 foo 外层的所指向的 window,而 window 没有 name 属性,所以后果是 undefined。

Object

1. 属性简洁表示法

let name = 'xx'
  let age = 18
  let obj = {
      name,
      age
  }

2. 属性名表达式

  let s = 'school'
  let obj = {
      foo: 'bar',
      [s]: 'xx'
  }

3.Object.is()

判断两个对象是否相等。

let obj1 = {// new Object()
    name: 'xx',
    age: 34
}

let obj2 = {// new Object()
    name: 'xx',
    age: 34
}
console.log(obj1 == obj2) // false

console.log(Object.is(obj1, obj2)) // false

let obj2 = obj1

console.log(Object.is(obj1, obj2)) // true

4.Object.assign()

Object.assign() 办法用于将所有可枚举属性的值从一个或多个源对象复制到指标对象,它将返回指标对象。

const target = {
    a: 1,
    b: 2
}
const source = {
    b: 4,
    c: 5
}

const returnedTarget = Object.assign(target, source)

console.log(target)
// expected output: Object {a: 1, b: 4, c: 5}

console.log(returnedTarget)
// expected output: Object {a: 1, b: 4, c: 5}

Class

1. 申明类

class Animal {constructor(type) {this.type = type}
    walk() {console.log( `I am walking`)
    }
}
let dog = new Animal('dog')
let monkey = new Animal('monkey')

2. Setters & Getters

对于类中的属性,能够间接在 constructor 中通过 this 间接定义,还能够间接在类的顶层来定义:

class Animal {constructor(type, age) {
        this.type = type
        this._age = age
    }
    get age() {return this._age}
    set age(val) {this._age = val}
}

3. 静态方法

在 ES6 中应用 static 的标记是不是静态方法,代码如下:

class Animal {constructor(type) {this.type = type}
    walk() {console.log( `I am walking`)
    }
    static eat() {console.log( `I am eating`)
    }
}

4. 继承

class Animal {constructor(type) {this.type = type}
    walk() {console.log( `I am walking`)
    }
    static eat() {console.log( `I am eating`)
    }
}

class Dog extends Animal {constructor () {super('dog')
  }
  run () {console.log('I can run')
  }
}

Symbol

ES6 引入了一种新的原始数据类型 Symbol,示意举世无双的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
1. 申明形式

let s = Symbol()

typeof s
// "symbol"

变量 s 就是一个举世无双的值。typeof 的后果阐明 s 是 Symbol 数据类型。

既然是举世无双的,那么两个 Symbol()就肯定是不相等的:

let s1 = Symbol()
let s2 = Symbol()
console.log(s1)
console.log(s2)
console.log(s1 === s2) // false

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

2. Symbol.for()

Symbol.for() 承受一个字符串作为参数,而后搜寻有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1 === s2) // true

Symbol.for()与 Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被注销在全局环境中供搜寻,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先查看给定的 key 是否曾经存在,如果不存在才会新建一个值。

3. Symbol.keyFor()

Symbol.keyFor()办法返回一个已登记的 Symbol 类型值的 key。

const s1 = Symbol('foo')
console.log(Symbol.keyFor(s1)) // undefined

const s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2)) // foo

4. 作为属性名

因为每一个 Symbol 值都是不相等的,这意味着 Symbol 值能够作为标识符,用于对象的属性名,就能保障不会呈现同名的属性。这对于一个对象由多个模块形成的状况十分有用,能避免某一个键被不小心改写或笼罩。

const stu1 = Symbol('李四')
const stu2 = Symbol('李四')
const grade = {[stu1]: {
        address: 'yyy',
        tel: '222'
    },
    [stu2]: {
        address: 'zzz',
        tel: '333'
    },
}
console.log(grade)
console.log(grade[stu1])
console.log(grade[stu2])

5. 属性遍历

const sym = Symbol('imooc')
class User {constructor(name) {
        this.name = name
        this[sym] = 'imooc.com'
    }
    getName() {return this.name + this[sym]
    }
}
const user = new User('xiecheng')
console.log(user.getName())

for (let key in user) {console.log(key)
}

for (let key of Object.keys(user)) {console.log(key)
}

for (let key of Object.getOwnPropertySymbols(user)) {console.log(key)
}

for (let key of Reflect.ownKeys(user)) {console.log(key)
}

6. 打消魔术字符串

魔术字符串指的是,在代码之中屡次呈现、与代码造成强耦合的某一个具体的字符串或者数值。格调良好的代码,应该尽量打消魔术字符串,改由含意清晰的变量代替。

function getArea(shape) {
    let area = 0
    switch (shape) {
        case 'Triangle':
            area = 1
            break
        case 'Circle':
            area = 2
            break
    }
    return area
}
console.log(getArea('Triangle'))

下面代码中,字符串 Triangle 和 Circle 就是魔术字符串。它屡次呈现,与代码造成“强耦合”,不利于未来的批改和保护。

应用 Symbol 就能够很好的解决这个问题:

const shapeType = {triangle: Symbol(),
    circle: Symbol()}

function getArea(shape) {
    let area = 0
    switch (shape) {
        case shapeType.triangle:
            area = 1
            break
        case shapeType.circle:
            area = 2
            break
    }
    return area
}
console.log(getArea(shapeType.triangle))

Set

在 JavaScript 里通常应用 Array 或 Object 来存储数据。然而在频繁操作数据的过程中查找或者统计并须要手动来实现,并不能简略的间接应用。比方如何保障 Array 是去重的,如何统计 Object 的数据总数等,必须本人去手动实现相似的需要,不是很不便。在 ES6 中为了解决上述痛点,新增了数据结构 Set 和 Map,它们别离对应传统数据结构的“汇合”和“字典”。
1. 根本语法

  • 生成 Set 实例

    let s = new Set()

    能够定义一个空的 Set 实例,也能够在实例化的同时传入默认的数据。

     let s = new Set([1, 2, 3, 4])

    初始化的参数必须是可遍历的,能够是数组或者自定义遍历的数据结构。

  • 增加数据

    s.add('hello')
    s.add('goodbye')
    
    或者
    s.add('hello').add('goodbye')

    Set 数据结构不容许数据反复,所以增加反复的数据是有效的

  • 删除数据
    删除数据分两种,一种是删除指定的数据,一种是删除全副数据。

    // 删除指定数据
    s.delete('hello') // true
    // 删除全副数据
    s.clear()
  • 统计数据
    Set 能够疾速进行统计数据,如数据是否存在、数据的总数。

    // 判断是否蕴含数据项,返回 true 或 false
    s.has('hello') // true
    // 计算数据项总数
    s.size // 2
  • 数组去重

    let arr = [1, 2, 3, 4, 2, 3]
    let s = new Set(arr)
    console.log(s)
  • 合并去重

    let arr1 = [1, 2, 3, 4]
    let arr2 = [2, 3, 4, 5, 6]
    let s = new Set([...arr1, ...arr2])
    console.log(s)
    console.log([...s])
    console.log(Array.from(s))
  • 交加

    let s1 = new Set(arr1)
    let s2 = new Set(arr2)
    let result = new Set(arr1.filter(item => s2.has(item)))
    console.log(Array.from(result))
  • 差集

    let arr3 = new Set(arr1.filter(item => !s2.has(item)))
    let arr4 = new Set(arr2.filter(item => !s1.has(item)))
    console.log(arr3)
    console.log(arr4)
    console.log([...arr3, ...arr4])

    2. 遍历形式

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():应用回调函数遍历每个成员
  • for…of:能够间接遍历每个成员

     console.log(s.keys()) // SetIterator {"hello", "goodbye"}
    console.log(s.values()) // SetIterator {"hello", "goodbye"}
    console.log(s.entries()) // SetIterator {"hello" => "hello", "goodbye" => "goodbye"}
    s.forEach(item => {console.log(item) // hello // goodbye
    })
    
    for (let item of s) {console.log(item)
    }
    
    for (let item of s.keys()) {console.log(item)
    }
    
    for (let item of s.values()) {console.log(item)
    }
    
    for (let item of s.entries()) {console.log(item[0], item[1])
    }

    3.WeakSet

    WeakSet 构造与 Set 相似,也是不反复的值的汇合。然而,它与 Set 有两个区别。WeakSet 的成员只能是对象,而不能是其余类型的值。

const ws = new WeakSet()
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
let ws = new WeakSet()
const obj1 = {name: 'imooc'}
const obj2 = {age: 5}
ws.add(obj1)
ws.add(obj2)
ws.delete(obj1)
console.log(ws)
console.log(ws.has(obj2))

WeakSet 没有 size 属性,没有方法遍历它的成员。

WeakSet 中的对象都是弱援用,即垃圾回收机制不思考 WeakSet 对该对象的援用,也就是说,如果其余对象都不再援用该对象,那么垃圾回收机制会主动回收该对象所占用的内存,不思考该对象还存在于 WeakSet 之中。

Map

ES6 提供了 Map 数据结构。它相似于对象,也是键值对的汇合,然而“键”的范畴不限于字符串,各种类型的值(包含对象)都能够当作键。也就是说,Object 构造提供了“字符串—值”的对应,Map 构造提供了“值—值”的对应,是一种更欠缺的 Hash 构造实现。如果你须要“键值对”的数据结构,Map 比 Object 更适合。
1. 根本语法

  • 实例化

    let map = new Map([iterable])

    Iterable 能够是一个数组或者其余 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, ‘one’], [2, ‘two’]])。每个键值对都会增加到新的 Map。null 会被当做 undefined。

  • 增加数据

    let keyObj = {}
    let keyFunc = function() {}
    let keyString = 'a string'
    
    // 增加键
    map.set(keyString, "和键'a string'关联的值")
    map.set(keyObj, '和键 keyObj 关联的值')
    map.set(keyFunc, '和键 keyFunc 关联的值')
  • 删除数据

    // 删除指定的数据
    map.delete(keyObj)
    // 删除所有数据
    map.clear()
  • 统计数据

    // 统计所有 key-value 的总数
    console.log(map.size) //2
    // 判断是否有 key-value
    console.log(map.has(keyObj)) // true
  • 查问数据
    get() 办法返回某个 Map 对象中的一个指定元素

    console.log(map.get(keyObj)) // 和键 keyObj 关联的值

    2. 遍历形式

  • keys() 返回一个新的 Iterator 对象。它蕴含依照程序插入 Map 对象中每个元素的 key 值
  • values() 办法返回一个新的 Iterator 对象。它蕴含按程序插入 Map 对象中每个元素的 value 值
  • entries() 办法返回一个新的蕴含 [key, value] 对的 Iterator ? 对象,返回的迭代器的迭代程序与 Map 对象的插入程序雷同
  • forEach() 办法将会以插入程序对 Map 对象中的每一个键值对执行一次参数中提供的回调函数
  • for…of 能够间接遍历每个成员

    map.forEach((value, key) => console.log(value, key))
    
    for (let [key, value] of map) {console.log(key, value)
    }
    
    for (let key of map.keys()) {console.log(key)
    }
    
    for (let value of map.values()) {console.log(value)
    }
    
    for (let [key, value] of map.entries()) {console.log(key, value)
    }

    其实 Object 也是按键值对存储和读取的,那么他俩之间除了咱们之前说的区别以外还有其余的吗?

  • 键的类型

一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键能够是任意值,包含函数、对象、根本类型。

  • 键的程序

Map 中的键值是有序的,而增加到对象中的键则不是。因而,当对它进行遍历时,Map 对象是按插入的程序返回键值。

  • 键值对的统计

你能够通过 size 属性间接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。

  • 键值对的遍历

Map 可间接进行迭代,而 Object 的迭代须要先获取它的键数组,而后再进行迭代。

  • 性能

Map 在波及频繁增删键值对的场景下会有些性能劣势。

3.WeekMap
WeakMap 构造与 Map 构造相似,也是用于生成键值对的汇合。

// WeakMap 能够应用 set 办法增加成员
const wm1 = new WeakMap()
const key = {foo: 1}
wm1.set(key, 2)
wm1.get(key) // 2

// WeakMap 也能够承受一个数组,// 作为构造函数的参数
const k1 = [1, 2, 3]
const k2 = [4, 5, 6]
const wm2 = new WeakMap([[k1, 'foo'],
    [k2, 'bar']
])
wm2.get(k2) // "bar"

WeakMap 与 Map 的区别有两点。

  • WeakMap 只承受对象作为键名(null 除外),不承受其余类型的值作为键名。

    const map = new WeakMap()
    map.set(1, 2)
    // TypeError: 1 is not an object!
    map.set(Symbol(), 2)
    // TypeError: Invalid value used as weak map key
    map.set(null, 2)
    // TypeError: Invalid value used as weak map key
  • WeakMap 的键名所指向的对象,不计入垃圾回收机制。

    String

    1.Unicode 表示法

ES6 增强了对 Unicode 的反对,容许采纳 \uxxxx 模式示意一个字符,其中 xxxx 示意字符的 Unicode 码点。

"\u0061"
// "a"

然而,这种表示法只限于码点在 \u0000~\uFFFF 之间的字符。超出这个范畴的字符,必须用两个双字节的模式示意。

"\uD842\uDFB7"
// "𠮷"

"\u20BB7"
// "7"

下面代码示意,如果间接在 \u 前面跟上超过 0xFFFF 的数值(比方 \u20BB7),JavaScript 会了解成 \u20BB+7。因为 \u20BB 是一个不可打印字符,所以只会显示一个空格,前面跟着一个 7。

ES6 对这一点做出了改良,只有将码点放入大括号,就能正确解读该字符。

"\u{20BB7}"
// "𠮷"

有了这种表示法之后,JavaScript 共有 6 种办法能够示意一个字符。

'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

2. 遍历器接口

ES6 为字符串增加了遍历器接口,详见 Iterator 一节,使得字符串能够被 for…of 循环遍历。

for (let item of 'imooc') {console.log(item)
}

3. 模板字符串

  • String Literals
    这个是用来解决字符串拼接问题,从 ES6 开始能够这样定义字符串了。

    `string text` 
    
    `string text line 1
     string text line 2`
    
    `string text ${expression} string text` 

    在这里你能够任意插入变量或者表达式,只有用 ${} 包起来就好。

  • Tag Literals
    后面的字符串字面量解决了字符串拼接的问题,对于蕴含简单逻辑的字符串并不是简略的表达式能搞定的。所以须要另一种解决方案:Tag Literals,例子:

    var retailPrice = 20
    var wholesalePrice = 16
    var type = 'retail'
    
    var showTxt = ''if (type ==='retail') {showTxt += '您此次的购买单价是:' + retailPrice} else {showTxt += '您此次的批发价是:' + wholesalePrice}

    当初能够定义一个 Tag 函数,而后用这个 Tag 函数来充当一个模板引擎:

    function Price(strings, type) {let s1 = strings[0]
      const retailPrice = 20
      const wholesalePrice = 16
      let txt = ''if (type ==='retail') {txt = ` 购买单价是:${retailPrice}` 
      } else {txt = ` 批发价是:${wholesalePrice}` 
      }
      return `${s1}${txt}` 
    }
    
    let showTxt = Price ` 您此次的 ${'retail'}` 
    
    console.log(showTxt) // 您此次的购买单价是:20

    strings 参数指的是 Tag 函数前面被变量宰割开的字符串汇合,type 参数是对应第一个变量,Tag 函数能够有多个 type 相似的参数

4. 扩大办法

  • String.prototype.fromCodePoint()
    用于从 Unicode 码点返回对应字符,并且能够辨认大于 0xFFFF 的字符。

    // ES5
    console.log(String.fromCharCode(0x20BB7))
    // ஷ
    
    // ES6
    console.log(String.fromCodePoint(0x20BB7))
    // 𠮷
  • String.prototype.includes()
    ES5 中能够应用 indexOf 办法来判断一个字符串是否蕴含在另一个字符串中,indexOf 返回呈现的下标地位,如果不存在则返回 -1。

    const str = 'isxxx'
    
    console.log(str.indexOf('is'))

    ES6 提供了 includes 办法来判断一个字符串是否蕴含在另一个字符串中,返回 boolean 类型的值。

    const str = 'isxxx'
    
    console.log(str.includes('xx'))
  • String.prototype.startsWith()
    判断参数字符串是否在原字符串的头部, 返回 boolean 类型的值。

    const str = 'isxxx'
    
    console.log(str.startsWith('is'))
  • String.prototype.endsWith()
    判断参数字符串是否在原字符串的尾部, 返回 boolean 类型的值。

    const str = 'isxxx'
    
    console.log(str.endsWith('xxx'))
  • String.prototype.repeat()
    repeat 办法返回一个新字符串,示意将原字符串反复 n 次。

    const str = 'isxxx'
    
    const newStr = str.repeat(10)
    
    console.log(newStr)

    RegExp

    1.y 修饰符
    ES6 为正则表达式增加了 y 修饰符,叫做“粘连”(sticky)修饰符。

y 修饰符的作用与 g 修饰符相似,也是全局匹配,后一次匹配都从上一次匹配胜利的下一个地位开始。不同之处在于,g 修饰符只有残余地位中存在匹配就可,而 y 修饰符确保匹配必须从残余的第一个地位开始,这也就是“粘连”的涵义。

const s = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null

下面代码有两个正则表达式,一个应用 g 修饰符,另一个应用 y 修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为雷同,残余字符串都是_aa_a。因为 g 润饰没有地位要求,所以第二次执行会返回后果,而 y 修饰符要求匹配必须从头部开始,所以返回 null。

如果改一下正则表达式,保障每次都能头部匹配,y 修饰符就会返回后果了。

const s = 'aaa_aa_a'
const r = /a+_/y

r.exec(s) // ["aaa_"]
r.exec(s) // ["aa_"]

应用 lastIndex 属性,能够更好地阐明 y 修饰符。

const regexp = /a/g

// 指定从 2 号地位(y)开始匹配
regexp.lastIndex = 2

// 匹配胜利
const match = regexp.exec('xaya')

// 在 3 号地位匹配胜利
console.log(match.index) // 3

// 下一次匹配从 4 号位开始
console.log(regexp.lastIndex) // 4

// 4 号位开始匹配失败
regexp.exec('xaxa') // null

下面代码中,lastIndex 属性指定每次搜寻的开始地位,g 修饰符从这个地位开始向后搜寻,直到发现匹配为止。

y 修饰符同样恪守 lastIndex 属性,然而要求必须在 lastIndex 指定的地位发现匹配。

const regexp = /a/y

// 指定从 2 号地位开始匹配
regexp.lastIndex = 2

// 不是粘连,匹配失败
regexp.exec('xaya') // null

// 指定从 3 号地位开始匹配
regexp.lastIndex = 3

// 3 号地位是粘连,匹配胜利
const match = regexp.exec('xaxa')
console.log(match.index) // 3
console.log(regexp.lastIndex) // 4

进一步说,y 润饰符号隐含了头部匹配的标记 ^。

const reg = /b/y
reg.exec('aba')
// null
console.log(reg.lastIndex)

sticky 模式在正则匹配过程中只会影响两件事:

  • 匹配必须从 re.lastIndex 开始(相当于正则表白中的 ^)
  • 如果匹配到会批改 re.lastIndex(相当于 g 模式)
    2.u 修饰符
    ES6 为正则表达式增加了 u 修饰符,含意为“Unicode 模式”,用来正确处理大于 \uFFFF 的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。

    /^\uD83D/u.test('\uD83D\uDC2A') // false
    
    /^\uD83D/.test('\uD83D\uDC2A') // true

    下面代码中,\uD83D\uDC2A 是一个四个字节的 UTF-16 编码,代表一个字符 “🐪”。然而,ES5 不反对四个字节的 UTF-16 编码,会将其辨认为两个字符,导致第二行代码后果为 true。加了 u 修饰符当前,ES6 就会辨认其为一个字符,所以第一行代码后果为 false。

一旦加上 u 润饰符号,就会批改上面这些正则表达式的行为。

  • 点字符
    点(.)字符在正则表达式中,含意是除了换行符以外的任意单个字符。对于码点大于 0xFFFF 的 Unicode 字符,点字符不能辨认,必须加上 u 修饰符。

    let s = '𠮷'
    
    /^.$/.test(s) // false
    
    /^.$/u.test(s) // true
  • Unicode 字符表示法
    ES6 新增了应用大括号示意 Unicode 字符,这种表示法在正则表达式中必须加上 u 修饰符,能力辨认。

    /\u{61}/.test('a') // false
    
    /\u{61}/u.test('a') // true
    
    /\u{20BB7}/u.test('𠮷') // true
  • 量词
    应用 u 修饰符后,所有量词都会正确识别码点大于 0xFFFF 的 Unicode 字符。

    /a{2}/.test('aa') // true
    
    /a{2}/u.test('aa') // true
    
    /𠮷{2}/.test('𠮷𠮷') // false
    
    /𠮷{2}/u.test('𠮷𠮷') // true
  • 预约义模式
    u 修饰符也影响到预约义模式,是否正确识别码点大于 0xFFFF 的 Unicode 字符。

    /^\S$/.test('𠮷') // false
    
    /^\S$/u.test('𠮷') // true

    下面代码的 \S 是预约义模式,匹配所有不是空格的字符。只有加了 u 修饰符,它能力正确匹配码点大于 0xFFFF 的 Unicode 字符。

利用这一点,能够写出一个正确返回字符串长度的函数。

function codePointLength(text) {const result = text.match(/[\s\S]/gu)
    return result ? result.length : 0
}

const s = '𠮷𠮷'

s.length // 4
codePointLength(s) // 2
  • i 修饰符
    有些 Unicode 字符的编码不同,然而字型很相近,比方,\u004B 与 \u212A 都是大写的 K。

    /[a-z]/i.test('\u212A') // false
    
    /[a-z]/iu.test('\u212A') // true

    下面代码中,不加 u 修饰符,就无奈辨认非标准的 K 字符。

    Number

    1. 二进制与八进制

  • JS 中如何把十进制转化为二进制?

    const a = 5 // 101
    
    console.log(a.toString(2))
  • 如何把八进制转化为二进制?

    const b = 101
    
    console.log(parseInt(b, 2))

    ES6 提供了二进制和八进制数值的新的写法,别离用前缀 0b(或 0B)和 0o(或 0O)示意。

    const a = 0B0101
    console.log(a)
    
    const b = 0O777
    console.log(b)

    2. 新增办法

  • Number.isFinite()
    用来查看一个数值是否为无限的(finite),即不是 Infinity。

    Number.isFinite(15) // true
    Number.isFinite(0.8) // true
    Number.isFinite(NaN) // false
    Number.isFinite(Infinity) // false
    Number.isFinite(-Infinity) // false
    Number.isFinite('foo') // false
    Number.isFinite('15') // false
    Number.isFinite(true) // false
  • Number.isNaN()
    用来查看一个值是否为 NaN。

    Number.isNaN(NaN) // true
    Number.isNaN(15) // false
    Number.isNaN('15') // false
    Number.isNaN(true) // false
    Number.isNaN(9 / NaN) // true
    Number.isNaN('true' / 0) // true
    Number.isNaN('true' / 'true') // true
  • Number.parseInt()
    ES6 将全局办法 parseInt()移植到 Number 对象下面,行为齐全放弃不变。这样做的目标,是逐渐缩小全局性办法,使得语言逐渐模块化。

    // ES5 的写法
    parseInt('12.34') // 12
    
    // ES6 的写法
    Number.parseInt('12.34') // 12
  • Number.parseFloat()
    ES6 将全局办法 parseFloat()移植到 Number 对象下面,行为齐全放弃不变。这样做的目标,是逐渐缩小全局性办法,使得语言逐渐模块化。

    // ES5 的写法
    parseFloat('123.45#') // 123.45
    
    // ES6 的写法
    Number.parseFloat('123.45#') // 123.45
  • Number.isInteger()
    用来判断一个数值是否为整数。

    Number.isInteger(25) // true
    Number.isInteger(25.1) // false
    
    Number.isInteger() // false
    Number.isInteger(null) // false
    Number.isInteger('15') // false
    Number.isInteger(true) // false
  • Number.MAX_SAFE_INTEGER

    Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true
    
    Number.MAX_SAFE_INTEGER === 9007199254740991 // true
  • Number.MIN_SAFE_INTEGER

    Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true
    
    Number.MIN_SAFE_INTEGER === -9007199254740991 // true
  • Number.isSafeInteger()
    JavaScript 可能精确示意的整数范畴在 -2^53 到 2^53 之间(不含两个端点),超过这个范畴,无奈准确示意这个值。

    Math.pow(2, 53) // 9007199254740992
    
    Math.pow(2, 53) === Math.pow(2, 53) + 1 // true

    3.Math 扩大

  • Math.trunc()
    办法用于去除一个数的小数局部,返回整数局部。

     console.log(Math.trunc(5.5)) // 5
    console.log(Math.trunc(-5.5)) // -5
    console.log(Math.trunc(true)) // 1
    console.log(Math.trunc(false)) // 0
    console.log(Math.trunc(NaN)) // NaN
    console.log(Math.trunc(undefined)) // NaN
    console.log(Math.trunc()) // NaN
  • Math.sign()
    办法用来判断一个数到底是负数、正数、还是零。对于非数值,会先将其转换为数值。

它会返回五种值。

  • 参数为负数,返回 +1
  • 参数为正数,返回 -1
  • 参数为 0,返回 0
  • 参数为 -0,返回 -0
  • 其余值,返回 NaN

    console.log(Math.sign(5)) // 1
    console.log(Math.sign(-5)) // -1
    console.log(Math.sign(0)) // 0
    console.log(Math.sign(NaN)) // NaN
    console.log(Math.sign(true)) // 1
    console.log(Math.sign(false)) // 0
  • Math.cbrt()
    办法用于计算一个数的立方根。

    console.log(Math.cbrt(8)) // 2
    
    console.log(Math.cbrt('xx')) // NaN

    Proxy

    在 ES6 规范中新增的一个十分弱小的性能是 Proxy,它能够自定义一些罕用行为如查找、赋值、枚举、函数调用等。通过 Proxy 这个名称也能够看进去它蕴含了“代理”的含意,只有有“代理”的诉求都能够思考应用 Proxy 来实现。

1. 根本语法

let p = new Proxy(target, handler)
参数 含意 必选
target 用 Proxy 包装的指标对象(能够是任何类型的对象,包含原生数组,函数,甚至另一个代理) y
handler 一个对象,其属性是当执行一个操作时定义代理的行为的函数 y

2. 罕用拦挡操作

  • get
    拦挡对象属性的读取,比方 proxy.foo 和 proxy[‘foo’]。

    let arr = [7, 8, 9]
    arr = new Proxy(arr, {get(target, prop) {// console.log(target, prop)
          return prop in target ? target[prop] : 'error'
      }
    })
    console.log(arr[1])
    console.log(arr[10])
    let dict = {
      'hello': '你好',
      'world': '世界'
    }
    dict = new Proxy(dict, {get(target, prop) {return prop in target ? target[prop] : prop
      }
    })
    console.log(dict['world'])
    console.log(dict['imooc'])
  • set
    拦挡对象属性的设置,比方 proxy.foo = v 或 proxy[‘foo’] = v,返回一个布尔值。

    let arr = []
    arr = new Proxy(arr, {set(target, prop, val) {if (typeof val === 'number') {target[prop] = val
              return true
          } else {return false}
      }
    })
    arr.push(5)
    arr.push(6)
    console.log(arr[0], arr[1], arr.length)
  • has
    拦挡 propKey in proxy 的操作,返回一个布尔值。

    let range = {
      start: 1,
      end: 5
    }
    
    range = new Proxy(range, {has(target, prop) {return prop >= target.start && prop <= target.end}
    })
    console.log(2 in range)
    console.log(9 in range)
  • ownKeys
    拦挡 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in 循环,返回一个数组。该办法返回指标对象所有本身的属性的属性名,而 Object.keys()的返回后果仅包含指标对象本身的可遍历属性。

    let userinfo = {
      username: 'xxx',
      age: 18,
      _password: '***'
    }
    userinfo = new Proxy(userinfo, {ownKeys(target) {return Object.keys(target).filter(key => !key.startsWith('_'))
      }
    })
    
    // for (let key in userinfo) {//     console.log(key)
    // }
    console.log(Object.keys(userinfo))
  • deleteProperty
    拦挡 delete proxy[propKey]的操作,返回一个布尔值。

    let user = {
      name: 'xxx',
      age: 18,
      _password: '***'
    }
    user = new Proxy(user, {get(target, prop) {if (prop.startsWith('_')) {throw new Error('不可拜访')
          } else {return target[prop]
          }
      },
      set(target, prop, val) {if (prop.startsWith('_')) {throw new Error('不可拜访')
          } else {target[prop] = val
              return true
          }
      },
      deleteProperty(target, prop) { // 拦挡删除
          if (prop.startsWith('_')) {throw new Error('不可删除')
          } else {delete target[prop]
              return true
          }
      },
      ownKeys(target) {return Object.keys(target).filter(key => !key.startsWith('_'))
      }
    })
    console.log(user.age)
    console.log(user._password)
    user.age = 18
    console.log(user.age)
    try {user._password = 'xxx'} catch (e) {console.log(e.message)
    }
    
    try {
      // delete user.age
      delete user._password
    } catch (e) {console.log(e.message)
    }
    console.log(user.age)
    
    for (let key in user) {console.log(key)
    }
  • apply
    拦挡 Proxy 实例作为函数调用的操作,比方 proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。

    let sum = (...args) => {
      let num = 0
      args.forEach(item => {num += item})
      return num
    }
    
    sum = new Proxy(sum, {apply(target, ctx, args) {return target(...args) * 2
      }
    })
    console.log(sum(1, 2))
    console.log(sum.call(null, 1, 2, 3))
    console.log(sum.apply(null, [1, 2, 3]))
  • construct
    拦挡 Proxy 实例作为结构函数调用的操作,比方 new proxy(…args)。

    let User = class {constructor(name) {this.name = name}
    }
    User = new Proxy(User, {construct(target, args, newTarget) {console.log('construct')
          return new target(...args)
      }
    })
    console.log(new User('imooc'))

    Reflect

    Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。

1. 设计目标

  • 将 Object 属于语言外部的办法放到 Reflect 上

    let obj = {}
    let newVal = ''Reflect.defineProperty(obj,'name', {get() {return newVal},
      set(val) {console.log('set')
          // this.name = val
          newVal = val
      }
    })
    obj.name = 'es'
    console.log(obj.name)
  • 批改某些 Object 办法的返回后果,让其变得更正当

    // 老写法
    try {Object.defineProperty(target, property, attributes)
      // success
    } catch (e) {// failure}
    
    // 新写法
    if (Reflect.defineProperty(target, property, attributes)) {// success} else {// failure}
  • 让 Object 操作变成函数行为

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
  • Reflect 对象的办法与 Proxy 对象的办法一一对应,只有是 Proxy 对象的办法,就能在 Reflect 对象上找到对应的办法。

    Proxy(target, {set: function(target, name, value, receiver) {var success = Reflect.set(target, name, value, receiver)
          if (success) {console.log('property' + name + 'on' + target + 'set to' + value)
          }
          return success
      }
    })

    Reflect 是一个内置的对象,它提供拦挡 JavaScript 操作的办法,这些办法与处理器对象的办法雷同。Reflect 不是一个函数对象,因而它是不可结构的。

2、罕用办法

  • Reflect.apply()

    Reflect.apply(target, thisArgument, argumentsList),target 为指标函数;thisArgument 为 target 函数调用时绑定的 this 对象;argumentsList 为 target 函数调用时传入的实参列表,该参数应该是一个类数组的对象

Reflect.apply(Math.floor, undefined, [1.75])
// 1

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4

Reflect.apply(''.charAt,'ponies', [3])
// "i"
  • Reflect.construct()
    Reflect.construct() 办法的行为有点像 new 操作符 构造函数,相当于运行 new target(…args)

    var d = Reflect.construct(Date, [1776, 6, 4])
    d instanceof Date // true
    d.getFullYear() // 1776
  • Reflect.define​Property()
    静态方法 Reflect.defineProperty() 根本等同于 Object.defineProperty() 办法,惟一不同是返回 Boolean 值。

    const student = {}
    Reflect.defineProperty(student, 'name', {value: 'Mike'}) // true
    student.name // "Mike"
  • Reflect.delete​Property()
    Reflect.deleteProperty 容许你删除一个对象上的属性。返回一个 Boolean 值示意该属性是否被胜利删除。它简直与非严格的 delete operator 雷同。

    var obj = {
      x: 1,
      y: 2
    }
    Reflect.deleteProperty(obj, "x") // true
    obj // {y: 2}
    
    var arr = [1, 2, 3, 4, 5]
    Reflect.deleteProperty(arr, "3") // true
    arr // [1, 2, 3, , 5]
    
    // 如果属性不存在,返回 true
    Reflect.deleteProperty({}, "foo") // true
    
    // 如果属性不可配置,返回 false
    Reflect.deleteProperty(Object.freeze({foo: 1}), "foo") // false
  • Reflect.get()
    Reflect.get() 办法的工作形式,就像从 object (target[propertyKey]) 中获取属性,但它是作为一个函数执行的。

    // Object
    var obj = {
      x: 1,
      y: 2
    }
    Reflect.get(obj, 'x') // 1
    
    // Array
    Reflect.get(['zero', 'one'], 1) // "one"
    
    // Proxy with a get handler
    var x = {p: 1}
    var obj = new Proxy(x, {get(t, k, r) {return k + 'bar'}
    })
    Reflect.get(obj, 'foo') // "foobar"
  • Reflect.get​OwnProperty​Descriptor()
    静态方法 Reflect.getOwnPropertyDescriptor() 与 Object.getOwnPropertyDescriptor() 办法类似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined。

    Reflect.getOwnPropertyDescriptor({x: 'hello'}, 'x')
    // {value: "hello", writable: true, enumerable: true, configurable: true}
    
    Reflect.getOwnPropertyDescriptor({x: 'hello'}, 'y')
    // undefined
    
    Reflect.getOwnPropertyDescriptor([], 'length')
    // {value: 0, writable: true, enumerable: false, configurable: false}
  • 更多办法能够参考 Reflect

Promise

1、根本语法

Promise 就是为了解决“回调天堂”问题的,它能够将异步操作的解决变得很优雅。回调天堂,代码难以保护,经常第一个的函数的输入是第二个函数的输出这种景象 promise 能够反对多个并发的申请,获取并发申请中的数据这个 promise 能够解决异步的问题,自身不能说 promise 是异步的。

创立 Promise 实例。

const promise = new Promise(function(resolve, reject) {
    // ... some code

    if (/* 异步操作胜利 */) {resolve(value)
    } else {reject(error)
    }
})

Promise 构造函数承受一个函数作为参数,该函数的两个参数别离是 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不必本人部署。

  • 处理结果失常的话,调用 resolve(处理结果值),将 Promise 对象的状态从“未实现”变为“胜利”(即从 pending 变为 resolved),在异步操作胜利时调用,并将异步操作的后果,作为参数传递进来
  • 处理结果谬误的话,调用 reject(Error 对象),将 Promise 对象的状态从“未实现”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的谬误,作为参数传递进来。
    Promise 实例生成当前,能够用 then 办法别离指定 resolved 状态和 rejected 状态的回调函数。

    promise.then(function(value) {// success}, function(error) {// failure})

    Promise 外部是有状态的(pending、fulfilled、rejected),Promise 对象依据状态来确定执行哪个办法。Promise 在实例化的时候状态是默认 pending 的,当异步操作是实现的,状态会被批改为 fulfilled,如果异步操作遇到异样,状态会被批改为 rejected。

2、Promise.prototype.then()

var promise = new Promise(function(resolve, reject) {resolve('传递给 then 的值')
})
promise.then(function(value) {console.log(value)
}, function(error) {console.error(error)
})
  • 当 handler 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 办法。
  • 定义的 handler 中产生异样的时候,这个值则会传递给 Promise 对象的 onRejected 办法。

3、Promise.prototype.catch()

捕捉异样是程序品质保障最根本的要求,能够应用 Promise 对象的 catch 办法来捕捉异步操作过程中呈现的任何异样

function test() {return new Promise((resolve, reject) => {reject(new Error('es'))
    })
}

test().catch((e) => {console.log(e.message) // es
})

4、Promise.resolve()

个别状况下咱们都会应用 new Promise() 来创立 Promise 对象,然而除此之外咱们也能够应用其余办法。

在这里,咱们将会学习如何应用 Promise.resolve 和 Promise.reject 这两个办法。

静态方法 Promise.resolve(value) 能够认为是 new Promise() 办法的快捷方式。

比方 Promise.resolve(42) 能够认为是以下代码的语法糖。

new Promise(function(resolve) {resolve(42)
})

办法 Promise.resolve(value) 的返回值也是一个 Promise 对象,所以咱们能够像上面那样接着对其返回值进行 .then 调用。

Promise.resolve(42).then(function(value) {console.log(value)
})

5、Promise.reject()

Promise.reject(error) 是和 Promise.resolve(value) 相似的静态方法,是 new Promise() 办法的快捷方式。

比方 Promise.reject(new Error(“ 出错了 ”)) 就是上面代码的语法糖模式。

new Promise(function(resolve, reject) {reject(new Error('出错了'))
})

这段代码的性能是调用该 Promise 对象通过 then 指定的 onRejected 函数,并将谬误(Error)对象传递给这个 onRejected 函数。

Promise.reject(new Error('BOOM!'))

6、Promise.all()

var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then(function(results) {console.log(results) // [1, 2, 3]
})

Promise.all 生成并返回一个新的 Promise 对象,所以它能够应用 Promise 实例的所有办法。参数传递 promise 数组中所有的 Promise 对象都变为 resolve 的时候,该办法才会返回,新创建的 Promise 则会应用这些 promise 的值。

如果参数中的任何一个 promise 为 reject 的话,则整个 Promise.all 调用会立刻终止,并返回一个 reject 的新的 Promise 对象。

因为参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以 Promise.all 能够解决不同类型的 Promise 对象。

7、Promise.race()

var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.race([p1, p2, p3]).then(function(value) {console.log(value) // 1
})

Promise.race 生成并返回一个新的 Promise 对象。

参数 promise 数组中的任何一个 Promise 对象如果变为 resolve 或者 reject 的话,该函数就会返回,并应用这个 Promise 对象的值进行 resolve 或者 reject。

Generator

Generators 是能够用来管制迭代器的函数。它们能够暂停,而后在任何时候复原。如果这句话不好了解,能够看下接下来的示例。

  1. 惯例循环

    for (let i = 0; i < 5; i += 1) {console.log(i)
    }
    // this will return immediately 0 -> 1 -> 2 -> 3 -> 4
  2. 利用 Generator

    function* generatorForLoop() {for (let i = 0; i < 5; i += 1) {yield console.log(i)
     }
    }
    
    const genForLoop = generatorForLoop()
    
    console.log(genForLoop.next()) // first console.log - 0
    console.log(genForLoop.next()) // 1
    console.log(genForLoop.next()) // 2
    console.log(genForLoop.next()) // 3
    console.log(genForLoop.next()) // 4

    比照下代码,惯例的循环只能一次遍历完所有值,Generator 能够通过调用 next 办法拿到顺次遍历的值,让遍历的执行变得“可控”。

1、根本语法

function* gen() {
    yield 1
    yield 2
    yield 3
}

let g = gen()
// "Generator {}"

这个是 Generator 的定义方法,有几个点值得注意:

  • 比一般函数多一个 *
  • 函数外部用 yield 来控制程序的执行的“暂停”
  • 函数的返回值通过调用 next 来“复原”程序执行

Generator 函数的定义不能应用箭头函数,否则会触发 SyntaxError 谬误

let generator = * () => {} // SyntaxError
let generator = () * => {} // SyntaxError
let generator = (*) => {} // SyntaxError

2、yield 表达式
yield 关键字用来暂停和复原一个生成器函数

  • yield 表达式的返回值是 undefined,然而遍历器对象的 next 办法能够批改这个默认值。
  • Generator 对象的 next 办法,遇到 yield 就暂停,并返回一个对象,这个对象包含两个属性:value 和 done。

     function* gen() {
        let val
        val = yield 1
        console.log(`1:${val}` ) // 1:undefined
        val = yield 2
        console.log(`2:${val}` ) // 2:undefined
        val = yield 3
        console.log(`3:${val}` ) // 3:undefined
    }
    
    var g = gen()
    
    console.log(g.next()) // {value: 1, done: false}
    console.log(g.next()) // {value: 2, done: false}
    console.log(g.next()) // {value: 3, done: false}
    console.log(g.next()) // {value: undefined, done: true}

    3、办法

Generator 对象有几个办法,next、return、throw。

  • next([value])
    Generator 对象通过 next 办法来获取每一次遍历的后果,这个办法返回一个对象,这个对象蕴含两个属性:value 和 done。value 是指以后程序的运行后果,done 示意遍历是否完结。

其实 next 是能够承受参数的,这个参数能够让你在 Generator 内部给外部传递数据,而这个参数就是作为 yield 的返回值。

function* gen() {
      var val = 100
      while (true) {console.log( `before ${val}` )
          val = yield val
          console.log(`return ${val}` )
      }
  }

  var g = gen()
  console.log(g.next(20).value)
  // before 100
  // 100
  console.log(g.next(30).value)
  // return 30
  // before 30
  // 30
  console.log(g.next(40).value)
  // return 40
  // before 40
  // 40
  • return()
    return 办法能够让 Generator 遍历终止,有点相似 for 循环的 break。

    function* gen() {
    yield 1
    yield 2
    yield 3
    }
    
    var g = gen()
    
    console.log(g.next()) // {value: 1, done: false}
    console.log(g.return()) // {value: undefined, done: true}
    console.log(g.next()) // {value: undefined, done: true}
  • throw()
    能够通过 throw 办法在 Generator 内部管制外部执行的“终断”。

    function* gen() {while (true) {
          try {yield 42} catch (e) {console.log(e.message)
          }
      }
    }
    
    let g = gen()
    console.log(g.next()) // {value: 42, done: false}
    console.log(g.next()) // {value: 42, done: false}
    console.log(g.next()) // {value: 42, done: false}
    // 中断操作
    g.throw(new Error('break'))
    
    console.log(g.next()) // {value: undefined, done: true}

    Iterator

    解决汇合中的每个项是很常见的操作。JavaScript 提供了许多迭代汇合的办法,从简略的 for 循环到 map()和 filter()。迭代器和生成器将迭代的概念间接带入外围语言,并提供了一种机制来自定义 for…of 循环的行为。

如果对 MDN 这个形容了解不是很到位的话,能够看下接下来这个小示例:

let authors = {
    allAuthors: {
        fiction: [
            'Agatha Christie',
            'J. K. Rowling',
            'Dr. Seuss'
        ],
        scienceFiction: [
            'Neal Stephenson',
            'Arthur Clarke',
            'Isaac Asimov',
            'Robert Heinlein'
        ],
        fantasy: [
            'J. R. R. Tolkien',
            'J. K. Rowling',
            'Terry Pratchett'
        ]
    }
}

这个数据结构是汇总了所有作者,每个作者按创作性质进行了分类。如果咱们想获取所有作者的名单,该怎么做呢?

for (let author of authors) {console.log(author)
}

你发现这个遍历遇到了报错:Uncaught TypeError: authors is not iterable

1、根本语法

Iterator 就是 ES6 中用来实现自定义遍历的接口,依照上述的示例,咱们来实现下这个接口:

authors[Symbol.iterator] = function() {
    let allAuthors = this.allAuthors
    let keys = Reflect.ownKeys(allAuthors)
    let values = []
    return {next() {if (!values.length) {if (keys.length) {values = allAuthors[keys[0]]
                    keys.shift()}
            }
            return {
                done: !values.length,
                value: values.shift()}
        }
    }
}

这个代码在数据结构上部署了 Iterator 接口,咱们就能够用 for…of 来遍历代码了:

for (let value of authors) {console.log( `${value}` )
}

2、可迭代协定和迭代器协定。

    1. 迭代器协定

这是两个概念:可迭代协定、迭代器协定。艰深的讲,迭代器协定要求合乎以下条件:

  • 首先,它是一个对象
  • 其次,这个对象蕴含一个无参函数 next
  • 最初,next 返回一个对象,对象蕴含 done 和 value 属性。其中 done 示意遍历是否完结,value 返回以后遍历的值。
    1. 可迭代协定
      可迭代协定容许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 构造中什么值能够被循环(失去)。一些内置类型都是内置的可迭代类型并且有默认的迭代行为, 比方 Array or Map, 另一些类型则不是 (比方 Object)。

为了变成可迭代对象,一个对象必须实现 @@iterator 办法, 意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性。

如果让一个对象是可遍历的,就要恪守可迭代协定,该协定要求对象要部署一个以 Symbol.iterator 为 key 的键值对,而 value 就是一个无参函数,这个函数返回的对象要恪守迭代器协定。

3、Generator

相熟了 Generator 之后,发现它是人造满足可迭代协定的。上述的代码咱们能够用 Generator 来实现:

authors[Symbol.iterator] = function*() {
    let allAuthors = this.allAuthors
    let keys = Reflect.ownKeys(allAuthors)
    let values = []
    while (1) {if (!values.length) {if (keys.length) {values = allAuthors[keys[0]]
                keys.shift()
                yield values.shift()} else {return false}
        } else {yield values.shift()
        }
    }
}

同一个场景,同一个数据结构,写法的确不同的,利用 Generator 就不再须要显示的写迭代协定了(next 办法和蕴含 done、value 属性的返回对象)。

Module

1、模块化的倒退状况

无模块化 –>CommonJS 标准 –>AMD 标准 –>CMD 标准 –>ES6 模块化

1、CommonJS 标准 Node 中模块化标准

Commonjs 的诞生给 js 模块化倒退有了重要的启发,Commonjs 十分受欢迎,然而局限性很显著:Commonjs 基于 Node 原生 api 在服务端能够实现模块同步加载,然而仅仅局限于服务端,客户端如果同步加载依赖的话工夫耗费十分大,所以须要一个 在客户端上基于 Commonjs 然而对于加载模块做改良的计划,于是 AMD 标准诞生了。

2、AMD 标准, 异步模块定义, 容许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。它采纳异步形式加载模块,模块的加载不影响它前面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载实现之后(前置依赖),这个回调函数才会运行。

3、CMD 标准,同样是受到 Commonjs 的启发,国内(阿里)诞生了一个 CMD(Common Module Definition)标准。该标准借鉴了 Commonjs 的标准与 AMD 标准,在两者根底上做了改良。

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

AMD 推崇依赖前置、提前执行 CMD 推崇依赖就近、提早执行。

4、到了 2015 年,ES6 标准中,终于将模块化纳入 JavaScript 规范,从此 js 模块化被官网扶正,也是将来 js 的规范. 在 ES6 中,咱们能够应用 import 关键字引入模块,通过 exprot 关键字导出模块,性能较之于前几个计划更为弱小,也是咱们所推崇的,然而因为 ES6 目前无奈在浏览器中执行,所以,咱们只能通过 babel 将不被反对的 import 编译为以后受到广泛支持的 require。

2、export

模块性能次要由两个命令形成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输出其余模块提供的性能。

一个模块就是一个独立的文件。该文件外部的所有变量,内部无奈获取。如果你心愿内部可能读取模块外部的某个变量,就必须应用 export 关键字输入该变量。

  • 导出变量或者常量

    export const name = 'hello'
    export let addr = 'BeiJing City'
    export var list = [1, 2, 3]

    或者

    const name = 'hello'
    let addr = 'BeiJing City'
    var list = [1, 2, 3]
    export {
    name,
    addr,
    list
    }
  • 导出函数

    export function say(content) {console.log(content)
    }
    export function run() {console.log('run')
    }

    或者

    const say = (content) => {console.log(content)
    }
    let run = () => {console.log('run')
    }
    export {
    say,
    run
    }
  • 导出 Object

    export ({
    code: 0,
    message: 'success'
    })

    或者

    let data = {
    code: 0,
    message: 'success'
    }
    export {data}
  • 导出 Class

    class Test {constructor() {this.id = 2}
    }
    export {Test}

    或者

    export class Test {constructor() {this.id = 2}
    }

    3、as

如果想为输出的变量从新取一个名字,import 命令要应用 as 关键字,将输出的变量重命名。

  const name = 'hello'
  let addr = 'BeiJing City'
  var list = [1, 2, 3]
  export {
      name as cname,
      addr as caddr,
      list
  }

4、export default

应用 import 命令的时候,用户须要晓得所要加载的变量名或函数名,否则无奈加载。然而,用户必定心愿疾速上手,未必违心浏览文档,去理解模块有哪些属性和办法。

为了给用户提供方便,让他们不必浏览文档就能加载模块,就要用到 export default 命令,为模块指定默认输入。

const name = 'hello'
let addr = 'BeiJing City'
var list = [1, 2, 3]
export {
  name as cname,
  addr as caddr
}
export default list

5、import

应用 export 命令定义了模块的对外接口当前,其余 JS 文件就能够通过 import 命令加载这个模块。

  • 间接导入
    假如导出模块 A 是这样的:

    const name = 'hello'
    let addr = 'BeiJing City'
    var list = [1, 2, 3]
    export {
    name as cname,
    addr as caddr
    }
    export default list

    则导入:

    import list, {
    cname,
    caddr
    } from A
  • 批改导入名称

    import list, {
    cname as name,
    caddr
    } from A
  • 批量导入

    import list, * as mod from A
    console.log(list)
    console.log(mod.cname)
    console.log(mod.caddr)

    ECMAScript2016(ES7)

    Array.prototype.includes()

    ES7 引入的 Array.prototype.includes() 办法用来判断一个数组是否蕴含一个指定的值,依据状况,如果蕴含则返回 true,否则返回 false。

  • 根本用法

    const arr = ['es6', 'es7', 'es8']
    console.log(arr.includes('es6')) // true
    console.log(arr.includes('es9')) // false
  • 接管俩个参数
    要搜寻的值和搜寻的开始索引。第二个参数可选。从该索引处开始查找 searchElement。如果为负值,

    const arr = ['es6', 'es7', 'es8']
    console.log(arr.includes('es7', 1)) // true
    console.log(arr.includes('es7', 2)) // false
    console.log(arr.includes('es7', -1)) // false
    console.log(arr.includes('es7', -2)) // true
  • 与 indexOf()比拟

    ['a', 'b', 'c'].includes('a') //true
    ['a', 'b', 'c'].indexOf('a') > -1 //true
    
    console.log(arr.indexOf('es7')) // 1
    console.log(arr.indexOf('es7') > -1) // true

    如果只想晓得某个值是否在数组中存在,而并不关怀它的索引地位,倡议应用 includes()。如果想获取一个值在数组中的地位,那么只能应用 indexOf 办法。

幂运算符 **

求幂运算

console.log(2 ** 10) // 1024

ECMAScript2017(ES8)

async / await

async 和 await 是一种更加优雅的异步编程解决方案,是 Promise 的拓展。

  • 根本语法
    后面增加了 async 的函数在执行后都会主动返回一个 Promise 对象:

    async function foo() {return 'xxx' // Promise.resolve('xxx')
    
      // let res =  Promise.resolve('xxx')
      // console.log(res)
    }
    console.log(foo()) // Promise
    foo()

    await 前面须要跟异步操作,不然就没有意义,而且 await 前面的 Promise 对象不用写 then,因为 await 的作用之一就是获取前面 Promise 对象胜利状态传递进去的参数。

    function timeout() {
      return new Promise(resolve => {setTimeout(() => {console.log(1)
              resolve() // resolve('success')
          }, 1000)
      })
    }
    
    // 不加 async 和 await 是 2、1   加了是 1、2
    async function foo() {await timeout() // let res = await timeout() res 是 success
      console.log(2)
    }
    foo()
  • 对于失败的解决

    function timeout() {return new Promise((resolve, reject) => {setTimeout(() => {// resolve('success')
              reject('error')
          }, 1000)
      })
    }
    async function foo() {return await timeout()
    }
    foo().then(res => {console.log(res)
    }).catch(err => {console.log(err)
    })

    Object 扩大

  • Object.values()
    Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的程序与通过手动循环对象的属性值所给出的程序雷同(for…in,然而 for…in 还会遍历原型上的属性值)。

    const obj = {
      name: 'baidu',
      web: 'www.baidu.com',
      course: 'es'
    }
    
    console.log(Object.values(obj))
    // ["baidu", "www.baidu.com", "es"]
  • Object.entries()
    Object.entries()办法返回一个给定对象本身可枚举属性的键值对数组,其排列与应用 for…in 循环遍历该对象时返回的程序统一。(区别在于 for-in 循环也枚举原型链中的属性)

    let grade = {
      'lilei': 98,
      'hanmei': 87
    }
    
    for (let [key, value] of grade) {console.log(key, value) // Uncaught TypeError: grade is not iterable
    }
  • Object.getOwnPropertyDescriptors()

    console.log(Object.getOwnPropertyDescriptor(data, 'Lima'))
    // {value: "58/40", writable: true, enumerable: false, configurable: true}

    String 扩大

    ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,容许将空字符串或其余字符串增加到原始字符串的结尾或结尾。

  • String.prototype.padStart()
    把指定字符串填充到字符串头部,返回新字符串。

    const str = 'xxx'
    console.log(str.padStart(8, 'x'))
    console.log(str.padEnd(8, 'y'))
    console.log(str.padStart(8))

    场景 1:日期格式化

    const now = new Date()
    const year = now.getFullYear()
    const month = (now.getMonth() + 1).toString().padStart(2, '0')
    const day = (now.getDate()).toString().padStart(2, '0')
    console.log(year, month, day)
    console.log(`${year}-${month}-${day}` )

    场景 2:数字替换

    // 数字替换,比方手机号,身份证号
    const tel = '13012345678'
    const newTel = tel.slice(-4).padStart(tel.length, '*')
    console.log(newTel) // *******5678
  • String.prototype.padEnd()
    办法会用一个字符串填充以后字符串(如果需要的话则反复填充),返回填充后达到指定长度的字符串。从以后字符串的开端(右侧)开始填充。

    const str1 = 'I am learning es in imooc'
    console.log(str1.padEnd(30, '.'))
    // I am learning es in imooc.....
    
    const str2 = '200'
    console.log(str2.padEnd(5))
    // "200"

    场景:工夫戳对立长度

    // 伪代码
    console.log(new Date().getTime()) // 工夫戳 13 位的
    timestamp = +String(timestamp).padEnd(13, '0')

    尾逗号 Trailing commas

    ES8 容许函数的最初一个参数有尾逗号(Trailing comma)。

    function clownsEverywhere(
      param1,
      param2,
    ) {/* ... */}
    
    clownsEverywhere(
      'foo',
      'bar',
    )

    ECMAScript2018(ES9)

    for await of

    异步迭代器(for-await-of):循环期待每个 Promise 对象变为 resolved 状态才进入下一步。

    function Gen(time) {return new Promise(function(resolve, reject) {setTimeout(function() {resolve(time)
          }, time)
      })
    }
    
    async function test() {let arr = [Gen(2000), Gen(100), Gen(3000)]
      for await (let item of arr) {console.log(Date.now(), item)
      }
    }
    
    test()
    // 1560092345730 2000
    // 1560092345730 100
    // 1560092346336 3000

    RegExp Updates

  • dotAll 模式
    正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,然而有两个例外。一个是四个字节的 UTF-16 字符,这个能够用 u 修饰符解决;另一个是行终止符(line terminator character)。
  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)
    如何判断以后正则是否应用了 dotAll 模式呢

    const re = /foo.bar/s // Or, `const re = new RegExp('foo.bar', 's')` .
    console.log(re.test('foo\nbar')) // true
    console.log(re.dotAll) // true
    console.log(re.flags) // 's'

    记住一句话就能够了解 dotAll 模式:它让 . 货真价实。

  • 具名组匹配
    咱们在写正则表达式的时候,能够把一部分用 () 包裹起来,被包裹起来的这部分称作“分组捕捉”。

    console.log('2020-05-01'.match(/(\d{4})-(\d{2})-(\d{2})/))
    // ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: undefined]

    这个正则匹配很简略,依照 match 的语法,没有应用 g 标识符,所以返回值第一个数值是正则表达式的残缺匹配,接下来的第二个值到第四个值是分组匹配(2020, 05, 01)。

此外 match 返回值还有几个属性,别离是 index、input、groups。

  1. index [匹配的后果的开始地位]
  2. input [搜寻的字符串]
  3. groups [一个捕捉组数组 或 undefined(如果没有定义命名捕捉组)]
    这里提到了命名捕捉组的概念,如果没有定义 groups 就是 undefined。很显著,咱们上述的返回值就是 undefined 间接阐明没有定义命名捕捉分组。那什么是命名捕捉分组呢?

    console.log('2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/))
    // ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]

    这段代码的返回值 groups 曾经是 Object 了,具体的值是:

    groups: {
     year: "2020",
     month: "05",
     day: "01"
    }

    这个 Object 的 key 就是正则表达式中定义的,也就是把捕捉分组进行了命名。想获取这些捕捉能够这样做:

    let t = '2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
    // ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]
    console.log(t.groups.year) // 2020
    console.log(t.groups.month) // 05
    console.log(t.groups.day) // 01
  4. 后行断言
    在 ES9 之前 JavaScript 正则只反对后行断言,不反对后行断言。简略温习下后行断言的常识:

    let test = 'hello world'
    console.log(test.match(/hello(?=\sworld)/))
    // ["hello", index: 0, input: "hello world", groups: undefined]

    这段代码要匹配前面是 world 的 hello,然而反过来就不成:

    let test = 'world hello'
    console.log(test.match(/hello(?=\sworld)/))
    // null

    比方咱们想判断后面是 world 的 hello,这个代码是实现不了的。在 ES9 就反对这个后行断言了:

    let test = 'world hello'
    console.log(test.match(/(?<=world\s)hello/))
    // ["hello", index: 6, input: "world hello", groups: undefined]

    (?<…)是后行断言的符号,(?…)是后行断言的符号,而后联合 =(等于)、!(不等)、\1(捕捉匹配)。

    Object Rest & Spread

    在 ES9 新增 Object 的 Rest & Spread 办法,间接看下示例:

    const input = {
      a: 1,
      b: 2
    }
    
    const output = {
      ...input,
      c: 3
    }
    
    console.log(output) // {a: 1, b: 2, c: 3}

    咱们再来看下 Object rest 的示例:

    const input = {
      a: 1,
      b: 2,
      c: 3
    }
    
    let {a, ...rest} = input
    
    console.log(a, rest) // 1 {b: 2, c: 3}

    当对象 key-value 不确定的时候,把必选的 key 赋值给变量,用一个变量收敛其余可选的 key 数据,这在之前是做不到的。

    Promise.prototype.finally()

    指定不论最初状态如何都会执行的回调函数。

Promise.prototype.finally() 办法返回一个 Promise,在 promise 执行完结时,无论后果是 fulfilled 或者是 rejected,在执行 then()和 catch()后,都会执行 finally 指定的回调函数。这为指定执行完 promise 后,无论后果是 fulfilled 还是 rejected 都须要执行的代码提供了一种形式,防止同样的语句须要在 then()和 catch()中各写一次的状况。

new Promise((resolve, reject) => {setTimeout(() => {resolve('success')
        // reject('fail')
    }, 1000)
}).then(res => {console.log(res)
}).catch(err => {console.log(err)
}).finally(() => {console.log('finally')
})

场景 1:loading 敞开

须要每次发送申请,都会有 loading 提醒,申请发送结束,就须要敞开 loading 提示框,不然界面就无奈被点击。不论申请胜利或是失败,这个 loading 都须要敞开掉,这时把敞开 loading 的代码写在 finally 里再适合不过了。

场景 2:数据库断开链接

let connection
db.open()
.then(conn => {
    connection = conn
    return connection.select({name: 'Jane'})
})
.then(result => {
    // Process result
    // Use `connection` to make more queries
})···
.catch(error => {// handle errors})
.finally(() => {connection.close()
})

字符串扩大

放松对标签模板里字符串本义的限度, 遇到不非法的字符串本义返回 undefined,并且从 raw 上可获取原字符串。

ES9 开始,模板字符串容许嵌套反对常见转义序列,移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限度。

// 带标签的模板字符串

const foo = (a, b, c, d) => {console.log(a)
    console.log(b)
    console.log(c)
    console.log(d)
}
// foo(1, 2, 3, 4)
const name = 'xxx'
const age = 18
foo ` 这是 ${name}, 他的年龄是 ${age}岁 ` 

ES9 规范移除了对 ECMAScript 带标签的模板字符串 中转义序列的语法限度。

function tag(strs) {console.log(strs)
    // strs[0] === undefined
    // strs.raw[0] === "\\unicode and \\u{55}"
}

// 在标签函数中应用
tag `\u{61} and \u{62}`  //
tag `\u{61} and \unicode`  // 后果是 undefined

// 之前的版本会报错:Invalid Unicode escape sequence
// 有效的 Unicode 转义序列

// 报错:let bad = `bad escape sequence: \unicode` 

ECMAScript2019(ES10)

Object.fromEntries()

Object.fromEntries() 把键值对列表转换为一个对象,这个办法是和 Object.entries() 绝对的。

Object.fromEntries([['foo', 1],
    ['bar', 2]
])
// {foo: 1, bar: 2}

String 扩大

  • String.prototype.trimStart()

    trimStart() 办法从字符串的结尾删除空格,trimLeft()是此办法的别名。

    let str = 'foo'
    console.log(str.length) // 8
    str = str.trimStart()
    console.log(str.length) // 5
  • String.prototype.trimEnd()

    trimEnd() 办法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。

    let str = 'foo'
    console.log(str.length) // 8
    str = str.trimEnd()
    console.log(str.length) // 6

    Array 扩大

  • Array.prototype.flat()

    flat() 办法会依照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

    const numbers = [1, 2, [3, 4, [5, 6]]]
    console.log(numbers.flat())
    // [1, 2, 3, 4, [5, 6]]

    此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会解决,不会再持续遍历外部的元素是否还有数组

    const numbers = [1, 2, [3, 4, [5, 6]]]
    console.log(numbers.flat(2))
    // [1, 2, 3, 4, 5, 6]

    当 flat 的参数大于等于 2,返回值就是 [1, 2, 3, 4, 5, 6] 了。

  • Array.prototype.flatMap()

    flatMap() 办法首先应用映射函数映射每个元素,而后将后果压缩成一个新数组。从办法的名字上也能够看进去它蕴含两局部性能一个是 map,一个是 flat(深度为 1)。

    const numbers = [1, 2, 3]
    numbers.map(x => [x * 2]) // [[2], [4], [6]]
    numbers.flatMap(x => [x * 2]) // [2, 4, 6]

    这个示例能够简略比照下 map 和 flatMap 的区别。当然还能够看下上面的示例:

    let arr = ['今天天气不错', '',' 早上好 ']
    arr.map(s => s.split(''))
    // [["今", "天", "天", "气", "不", "错"],[""],[" 早 "," 上 "," 好 "]]
    arr.flatMap(s => s.split(''))
    // ["今", "天", "天", "气", "不", "错", ""," 早 "," 上 "," 好 "]

    订正 Function.prototype.toString()

    函数是对象,并且每个对象都有一个 .toString() 办法,因为它最后存在于 Object.prototype.toString() 上。所有对象(包含函数)都是通过基于原型的类继承从它继承的。这意味着咱们以前曾经有 funcion.toString() 办法了。

Function.prototype.toString() 办法返回一个示意以后函数源代码的字符串。

这意味着还将返回正文、空格和语法详细信息。

function foo() {
    // es10 新个性
    console.log('xxxx')
}
console.log(foo.toString())

// 间接在办法名 toString()
console.log(Number.parseInt.toString())

可选的 Catch Binding

在 ES10 之前咱们都是这样捕捉异样的:

try {// tryCode} catch (err) {// catchCode}

在这里 err 是必须的参数,在 ES10 能够省略这个参数:

try {console.log('Foobar')
} catch {console.error('Bar')
}

JSON 扩大

  • JSON superset

    什么是 JSON 超集?,简而言之就是让 ECMAScript 兼容所有 JSON 反对的文本。ECMAScript 曾在规范 JSON.parse 局部说明 JSON 确为其一个子集,但因为 JSON 内容能够失常蕴含 U+2028 行分隔符 与 U+2029 段分隔符,而 ECMAScript 却不行。

  • JSON.stringify() 加强能力

    JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展现谬误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无奈编码成 UTF-8 进而导致显示谬误。在 ES10 它会用转义字符的形式来解决这部分字符而非编码的形式,这样就会失常显示了。

    // \uD83D\uDE0E  emoji 多字节的一个字符
    console.log(JSON.stringify('\uD83D\uDE0E')) // 笑脸
    
    // 如果咱们只去其中的一部分  \uD83D 这其实是个有效的字符串
    // 之前的版本,这些字符将替换为特殊字符,而当初将未配对的代理代码点示意为 JSON 转义序列
    console.log(JSON.stringify('\uD83D')) // "\ud83d"

    Symbol 扩大

  • Symbol.prototype.description

    只读属性,返回 Symbol 对象的可选形容的字符串。

咱们晓得,Symbol 的形容只被存储在外部的 Description,没有间接对外裸露,咱们只有调用 Symbol 的 toString() 时才能够读取这个属性:

const name = Symbol('es')
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === 'Symbol(es)') // false
console.log(name.toString() === 'Symbol(es)') // true

当初能够通过 description 办法获取 Symbol 的形容:

const name = Symbol('es')
console.log(name.description) // es
console.log(name.description === 'es') // true

ECMAScript2020(ES11)

String 扩大

  • String.prototype.matchAll()

    matchAll() 办法返回一个蕴含所有匹配正则表达式及分组捕捉后果的迭代器

    const str = `
    <html>
      <body>
        <div> 第一个 div</div>
        <p> 这是一个 p </p>
        <span>span</span>
        <div> 第二个 div</div>
      <body>
    </html>
    `

    请找出所有的 div 元素。

    function selectDiv(regExp, str) {let matches = []
      for (let match of str.matchAll(regExp)) {matches.push(match[1])
      }
      return matches
    }
    const res = selectDiv(regExp, str)
    console.log(res)

    Dynamic Import

    按需 import 提案几年前就已提出,现在终于能进入 ES 正式标准。这里集体了解成“按需”更为贴切。古代前端打包资源越来越大,打包成几 M 的 JS 资源已成常态,而往往前端利用初始化时基本不须要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比方懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。

页面上有一个按钮,点击按钮才去加载 ajax 模块。

const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {import('./ajax').then(mod => {// console.log(mod)
        mod.default('static/a.json', res => {console.log(res)
        })
    })
})

当然,webpack 目前已很好的反对了该个性。

BigInt

在 ES10 减少了新的原始数据类型:BigInt,示意一个任意精度的整数,能够示意超长数据,能够超出 2 的 53 次方。

Js 中 Number 类型只能平安的示意 -(2^53-1)至 2^53-1 范的值

console.log(2 ** 53) // es7 幂运算符
console.log(Number.MAX_SAFE_INTEGER) // 最大值 -1

应用 BigInt 有两种形式:

  • 形式一:数字前面减少 n

    const bigInt = 9007199254740993n
    console.log(bigInt)
    console.log(typeof bigInt) // bigint
    
    console.log(1n == 1) // true
    console.log(1n === 1) // false
  • 形式二:应用 BigInt 函数

    const bigIntNum = BigInt(9007199254740993n)
    console.log(bigIntNum)

    Promise.allSettled()

    学习了 ES 新个性,咱们都晓得 Promise.all() 具备并发执行异步工作的能力。但它的最大问题就是如果其中某个工作出现异常(reject),所有工作都会挂掉,Promise 间接进入 reject 状态。而 Promise.allSettled 返回一个在所有给定的 promise 已被决定或被回绝后决定的 promise,并带有一个对象数组,每个对象示意对应的 promise 后果。

    Promise.allSettled([
      Promise.reject({
          code: 500,
          msg: '服务异样'
      }),
      Promise.resolve({
          code: 200,
          data: ['1', '2', '3']
      }),
      Promise.resolve({
          code: 200,
          data: ['4', '5', '6']
      })
    ]).then(res => {console.log(res)
      // console.log('胜利')
      const data = res.filter(item => item.status === 'fulfilled')
      console.log(data)
    }).catch(err => {console.log(err)
      console.log('失败')
    })

    globalThis

    Javascript 在不同的环境获取全局对象有不通的形式:

  • node 中通过 global
  • web 中通过 window, self 等.

    self:关上任何一个网页,浏览器会首先创立一个窗口,这个窗口就是一个 window 对象,也是 js 运行所附丽的全局环境对象和全局作用域对象。self 指窗口自身,它返回的对象跟 window 对象是截然不同的。也正因为如此,window 对象的罕用办法和函数都能够用 self 代替 window。

globalThis 提供了一个规范的形式来获取不同环境下的全局 this 对象(也就是全局对象本身)。不像 window 或者 self 这些属性,它确保能够在有无窗口的各种环境下失常工作。所以,你能够安心的应用 globalThis,不用放心它的运行环境。为便于记忆,你只须要记住,全局作用域中的 this 就是 globalThis。

console.log(globalThis)

可选链 Optional chaining

可让咱们在查问具备多层级的对象时,不再须要进行冗余的各种前置校验。

const user = {
    address: {
        street: 'xx 街道',
        getNum() {return '80 号'}
    }
}

在之前的语法中,想获取到深层属性或办法,不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property… 这种谬误,这极有可能让你整个利用挂掉。

const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)

用了 Optional Chaining,下面代码会变成

const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)

可选链中的 ? 示意如果问号右边表达式有值, 就会持续查问问号前面的字段。依据下面能够看出,用可选链能够大量简化相似繁琐的前置校验操作,而且更平安。

空值合并运算符(Nullish coalescing Operator)

空值合并运算符(??)是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。

当咱们查问某个属性时,常常会遇到,如果没有该属性就会设置一个默认的值。

const b = 0 // 或者 null undefined false
const a = b || 5
console.log(a)

空值合并运算符 ?? 咱们仅在第一项为 null 或 undefined 时设置默认值

// false 0  有效
const a = b ?? 123
console.log(a)

ECMAScript2021(ES12)

replaceAll

返回一个全新的字符串,所有合乎匹配规定的字符都将被替换掉

const str = 'hello world';
str.replaceAll('l', ''); //"heo word"

Promise.any

Promise.any() 接管一个 Promise 可迭代对象,只有其中的一个 promise 胜利,就返回那个曾经胜利的 promise。如果可迭代对象中没有一个 promise 胜利(即所有的 promises 都失败 / 回绝),就返回一个失败的 promise。

const promise1 = new Promise((resolve, reject) => reject('我是失败的 Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失败的 Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList)
.then(values=>{console.log(values);
})
.catch(e=>{console.log(e);
});

WeakRefs

WeakRefs 的 Class 类创立对对象的弱援用(对对象的弱援用是指当该对象应该被 GC 回收时不会阻止 GC 的回收行为)

当咱们通过(const、let、var)创立一个变量时,垃圾收集器 GC 将永远不会从内存中删除该变量,只有它的援用依然存在可拜访。WeakRef 对象蕴含对对象的弱援用。对对象的弱援用是不会阻止垃圾收集器 GC 复原该对象的援用,则 GC 能够在任何时候删除它。

WeakRefs 在很多状况下都很有用,比方应用 Map 对象来实现具备很多须要大量内存的键值缓存,在这种状况下最不便的就是尽快开释键值对占用的内存。

目前,能够通过 WeakMap()或者 WeakSet()来应用 WeakRefs

我想要跟踪特定的对象调用某一特定办法的次数,超过 1000 条则做对应提醒

let map = new Map()
function doSomething(obj){...}
function useObject(obj){doSomething(obj)
  
  let called = map.get(obj) || 0
  called ++ 
  
  if(called>1000){console.log('以后调用次数曾经超过 1000 次了,over')
  }
  
  map.set(obj, called)
}

如上尽管能够实现咱们的性能,然而会产生内存溢出, 因为传递给 doSomething 函数的每个对象都永恒保留在 map 中,并且不会被 GC 回收,因而咱们能够应用 WeakMap

let wmap = new WeakMap()
function doSomething(obj){...}
function useObject(obj){doSomething(obj)
  
  let called = wmap.get(obj) || 0
  
  called ++
  
  if(called>1000){console.log('以后调用次数曾经超过 1000 次了,over')
  }
  
  wmap.set(obj, called)
}

逻辑运算符和赋值表达式

运算符和赋值表达式,新个性联合了逻辑运算符(&&,||,??)和赋值表达式而 JavaScript 已存在的 复合赋值运算符有如下

a ||= b
// 等价于
a = a || (a = b)

a &&= b
// 等价于
a = a && (a = b)

a ??= b
// 等价于
a = a ?? (a = b)

数字分隔符

数字分隔符,能够在数字之间创立可视化分隔符,通过_下划线来宰割数字,使数字更具可读性

const money = 1_000_000_000;
// 等价于
const money = 1000000000;

1_000_000_000 === 1000000000; // true

参考文章

  • JavaScript ES12 新个性领先体验
  • JS 语法 ES6、ES7、ES8、ES9、ES10、ES11、ES12 新个性
  • ECMAScript2015~2020 语法全解析
退出移动版