ES6、ES7、ES8、ES9、ES10新特性

倖福魔咒の 提交于 2020-01-07 13:19:33

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

ES6新特性(2015)

类 Class

传统的javascript中只有对象,没有类的概念。它是基于原型的面向对象语言。原型对象特点就是将自身的属性共享给新对象。这样的写法相对于其它传统面向对象语言来讲,很有一种独树一帜的感脚!非常容易让人困惑!

如果要生成一个对象实例,需要先定义一个构造函数,然后通过new操作符来完成。构造函数示例:

//函数名和实例化构造名相同且大写(非强制,但这么写有助于区分构造函数和普通函数)

function Person(name,age) {

    this.name = name;

    this.age=age;

}

Person.prototype.say = function(){

    return "我的名字叫" + this.name+"今年"+this.age+"岁了";

}

var obj=new Person("laotie",88);//通过构造函数创建对象,必须使用new 运算符

console.log(obj.say());//我的名字叫laotie今年88岁了

构造函数生成实例的执行过程:

1.当使用了构造函数,并且new 构造函数(),后台会隐式执行new Object()创建对象;

2.将构造函数的作用域给新对象,(即new Object()创建出的对象),而函数体内的this就代表new Object()出来的对象。

3.执行构造函数的代码。

4.返回新对象(后台直接返回);

ES6引入了Class(类)这个概念,通过class关键字可以定义类。该关键字的出现使得其在对象写法上更加清晰,更像是一种面向对象的语言。如果将之前的代码改为ES6的写法就会是这个样子:

class Person{//定义了一个名字为Person的类

    constructor(name,age){//constructor是一个构造方法,用来接收参数

        this.name = name;//this代表的是实例对象

        this.age=age;

    }

    say(){//这是一个类的方法,注意千万不要加上function

        return "我的名字叫" + this.name+"今年"+this.age+"岁了";

    }

}

var obj=new Person("laotie",88);

console.log(obj.say());//我的名字叫laotie今年88岁了

注意项:

• 1.在类中声明方法的时候,千万不要给该方法加上function关键字

• 2.方法之间不要用逗号分隔,否则会报错

由下面代码可以看出类实质上就是一个函数。类自身指向的就是构造函数。所以可以认为ES6中的类其实就是构造函数的另外一种写法!

console.log(typeof Person);//function

console.log(Person===Person.prototype.constructor);//true

模块化(Module)

//导出变量

 var name = 'Rainbow';

 var age = '24';

 export {name, age};

// 导出函数

