从ES6到ES10的新特性万字大总结(不得不收藏)

介绍

ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会)在标准ECMA-262中定义的脚本语言规范。这种语言在万维网上应用普遍,它每每被称为JavaScript或JScript,但实际上后二者是ECMA-262标准的实现和扩展。javascript

历史版本

至发稿日为止有九个ECMA-262版本发表。其历史版本以下:html

  1. 1997年6月:初版
  2. 1998年6月:修改格式,使其与ISO/IEC16262国际标准同样
  3. 1999年12月:强大的正则表达式,更好的词法做用域链处理,新的控制指令,异常处理,错误定义更加明确,数据输出的格式化及其它改变
  4. 2009年12月:添加严格模式("use strict")。修改了前面版本模糊不清的概念。增长了getters,setters,JSON以及在对象属性上更完整的反射。
  5. 2011年6月:ECMAScript标5.1版形式上彻底一致于国际标准ISO/IEC 16262:2011。
  6. 2015年6月:ECMAScript 2015(ES2015),第 6 版,最先被称做是 ECMAScript 6(ES6),添加了类和模块的语法,其余特性包括迭代器,Python风格的生成器和生成器表达式,箭头函数,二进制数据,静态类型数组,集合(maps,sets 和 weak maps),promise,reflection 和 proxies。做为最先的 ECMAScript Harmony 版本,也被叫作ES6 Harmony。
  7. 2016年6月:ECMAScript 2016(ES2016),第 7 版,多个新的概念和语言特性。
  8. 2017年6月:ECMAScript 2017(ES2017),第 8 版,多个新的概念和语言特性。
  9. 2018年6月:ECMAScript 2018 (ES2018),第 9 版,包含了异步循环,生成器,新的正则表达式特性和 rest/spread 语法。
  10. 2019年6月:ECMAScript 2019 (ES2019),第 10 版。

发展标准

TC39(Technical Committee 39)是一个推进JavaScript发展的委员会,它的成语来自各个主流浏览器的表明成语。会议实行多数决,每一项决策只有大部分人赞成且没有强烈反对才能去实现。java

TC39成员制定着ECMAScript的将来。git

每一项新特性最终要进入到ECMAScript规范里,须要经历5个阶段,这5个阶段以下:es6

  • Stage 0: Strawperson

    只要是TC39成员或者贡献者,均可以提交想法github

  • Stage 1: Proposal

    这个阶段肯定一个正式的提案web

  • Stage 2: draft

    规范的第一个版本,进入此阶段的提案大几率会成为标准ajax

  • Stage 3: Candidate

    进一步完善提案细则正则表达式

  • Stage 4: Finished

    表示已准备好将其添加到正式的ECMAScript标准中算法

因为ES6之前的属性诞生年末久远,咱们使用也比较广泛,遂不进行说明,ES6以后的语言风格跟ES5之前的差别比较大,因此单独拎出来作个记录。

ES6(ES2015)

ES6是一次重大的革新,比起过去的版本,改动比较大,本文仅对经常使用的API以及语法糖进行讲解。

Let 和 Const

在ES6之前,JS只有var一种声明方式,可是在ES6以后,就多了letconst这两种方式。用var定义的变量没有块级做用域的概念,而letconst则会有,由于这三个关键字建立是不同的。

区别以下:

{
    var a = 10
    let b = 20
    const c = 30
}
a // 10
b // Uncaught ReferenceError: b is not defined
c // c is not defined
let d = 40
const e = 50
d = 60
d // 60
e = 70 // VM231:1 Uncaught TypeError: Assignment to constant variable.
var let const
变量提高 × ×
全局变量 × ×
重复声明 × ×
从新赋值 ×
暂时死区 ×
块做用域 ×
只声明不初始化 ×

类(Class)

在ES6以前,若是咱们要生成一个实例对象,传统的方法就是写一个构造函数,例子以下:

function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.information = function () {
    return 'My name is ' + this.name + ', I am ' + this.age
}

可是在ES6以后,咱们只须要写成如下形式:

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    information() {
        return 'My name is ' + this.name + ', I am ' + this.age
    }
}

箭头函数(Arrow function)

箭头函数表达式的语法比函数表达式更简洁,而且没有本身的thisargumentssupernew.target。这些函数表达式更适用于那些原本须要匿名函数的地方,而且它们不能用做构造函数。

在ES6之前,咱们写函数通常是:

var list = [1, 2, 3, 4, 5, 6, 7]
var newList = list.map(function (item) {
    return item * item
})

可是在ES6里,咱们能够:

const list = [1, 2, 3, 4, 5, 6, 7]
const newList = list.map(item => item * item)

看,是否是简洁了很多

函数参数默认值(Function parameter defaults)

在ES6以前,若是咱们写函数须要定义初始值的时候,须要这么写:

function config (data) {
    var data = data || 'data is empty'
}

这样看起来也没有问题,可是若是参数的布尔值为falsy时就会出问题,例如咱们这样调用config:

config(0)
config('')

那么结果就永远是后面的值

若是咱们用函数参数默认值就没有这个问题,写法以下:

const config = (data = 'data is empty') => {}

模板字符串(Template string)

