ECMAScript2015-2018简单总结

131 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

大家好,为方便学习与交流使用,本人在这里对ES2015到ES2018新特性进行了简单的总结。

个人在开发中常用的ES6(es6泛指es2015及后续版本)新特性主要是这些,所以就先总结到这里了。

以下内容只是个人的简单总结,有些可能描述不太准确,大家具体可查询MDN。

一、ES2015

let和const

  • leta或const声明的成员只会在所声明的作用域内生效。
  • 与var不同,不具有变量提升特性。
  • const定义是常量不可修改,恒量要在声明同时赋值。
{
    // 必须先声明
    console.log(str) // undefined
    let str = 'hello'
    str = 'hi'
    console.log(str) // hi
    
    // 基础数据类型不可修改
    const PI = 3.14
}
// 作用域外无法访问
console.log(str, PI) // undefined undefined

数组解构

  • 数组解构赋值可以更方便地将数组成员提取出来进行使用。
  • 可提取所有、部份或某一个成员以及剩余成员。
// 将数组所有成员分别赋值到a b c变量中
const arr = [100, 200, 300]
const [a, b, c] = arr
console.log(a, b, c) // 100 200 300
// 将数组第三个成员赋值到c变量中
const [, , c] = arr
console.log(c) // 300
// 解构时获取数组剩余成员
const [a, ...rest] = arr
console.log(a, rest) // 100 [200, 300]
// 超出数组长度的成员为undefined
const [a, b, c, more] = arr
console.log(more) // undefined
// 超出数组长度的成员可以进行赋值,就相当于顺便在这里定义一个变量并赋值
const [a, b, c, default='default value'] = arr
console.log(c, default) // 300 default value
// 应用:获取根目录
const path = '/src/main.js'
const [, rootDir] = path.split('/') // ['', 'src', 'main.js']
console.log(rootDir) // src

对象解构

  • 与数组解构基本类似。
  • 不同在于对象是无序的,解构时是跟据键值进行解构的,键值与想要赋值的变量名不一致时可重命名。
// 将对象中所有成员分别赋值到name age变量中
const obj = {
    name: 'ma',
    age: 27
}
const { name, age } = obj
console.log(name, age) // ma 27
// 对象中不存在的键为undefined
const { other } = obj
console.log(other) // undefined
// 重命名变量赋值
const { name: objName } = obj
console.log(objName) // ma
// 对重命名变量账值无效
const { name: objName='jack' } = obj // 解构对象时对重命名变量进行赋值无效
console.log(objName) // ma
// 应用:在对象中解构常用方法
const { log } = console
log('txt')

模板字符串

  • 模板字符串支持换行,可以嵌入表达式。
  • 可以使模板字符串标签(一个特殊的函数)。
// 支持换行
const str = `Hello ES2015
this is \`string\``
console.log(str)
/*
Hello ES2015
this is `string`
*/
// 可通过${}插入表达式
const name = 'ma'
const str = `Hey, ${name}. ${1+2}, ${Math.random}`
console.log(str) // Hey, ma. 3, 0.0261460237503921
// 模板字符串标签
const name = 'ma'
const gender = true
// 模板字符串标签是一个特殊的函数
function myTagFunc (string, name, gender) {
    // string是以表达式为依据进行拆分的字符串数组
    console.log(string, name, gender) // ['I am ', ' is a ', '.'] ma true
    const sex = gender ? 'male' : 'female'
    return string[0] + name + stirng[1] + sex + string[2]
}
const res = myTagFunc`I am ${name} is a ${gender}.`
console.log(res) // I am ma is a male

字符串扩展方法

  • startsWidth是否以某字符串开头。
  • endsWith是否以某字符串结尾。
  • includes是否包含某字符串。
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error')) // true
console.log(message.endsWith('.')) // true
console.log(message.includes('foo')) // true

函数参数默认值

  • 函数参数可带默认值。
  • 一般带默认参数放最后。
