ES2016 和 ES2017 学习

2021-11-26 12:43:26 浏览数 (1)

ES6 发布之后,TC-39 小组每年发布一次 ECMAScript 语言新特性,这个 repository tc39/ecma262 中记录着最新版的提议。新版本的 ECMAScript 使用年份来表示版本,所以 ES6 被称为 ES2015, ES7 被称为 ES2016,所以标准起见,以后我们也称之为 ES2016 和 ES2017。

ECMAScript 2016

ES2016 只有两个新特性

  1. Array.prototype.includes
  2. 求冥运算(Exponentiation Operator)

Array.prototype.includes

includes 查找一个值是否在数组中

代码语言:javascript复制
[1, 2, 3].includes(3)         //true
['a', 'b', 'c'].includes('d') //false

includes 还可以接收两个参数,第一个表示要查找的值,第二个表示从数组第 N 个元素开始查找。

代码语言:javascript复制
[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, 3].includes(3, 2)  // true
[1, 2, 3].includes(3, 3)  // false
[1, 2, 3].includes(3, -1) // true
[1, 2, NaN].includes(NaN) // true

注意上面 [1, 2, NaN].includes(NaN) 的返回值为 true,虽然 NaN === NaN 的结果为 false,所以『包含』和『相等』还是有区别的。

代码语言:javascript复制
const tt = [-0, 1, NaN]
tt.includes(0)   // true
tt.indexOf(NaN)  // -1
tt.includes(NaN) // true

测试发现 includes 和 indexOf 在 node 8 / chrome 61 下速度差异不大,因此在使用的时候不用考虑性能的问题。

在 ES2015 中,String 对象也有 includes 方法,String.prototype.includes,但是只能用于 String,不能用于 characters。

幂运算 Exponentiation operator

ES2016 新增幂运算符改进语法

代码语言:javascript复制
3 ** 3 // 27
Math.pow(5, 2) === 5 ** 2 // true

let a = 3
a **= 3 // 27

幂运算符的优先级高于二元运算符,低于一元运算符。

代码语言:javascript复制
2 * 5 ** 2  // 50
-(5 ** 2)   // -25
(-5) ** 2   // 25
// 运算符左侧不能是除了    或 -- 之外的任意一元表达式
-5 ** 2     // Uncaught SyntaxError: Unexpected token ** 

let num = 2
  num ** 2    // 9
num-- ** 2    // 9

ECMAScript 2017

主要新特性:

  1. 异步函数(Async/Await)
  2. 共享内存和原子(Shared memory and atomics)

小改款

  1. Object.values() 和 Object.entries()
  2. 字符串填充(padStart 和 padEnd)
  3. Object.getOwnPropertyDescriptors()
  4. 函数参数列表和调用中的尾逗号(Trailing commas)

async/await

  • async 函数声明: async function foo () {}
  • async 函数表达式: const foo = async function () {}
  • async 函数定义: let obj = { async foo () {} }
  • async 箭头函数: const foo = async () => {}

关于 async/await 很早以前就写过了,而且现在基本上已经成了异步代码必备了,这里就不赘述了。

详情参考JavaScript异步解决方案async/await

共享内存和原子(Shared memory and atomics)

共享内存和原子内容比较多,后面会单独写一篇文章,暂时留坑。

Object.entries() 和 Object.values()

(1) Object.entries()

该方法将一个对象中所有可枚举的属性与值按照二维数组的方式返回,如果对象是数组,则数组的下标作为键值。

代码语言:javascript复制
Object.entries({ one: 1, two: 2}) // [['one', 1], ['two', 2]]
Object.entries([1, 2]) // [['0', 1], ['1', 2]]

返回数组的顺序与 Object.keys() 一致。

代码语言:javascript复制
let obj = {3: 'a', 2: 'b', 1: 'c'}
Object.entries(obj)  // [['1', 'c'], ['2', 'b'], ['3', 'c']]
Object.keys(obj)     // ['1', '2', '3']

Object.entries() 会忽略对象中 key 为 Symbol 的键值对。