在ES6以前,若是咱们要拼接字符串,则须要像这样:

var name = 'kris'
var age = 24
var info = 'My name is ' + this.name + ', I am ' + this.age

可是在ES6以后,咱们只须要写成如下形式:

const name = 'kris'
const age = 24
const info = `My name is ${name}, I am ${age}`

解构赋值(Destructuring assignment)

咱们经过解构赋值, 能够将属性/值从对象/数组中取出,赋值给其余变量。

好比咱们须要交换两个变量的值,在ES6以前咱们可能须要:

var a = 10
var b = 20
var temp = a
a = b
b = temp

可是在ES6里,咱们有:

let a = 10
let b = 20
[a, b] = [b, a]

是否是方便不少

模块化(Module)

在ES6以前,JS并无模块化的概念,有的也只是社区定制的相似CommonJS和AMD之类的规则。例如基于CommonJS的NodeJS:

// circle.js
// 输出
const { PI } = Math
exports.area = (r) => PI * r ** 2
exports.circumference = (r) => 2 * PI * r

// index.js
// 输入
const circle = require('./circle.js')
console.log(`半径为 4 的圆的面积是 ${circle.area(4)}`)

在ES6以后咱们则能够写成如下形式:

// circle.js
// 输出
const { PI } = Math
export const area = (r) => PI * r ** 2
export const circumference = (r) => 2 * PI * r

// index.js
// 输入
import {
    area
} = './circle.js'
console.log(`半径为 4 的圆的面积是: ${area(4)}`)

扩展操做符(Spread operator)

扩展操做符能够在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还能够在构造字面量对象时, 将对象表达式按key-value的方式展开。

好比在ES5的时候,咱们要对一个数组的元素进行相加,在不使用reduce或者reduceRight的场合,咱们须要:

function sum(x, y, z) {
    return x + y + z;
}
var list = [5, 6, 7]
var total = sum.apply(null, list)

可是若是咱们使用扩展操做符,只须要以下:

const sum = (x, y, z) => x + y + z
const list = [5, 6, 7]
const total = sum(...list)

很是的简单,可是要注意的是扩展操做符只能用于可迭代对象

若是是下面的状况,是会报错的:

var obj = {'key1': 'value1'}
var array = [...obj] // TypeError: obj is not iterable

对象属性简写(Object attribute shorthand)

在ES6以前,若是咱们要将某个变量赋值为一样名称的对象元素,则须要:

var cat = 'Miaow'
var dog = 'Woof'
var bird = 'Peet peet'

var someObject = {
  cat: cat,
  dog: dog,
  bird: bird
}

可是在ES6里咱们就方便不少:

let cat = 'Miaow'
let dog = 'Woof'
let bird = 'Peet peet'

let someObject = {
  cat,
  dog,
  bird
}

console.log(someObject)

//{
//  cat: "Miaow",
//  dog: "Woof",
//  bird: "Peet peet"
//}

很是方便

Promise

Promise 是ES6提供的一种异步解决方案,比回调函数更加清晰明了。

Promise 翻译过来就是承诺的意思,这个承诺会在将来有一个确切的答复,而且该承诺有三种状态,分别是:

  1. 等待中(pending)
  2. 完成了 (resolved)
  3. 拒绝了(rejected)

这个承诺一旦从等待状态变成为其余状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变

new Promise((resolve, reject) => {
  resolve('success')
  // 无效
  reject('reject')
})

当咱们在构造 Promise 的时候,构造函数内部的代码是当即执行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh

Promise 实现了链式调用,也就是说每次调用 then 以后返回的都是一个 Promise,而且是一个全新的 Promise,缘由也是由于状态不可变。若是你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装

Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包装成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

固然了,Promise 也很好地解决了回调地狱的问题,例如:

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})

能够改写成:

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

for...of

for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上建立一个迭代循环,调用自定义迭代钩子,并为每一个不一样属性的值执行语句。

例子以下:

const array1 = ['a', 'b', 'c'];

for (const element of array1) {
      console.log(element)
}

// "a"
// "b"
// "c"

Symbol

symbol 是一种基本数据类型,Symbol()函数会返回symbol类型的值,该类型具备静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且相似于内建对象类,但做为构造函数来讲它并不完整,由于它不支持语法:"new Symbol()"。

每一个从Symbol()返回的symbol值都是惟一的。一个symbol值能做为对象属性的标识符;这是该数据类型仅有的目的。

例子以下:

const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1); // "symbol"
console.log(symbol3.toString()); // "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo')); // false

迭代器(Iterator)/ 生成器(Generator)

迭代器(Iterator)是一种迭代的机制,为各类不一样的数据结构提供统一的访问机制。任何数据结构只要内部有 Iterator 接口,就能够完成依次迭代操做。

一旦建立,迭代器对象能够经过重复调用next()显式地迭代,从而获取该对象每一级的值,直到迭代完,返回{ value: undefined, done: true }

虽然自定义的迭代器是一个有用的工具,但因为须要显式地维护其内部状态,所以须要谨慎地建立。生成器函数提供了一个强大的选择:它容许你定义一个包含自有迭代算法的函数, 同时它能够自动维护本身的状态。 生成器函数使用 function*语法编写。 最初调用时,生成器函数不执行任何代码,而是返回一种称为Generator的迭代器。 经过调用生成器的下一个方法消耗值时,Generator函数将执行,直到遇到yield关键字。