function foo (enable = true) {
    console.log('foo invoked - enable:')
    console.log(enable)
}
foo() // foo invoked - enable: true

剩余参数

  • ...变量名 方式获取函数剩余参数。
  • 剩余参数只能放到最后。
function foo (first, ...args) {
    console.log(first, args)
}
foo(1, 2, 3, 4, 5) // 1 [2, 3, 4, 5]

展开数组参数

  • ...变量名 方式展开数组所有成员
const arr = ['foo', 'bar', 'baz']
// console.log.apply(console, arr)
console.log(...arr) // foo bar baz

箭头函数

  • 单个参数括号()可以省略。
  • 单行语句大括号{}和return可以省略。
const inc = n => n + 1
console.log(inc(1)) // 2

// 函数传入多个参数需要括号,多行语句时需要大括号
const incre = (n, m) => {
    console.log('incre invoked')
    return n + m
}
console.log(incre(1, 2)) // 3

箭头函数与this

  • 箭头函数不会改变this指向,从作用域上层继承this。
const person = {
    name: 'ma',
    // 在浏览器环境下顶层this是window对象,node环境下是空对象{}
    sayHi: () => {
        console.log(`my name is ${this.name}`)
    }
    // function定义的函数,谁调用this就指向谁
    sayHi2: function() {
         console.log(`my name is ${this.name}`)
    }
    sayHiAsync: function() {
        setTimeout(() => {
            console.log(`I am ${this.name}`)
        }, 1000)
    }
}
person.sayHi() // my name is undefined
// 箭头函数在执行中没有this,而是直接拿变量,所以bind、call、apply是无效的!
// person.sayHi.call(person) // my name is undefined 
person.sayHi2() // my name is ma
person.sayHiAsync() // I am ma

对象字面量

  • 属性与变量名相同,可以省略。
  • 定义方法可以省略 :function。
  • 通过[]可以让表达式结果作为属性名。
const key = '345'
const obj = {
    foo: '123',
    key, // 属性与变量名相同,可以省略
    func () { // 方法可以省略 :function
        console.log('func')
        console.log(this) // 这种方法就是普通的函数,同样影响this指向
    },
    [key]: '666', // 通过[]可以让表达式结果作为属性名
}
obj[Math.random] = '999'
console.log(obj)
/*
{
    foo: '123',
    key: '345',
    func: [Function func],
    '345': '666',
    '0.6701959833170565': '999'
}
*/

Object.assign方法

  • 合并对象到第一个对象上,前一个对象属性会被后一个对象属性覆盖。
const source1 = {
    a: '1',
    b: '2'
}
const source2 = {
    b: '22',
    d: '44'
}
const target = {
    a: '111',
    c: '333'
}
// 合并对象到第一个对象上,前一个对象属性会被后一个对象属性覆盖
const res = Object.assign(target, source1, source2) 
console.log(target) // { a: '1', b: '22', c: '333', d: '44' }
console.log(res === target) // true
// 应用:拷贝对象
function func (obj) {
    const funcObj = Object.assign({}, obj) // 将ojb对象属性覆盖到一个空对象上
    funcObj.name = 'Jack' // 修改属性不会影响原对象
    console.log(funcObj)
}
const obj = { name: 'Rose' }
func(obj) // { name: 'Jack' }
console.log(obj) // { name: 'Rose' }

Object.is方法

  • 可对正负零进行判断。
  • 判断两个NaN是相等的。
console.log(0 == false) // true
console.log(0 === false) // false
console.log(+0 === -0) // true
console.log(NaN === NaN) // false
console.log(Object.is(+0, -0)) // false
console.log(Object.is(NaN, NaN)) // true

Proxy对象

  • 可监视get和set以外的操作。
  • 可方便监视数组操作。
  • 不需要入侵对象。