代码语言:javascript复制
Object.entries({ [Symbol()]: 123, foo: 'abc' }) // [ [ 'foo', 'abc' ] ]

通过 Object.entries() 设置一个 Map 对象。

代码语言:javascript复制
let map = new Map(Object.entries({
    one: 1,
    two: 2,
}))
JSON.stringify([...map])  // [["one",1],["two",2]]

通过 Object.entries() 遍历对象。

代码语言:javascript复制
let obj = { one: 1, two: 2 }
for (let [k,v] of Object.entries(obj)) {
    console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`)
}
// Output:
// "one": 1
// "two": 2

(2) Object.values()

该方法返回对象可枚举键值对中所有的 value。

代码语言:javascript复制
Object.values({ one: 1, two: 2 })  // [ 1, 2 ]

字符串填充(padStart 和 padEnd)

(1) String.prototype.padStart

padStart 函数通过填充字符串首部使字符串达到一定的长度。该方法接受两个参数,第一个表示目标字符串长度,第二个表示填充内容,默认填充内容为空格。

代码语言:javascript复制
'abc'.padStart(10)         // "       abc"
'abc'.padStart(10, "foo")  // "foofoofabc"
'abc'.padStart(6,"123465") // "123abc"
'abc'.padStart(8, "0")     // "00000abc"
'abc'.padStart(1)          // "abc"

(2) String.prototype.padEnd

padEnd 填充字符串的时候从尾部开始填充,其它均与 padStart 相同。

代码语言:javascript复制
'abc'.padEnd(10)          // "abc       "
'abc'.padEnd(10, "foo")   // "abcfoofoof"
'abc'.padEnd(6, "123456") // "abc123"
'abc'.padEnd(1)           // "abc"

Object.getOwnPropertyDescriptors()

该方法获取目标对象所有属性的属性描述符,该属性必须是自己定义的,不能是通过原型链继承来的。

关于属性描述符的作用,可以查看使用 Object.defineProperty 为对象定义属性

代码语言:javascript复制
const obj = {
    [Symbol('foo')]: 123,
    get bar() { return 'abc' },
};
console.log(Object.getOwnPropertyDescriptors(obj));

// Output:
// { [Symbol('foo')]:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

使用 Object.assign() copy 一个对象/属性 的时候,不能正确 copy 属性的 get 和 set,而通过 getOwnPropertyDescriptors() 能够实现正确 copy 一个对象/对象属性。

代码语言:javascript复制
let Leo =  Object.defineProperty({}, 'name', {
    get: function() {
        return name
    },
    set: function(newName) {
        name = newName
    },
    enumerable: true,
    configurable: true
})
const result = {}
Object.assign(result, Leo)
Object.getOwnPropertyDescriptor(result, 'name')
// {value: "", writable: true, enumerable: true, configurable: true}

我们发现通过 Object.assign() copy 后的 ‘name’ 属性,其 ‘get’, ‘set’ 属性不见了

代码语言:javascript复制
const result2 = {}
Object.defineProperties(result2, Object.getOwnPropertyDescriptors(Leo))
Object.getOwnPropertyDescriptor(result2, 'name')
// {enumerable: true, configurable: true, get: ƒ, set: ƒ}

使用 Object.getOwnPropertyDescriptors 配合 Object.defineProperties 就可以实现正确 copy 了。

函数参数列表和调用中的尾逗号(Trailing commas)

这个新特性很简单,就是允许我们在定义或者调用函数的时候参数后面多加一个逗号而不报错。

代码语言:javascript复制
function foo (a, b,) {} // correct

foo ('abc', 'def',)  // correct

在数组和对象中这样的写法也没有问题。

代码语言:javascript复制
let arr = ['red', 'green', 'blue',]

let obj = {
	first: 'Leo',
	last: 'Li',
}

新加入这个特性的好处就是当我们调整参数或者代码结构的时候,不需要再额外地添加或者删除逗号了,尤其是对代码进行注释的时候会方便很多。在版本管理上,不会因为出现一个逗号,导致原本只有一行的修改变成两行。

参考资料

Exploring ES2016 and ES2017

0 人点赞