能够根据须要屡次调用该函数,而且每次都返回一个新的Generator,但每一个Generator只能迭代一次。

因此咱们能够有如下例子:

function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
    for (let i = start; i < end; i += step) {
        yield i;
    }
}
var a = makeRangeIterator(1,10,2)
a.next() // {value: 1, done: false}
a.next() // {value: 3, done: false}
a.next() // {value: 5, done: false}
a.next() // {value: 7, done: false}
a.next() // {value: 9, done: false}
a.next() // {value: undefined, done: true}

Set/WeakSet

Set 对象容许你存储任何类型的惟一值,不管是原始值或者是对象引用。

因此咱们能够经过Set实现数组去重

const numbers = [2,3,4,4,2,3,3,4,4,5,5,6,6,7,5,32,3,4,5]
console.log([...new Set(numbers)]) 
// [2, 3, 4, 5, 6, 7, 32]

WeakSet 结构与 Set 相似,但区别有如下两点:

  • WeakSet 对象中只能存放对象引用, 不能存放值, 而 Set 对象均可以。
  • WeakSet 对象中存储的对象值都是被弱引用的, 若是没有其余的变量或属性引用这个对象值, 则这个对象值会被当成垃圾回收掉. 正由于这样, WeakSet 对象是没法被枚举的, 没有办法拿到它包含的全部元素。

因此代码以下:

var ws = new WeakSet()
var obj = {}
var foo = {}

ws.add(window)
ws.add(obj)

ws.has(window) // true
ws.has(foo)    // false, 对象 foo 并无被添加进 ws 中 

ws.delete(window) // 从集合中删除 window 对象
ws.has(window)    // false, window 对象已经被删除了

ws.clear() // 清空整个 WeakSet 对象

Map/WeakMap

Map 对象保存键值对。任何值(对象或者原始值) 均可以做为一个键或一个值。

例子以下,咱们甚至可使用NaN来做为键值:

var myMap = new Map();
myMap.set(NaN, "not a number");

myMap.get(NaN); // "not a number"

var otherNaN = Number("foo");
myMap.get(otherNaN); // "not a number"

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值能够是任意的。

Map的区别与SetWeakSet的区别类似,具体代码以下:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // value能够是任意值,包括一个对象
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值能够是任意对象,甚至另一个WeakMap对象
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined,wm2中没有o2这个键
wm2.get(o3); // undefined,值就是undefined

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即便值是undefined)

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined,wm3已被清空
wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false

Proxy/Reflect

Proxy 对象用于定义基本操做的自定义行为(如属性查找,赋值,枚举,函数调用等)。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操做的方法。这些方法与 Proxy 的方法相同。Reflect不是一个函数对象,所以它是不可构造的。

ProxyReflect是很是完美的配合,例子以下:

const observe = (data, callback) => {
      return new Proxy(data, {
            get(target, key) {
                return Reflect.get(target, key)
            },
            set(target, key, value, proxy) {
                  callback(key, value);
                  target[key] = value;
                    return Reflect.set(target, key, value, proxy)
            }
      })
}

const FooBar = { open: false };
const FooBarObserver = observe(FooBar, (property, value) => {
  property === 'open' && value 
          ? console.log('FooBar is open!!!') 
          : console.log('keep waiting');
});
console.log(FooBarObserver.open) // false
FooBarObserver.open = true // FooBar is open!!!

固然也不是什么均可以被代理的,若是对象带有configurable: falsewritable: false 属性,则代理失效。

Regex对象的扩展

正则新增符号

  • i 修饰符

    // i 修饰符
    /[a-z]/i.test('\u212A') // false
    /[a-z]/iu.test('\u212A') // true
  • y修饰符

    // y修饰符
    var s = 'aaa_aa_a';
    var r1 = /a+/g;
    var r2 = /a+/y;
    
    r1.exec(s) // ["aaa"]
    r2.exec(s) // ["aaa"]
    
    r1.exec(s) // ["aa"]
    r2.exec(s) // null
  • String.prototype.flags

    // 查看RegExp构造函数的修饰符
    var regex = new RegExp('xyz', 'i')
    regex.flags // 'i'
  • unicode模式

    var s = '𠮷'
    /^.$/.test(s) // false
    /^.$/u.test(s) // true
  • u转义

    // u转义
    /\,/ // /\,/
    /\,/u // 报错 没有u修饰符时,逗号前面的反斜杠是无效的,加了u修饰符就报错。
  • 引用

    const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
    RE_TWICE.test('abc!abc') // true
    RE_TWICE.test('abc!ab') // false
    
    const RE_TWICE = /^(?<word>[a-z]+)!\1$/;
    RE_TWICE.test('abc!abc') // true
    RE_TWICE.test('abc!ab') // false

字符串方法的实现改成调用RegExp方法

  • String.prototype.match 调用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 调用 RegExp.prototype[Symbol.search]
  • String.prototype.split 调用 RegExp.prototype[Symbol.split]