// 基本用法
const person = {
    name: 'ma',
    age: 27
}
const personProxy = new Proxy(person, {
    // 监视属性读取
    get (target, property) {
        console.log(target, property) // { name: 'ma', age: 27 } age
        return property in target ? target[property] : 'default'
    },
    // 监视属性设置
    set (target, property, value) {
        if (property === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError(`${value} is not a int`)
            }
        }
        target[property] = value
    }
})
console.log(personProxy.age)
/*
{ name: 'ma', age: 27 } age
27
*/
personProxy.age = 18
personProxy.gender = true
console.log(personProxy) // { name: 'ma', age: 18, gender: true }

Proxy与Object.defineProperty的区别。

// 01 Proxy可以监听读写以外的操作
const person = {
    name: 'ma',
    age: 27
}
const personProxy = new Proxy(person, {
    deleteProperty (target, property) {
        // console.log('delete', property)
        delete target[property]
    }
})
delete personProxy.age
console.log(personProxy) // { name: 'ma' }
// 02 可以方便监视数组操作
const list = []
const listProxy = new Proxy(list, {
    set (target, property, value) {
        console.log('set', property, value)
        target[property] = value
        return true // 表示设置成功
    }
})
listProxy.push(100)
/*
set 0 100
set length 1
*/
listProxy.push(200)
/*
set 1 200
set length 2
*/
console.log(list) // [100, 200]
// 03 Proxy不需要入侵对象

// 1、Object.defineProperty方式
const person = {}
Object.defineProperty(person, 'name', {
    get () {
        console.log('name', 被访问)
        return person._name
    },
    set (value) {
        console.log('name', 被设置)
        person._name = value // 如果这里是name不是_name则会死循环
    }
})
Object.defindProperty(person, 'age', {
    get () {
        console.log('age', 被访问)
        return person._age
    },
    set (value) {
        console.log('age', 被设置)
        person._age = value
    }
})
person.name = 'ma'
person.age = 27
console.log(person) // { _name: 'ma', _age: 27 }

// 2、Proxy方式
const person = {
    name: 'ma',
    age: 27
}
const personProxy = new Proxy(person, {
    get (target, property) {
        console.log('get', property)
        return target[property]
    },
    set (target, property, value) {
        console.log('set', property)
        target[property] = value
    }
})
personProxy.name = 'Jack'
console.log(personProxy) // { name: 'Jack', age: 27 }

Reflect对象

  • Reflect对象提供用来拦截js操作的方法,它不是构造函数不可以new,只有静态方法。主要用来统一一下对象的操作。
const person = {
    name: 'ma',
    age: 27
}
const personProxy = new Proxy(person, {
    get (target, property) {
        console.log('watch logic')
        return Reflect.get(target, property)
    }
})
console.log(personProxy.name) // ma
const obj = {
    name: 'Jack',
    age: 18
}
// before
console.log('name' in obj) // true
console.log(delete obj['age']) // true
console.log(Object.keys(obj)) // [ 'name' ]
// Reflect方式
console.log(Reflect.has(obj, 'name')) // true
console.log(Reflect.deleteProperty(obj, 'age')) // true
console.log(Reflect.ownKeys(obj)) // [ 'name' ]

class关键字

  • 类是用于创建对象的模板。
  • 他们用代码封装数据以处理该数据。
  • JS中的类建立在原型上,但也具有某些语法和语义未与 ES5 类相似语义共享。
// 基本用法
class Person {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(`hello, my name is ${this.name}`)
    }
}
const p = new Person('Jack')
p.say() // hello, my name is Jack
// static 静态方法关键字
class Person2 {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(`I am ${this.name}`)
    }
    static create (name) {
        return new Person2(name)
    }
}
const p2 = Person2.create('Rose')
p2.say() // I am Rose
// extends 继承
class People {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(`hi, my name is ${this.name}`)
    }
}
class Student extends People {
    constructor (name, number) {
        super(name) // 父类构造函数
        this.number = number
    }
    hello () {
        super.say() // 调用父类成员
        console.log(`my school number is ${this.number}`)
    }
}
const stu = new Student('Tom', 007)
stu.hello()
/* 
hi, my name is Tommy
school number is 007
*/

