ES7和ES8新特性介绍

时间:2022-04-24
本文章向大家介绍ES7和ES8新特性介绍,主要内容包括概述、ES7 新特性、Exponentiation Operator、ES8、字符串填充、Object.getOwnPropertyDescriptors、异步函数、共享内存与原子操作、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

概述

JavaScript,作为一门处于高速发展期的开发语言,正在变的越来越完善、稳定。我们必须拥抱这些变化,并且我们需要把ES8加入到我们的技术栈中。 ECMAScript 是标准化的 JavaScript 语言,1997 年发布了第一版,1998 年和 1999 年发布了第二和第三个版本,之后沉寂 了许多年,直到 Ajax 流行起来后标准工作才再次起步,2009 年发布了第五个版本,自 2015 年起每年发布一个版本。现在为开发者所熟知的ES6就是2015年发布的。 ES7新增特性比较少,在这个版本中,ES7只增加了两种新特性:Array.prototype.includes和Exponentiation Operator(求冥运算)。而在最新的ES8版本中,新增的特性比较多,主要包括:Object.values/Object.entries、字符串填充、Object.getOwnPropertyDescriptor、尾随逗号、异步函数、共享内存和原子等。

ES7 新特性

Array.prototype.includes

Array.prototype.includes用法都容易和简单。它是一个替代indexOf,开发人员用来检查数组中是否存在值,indexOf是一种尴尬的使用,因为它返回一个元素在数组中的位置或者-1当这样的元素不能被找到的情况下。所以它返回一个数字,而不是一个布尔值(开发人员需要实施额外的检查)。在ES6,要检查是否存在值,你需要做一些判断,因为他们没有匹配到值,Array.prototype.indexOf返回-1变成了true(转换成true),但是当匹配的元素为0位置时候,该数组包含元素,却变成了false。

let arr = ['react', 'angular', 'vue']

// WRONG
if (arr.indexOf('react')) { // 0 -> evaluates to false, definitely as we expected
  console.log('Can use React') // this line would never be executed
}

// Correct
if (arr.indexOf('react') !== -1) {
  console.log('Can use React')
}

或者使用一点点hack 位运算符 ~ 使代码更加紧凑一些,因为~(位异或)对任何数字相当于-(a + 1)。

let arr = ['react', 'angular', 'vue']

// Correct
if (~arr.indexOf('react')) {
  console.log('Can use React')
}

在ES7中,使用includes代码格式如下:

let arr = ['react', 'angular', 'vue']

// Correct
if (arr.includes('react')) {
  console.log('Can use React')
}
let str = 'React Quickly'

// Correct
if (str.toLowerCase().includes('react')) {  // true
  console.log('Found "react"')  
}

有趣的是,许多JavaScript库已经实现includes或类似功能contains的功能。常见的有:

  • jQuery: $.inArray
  • Underscore.js: _.contains
  • Lodash: .includes (在版本3或者早期版本中是.contains 和 Underscore一样)
  • CoffeeScript: in 操作(example)
  • Darf: list.contains (example) 除了增强了可读性语义化,实际上给开发者返回布尔值,而不是匹配的位置。includes也可以在NaN(非数字)使用。最后,includes第二可选参数fromIndex,这对于优化是有好处的,因为它允许从特定位置开始寻找匹配。

上面的说明例子:

console.log([1, 2, 3].includes(2)) // === true)
console.log([1, 2, 3].includes(4)) // === false)
console.log([1, 2, NaN].includes(NaN)) // === true)
console.log([1, 2, -0].includes(+0)) // === true)
console.log([1, 2, +0].includes(-0)) // === true)
console.log(['a', 'b', 'c'].includes('a')) // === true)
console.log(['a', 'b', 'c'].includes('a', 1)) // === false)

Exponentiation Operator

求幂运算大多数是为开发者做一些数学计算,对于3D,VR,SVG还有数据可视化非常有用。在ES6或者早些版本,你不得不创建一个循环,创建一个递归函数或者使用Math.pow。例如,使用Math.pow创建一个递归箭头函数。

calculateExponent = (base, exponent) => base*((--exponent>1)?calculateExponent(base, exponent):base)
console.log(calculateExponent(7,12) === Math.pow(7,12)) // true
console.log(calculateExponent(2,7) === Math.pow(2,7)) // true

而在ES7/ES2016语法规则中,开发者可以使用更短的语法实现求冥运算。

let a = 7 ** 12
let b = 2 ** 7
console.log(a === Math.pow(7,12)) // true
console.log(b === Math.pow(2,7)) // true

许多ES新特性是从其他语言(如CoffeeScript、Ruby)模仿而来。例如,ES7中指数运算符在其他语言的存在形式。

  • Python: x ** y
  • CoffeeScript: x ** y
  • F#: x ** y
  • Ruby: x ** y
  • Perl: x ** y
  • Lua, Basic, MATLAB: x ^ y