export function myModule(someArg) {

  return someArg;

// 导入

import {myModule} from 'myModule';// main.js

import {name,age} from 'test';// test.js

箭头函数

基础语法

(参数1, 参数2, …, 参数N) => { 函数声明 }

var f = (num1, num2) => { 

    return num1*num2 

};

console.log(f(3, 4)); // 12

可以简写成:

(参数1, 参数2, …, 参数N) => 表达式(单一)

例如:

var f = (num1, num2) => num1*num2;

console.log(f(3, 4)); // 12

引入箭头函数有两个方面的作用:简化函数和不需要绑定 this。

简化函数

var materials = [

    'Hydrogen',

    'Helium',

    'Lithium',

    'Beryllium'

  ];

var f = materials.map(function(material) { 

    return material.length; 

  }); 

console.log(f) // [8, 6, 7, 9]

使用箭头函数简化:

var f = materials.map(material =>  material.length);

    

console.log(f) // [8, 6, 7, 9]

不绑定 this

在 ECMAScript 3/5中,通过将 this 值分配给封闭的变量,可以解决 this 问题。

function Person() {

    this.age = 0;

  

    var self = this;

    setInterval(function growUp() {

      self.age++;

      console.log(self.age);

    }, 1000);

  }

var p = new Person();

使用箭头函数则不用绑定 this:

function Person() {

    this.age = 0;

  

    // var self = this;

    setInterval(() => {

      this.age++;

      console.log(this.age);

    }, 1000);

  }

var p = new Person();

注意:

• 对原型方法拓展的时候,如果使用箭头函数,this指向的是window,而不再是实例对象了

举例:

Array.prototype._reduce = function (cb, initVal) {

  let i = 0;

  let result = initVal;

  if (initVal.isType('Null') || initVal.isType('Undefined')) {

    result = this[0];

    i++;

  }

  // 使用箭头函数这里的this值会有问题

  for (i; i<this.length; i++) {

    result = cb(result, this[i]);

  }

  return result;

}

• 不可以作为构造函数来使用

• 不绑定arguments(如果有要使用arguments的时候可以使用rest参数代替)

var foo = (...args) => {

        console.log(args); // [1, 2, 3]

    };

    foo(1, 2, 3);

• 不可以使用yield命令,因此箭头函数不能用作Generator函数

函数参数默认值

function foo(height = 50, color = 'red')

{

    // ...

}

模板字符串

var name = `Your name is ${first} ${last}.`

解构赋值

var foo = ["one", "two", "three", "four"];

var [one, two, three] = foo;

//如果你要忽略某些值,你可以按照下面的写法获取你想要的值

var [first, , , last] = foo;

console.log(first); // "one"

console.log(last); // "four"

//你也可以这样写

var a, b; //先声明变量

[a, b] = [1, 2];

console.log(a); // 1

console.log(b); // 2

// 解构赋值可以方便的交换两个变量的值。

var a = 1;

var b = 3;

[a, b] = [b, a];

console.log(a); // 3

console.log(b); // 1

// 获取对象中的值

const student = {

  name:'Ming',

  age:'18',

  city:'Shanghai'  

};

const {name,age,city} = student;

console.log(name); // "Ming"

console.log(age); // "18"

console.log(city); // "Shanghai"

延展操作符(Spread operator)

// 函数调用

myFunction(...iterableObj);

// 数组构造或字符串:

[...iterableObj, '4', ...'hello', 6];

// 构造对象时,进行克隆或者属性拷贝

let objClone = { ...obj };

对象属性简写

const name='Ming',age='18',city='Shanghai';

const student = {

    name,

    age,

    city

};

console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}

Promise

var waitSecond = new Promise(function(resolve, reject)

{

    setTimeout(resolve, 1000);

});

waitSecond

    .then(function()

    {

      console.log("Hello"); // 1秒后输出"Hello"

      return waitSecond;

    })

    .then(function()

    {

        console.log("Hi"); // 2秒后输出"Hi"

    });

支持let与const

// 使用let与const定义的变量为块级作用域:

{

  let a = 10;

}

console.log(a); //-1 or Error“ReferenceError: a is not defined”

ES7(2016)

Array.prototype.includes() 方法

在 ES6 中我们有 String.prototype.includes() 可以查询给定字符串是否包含一个字符,而在 ES7 中,我们在数组中也可以用 Array.prototype.includes 方法来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。

const arr = [1, 3, 5, 2, '8', NaN, -0]

arr.includes(1) // true

arr.includes(1, 2) // false 该方法的第二个参数表示搜索的起始位置,默认为 0

arr.includes('1') // false

arr.includes(NaN) // true

arr.includes(+0) // true

对比 indexOf()

indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回 -1。

if (arr.indexOf(el) !== -1) {

  // ...

}

不过这种方法有两个缺点,一是不够语义化,要先找到参数值的第一个出现位置,所以要去比较是否不等于 -1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对 NaN 的误判。

[NaN].indexOf(NaN)// -1

find() 和 findIndex()

数组实例的 find 方法,用于找出第一个符合条件的数组成员。另外,这两个方法都可以发现 NaN,弥补了数组的 indexOf 方法的不足。

[1, 4, -5, 10].find((n) => n < 0) // -5

[1, 5, 10, 15].findIndex(function(value) {

  return value > 9;

}) // 2

[NaN].findIndex(y => Object.is(NaN, y)) // 0

求幂运算符 **

a ** b指数运算符,具有与  Math.pow(a, b)  等效的计算结果

console.log(2**10);// 输出 1024zhicc

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

ES8新特性(2017)

async/await

Promise

