ES6 新特性总结

309 阅读7分钟

本文总结了 ES6 的新特性,用于复习。

ES 与 JS

ES 是一个语言标准,JS 实现了这个标准并进行了扩展。在浏览器环境下,就是 JS + DOM + BOM,在node环境下可以进行读写文件、网络等操作。

ES 从 2015 年开始,每年一个大版本迭代。ES6 有时指 ES2015,有时指 ES2015 起的所有新版本。

准备工作:使用浏览器运行即可,也可以使用nodemon。

ES6 的新特性

let

块级作用域

if(true){
    var name = "zhangsan";
}

console.log(name)

es6

if(true){
    let name = "zhangsan";
}

console.log(name) // 报错

另一个

for (var i = 0; i < 3; i++) {
    for (var i = 0; i < 3; i++) {
        console.log(i);
    }
}

es6

for (let i = 0; i < 3; i++) {
    for (let i = 0; i < 3; i++) {
        console.log(i);
    }
}

另一个

var elements = [{},{},{}]
for(var i = 0;i<3;i++){
    elements[i].onclick = function(){
        console.log(i);
    }
}

elements[2].onclick() // 3

可用闭包解决

var elements = [{}, {}, {}]
for (var i = 0; i < 3; i++) {
    elements[i].onclick = (function (i) {
        return function () {
            console.log(i);
        }
    })(i)
}

elements[0].onclick()

es6

var elements = [{}, {}, {}]
for (let i = 0; i < 3; i++) {
    elements[i].onclick = function () {
        console.log(i);
    }
}

elements[1].onclick()

且 for 循环中let有两个作用域

for (let i = 0; i < 3; i++) {
    let i = "hello"
    console.log(i)
}
// 打印 3 次 hello

let 不会变量提升

console.log(foo)  // undefined
var foo = "hello"

而 es6

console.log(foo) // 报错
let foo = "hello"

const

在 let 基础上添加只读特性。const 只能在声明时赋值。

const obj = {}
obj.name = 'zs' // ok
obj = {} // error

最佳实践:不用 var,主用 const,配合使用 let。

数组解构

const arr = [1, 2, 3]
const [foo, bar] = arr
const [, , baz, qux, foobar = 100] = arr
const [, ...rest] = arr

console.log(foo, bar, baz, qux, foobar) // 1 2 3 undefined 100
console.log(rest) // [ 2, 3 ]

对象解构

const obj = { name: 'zs', age: 20 }
const { name, gender = '男', age: myAge = 18 } = obj
console.log(name, gender, myAge)

模板字符串

const name = 'zhangsan'
const str = `
    hello \`html\`,
    I am ${name}, I am ${10 + 8} years old.
`
console.log(str)

带标签的模板字符串

const str = console.log`hello world!` // [ 'hello world!' ]

const name = 'Jack'
const gender = 'boy'

function func(strings, name, gender) {
    console.log(strings)
    console.log(name)
    console.log(gender)
}

const result = func`My name is ${name}, I am a ${gender}.` 
/*
[ 'My name is ', ', I am a ', '.' ]
Jack
boy
*/

字符串的扩展方法

  • includes
  • startsWith
  • endsWith
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error'));
console.log(message.endsWith('.'));
console.log(message.includes('foo'));

参数默认值

ES5:

function foo(enable) {
    enable = enable === undefined ? true : enable
    console.log(enable)
}

ES6:

function foo(enable = true) {
    console.log(enable)
}

注:默认参数需要在参数列表的最后。

剩余参数

ES5:

function foo() {
    console.log(arguments)
}
foo(1, 2, 3, 4) // [Arguments] { '0': 1, '1': 2, '2': 3 }

ES6:

function foo(...args) {
    console.log(args)
}
foo(1, 2, 3)

注:剩余参数需要在参数列表的最后,且只能出现一次。

展开数组

ES5:

const arr = ['foo', 'bar', 'baz']
console.log.apply(console, arr) // foo bar baz

ES6:

const arr = ['foo', 'bar', 'baz']
console.log(...arr) // foo bar baz

箭头函数

const func = n => n + 1
console.log(func(1)) // 2

const foo = (n, m) => {
    return n + m
}

箭头函数不会改变 this 的指向。

const person = {
    name: 'tom',
    say1: function () {
        console.log('1:' + this.name)
    },
    say2: () => {
        console.log('2:' + this.name)
    },
    say3: function () {
        setTimeout(function () {
            console.log('3:' + this.name)
        })
    },
    say4: function () {
        const _this = this
        setTimeout(function () {
            console.log('4:' + _this.name)
        })
    },
    say5: function () {
        setTimeout(() => {
            console.log('5:' + this.name)
        })
    },
    say6: () => {
        setTimeout(() => {
            console.log('6:' + this.name)
        })
    }
}
person.say1() // 1:tom
person.say2() // 2:undefined
person.say3() // 3:undefined
person.say4() // 4:tom
person.say5() // 5:tom
person.say6() // 6:undefined