ES8

ES8 在上一个版本的基础上增加了很多新特性!ES8标准已于2017年6月发布。

Object.values/Object.entries

在ES8 /ES2017之前,Javascript开发者需要迭代一个对象的自身属性时候不得不用Object.keys,通过迭代且使用obj[key]获取value值返回一个数组。

let obj = {a: 1, b: 2, c: 3}
Object.keys(obj).forEach((key, index)=>{
  console.log(key, obj[key])
})

而在ES6/ES2015中,使用for/of会稍微好点。

let obj = {a: 1, b: 2, c: 3}
for (let key of Object.keys(obj)) {
  console.log(key, obj[key])
}

虽然使用老方式for/in也许会非常好用,但是当它碰到迭代枚举属性时,会破坏像prototype和tostring的属性得到意想不到的值。在ES8版本中,JordanHarband提出的Object.entries引入了一个entry概念。对象是键值对的数据结构,每个键值对都是entry。Object.entries(x)强制转换x为对象,并以数组的方式返回其可枚举的自定义字符串。例如:

>>Object.entries({foo:1,bar:2})  
     [['foo',1],['bar',2]]

与Object.entries非常相似,Object.values返回一个数组,其值为可枚举的字符串键值属性。

>> Object.values({foo:1,bar:2})  
     [1,2]

Object.entries( )和Object.values( )输出结果如下:

Object.entries(value:any):Array <[string,any]>  
Object.values(value:any):Array <any>

字符串填充

String.prototype.padStart 和 String.prototype.padEnd在javascript字符操作是一个不错的体验,引入padStart 和 padEnd,可以使开发人员更好地控制字符串原语。

padStart()在开始部位填充,返回一个给出长度的字符串,填充物给定字符串,把字符串填充到期望的长度。一个经典例子是使用空格创建列,使用它,可以帮助我们格式化一些字符串。

console.log('react'.padStart(10).length)         // "       react" is 10
console.log('backbone'.padStart(10).length)         // "  backbone" is 10

padEnd顾名思义就是从字符串的尾端右边开始填充。例如:

console.log('react'.padEnd(10, ':-)'))  // "react:-):-" is 10
console.log('backbone'.padEnd(10, '*')) // "backbone**" is 10

Object.getOwnPropertyDescriptors

Object.getOwnPropertyDescriptors返回对象obj所有自身属性描述。这是一个多参数版本的Object.getOwnPropertyDescriptors(obj,propName)将会返回obj中propName属性的一个单独描述。

在ES5中,ECMAScript中没有单个方法来简化两个对象之间的正确拷贝。开发者要使用Object.assign()来拷贝对象, Object.assign()分配属性只有copy和定义新的属性。当我们使用更加复杂对象和类原型,这可能会出问题。

Object.getOwnPropertyDescriptors允许创建真实的对象浅副本并创建子类,它通过给开发者描述符来做到这一点.在Object.create(prototype, object)放入描述符后,返回一个真正的浅拷贝。

Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

异步函数

异步函数(或者async/await)特性操作是Promise最重要的功能。使用Promises处理Async函数的一种普遍写法如下所示:

function fetchData(url) {  
  return fetch(url)  
    .then(request => request.text())  
    .then(text => {  
      return JSON.parse(text);  
     })  
    .catch(err => {  
      console.log(`Error: ${err.stack}`);  
    });  
}

在ES8中,新增async和await关键字,我们可以利用它来实现同步读取操作。在内部,Async功能与生成器的功能相同,但是却不能转换为Generator Functions。使用ES2017中的Async函数如下:

async function fetchData(url) {  
  try {  
   let request = await fetch(url);  
   let text = await request.text();  
   return JSON.parse(text);  
  }  
  catch (err) {  
    console.log(`Error: ${err.stack}`);  
  }  
}

有了 async/await,我们的代码执行异步看起来像执行同步一样。可以从头到尾读起来非常简单和易懂,因为出现结果顺序和函数题中从头到尾顺序一样啊!

共享内存与原子操作

当内存被共享时,多个线程可以并发读、写内存中相同的数据。原子操作可以确保那些被读、写的值都是可预期的,即新的事务是在旧的事务结束之后启动的,旧的事务在结束之前并不会被中断。

Atomic 对象类似于 Math 对象,拥有许多静态方法,所以我们不能把它当做构造函数。 引入此功能后,低级别Atomics命名空间对象和一个SharedArrayBuffer构造函数,能够让开发人员开发共享多个service worker和核心线程之间的SharedArrayBuffer对象的数据,从而可以改善worker之间的协调。