set数据结构

  • 可以存储任意类型数据,但元素是唯一的。
const s = new Set()
s.add(1).add(2),add(3).add(4).add(2)
console.log(s) // Set(4) { 1, 2, 3, 4 }
s.forEach(i => console.log(i))
for (let i of s) {
    console.log(i)
}
/*
1
2
3
4
*/
console.log(s.size) // 4
console.log(s.has(100)) // false
s.delete(3)
console.log(s) // Set(3) { 1, 2, 4 }
s.clear()
console.log(s) // Set(0) {}
// 应用场景:数组去重
const arr = [1, 2, 1, 3, 4, 1]
// const res = Array.from(new Set(arr))
const res = [...new Set(arr)]
console.log(res) // [1, 2, 3, 4]

/*
弱引用版本 WeakSet,不同的是 Set中会对所使用到的数据产生引用,即便这个数据在外面被消耗,
但是由于Set引用了这个数据,所以依然不会回收,而 WeakSet 的特点就是不会产生引用,一旦数据销毁,
就可以被回收,所以不会产生内存泄漏问题。
*/

map数据结构

  • 能够记住键的原始插入顺序。
  • 键值可以是引用数据类型。
const tom = {
    name: 'tom'
}
const m = new Map()
m.set(tom, 90)
console.log(m) // { { name: 'tom' } => 90 }
console.log(m.get(tome)) // 90
// m.has()
// m.delete()
// m.clear()
m.forEach((val, key) => {
    console.log(val, key)
})
90 { name: 'tom' }

Symbol数据类型

  • 是一个基础数据类型。
  • Symbol返回的值是唯一的。
  • 一个symbol值能作为对象属性的标识符。这是该数据类型仅有的目的。
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // Symbol

// 两个Symbol永远不会相等
console.log(Symbol() === Symbol()) // false
// 添加描述文本
console.log(Symbol('foo')) // Symbol('foo')
console.log(Symbol('bar')) // Symbol('bar')
// 使用Symbol为对象添加不重复的键
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // { [Symbol()]: '123', [Symbol()]: '456' }
// 在计算属性名中使用
const obj2 = {
    [Symbol()]: 123
}
console.log(obj2) // { [Symbol()]: 123 }
// 模拟私有成员
/* a.js */
const name = Symbol()
const person = {
    [name]: 'ma',
    say () {
        console.log(`it is ${this[name]}`)
    }
}
// 只对外暴露person

/* b.js */
console.log(person[Symbol]) // undefined
person.say() // it is ma
// 描述一样不等于相同
consle.log(Symbol('foo') === Symbol('foo')) // false
// Symbol全局注册
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
// 全局注册可以使两个Symbol相等
console.log(s1 === s2) // true

console.log(
    Symbol.for(true) === Symbol.for('true') // Symbol.for会自动将其他类型的值转化为字符串
) // true
// 内置Symbol常量
console.log(Symbol.iterator) // Symbol(Symbol.iterator)
console.log(Symbol.hasInstance) // Symbol(Symbol.hasInstance)
const obj = {
    [Symbol.toStringTag]: 'Module'
}
console.log(obj.toString()) // [Object Module]
// Symbol属性名获取
const obj = {
    [Symbol()]: 'symbol value',
    foo: 'normal value'
}
console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol() ]
console.log(obj[Object.getOwnPropertySymbols(obj)[0]]) // symbol value

for...of循环

  • 在可迭代对象上创建一个迭代循环。