对象字面量增强

const bar = '345'

const obj = {
    foo: 123,
    // bar: bar,
    bar,
    func1: function () { },
    func2() { }, // this 指向与普通 function 定义的方法相同
    [Math.random()]: 123,
    [bar]: '345'
}

console.log(obj)
/*
{
  '345': '345',
  foo: 123,
  bar: '345',
  func1: [Function: func1],
  func2: [Function: func2],
  '0.18417476633785768': 123
}
*/

对象扩展的方法

  • Object.assign 将多个源对象的属性复制到一个目标属性中
const source1 = {
    a: 1,
    b: 2
}

const source2 = {
    b: 3,
    d: 4
}

const target = {
    a: 100,
    c: 200
}

const result = Object.assign(target, source1, source2)
console.log(result) // { a: 1, c: 200, b: 3, d: 4 }
console.log(result === target) // true
const obj = Object.assign({}, source1, source2) // 浅拷贝
  • Object.is

判断某些特殊情况下的相等性

console.log(
    +0 === -0, // true
    Object.is(+0, -0), // false
    NaN === NaN, // false
    Object.is(NaN, NaN) // true
)

监听对象属性的变化

ES5: Object.defineProperty

ES6: Proxy

const person = {
    name: 'zs',
    age: 20
}

const proxy = new Proxy(person, {
    get(target, property) {
        // console.log(target, property)
        // return 100
        return property in target ? target[property] : '默认'
    },
    set(target, property, value) {
        if (property === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('error')
            }
        }
        target[property] = value
        // console.log(target, property, value)
    },
    deleteProperty(target, property) {
        console.log('delete', property)
        delete target[property]
    }
})

console.log(proxy.name) // zs
console.log(proxy.score) // 默认
delete proxy.name // delete name
console.log(person) // { age: 20 }
proxy.age = '20' // error

Proxy 属性

使用 Proxy 监视数组

const list = []
const proxy = new Proxy(list, {
    set(target, property, value) {
        console.log('set', property, value)
        target[property] = value
        return true // 表示设置成功
    }
})

proxy.push(1)
/*
set 0 1
set length 1
*/

Reflect

只提供静态方法,内部封装了一系列操作对象的方法

const obj = {
    name: 'zs',
    age: 20
}

// ES5 中操作对象的方式太过凌乱
console.log('name' in obj)
console.log(Object.keys(obj))
console.log(delete obj['age'])

// ES6 统一了操作对象的方法
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.ownKeys(obj))
console.log(Reflect.deleteProperty(obj, 'age'))

Class

ES5:

function Person(name) {
    this.name = name
}

Person.prototype.say = function () {
    console.log(this.name)
}

ES6:

class Person {
    constructor(name) {
        this.name = name
    }
    say() {
        console.log(this.name)
    }
    static eat() {
        console.log('eat')
    }
}

const person = new Person('张三')
person.say() // 张三
Person.eat() // eat

class Student extends Person {
    constructor(name, number) {
        super(name)
        this.number = number
    }
    speak() {
        super.say()
        console.log('Hi')
    }
}
const student = new Student('李四', 2020)
student.speak() // 李四 Hi

Set 集合

const s = new Set()
s.add(1).add(2).add(1)
console.log(s) // Set { 1, 2 }
console.log(s.size) // 2
console.log(s.has(100)) // false
s.forEach(i => console.log(i)) // 1 2
console.log(s.delete(1)) // true
console.log(s) // Set { 2 }
s.clear()
console.log(s) //Set {}

数组去重

const arr = [1, 2, 1, 3, 4, 1, 1]
const arr2 = Array.from(new Set(arr))
const arr3 = [...new Set(arr)]
console.log(arr2) // [ 1, 2, 3, 4 ]
console.log(arr3) // [ 1, 2, 3, 4 ]

Map

普通对象中的键都是字符串,不是字符串的也会被转为字符串

const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ name: 'zs' }] = '张三'
console.log(Object.keys(obj)) // [ '123', 'true', '[object Object]' ]
console.log(obj['[object Object]']) // 张三

ES6 中的 Map

const m = new Map()
const obj = { name: 'tom' }
m.set(obj, 99)
// m.has()
// m.delete()
// m.clear()
console.log(m) // Map { { name: 'tom' } => 99 }
console.log(m.get(obj)) // 99
m.forEach((key, val) => { // 99 { name: 'tom' }
    console.log(key, val) 
})