正则新增属性

  • RegExp.prototype.sticky 表示是否有y修饰符

    /hello\d/y.sticky // true
  • RegExp.prototype.flags获取修饰符

    /abc/ig.flags // 'gi'

Math对象的扩展

  • 二进制表示法 : 0b或0B开头表示二进制(0bXX0BXX)
  • 二进制表示法 : 0b或0B开头表示二进制(0bXX0BXX)
  • 八进制表示法 : 0o或0O开头表示二进制(0oXX0OXX)
  • Number.EPSILON : 数值最小精度
  • Number.MIN_SAFE_INTEGER : 最小安全数值(-2^53)
  • Number.MAX_SAFE_INTEGER : 最大安全数值(2^53)
  • Number.parseInt() : 返回转换值的整数部分
  • Number.parseFloat() : 返回转换值的浮点数部分
  • Number.isFinite() : 是否为有限数值
  • Number.isNaN() : 是否为NaN
  • Number.isInteger() : 是否为整数
  • Number.isSafeInteger() : 是否在数值安全范围内
  • Math.trunc() : 返回数值整数部分
  • Math.sign() : 返回数值类型(正数1负数-1零0)
  • Math.cbrt() : 返回数值立方根
  • Math.clz32() : 返回数值的32位无符号整数形式
  • Math.imul() : 返回两个数值相乘
  • Math.fround() : 返回数值的32位单精度浮点数形式
  • Math.hypot() : 返回全部数值平方和的平方根
  • Math.expm1() : 返回e^n - 1
  • Math.log1p() : 返回1 + n的天然对数(Math.log(1 + n))
  • Math.log10() : 返回以10为底的n的对数
  • Math.log2() : 返回以2为底的n的对数
  • Math.sinh() : 返回n的双曲正弦
  • Math.cosh() : 返回n的双曲余弦
  • Math.tanh() : 返回n的双曲正切
  • Math.asinh() : 返回n的反双曲正弦
  • Math.acosh() : 返回n的反双曲余弦
  • Math.atanh() : 返回n的反双曲正切

Array对象的扩展

  • Array.prototype.from:转换具备Iterator接口的数据结构为真正数组,返回新数组。

    console.log(Array.from('foo')) // ["f", "o", "o"]
    console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6]
  • Array.prototype.of():转换一组值为真正数组,返回新数组。

    Array.of(7)       // [7] 
    Array.of(1, 2, 3) // [1, 2, 3]
    
    Array(7)          // [empty, empty, empty, empty, empty, empty]
    Array(1, 2, 3)    // [1, 2, 3]
  • Array.prototype.copyWithin():把指定位置的成员复制到其余位置,返回原数组

    const array1 = ['a', 'b', 'c', 'd', 'e']
    
    console.log(array1.copyWithin(0, 3, 4)) // ["d", "b", "c", "d", "e"]
    
    console.log(array1.copyWithin(1, 3)) // ["d", "d", "e", "d", "e"]
  • Array.prototype.find():返回第一个符合条件的成员

    const array1 = [5, 12, 8, 130, 44]
    
    const found = array1.find(element => element > 10)
    
    console.log(found) // 12
  • Array.prototype.findIndex():返回第一个符合条件的成员索引值

    const array1 = [5, 12, 8, 130, 44]
    
    const isLargeNumber = (element) => element > 13
    
    console.log(array1.findIndex(isLargeNumber)) // 3
  • Array.prototype.fill():根据指定值填充整个数组,返回原数组

    const array1 = [1, 2, 3, 4]
    
    console.log(array1.fill(0, 2, 4)) // [1, 2, 0, 0]
    
    console.log(array1.fill(5, 1)) // [1, 5, 5, 5]
    
    console.log(array1.fill(6)) // [6, 6, 6, 6]
  • Array.prototype.keys():返回以索引值为遍历器的对象

    const array1 = ['a', 'b', 'c']
    const iterator = array1.keys()
    
    for (const key of iterator) {
          console.log(key)
    }
    
    // 0
    // 1
    // 2
  • Array.prototype.values():返回以属性值为遍历器的对象

    const array1 = ['a', 'b', 'c']
    const iterator = array1.values()
    
    for (const key of iterator) {
          console.log(key)
    }
    
    // a
    // b
    // c
  • Array.prototype.entries():返回以索引值和属性值为遍历器的对象

    const array1 = ['a', 'b', 'c']
    const iterator = array1.entries()
    
    console.log(iterator.next().value) // [0, "a"]
    console.log(iterator.next().value) // [1, "b"]
  • 数组空位:ES6明确将数组空位转为undefined或者empty

    Array.from(['a',,'b']) // [ "a", undefined, "b" ]
    [...['a',,'b']] // [ "a", undefined, "b" ]
    Array(3) //  [empty × 3]
    [,'a'] // [empty, "a"]

ES7(ES2016)

Array.prototype.includes()

includes() 方法用来判断一个数组是否包含一个指定的值,根据状况,若是包含则返回 true,不然返回false。

代码以下:

const array1 = [1, 2, 3]
console.log(array1.includes(2)) // true

const pets = ['cat', 'dog', 'bat']
console.log(pets.includes('cat')) // true
console.log(pets.includes('at')) // false

幂运算符**