假如有这样一个使用场景:需要先请求 a 链接,等返回信息之后,再请求 b 链接的另外一个资源。下面代码展示的是使用 fetch 来实现这样的需求,fetch 被定义在 window 对象中,它返回的是一个 Promise 对象。

fetch('https://blog.csdn.net/')

  .then(response => {

    console.log(response)

    return fetch('https://juejin.im/')

  })

  .then(response => {

    console.log(response)

  })

  .catch(error => {

    console.log(error)

  })

虽然上述代码可以实现这个需求,但语义化不明显,代码不能很好地表示执行流程。基于这个原因,ES8 引入了 async/await,这是 JavaScript 异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰。

async function foo () {

  try {

    let response1 = await fetch('https://blog.csdn.net/')

    console.log(response1)

    let response2 = await fetch('https://juejin.im/')

    console.log(response2)

  } catch (err) {

    console.error(err)

  }

}

foo()

通过上面代码,你会发现整个异步处理的逻辑都是使用同步代码的方式来实现的,而且还支持 try catch 来捕获异常,这感觉就在写同步代码,所以是非常符合人的线性思维的。需要强调的是,await 不可以脱离 async 单独使用,await 后面一定是 Promise 对象,如果不是会自动包装成 Promise 对象。

根据 MDN 定义,async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。

async function foo () {

  return '浪里行舟'

}

foo().then(val => {

  console.log(val) // 浪里行舟

})

上述代码,我们可以看到调用 async 声明的 foo 函数返回了一个 Promise 对象,等价于下面代码:

async function foo () {

  return Promise.resolve('浪里行舟')

}

foo().then(val => {

  console.log(val) // 浪里行舟

})

Object.values(),Object.entries()

ES5 引入了 Object.keys 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。ES8 引入了跟 Object.keys 配套的 Object.values 和 Object.entries,作为遍历一个对象的补充手段,供 for...of 循环使用。

Object.values 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

const obj = { foo: 'bar', baz: 42 };

Object.values(obj) // ["bar", 42]

const obj = { 100: 'a', 2: 'b', 7: 'c' };

Object.values(obj) // ["b", "c", "a"]

需要注意的是,如果属性名为数值的属性,是按照数值大小,从小到大遍历的,因此返回的顺序是 b、c、a。

Object.entries() 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。这个特性我们后面介绍 ES10 的 Object.fromEntries() 还会再提到。

const obj = { foo: 'bar', baz: 42 };

Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]

const obj = { 10: 'xxx', 1: 'yyy', 3: 'zzz' };

Object.entries(obj); // [['1', 'yyy'], ['3', 'zzz'], ['10': 'xxx']]

String padding

在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。我们先看下使用语法:

String.padStart(targetLength,[padString])

• targetLength(必填): 当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。

• padString(可选): 填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为 " "。

'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'

有时候我们处理日期、金额的时候经常要格式化,这个特性就派上用场:

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"

'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

Object.getOwnPropertyDescriptors()

ES5 的 Object.getOwnPropertyDescriptor() 方法会返回某个对象属性的描述对象(descriptor)。ES8 引入了 Object.getOwnPropertyDescriptors() 方法,返回指定对象所有自身属性(非继承属性)的描述对象。

const obj = {

  name: '浪里行舟',

  get bar () {

    return 'abc'

  }

}

console.log(Object.getOwnPropertyDescriptors(obj))

得到如下结果:

该方法的引入目的,主要是为了解决 Object.assign() 无法正确拷贝 get 属性和 set 属性的问题。我们来看个例子:

const source = {

  set foo (value) {

    console.log(value)

  },

  get bar () {

    return '浪里行舟'

  }

}

const target1 = {}

Object.assign(target1, source)

console.log(Object.getOwnPropertyDescriptor(target1, 'foo'))

返回如下结果:

上面代码中,source 对象的 foo 属性的值是一个赋值函数,Object.assign 方法将这个属性拷贝给 target1 对象,结果该属性的值变成了 undefined。这是因为 Object.assign 方法总是拷贝一个属性的值,而不会拷贝它背后的赋值方法或取值方法。