Symbol

// 引入第三方插件,由于对象的属性都是字符串
// 可能会引发一些错误
// plugin.js
const plugin = {}

// a.js ========= 在 a.js 中扩展
plugin['foo'] = '1'

// b.js ========= 在 b.js 中扩展
plugin['foo'] = '2' // 发生冲突,覆盖了

ES6 中的 Symbol 是一个新的原始类型,表示独一无二的值。每一个通过 Symbol 函数创建的值都是不同的

const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // Symbol
console.log(Symbol() === Symbol()) // false

为了便于打印调试,Symbol 函数可以传递一个字符串

console.log(Symbol('foo')) // Symbol(foo)
console.log(Symbol('bar')) // Symbol(bar)
console.log(Symbol('foo') === Symbol('foo')) // false

可以将 Symbol 用于对象属性,避免属性名冲突:

const a = Symbol()
const b = Symbol()
const obj = {
    [a]: 1,
    [b]: 2
}

console.log(obj) // { [Symbol()]: 1, [Symbol()]: 2 }
console.log(obj[a]) // 1

由于 Symbol 的唯一性,可以用它实现对象的私有属性:

如,在 a.js 中创建一个对象,其中 name 属性想要设为私有,则:

// a.js ===========
const name = Symbol()

const obj = {
    [name]: '张三',
    say() {
        console.log(this[name])
    }
}

这样在 b.js 中便不能访问 obj 的 name 属性了:

// b.js ===========
console.log(obj['name']) // undefined
console.log(obj[Symbol()]) // undefined

注:Symbol 目前最主要的作用就是为对象添加一个独一无二的属性名,且最常用于私有属性。无法通过 for ... inObject.keysJSON.stringify 获取对象的 Symbol 属性。可以通过 Object.getOwnPropertySymbols(obj) 获取对象的 Symbols 属性。

如果想创建两个一样的 Symbol,可以使用 Symbol.for() 函数:

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

Symbol.for() 使用字符串与 Symbol 对象做关联,因此会将传入的参数转为字符串类型。

console.log(Symbol.for(true) === Symbol.for('true')) // true

Symbol 内部有一些静态属性,用于实现不同目的:

const obj = {
    [Symbol.toStringTag]: 'Oh My Object' // 重写对象的 toString 的返回值
}

console.log('' + obj) // [object Oh My Object]
// console.log(Symbol.iterator)
// console.log(Symbol.hasInstance)

遍历

  • for 遍历数组
  • for...in 遍历键值对
  • arr.forEach 遍历数组,不能 break 循环
  • arr.some, arr.every 遍历数组,可以提前终止
  • arr.map 遍历数组,不能提前终止

以上的遍历方式都有一定的局限性。ES6 引入了全新的 for...of 循环,用于遍历所有数据结构的统一方式。

const arr = [1, 2, 3, 4, 5]
for (const item of arr) {
    console.log(item)
    if (item > 3) break
}

for...of 还能遍历伪数组,Set,Map。

const map = new Map()
map.set('zs', 23)
map.set('ls', 25)
for (const item of map) { // 也可用数组解构语法 for (const [key, value] of map) 
    console.log(item)
}
/*
[ 'zs', 23 ]
[ 'ls', 25 ]
*/

ES6 提供了 Iterable 接口,要想使用 for...of ,必须先实现 Iterable 接口。如,使用 for...of 遍历 Set 对象是可以的,因为 Set 实现了这个接口:

const set = new Set([1, 2])
const iterator = set[Symbol.iterator]()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

for...of 通过调用每个对象迭代器的 next 方法实现遍历。

for...of 不能直接遍历对象,要实现 Iterable 接口:

const obj = { // 实现 Iterable 接口
    nums: [1, 2, 3, 4],
    [Symbol.iterator]() {
        let index = 0
        const self = this
        return { // 实现 Iterator 接口
            next() {
                const res = { // 实现 IterationResult 接口
                    value: self.nums[index],
                    done: index >= self.nums.length
                }
                index += 1
                return res
            }
        }
    }
}

for (const o of obj) {
    console.log(o) // 1 2 3 4
}

迭代器模式

实现一:

const todoList = {
    life: ['吃饭', '睡觉'],
    study: ['英语', '数学'],
    work: ['设计', '写代码'],

    each(callback) {
        const list = [].concat(this.life, this.study, this.work)
        list.forEach(item => callback(item))
    }
}

todoList.each(item => console.log(item)) // 吃饭 睡觉 英语 数学 设计 写代码