幂运算符**,具备与Math.pow()同样的功能,代码以下:

console.log(2**10) // 1024
console.log(Math.pow(2, 10)) // 1024

模板字符串(Template string)

自ES7起,带标签的模版字面量遵照如下转义序列的规则:

  • Unicode字符以"u"开头,例如\u00A9
  • Unicode码位用"u{}"表示,例如\u{2F804}
  • 十六进制以"x"开头,例如\xA9
  • 八进制以""和数字开头,例如\251

这表示相似下面这种带标签的模版是有问题的,由于对于每个ECMAScript语法,解析器都会去查找有效的转义序列,可是只能获得这是一个形式错误的语法:

latex`\unicode`
// 在较老的ECMAScript版本中报错(ES2016及更早)
// SyntaxError: malformed Unicode character escape sequence

ES8(ES2017)

async/await

虽然Promise能够解决回调地狱的问题,可是链式调用太多,则会变成另外一种形式的回调地狱 —— 面条地狱,因此在ES8里则出现了Promise的语法糖async/await,专门解决这个问题。

咱们先看一下下面的Promise代码:

fetch('coffee.jpg')
    .then(response => response.blob())
    .then(myBlob => {
          let objectURL = URL.createObjectURL(myBlob)
          let image = document.createElement('img')
          image.src = objectURL
          document.body.appendChild(image)
    })
    .catch(e => {
          console.log('There has been a problem with your fetch operation: ' + e.message)
    })

而后再看看async/await版的,这样看起来是否是更清晰了。

async function myFetch() {
      let response = await fetch('coffee.jpg')
      let myBlob = await response.blob()

      let objectURL = URL.createObjectURL(myBlob)
      let image = document.createElement('img')
      image.src = objectURL
      document.body.appendChild(image)
}

myFetch()

固然,若是你喜欢,你甚至能够二者混用

async function myFetch() {
      let response = await fetch('coffee.jpg')
      return await response.blob()
}

myFetch().then((blob) => {
      let objectURL = URL.createObjectURL(blob)
      let image = document.createElement('img')
      image.src = objectURL
      document.body.appendChild(image)
})

Object.values()

Object.values()方法返回一个给定对象自身的全部可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

代码以下:

const object1 = {
      a: 'somestring',
      b: 42,
      c: false
}
console.log(Object.values(object1)) // ["somestring", 42, false]

Object.entries()

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。

代码以下:

const object1 = {
      a: 'somestring',
      b: 42
}

for (let [key, value] of Object.entries(object1)) {
      console.log(`${key}: ${value}`)
}

// "a: somestring"
// "b: 42"

padStart()

padStart() 方法用另外一个字符串填充当前字符串(重复,若是须要的话),以便产生的字符串达到给定的长度。填充从当前字符串的开始(左侧)应用的。

代码以下:

const str1 = '5'
console.log(str1.padStart(2, '0')) // "05"

const fullNumber = '2034399002125581'
const last4Digits = fullNumber.slice(-4)
const maskedNumber = last4Digits.padStart(fullNumber.length, '*') 
console.log(maskedNumber) // "************5581"

padEnd()