const arr = [100, 200, 300]
for (const item of arr) {
    consle.log(item)
}
/*
100
200
300
*/
// for of可替代forEach方法,因为forEach无法跳出循环,必须使用some、every方法
for (const item of arr) {
    console.log(item)
    if (item > 100) break
}
/*
100
*/
// for of遍历Set和遍历数组相同
const s = new Set(['foo', 'bar'])
for (const item of s) {
    console.log(item)
}
/*
foo
bar
*/
// for of遍历Map可以使用数组解构语法,直接取键值
const m = new Map()
m.set('foo', 123)
m.set('bar', 456)
for (const [key, val] of m) {
    console.log(key, val)
}
/*
foo 123
bar 456
*/
// 普通对象不能直接被for of遍历,因为普通对象__proto__上没有Symbol(Symbol.iterator)创建迭代器方法
const obj = {
    foo: 123,
    bar: 456
}
for (const item of obj) {
    console.log(item)
}
// TypeError: obj is not iterable

迭代器

  • 处理集合中的每个项是很常见的操作。
  • 这是一种for of循环机制。
const set = new Set(['foo', 'bar', 'baz'])
// Symbol.iterator是Symbol的内置常量,用于存放创建迭代器方法,存放于此属性名就可以被for of识别
const iterator = set[Symbol.iterator]() // set对象原型上创建迭代器方法
console.log(iterator.next()) // { vaule: 'foo', done: false }
console.log(iterator.next()) // { vaule: 'bar', done: false }
console.log(iterator.next()) // { vaule: 'baz', done: false }
console.log(iterator.next()) // { vaule: undefined, done: true }

while (true) {
    const current = iterator.next()
    if (current.done) break
    console.log(current.value)
}
/*
foo
bar
baz
*/
// 实现可迭代接口(Iterable)
const obj = {
    store: ['foo', 'bar', 'baz'],
    [Symbol.iterator]: funciton () {
        const self = this
        let index = 0
        return {
            next: function () {
                const res = {
                    value: self.store[index],
                    done: index >= self.store.length
                }
                index++
                return res
            }
        }
    }
}
for (const item of obj) {
    console.log(item)
}
/*
foo
bar
baz
*/
// 迭代器设计模式
const todos = {
    life: ['eat', 'drink', 'sleep'],
    study: ['chinese', 'math', 'english'],
    work: ['coding'],
    // 提供统一遍历访问接口
    each: function (cb) {
        const all = [].concat(life, study, work)
        for (const item of all) {
            cb(item)
        }
    },
    // 提供迭代器(ES2016统一遍历访问接口)
    [Symbol.iterator]: function () {
        const all = [...life, ...study, ...work]
        let index = 0
        return {
            next: function () {
                const res = {
                    value: all[index],
                    done: index >= all.length 
                }
                index++
                return res
            }
        }
    }
}

todos.each(item => {
    console.log(item)
})

for (const item of todos) {
    console.log(item)
}
/*
eat
drink
sleep
chinese
math
english
coding
*/

生成器

  • 它不像自定议的迭代器显式地维护其内部状态,它定义一个包含自有迭代算法的函数,同时也可以自动维护自己的状态。
function * foo () {
    console.log('one')
    yield 1
    console.log('two')
    yield 2
}
const generator = foo()
console.log(generator.next())
/*
one
{ value: 'one', done: false }
*/
console.log(generator.next())
/*
two
{ value: 'two', done: false }
*/
console.log(generator.next()) // { value: undefined, done: true }
// 应用:发号器
function * createIdMaker () {
    let id = 1
    while (true) {
        yield id++
    }
}
const idMaker = createIdMaker()
console.log(idMaker.next().value) // 1
console.log(idMaker.next().value) // 2
// 应用:使用Generator实现Iterator方法
/*
可以这样理解:
Iterator方法返回一个对象,该对象有next方法,next方法返回{value: 'val', done: false}。
生成器也是返回一个对象并有next方法也一样返回{value: 'val', done: false},其状态还是自动维护。
*/
const todos = {
    life: ['eat', 'drink', 'sleep'],
    study: ['chinese', 'math', 'english'],
    work: ['coding'],
    [Symbol.iterator]: function * () {
        const all = [...life, ...study, ...work]
        for (const item of all) {
            yield item
        }
    },
    /*
    [Symbol.iterator]: function () {
        const all = [...life, ...study, ...work]
        let index = 0
        return {
            next: function () {
                const res = { value: all[index], done: index >= all.length }
                index++
                return res
            }
        }
    }
    */
}
for (const item of todos) {
    console.log(item)
}
/*
eat
drink
sleep
chinese
math
english
coding
*/