实现二:

const todoList = {
    life: ['吃饭', '睡觉'],
    study: ['英语', '数学'],
    work: ['设计', '写代码'],

    each(callback) {
        const list = [].concat(this.life, this.study, this.work)
        list.forEach(item => callback(item))
    },

    // 实现 Iterable 接口
    [Symbol.iterator]() {
        const list = [...this.life, ...this.study, ...this.work]
        let index = 0
        return {
            next() {
                return {
                    value: list[index],
                    done: index++ >= list.length
                }
            }
        }
    }
}

// todoList.each(item => console.log(item))
for (const item of todoList) { // 吃饭 睡觉 英语 数学 设计 写代码
    console.log(item)
}

生成器

先来看一下生成器函数的简单使用。

function* foo() {
    console.log('test')
    return 1
}

const gen = foo()
console.log(gen.next()) // test { value: 1, done: true }

通过在 foo 函数定义时加上一个 * 号,便定义了一个生成器函数。直接调用 foo 函数,会生成一个生成器对象,再调用生成器对象的 next 方法,会得到前面所说的相同的 IterationResult 接口,即 { value: 1, done: true }

生成器函数通常配合 yield 关键字使用。

function* foo() {
    console.log(1)
    yield 100
    console.log(2)
    yield 200
    console.log(3)
    yield 300
}

const gen = foo()
console.log(gen.next()) // 1 { value: 100, done: false }
console.log(gen.next()) // 2 { value: 200, done: false }
console.log(gen.next()) // 3 { value: 300, done: false }
console.log(gen.next()) // { value: undefined, done: true }

应用:

  • ID 自动增长
function* createIDMaker() {
    let id = 1
    while (true) {
        yield id++
    }
}

const IDMaker = createIDMaker()
console.log(IDMaker.next().value) // 1
console.log(IDMaker.next().value) // 2
console.log(IDMaker.next().value) // 3
  • 迭代器
const todoList = {
    life: ['吃饭', '睡觉'],
    study: ['英语', '数学'],
    work: ['设计', '写代码'],
    // 实现 Iterable 接口
    [Symbol.iterator]: function* () {
        const list = [...this.life, ...this.study, ...this.work]
        for (const item of list) {
            yield item
        }
    }
}

for (const item of todoList) { // 吃饭 睡觉 英语 数学 设计 写代码
    console.log(item)
}

ES 2016

数组的 includes 方法

首先看 ES5 中数组的 indexOf 方法:

const arr = ['foo', 1, NaN, false]
console.log(arr.indexOf('foo')) // 0
console.log(arr.indexOf(1)) // 1
console.log(arr.indexOf(false)) // 3
console.log(arr.indexOf(NaN)) // -1

indexOf 不能查找 NaN,而 includes 方法可以:

const arr = ['foo', 1, NaN, false]
console.log(arr.includes('foo')) // true
console.log(arr.includes(1)) // true
console.log(arr.includes(false)) // true
console.log(arr.includes(NaN)) // true

指数运算符

ES5 中使用 Math.pow 来进行指数运算,ES 2016 新增了指数运算符 **

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

ES 2017

Object.values

返回对象中所有值组成的数组:

const obj = {
    foo: 'value1',
    bar: 'value2'
}

console.log(Object.values(obj)) // [ 'value1', 'value2' ]

Object.entries

返回对象中所有键值对组成的数组:

const obj = {
    foo: 'value1',
    bar: 'value2'
}

console.log(Object.entries(obj)) // [ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]

如:

const obj = {
    foo: 'value1',
    bar: 'value2'
}

for (const [key, value] of Object.entries(obj)) {
    console.log(key, value)
}
/*
foo value1
bar value2
*/

const map = new Map(Object.entries(obj))
console.log(map) // Map { 'foo' => 'value1', 'bar' => 'value2' }

Object.getOwnPropertyDescriptors

返回给定对象所有的属性描述。

const obj = {
    name: 'zs'
}

console.log(Object.getOwnPropertyDescriptors(obj))
/*
{
  name: { value: 'zs', writable: true, enumerable: true, configurable: true }
}
*/

字符串 padStart / padEnd

字符串填充方法

const books = {
    html5: 30,
    css3: 8,
    javascript: 120
}

for (const [name, count] of Object.entries(books))
    console.log(name.padEnd(12, '-') + count.toString().padStart(3, '0'))
/*
html5-------030
css3--------008
javascript--120
*/

函数参数支持尾逗号

function foo(
    bar,
    baz, // 此处添加逗号不报错
) { }

async / await

async / await 及异步编程会开一篇文章进行讲解。