padEnd() 方法会用一个字符串填充当前字符串(若是须要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

const str1 = 'Breaded Mushrooms'
console.log(str1.padEnd(25, '.')) // "Breaded Mushrooms........"
const str2 = '200'
console.log(str2.padEnd(5)) // "200  "

函数参数结尾逗号(Function parameter lists and calls trailing commas)

在ES5里就添加了对象的尾逗号,不过并不支持函数参数,可是在ES8以后,便开始支持这一特性,代码以下:

// 参数定义
function f(p) {}
function f(p,) {} 

(p) => {}
(p,) => {}

class C {
  one(a,) {},
  two(a, b,) {},
}

var obj = {
  one(a,) {},
  two(a, b,) {},
};

// 函数调用
f(p)
f(p,)

Math.max(10, 20)
Math.max(10, 20,)

可是如下的方式是不合法的:

仅仅包含逗号的函数参数定义或者函数调用会抛出 SyntaxError。 并且,当使用剩余参数的时候,并不支持尾后逗号,例子以下:

function f(,) {} // SyntaxError: missing formal parameter
(,) => {}       // SyntaxError: expected expression, got ','
f(,)             // SyntaxError: expected expression, got ','

function f(...p,) {} // SyntaxError: parameter after rest parameter
(...p,) => {}        // SyntaxError: expected closing parenthesis, got ','

在解构里也可使用,代码以下:

// 带有尾后逗号的数组解构
[a, b,] = [1, 2]

// 带有尾后逗号的对象解构
var o = {
  p: 42, 
  q: true,
}
var {p, q,} = o

一样地,在使用剩余参数时,会抛出 SyntaxError,代码以下:

var [a, ...b,] = [1, 2, 3] // SyntaxError: rest element may not have a trailing comma

ShareArrayBuffer(因安全问题,暂时在Chrome跟FireFox中被禁用)

SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,相似于 ArrayBuffer 对象,它们均可以用来在共享内存(shared memory)上建立视图。与 ArrayBuffer 不一样的是,SharedArrayBuffer 不能被分离。

代码以下:

let sab = new SharedArrayBuffer(1024) // 必须实例化
worker.postMessage(sab)

Atomics对象

Atomics对象 提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操做。

方法以下:

  • Atomics.add() :将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。
  • Atomics.and():将指定位置上的数组元素与给定的值相与,并返回与操做前该元素的值。
  • Atomics.compareExchange():若是数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。
  • Atomics.exchange():将数组中指定的元素更新为给定的值,并返回该元素更新前的值。
  • Atomics.load():返回数组中指定元素的值。
  • Atomics.or():将指定位置上的数组元素与给定的值相或,并返回或操做前该元素的值。
  • Atomics.store():将数组中指定的元素设置为给定的值,并返回该值。
  • Atomics.sub():将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。
  • Atomics.xor():将指定位置上的数组元素与给定的值相异或,并返回异或操做前该元素的值。
  • Atomics.wait():检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 "ok"、"not-equal" 或 "time-out"。调用时,若是当前线程不容许阻塞,则会抛出异常(大多数浏览器都不容许在主线程中调用 wait())。
  • Atomics.wake():唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。
  • Atomics.isLockFree(size):能够用来检测当前系统是否支持硬件级的原子操做。对于指定大小的数组,若是当前系统支持硬件级的原子操做,则返回 true;不然就意味着对于该数组,Atomics 对象中的各原子操做都只能用锁来实现。此函数面向的是技术专家。

Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors() 方法用来获取一个对象的全部自身属性的描述符。代码以下:

const object1 = {
  property1: 42
}

const descriptors1 = Object.getOwnPropertyDescriptors(object1)

console.log(descriptors1.property1.writable) // true

console.log(descriptors1.property1.value) // 42

// 浅拷贝一个对象
Object.create(
  Object.getPrototypeOf(obj), 
  Object.getOwnPropertyDescriptors(obj) 
)

// 建立子类
function superclass() {}
superclass.prototype = {
  // 在这里定义方法和属性
}
function subclass() {}
subclass.prototype = Object.create(superclass.prototype, Object.getOwnPropertyDescriptors({
  // 在这里定义方法和属性
}))

ES9(ES2018)

for await...of

for await...of 语句会在异步或者同步可迭代对象上建立一个迭代循环,包括 StringArrayArray-like 对象(好比arguments 或者NodeList),TypedArrayMapSet和自定义的异步或者同步可迭代对象。其会调用自定义迭代钩子,并为每一个不一样属性的值执行语句。

配合迭代异步生成器,例子以下:

async function* asyncGenerator() {
      var i = 0
      while (i < 3) {
            yield i++
      }
}

(async function() {
      for await (num of asyncGenerator()) {
            console.log(num)
      }
})()
// 0
// 1
// 2

模板字符串(Template string)

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

不过,非法转义序列在"cooked"当中仍然会体现出来。它们将以undefined元素的形式存在于"cooked"之中,代码以下:

function latex(str) { 
 return { "cooked": str[0], "raw": str.raw[0] }
} 

latex`\unicode` // { cooked: undefined, raw: "\\unicode" }

正则表达式反向(lookbehind)断言

首先咱们得先知道什么是断言(Assertion)

断言(Assertion)是一个对当前匹配位置以前或以后的字符的测试, 它不会实际消耗任何字符,因此断言也被称为“非消耗性匹配”或“非获取匹配”。

正则表达式的断言一共有 4 种形式:

  • (?=pattern) 零宽正向确定断言(zero-width positive lookahead assertion)
  • (?!pattern) 零宽正向否认断言(zero-width negative lookahead assertion)
  • (?<=pattern) 零宽反向确定断言(zero-width positive lookbehind assertion)
  • (?<!pattern) 零宽反向否认断言(zero-width negative lookbehind assertion)

在ES9以前,JavaScript 正则表达式,只支持正向断言。正向断言的意思是:当前位置后面的字符串应该知足断言,可是并不捕获。例子以下:

'fishHeadfishTail'.match(/fish(?=Head)/g) // ["fish"]

反向断言和正向断言的行为同样,只是方向相反。例子以下:

'abc123'.match(/(?<=(\d+)(\d+))$/) //  ["", "1", "23", index: 6, input: "abc123", groups: undefined]

正则表达式 Unicode 转义

正则表达式中的Unicode转义符容许根据Unicode字符属性匹配Unicode字符。 它容许区分字符类型,例如大写和小写字母,数学符号和标点符号。

部分例子代码以下:

// 匹配全部数字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true

// 匹配全部空格
\p{White_Space}

// 匹配各类文字的全部字母,等同于 Unicode 版的 \w
[\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配各类文字的全部非字母的字符,等同于 Unicode 版的 \W
[^\p{Alphabetic}\p{Mark}\p{Decimal_Number}\p{Connector_Punctuation}\p{Join_Control}]

// 匹配 Emoji
/\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu

// 匹配全部的箭头字符
const regexArrows = /^\p{Block=Arrows}+$/u;
regexArrows.test('←↑→↓↔↕↖↗↘↙⇏⇐⇑⇒⇓⇔⇕⇖⇗⇘⇙⇧⇩') // true

具体的属性列表可查看:https://developer.mozilla.org...

正则表达式 s/dotAll 模式

在以往的版本里,JS的正则的.只能匹配emoji跟行终结符之外的全部文本,例如:

let regex = /./;

regex.test('\n');       // false
regex.test('\r');       // false
regex.test('\u{2028}'); // false
regex.test('\u{2029}'); // false

regex.test('\v');       // true
regex.test('\f');       // true
regex.test('\u{0085}'); // true

/foo.bar/.test('foo\nbar');     // false
/foo[^]bar/.test('foo\nbar');   // true

/foo.bar/.test('foo\nbar');     // false
/foo[\s]bar/.test('foo\nbar');   // true

可是在ES9以后,JS正则增长了一个新的标志 s 用来表示 dotAll,这能够匹配任意字符。代码以下:

/foo.bar/s.test('foo\nbar');    // true

const re = /foo.bar/s;  //  等价于 const re = new RegExp('foo.bar', 's');
re.test('foo\nbar');    // true
re.dotAll;      // true
re.flags;       // "s"

正则表达式命名捕获组

在以往的版本里,JS的正则分组是没法命名的,因此容易混淆。例以下面获取年月日的例子,很容易让人搞不清哪一个是月份,哪一个是年份:

const matched = /(\d{4})-(\d{2})-(\d{2})/.exec('2019-01-01')
console.log(matched[0]);    // 2019-01-01
console.log(matched[1]);    // 2019
console.log(matched[2]);    // 01
console.log(matched[3]);    // 01

ES9引入了命名捕获组,容许为每个组匹配指定一个名字,既便于阅读代码,又便于引用。代码以下:

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

const RE_OPT_A = /^(?<as>a+)?$/;
const matchObj = RE_OPT_A.exec('');

matchObj.groups.as // undefined
'as' in matchObj.groups // true

对象扩展操做符

ES6中添加了数组的扩展操做符,让咱们在操做数组时更加简便,美中不足的是并不支持对象扩展操做符,可是在ES9开始,这一功能也获得了支持,例如:

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }

上面即是一个简便的浅拷贝。这里有一点小提示,就是Object.assign() 函数会触发 setters,而展开语法则不会。因此不能替换也不能模拟Object.assign()

若是存在相同的属性名,只有最后一个会生效。

Promise.prototype.finally()

finally()方法会返回一个Promise,当promise的状态变动,无论是变成rejected或者fulfilled,最终都会执行finally()的回调。

例子以下:

fetch(url)
      .then((res) => {
        console.log(res)
      })
      .catch((error) => { 
        console.log(error)
      })
      .finally(() => { 
        console.log('结束')
    })

ES10(ES2019)

Array.prototype.flat() / flatMap()

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

flatMap()map() 方法和深度为1的 flat() 几乎相同.,不过它会首先使用映射函数映射每一个元素,而后将结果压缩成一个新数组,这样效率会更高。

例子以下:

var arr1 = [1, 2, 3, 4]

arr1.map(x => [x * 2]) // [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]) // [2, 4, 6, 8]

// 深度为1
arr1.flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]]