二、ES2016

Array.prototype.includes

  • 直接返回是否存在指定元素。
const arr = ['foo', NaN, false]

console.log(arr.indexOf('foo')) // 0 (找不到返回-1)
console.log(arr.indexOf(NaN)) // -1 (无法找到数组中的NaN)

// 直接返回是否存在指定元素
console.log(arr.includes('foo')) // true
console.log(arr.includes(NaN)) // true (可以查找NaN)

指数运算符

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

三、ES2017

Object.values

  • 获取对象所有的值。
const obj = {
    foo: 'value01',
    bar: 'value02'
}
console.log(Object.values(obj)) // ['value01', 'value02']

Object.entries

  • 获取对象所有的键、值。
const obj = {
    foo: 'val1',
    bar: 'val2'
}

console.log(Object.entries(obj)) // [['foo', 'val1'], ['bar', 'val2']]
for (const [key, val] of Object.entries(obj)) {
    console.log(key, val)
}
/*
foo val1
bar val2
*/

Object.getOwnPropertyDescriptors

  • 获取一个对象的所有自身属性的描述符。
const p1 = {
    firstName: 'ma',
    lastName: 'qf',
    get fullName () {
        return `${this.firstName} ${this.lastName}`
    }
}
const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
/*
{
  firstName: {
    value: 'ma',
    writable: true,
    enumerable: true,
    configurable: true
  },
  lastName: {
    value: 'qf',
    writable: true,
    enumerable: true,
    configurable: true
  },
  fullName: {
    get: [Function: get fullName],
    set: undefined,
    enumerable: true,
    configurable: true
  }
}
*/

const p2 = Object.defineProperties({}, descriptors)
console.log(p2.firstName) // ma
console.log(p2.fullName) // ma qf

Object.prototype.padStart / Object.prototype.padEnd

  • 向左或向右填充字符串。
const books = {
    html: 3,
    css: 5,
    js: 128
}

for (const [key, val] of Object.entries(books)) {
    console.log(`${key.padEnd(16, '-')} ${val.padStart(3, '0')}`)
}
/*
html------------|003
css-------------|005
js--------------|128
*/

在函数参数中带尾逗号

  • 就是函数最后一个参数可以带逗号。
function foo (
    foo,
    bar, // <--
){
    console.log(foo, bar)
}

四、ES2018

Rest(剩余)/Spread(展开) 属性

  • ES6 在处理数组解构时,引入了 rest(剩余)元素的概念,ES2018 为对象引入了类似的功能。
1.  const { one, two, ...others } = { one: 1, two: 2, three: 3, four: 4, five: 5 }
1.  console.log(one) // 1
1.  console.log(two) // 2
1.  console.log(others) // { three: 3, four: 4, five: 5 }
// spread(展开) 属性可以通过组合展开运算符...之后传递的对象属性来创建新的对象。
1.  const items = { one, two, ...others }
1.  console.log(items) // { one: 1, two: 2, three: 3, four: 4, five: 5 }

异步迭代async/await

  • ES2018引入异步迭代器(asynchronous iterators),这就像常规迭代器,除了next()方法还返回一个Promise。
async function process () {
    await new Promise(resolve => {
        console.log('doing')
        setTimeout(() => {
            resolve()
        }, 1000)
    })
    console.log('done')
}
// 新的for await of构造允许使用异步可迭代对象作为循环迭代
async function process (filePath) {
    for await (const line of readLines(filePath)) {
        console.log(line)
    }
}

Promise.prototype.finally()

  • 无论promise的执行成功或失败都会执行。
fetch('data.json')
    .then(data => data.json())
    .catch(error => console.error(error))
    .finally(() => console.log('finished'))

-EOF-