这时 Object.getOwnPropertyDescriptors() 方法配合 Object.defineProperties() 方法,就可以实现正确拷贝。

返回如下结果:

其他

• 函数参数列表结尾允许逗号

• ShareArrayBuffer和Atomics对象,用于从共享内存位置读取和写入

ES9新特性(2018)

for await of

for of 方法能够遍历具有 Symbol.iterator 接口的同步迭代器数据,但是不能遍历异步迭代器。ES9 新增的 for await of 可以用来遍历具有 Symbol.asyncIterator 方法的数据结构,也就是异步迭代器,且会等待前一个成员的状态改变后才会遍历到下一个成员,相当于 async 函数内部的 await。现在我们有三个异步任务,想要实现依次输出结果,该如何实现呢?

// for of 遍历

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 (let item of arr) {

    console.log(Date.now(), item.then(console.log))

  }

}

test()

得到如下结果:

上述代码证实了 for of 方法不能遍历异步迭代器,得到的结果并不是我们所期待的,于是 for await of 就粉墨登场啦!

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()

// 1575536194608 2000

// 1575536194608 100

// 1575536195608 3000

注意:在 for 后面加上了 await

使用 for await of 遍历时,会等待前一个 Promise 对象的状态改变后,再遍历到下一个成员。

延展操作符 Object Rest Spread 拓展

ES6 中添加的最意思的特性之一是 spread (延展)操作符。你不仅可以用它替换 cancat() 和 slice() 方法,使数组的操作 (复制、合并) 更加简单,还可以在数组必须以拆解的方式作为函数参数的情况下,spread 操作符也很实用。

const arr1 = [10, 20, 30];

const copy = [...arr1]; // 复制

console.log(copy); // [10, 20, 30]

const arr2 = [40, 50];

const merge = [...arr1, ...arr2]; // 合并

console.log(merge); // [10, 20, 30, 40, 50]

console.log(Math.max(...arr)); // 30 拆解

ES9 通过向对象文本添加扩展属性进一步扩展了这种语法。他可以将一个对象的属性拷贝到另一个对象上,参考以下情形:

const input = {

  a: 1,

  b: 2,

  c: 1

}

const output = {

  ...input,

  c: 3

}

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

上面代码可以把 input 对象的数据都添加到 output 对象中,需要注意的是,如果存在相同的属性名,只有最后一个会生效(后面的会覆盖前面的)。

const input = {

  a: 1,

  b: 2

}

const output = {

  ...input,

  c: 3

}

input.a='浪里行舟'

console.log(input,output) // {a: "浪里行舟", b: 2} {a: 1, b: 2, c: 3}

上面例子中,修改 input 对象中的值,output 并没有改变,说明扩展运算符拷贝一个对象(类似这样 obj2 = {...obj1}),实现只是一个对象的浅拷贝。值得注意的是,如果属性的值是一个对象的话,该对象的引用会被拷贝:

const obj = {x: {y: 10}};

const copy1 = {...obj};

const copy2 = {...obj};

obj.x.y='浪里行舟'

console.log(copy1,copy2) // x: {y: "浪里行舟"} x: {y: "浪里行舟"}

console.log(copy1.x === copy2.x); // → true

copy1.x 和 copy2.x 指向同一个对象的引用,所以他们严格相等。

我们再来看下 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 数据,这在之前是做不到的。注意,rest 属性必须始终出现在对象的末尾,否则将抛出错误。

Promise.prototype.finally()

Promise.prototype.finally() 方法返回一个 Promise,在 promise 执行结束时,无论结果是 fulfilled 或者是 rejected,在执行 then() 和 catch() 后,都会执行 finally 指定的回调函数。

fetch('https://www.google.com')

  .then((response) => {

    console.log(response.status);

  })

  .catch((error) => {

    console.log(error);

  })

  .finally(() => {

    document.querySelector('#spinner').style.display = 'none';

  });