flatMap()能够代替reduce()concat(),例子以下:

var arr = [1, 2, 3, 4]
arr.flatMap(x => [x, x * 2]) // [1, 2, 2, 4, 3, 6, 4, 8]
// 等价于
arr.reduce((acc, x) => acc.concat([x, x * 2]), []) // [1, 2, 2, 4, 3, 6, 4, 8]

但这是很是低效的,在每次迭代中,它建立一个必须被垃圾收集的新临时数组,而且它将元素从当前的累加器数组复制到一个新的数组中,而不是将新的元素添加到现有的数组中。

String.prototype.trimStart() / trimLeft() / trimEnd() / trimRight()

在ES5中,咱们能够经过trim()来去掉字符首尾的空格,可是却没法只去掉单边的,可是在ES10以后,咱们能够实现这个功能。

若是咱们要去掉开头的空格,可使用trimStart()或者它的别名trimLeft()

一样的,若是咱们要去掉结尾的空格,咱们可使用trimEnd()或者它的别名trimRight()

例子以下:

const Str = '   Hello world!  '
console.log(Str) // '   Hello world!  '
console.log(Str.trimStart()) // 'Hello world!  '
console.log(Str.trimLeft()) // 'Hello world!  '
console.log(Str.trimEnd()) // '   Hello world!'
console.log(Str.trimRight()) // '   Hello world!'

不过这里有一点要注意的是,trimStart()trimEnd()才是标准方法,trimLeft()trimRight()只是别名。

在某些引擎里(例如Chrome),有如下的等式:

String.prototype.trimLeft.name === "trimStart"

String.prototype.trimRight.name === "trimEnd"

Object.fromEntries()

Object.fromEntries() 方法把键值对列表转换为一个对象,它是Object.entries()的反函数。

例子以下:

const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
])

const obj = Object.fromEntries(entries)

console.log(obj) // Object { foo: "bar", baz: 42 }

Symbol.prototype.description

description 是一个只读属性,它会返回Symbol对象的可选描述的字符串。与 Symbol.prototype.toString() 不一样的是它不会包含Symbol()的字符串。例子以下:

Symbol('desc').toString();   // "Symbol(desc)"
Symbol('desc').description;  // "desc"
Symbol('').description;      // ""
Symbol().description;        // undefined

// 具名 symbols
Symbol.iterator.toString();  // "Symbol(Symbol.iterator)"
Symbol.iterator.description; // "Symbol.iterator"

//全局 symbols
Symbol.for('foo').toString();  // "Symbol(foo)"
Symbol.for('foo').description; // "foo"

String.prototype.matchAll

matchAll() 方法返回一个包含全部匹配正则表达式的结果及分组捕获组的迭代器。而且返回一个不可重启的迭代器。例子以下:

var regexp = /t(e)(st(\d?))/g
var str = 'test1test2'

str.match(regexp) // ['test1', 'test2']
str.matchAll(regexp) // RegExpStringIterator {}
[...str.matchAll(regexp)] // [['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4], ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]]

Function.prototype.toString() 返回注释与空格

在以往的版本中,Function.prototype.toString()获得的字符串是去掉空白符号的,可是从ES10开始会保留这些空格,若是是原生函数则返回你控制台看到的效果,例子以下:

function sum(a, b) {
      return a + b;
}

console.log(sum.toString())
// "function sum(a, b) {
//         return a + b;
//  }"

console.log(Math.abs.toString()) // "function abs() { [native code] }"

try-catch

在以往的版本中,try-catchcatch后面必须带异常参数,例如:

// ES10以前
try {
      // tryCode
} catch (err) {
      // catchCode
}

可是在ES10以后,这个参数却不是必须的,若是用不到,咱们能够不用传,例如:

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

BigInt

BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这本来是 Javascript中能够用 Number 表示的最大数字。BigInt 能够表示任意大的整数。

能够用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数BigInt()

在以往的版本中,咱们有如下的弊端:

// 大于2的53次方的整数,没法保持精度
2 ** 53 === (2 ** 53 + 1)
// 超过2的1024次方的数值,没法表示
2 ** 1024 // Infinity

可是在ES10引入BigInt以后,这个问题便获得了解决。

如下操做符能够和 BigInt 一块儿使用: +*-**% 。除 >>> (无符号右移)以外的位操做也能够支持。由于 BigInt 都是有符号的, >>> (无符号右移)不能用于 BigIntBigInt 不支持单目 (+) 运算符。

/ 操做符对于整数的运算也没问题。但是由于这些变量是 BigInt 而不是 BigDecimal ,该操做符结果会向零取整,也就是说不会返回小数部分。

BigIntNumber不是严格相等的,可是宽松相等的。

因此在BigInt出来之后,JS的原始类型便增长到了7个,以下:

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol (ES6)
  • BigInt (ES10)

globalThis

globalThis属性包含相似于全局对象 this值。因此在全局环境下,咱们有:

globalThis === this // true

import()

静态的import 语句用于导入由另外一个模块导出的绑定。不管是否声明了 严格模式,导入的模块都运行在严格模式下。在浏览器中,import 语句只能在声明了 type="module"script 的标签中使用。

可是在ES10以后,咱们有动态 import(),它不须要依赖 type="module" 的script标签。

因此咱们有如下例子:

const main = document.querySelector("main")
for (const link of document.querySelectorAll("nav > a")) {
      link.addEventListener("click", e => {
            e.preventDefault()

            import('/modules/my-module.js')
              .then(module => {
                    module.loadPageInto(main);
              })
              .catch(err => {
                    main.textContent = err.message;
              })
      })
}

私有元素与方法

在ES10以前,若是咱们要实现一个简单的计数器组件,咱们可能会这么写:

// web component 写法
class Counter extends HTMLElement {
      get x() { 
              return this.xValue
      }
      set x(value) {
              this.xValue = value
              window.requestAnimationFrame(this.render.bind(this))
      }

      clicked() {
            this.x++
      }

      constructor() {
            super()
            this.onclick = this.clicked.bind(this)
            this.xValue = 0
      }

      connectedCallback() { 
              this.render()
      }

      render() {
            this.textContent = this.x.toString()
      }
}
window.customElements.define('num-counter', Counter)

可是在ES10以后咱们可使用私有变量进行组件封装,以下:

class Counter extends HTMLElement {
      #xValue = 0

      get #x() { 
          return #xValue
      }
      set #x(value) {
            this.#xValue = value
            window.requestAnimationFrame(this.#render.bind(this))
      }

      #clicked() {
            this.#x++
      }

      constructor() {
            super();
            this.onclick = this.#clicked.bind(this)
      }

      connectedCallback() { 
              this.#render()
      }

      #render() {
            this.textContent = this.#x.toString()
      }
}
window.customElements.define('num-counter', Counter)

参考资料

  1. ECMAScript 6 入门
  2. 1.5万字归纳ES6所有特性
  3. MDN
  4. ES2018 新特征之:非转义序列的模板字符串
  5. 正则表达式反向(lookbehind)断言
  6. Unicode property escapes
  7. exnext提案
  8. ES七、ES八、ES九、ES10新特性大盘点
  9. Ecma TC39
  10. [[ECMAScript] TC39 process](https://www.jianshu.com/p/b08...
  11. The TC39 Process
相关文章
相关标签/搜索