无论操作是否成功,当您需要在操作完成后进行一些清理时,finally() 方法就派上用场了。这为指定执行完 promise 后,无论结果是 fulfilled 还是 rejected 都需要执行的代码提供了一种方式,避免同样的语句需要在 then() 和 catch() 中各写一次的情况。

新的正则表达式特性

ES9 为正则表达式添加了四个新特性,进一步提高了 JavaScript 的字符串处理能力。这些特点如下:

• s (dotAll) 标志

• 命名捕获组(Regular Expression Named Capture Groups)

• Lookbehind 后行断言

• Unicode 属性转义

• 非转义序列的模板字符串

s(dotAll)flag

正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用 u 修饰符解决;另一个是行终止符, 如换行符 (\n) 或回车符 (\r), 这个可以通过 ES9 的 s(dotAll)flag,在原正则表达式基础上添加 s 表示:

console.log(/foo.bar/.test('foo\nbar')) // false

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

那如何判断当前正则是否使用了 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'

命名捕获组 

在一些正则表达式模式中,使用数字进行匹配可能会令人混淆。例如,使用正则表达式 /(\d{4})-(\d{2})-(\d{2})/ 来匹配日期。因为美式英语中的日期表示法和英式英语中的日期表示法不同,所以很难区分哪一组表示日期,哪一组表示月份:

const re = /(\d{4})-(\d{2})-(\d{2})/;

const match= re.exec('2019-01-01');

console.log(match[0]); // → 2019-01-01

console.log(match[1]); // → 2019

console.log(match[2]); // → 01

console.log(match[3]); // → 01

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

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

const match = re.exec('2019-01-01');

console.log(match.groups); // → {year: "2019", month: "01", day: "01"}

console.log(match.groups.year); // → 2019

console.log(match.groups.month); // → 01

console.log(match.groups.day); // → 01

上面代码中,“命名捕获组”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?),然后就可以在 exec 方法返回结果的 groups 属性上引用该组名。

命名捕获组也可以使用在 replace() 方法中,例如将日期转换为美国的 MM-DD-YYYY 格式:

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

const usDate = '2018-04-30'.replace(re, '$<month>-$<day>-$<year>')

console.log(usDate) // 04-30-2018

Lookbehind 后行断言 

JavaScript 语言的正则表达式,只支持先行断言,不支持后行断言,先行断言我们可以简单理解为"先遇到一个条件,再判断后面是否满足",如下面例子:

let test = 'hello world'

console.log(test.match(/hello(?=\sworld)/))

// ["hello", index: 0, input: "hello world", groups: undefined]

但有时我们想判断前面是 world 的 hello,这个代码是实现不了的。在 ES9 就支持这个后行断言了:

let test = 'world hello'

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

// ["hello", index: 6, input: "world hello", groups: undefined]

(?<…) 是后行断言的符号,(?..) 是先行断言的符号,然后结合 =(等于)、!(不等)、\1(捕获匹配)。

Unicode 属性转义

ES2018 引入了一种新的类的写法\p{...}和\P{...},允许正则表达式匹配符合 Unicode 某种属性的所有字符。比如你可以使用\p{Number}来匹配所有的 Unicode 数字,例如,假设你想匹配的 Unicode 字符㉛字符串:

const str = '㉛';

console.log(/\d/u.test(str)); // → false

console.log(/\p{Number}/u.test(str)); // → true

同样的,你可以使用\p{Alphabetic}来匹配所有的 Unicode 单词字符:

const str = 'ض';

console.log(/\p{Alphabetic}/u.test(str)); // → true

// the \w shorthand cannot match ض

console.log(/\w/u.test(str)); // → false

同样有一个负向的 Unicode 属性转义模板 \P{...}:

console.log(/\P{Number}/u.test('㉛')); // → false

console.log(/\P{Number}/u.test('ض')); // → true

console.log(/\P{Alphabetic}/u.test('㉛')); // → true

console.log(/\P{Alphabetic}/u.test('ض')); // → false

除了字母和数字之外,Unicode 属性转义中还可以使用其他一些属性。

ES10

Array.prototype.flat()

多维数组是一种常见的数据格式,特别是在进行数据检索的时候。将多维数组打平是个常见的需求。通常我们能够实现,但是不够优雅。

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

newArray = arr.flat(depth)

// depth 是指定要提取嵌套数组的结构深度,默认值为 1

接下来我们看两个例子:

const numbers1 = [1, 2, [3, 4, [5, 6]]]

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

const numbers2 = [1, 2, [3, 4, [5, 6]]]

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

上面两个例子说明 flat 的参数没有设置,取默认值 1,也就是说只扁平化第一级;当 flat 的参数大于等于 2,返回值就是 [1, 2, 3, 4, 5, 6] 了。

Array.prototype.flatMap()

有了 flat 方法,那自然而然就有 Array.prototype.flatMap 方法,flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为 1)。

let arr = [1, 2, 3]

console.log(arr.map(item => [item * 2]).flat()) // [2, 4, 6]

console.log(arr.flatMap(item => [item * 2])) // [2, 4, 6]

实际上 flatMap 是综合了 map 和 flat 的操作,所以 它也只能打平一 层。

Object.fromEntries()

Object.fromEntries 这个新的 API 实现了与 Object.entries 相反的操作。这使得根据对象的 entries 很容易得到 object。

const object = { x: 23, y:24 };

const entries = Object.entries(object); // [['x', 23], ['y', 24]]

const result = Object.fromEntries(entries); // { x: 23, y: 24 }

ES2017 引入了 Object.entries, 这个方法可以将对象转换为数组, 这样对象就可以使用数组原型中的众多内置方法,比如 map, filter、reduce,举个例子,我们想提取下列对象 obj 中所有 value 大于 21 的键值对,如何操作呢?

// ES10 之前

const obj = {

  a: 21,

  b: 22,

  c: 23

}

console.log(Object.entries(obj)) // [['a',21],["b", 22],["c", 23]]

let arr = Object.entries(obj).filter(([a, b]) => b > 21) // [["b", 22],["c", 23]]

let obj1 = {}

for (let [name, age] of arr) {

  obj1[name] = age

}

console.log(obj1) // {b: 22, c: 23}

上例中得到了数组 arr,想再次转化为对象,就需要手动写一些代码来处理,但是有了 Object.fromEntries() 就很容易实现。

// 用 Object.fromEntries() 来实现

const obj = {

  a: 21,

  b: 22,

  c: 23

}

let res = Object.fromEntries(Object.entries(obj).filter(([a, b]) => b > 21))

console.log(111, res) // {b: 22, c: 23}

String.trimStart 和 String.trimEnd

移除开头和结尾的空格,之前我们用正则表达式来实现,现在 ES10 新增了两个新特性,让这变得更简单!

trimStart() 方法从字符串的开头删除空格,trimLeft() 是此方法的别名。

let str = ' 前端工匠 '

console.log(str.length) // 6

str = str.trimStart()

console.log(str.length) // 5

let str1 = str.trim() // 清除前后的空格

console.log(str1.length) // 4

str.replace(/^\s+/g, '') // 也可以用正则实现开头删除空格

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

let str = ' 浪里行舟 '

console.log(str.length) // 6

str = str.trimEnd()

console.log(str.length) // 5

let str1 = str.trim() // 清除前后的空格

console.log(str1.length) // 4

str.replace(/\s+$/g, '') // 也可以用正则实现右端移除空白字符

String.prototype.matchAll

如果一个正则表达式在字符串里面有多个匹配,现在一般使用 g 修饰符或 y 修饰符,在循环里面逐一取出。

function collectGroup1 (regExp, str) {

  const matches = []

  while (true) {

    const match = regExp.exec(str)

    if (match === null) break

    matches.push(match[1])

  }

  return matches

}

console.log(collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`))

// [ 'foo', 'bar', 'baz' ]

值得注意的是,如果没有修饰符 /g, .exec() 只返回第一个匹配。现在通过 ES9 的 String.prototype.matchAll 方法,可以一次性取出所有匹配。

function collectGroup1 (regExp, str) {

  let results = []

  for (const match of str.matchAll(regExp)) {

    results.push(match[1])

  }

  return results

}

console.log(collectGroup1(/"([^"]*)"/g, `"foo" and "bar" and "baz"`))

// ["foo", "bar", "baz"]

上面代码中,由于 string.matchAll(regex) 返回的是遍历器,所以可以用 for...of 循环取出。

try…catch

在 ES10 中,try-catch 语句中的参数变为了一个可选项。以前我们写 catch 语句时,必须传递一个异常参数。这就意味着,即便我们在 catch 里面根本不需要用到这个异常参数也必须将其传递进去

// ES10 之前

try {

  // tryCode

} catch (err) {

  // catchCode

}

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

// ES10

try {

  console.log('Foobar')

} catch {

  console.error('Bar')

}

BigInt

JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于 2 的 1024 次方的数值,JavaScript 无法表示,会返回 Infinity。

// 超过 53 个二进制位的数值,无法保持精度

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

// 超过 2 的 1024 次方的数值,无法表示

Math.pow(2, 1024) // Infinity

现在 ES10 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

创建 BigInt 类型的值也非常简单,只需要在数字后面加上 n 即可。例如,123 变为 123n。也可以使用全局方法 BigInt(value) 转化,入参 value 为数字或数字字符串。

const aNumber = 111;

const aBigInt = BigInt(aNumber);

aBigInt === 111n // true

typeof aBigInt === 'bigint' // true

typeof 111 // "number"

typeof 111n // "bigint"

如果算上 BigInt,JavaScript 中原始类型就从 6 个变为了 7 个。

• Boolean

• Null

• Undefined

• Number

• String

• Symbol (new in ECMAScript 2015)

• BigInt (new in ECMAScript 2019)

Symbol.prototype.description

我们知道,Symbol 的描述只被存储在内部的 [[Description]],没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:

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

Symbol('').description; // ""

Symbol().description; // undefined

Function.prototype.toString()

ES2019 中,Function.toString() 发生了变化。之前执行这个方法时,得到的字符串是去空白符号的。而现在,得到的字符串呈现出原本源码的样子:

function sum(a, b) {

  return a + b;

}

console.log(sum.toString());

// function sum(a, b) {

// return a + b;

// }

ES 新特性

类的私有变量

class Counter {

  #x = 0;

  #increment() {

    this.#x++;

  }

  onClick() {

    this.#increment();

  }

}

const c = new Counter();

c.onClick(); // 正常

c.#increment(); // 报错

现在,此特性可在最新版本的 Chrome 和 Node.js中使用

可选链操作符

let nestedProp = obj && obj.first && obj.first.second;

有了可选链式调用 ,你只要这样写就可以做同样的事情:

let nestedProp = obj?.first?.second;

如果obj或obj.first是null/undefined,表达式将会短路计算直接返回undefined。

空位合并操作符

let c = a ? a : b // 方式1

let c = a || b // 方式2

这两种方式有个明显的弊端,它都会覆盖所有的假值,如(0, '', false),这些值可能是在某些情况下有效的输入。

为了解决这个问题,有人提议创建一个“nullish”合并运算符,用 ?? 表示。有了它,我们仅在第一项为 null 或 undefined 时设置默认值。

let c = a ?? b;

// 等价于let c = a !== undefined && a !== null ? a : b;

举例:

const x = null;

const y = x ?? 500;

console.log(y); // 500

const n = 0

const m = n ?? 9000;

console.log(m) // 0

static 字段

它允许类拥有静态字段,类似于大多数OOP语言。静态字段可以用来代替枚举,也可以用于私有字段。

class Colors {

  // public static 字段

  static red = '#ff0000';

  static green = '#00ff00';

  // private static 字段

  static #secretColor = '#f0f0f0';

}

font.color = Colors.red;

font.color = Colors.#secretColor; // 出错

现在,此特性可在最新版本的 Chrome 和 Node.js中使用。

Top-level await

ES2017(ES8)中的 async/await 特性仅仅允许在 async 函数内使用 await 关键字,新的提案旨在允许 await 关键字在顶层内容中的使用,例如可以简化动态模块加载的过程:

const strings = await import(`/i18n/${navigator.language}`);

这个特性在浏览器控制台中调试异步内容(如 fetch)非常有用,而无需将其包装到异步函数中。

另一个使用场景是,可以在以异步方式初始化的 ES 模块的顶层使用它(比如建立数据库连接)。当导入这样的“异步模块”时,模块系统将等待它被解析,然后再执行依赖它的模块。这种处理异步初始化方式比当前返回一个初始化promise并等待它解决来得更容易。一个模块不知道它的依赖是否异步。

// db.mjs

export const connection = await createConnection();

// server.mjs

import { connection } from './db.mjs';

server.start();

在此示例中,在server.mjs中完成连接之前不会执行任何操作db.mjs。

现在,此特性可在最新版本的 Chrome中使用。

WeakRef

一般来说,在 JavaScript 中,对象的引用是强保留的,这意味着只要持有对象的引用,它就不会被垃圾回收。

const ref = { x: 42, y: 51 };

// 只要我们访问 ref 对象(或者任何其他引用指向该对象),这个对象就不会被垃圾回收

目前在 Javascript 中,WeakMap 和 WeakSet 是弱引用对象的唯一方法:将对象作为键添加到 WeakMap 或 WeakSet 中,是不会阻止它被垃圾回收的。

const wm = new WeakMap();

{

  const ref = {};

  const metaData = 'foo';

  wm.set(ref, metaData);

  wm.get(ref);

  // 返回 metaData

}

// 在这个块范围内,我们已经没有对 ref 对象的引用。

// 因此,虽然它是 wm 中的键,我们仍然可以访问,但是它能够被垃圾回收。

const ws = new WeakSet();

ws.add(ref);

ws.has(ref);// 返回 true

JavaScript 的 WeakMap 并不是真正意义上的弱引用:实际上,只要键仍然存活,它就强引用其内容。WeakMap 仅在键被垃圾回收之后,才弱引用它的内容。

WeakRef 是一个更高级的 API,它提供了真正的弱引用,Weakref 实例具有一个方法 deref,该方法返回被引用的原始对象,如果原始对象已被收集,则返回undefined对象。

const cache = new Map();

const setValue =  (key, obj) => {

  cache.set(key, new WeakRef(obj));

};

const getValue = (key) => {

  const ref = cache.get(key);

  if (ref) {

    return ref.deref();

  }

};

// this will look for the value in the cache

// and recalculate if it's missing

const fibonacciCached = (number) => {

  const cached = getValue(number);

  if (cached) return cached;

  const sum = calculateFibonacci(number);

  setValue(number, sum);

  return sum;

};

总而言之,JavaScript 中对象的引用是强引用,WeakMap 和 WeakSet 可以提供部分的弱引用功能,若想在 JavaScript 中实现真正的弱引用,可以通过配合使用 WeakRef 和终结器(Finalizer)来实现。

若有收获,就赏束稻谷吧

0 颗稻谷

今天 11:48阅读数 13

大纲

• 脑图

• ES6新特性(2015)

• 类 Class

• 模块化(Module)

• 箭头函数

• 函数参数默认值

• 模板字符串

• 解构赋值

• 延展操作符(Spread operator)

• 对象属性简写

• Promise

• 支持let与const

• ES7(2016)

• Array.prototype.includes() 方法

• 求幂运算符 **

• ES8新特性(2017)

• async/await

• Object.values(),Object.entries()

• String padding

• Object.getOwnPropertyDescriptors()

• 其他

• ES9新特性(2018)

• for await of

• 延展操作符 Object Rest Spread 拓展

• Promise.prototype.finally()

• 新的正则表达式特性

• ES10

• Array.prototype.flat()

• Array.prototype.flatMap()

• Object.fromEntries()

• String.trimStart 和 String.trimEnd

• String.prototype.matchAll

• try…catch

• BigInt

• Symbol.prototype.description

• Function.prototype.toString()

• ES 新特性

• 类的私有变量

• 可选链操作符

• 空位合并操作符

• static 字段

• Top-level await

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!