ECMAScript新特性

348 阅读4分钟

为什么还要说ECMAScript呢?

事实上,很多开发者并没有理解语言和平台之间的关系,什么?你说不了解这些也照样写代码。no!no!no!有这种想法的你很危险啊,少年,思过崖面壁一个月。

一、ECMAScript与JavaScript

  • ECMAScript(ES)也是一门脚本语言,通常被看作JavaScript的标准化规范。
  • ECMAScript中只提供了最基本的语法
  • JavaScript实现了ECMAScript的标准,并做了一定的扩展,所以实际上JavaScript是ECMAScript的扩展语言

二、ECMAScript的发展过程

JavaScript语言本身指的就是ECMAScript,2015年开始ES保持每年一个版本的迭代,伴随着新版本的迭代,很多新特性陆续出现,这也导致JavaScript语言本身变得越来越高级、越来越便捷。ES2015相较上一个版本经过了将近6年时间才有了一个比较完整的标准,这个版本有了很多颠覆式的新功能,由于迭代的时间过长,发布的内容过多,所以之后的版本发布的更加频繁,也更符合当下互联网小步快跑的精神,且ES2015之后都使用发行年份去命名。

三、ECMAScript 2015的新特性

let块级作用域

  1. 作用域定义:作用域—某个成员能起作用的范围
  2. 作用域分类:ES2015前只有全局作用域函数作用域,ES2015中又有了块级作用域
// 模拟注册事件
var elements = [{}, {}, {}];
for(var i = 0; i < elements.length; i++) {
    elements[i].onclick = function () {
        console.log(i);
    }
}
elements[2].onclick(); // 3, 循环已经完成之后i已经变为3,不管第几个元素的事件打印的都是3
// 闭包匿名函数自调
var elements = [{}, {}, {}];
for(var i = 0; i < elements.length; i++) {
    elements[i].onclick = (function (i) {
        return function () {
            console.log(i);
        }
    })(i)
}
elements[0].onclick(); // 0
// 使用let
var elements = [{}, {}, {}];
for(let i = 0; i < elements.length; i++) {
    elements[i].onclick = function () {
        console.log(i);
    }
}
elements[1].onclick(); // 1

let 自带块级作用域,也不会产生变量提升,需要先声明再使用,否则会报错

const 恒量 / 常量

在let基础上多了一个只读特性,变量一旦声明后就不允许更改

数组的解构

const arr = [100, 200, 300];
// 原始方式获取数组中的值
const foo = arr[0]
const bar = arr[1]
const baz = arr[2]
console.log(foo, bar, baz);
// 解构方式获取
const [foo, bar, baz] = arr;
console.log(foo, bar, baz);
// 提取指定位置成员
const [, , baz] = arr;
console.log(baz); // 300
// 提取从当前位置开始往后的所有成员,只能在解构位置的最后一个成员上使用
const [foo, ...rest] = arr;
console.log(rest); // [200, 300]
// 如果解构位置的成员个数小于数组中成员个数
const [foo] = arr;
console.log(foo); // 100
// 如果解构位置的成员个数大于数组中成员个数
const [foo, bar, baz, more] = arr;
console.log(more); // undefined
// 可以给解构成员设置默认值,如果未提取到数组中对应的成员,则变量得到默认值
const [foo, bar, baz, more = 'default'] = arr;
console.log(more); // default 

对象的解构

const obj = { name: 'jack', age: 18 }
const { name } = obj
console.log(name) // jack
// 冲突时可以用重命名的方式
const name = 'rose'
const { name: objName } = obj
console.log(objName); // jack
// 也可以同时去添加默认值
const name = 'rose'
const { name: objName = 'curry' } = obj
console.log(objName); // jack

模板字符串

// 支持换行
const str = `hello 
es2015, this is a \`string\``
console.log(str)
// 可以通过插值表达式的方式在字符串中嵌入对应的值
const name = 'jack'
const msg = `hello, ${name}----${1+2}---${Math.floor(4.5)}`
console.log(msg)

带标签的模板字符串

// 标签就是一个函数,添加这个标签就相当于调用这个函数
const str = console.log`hello world` //['hello world']
// 打印数组当中的内容就是按照表达式分割过后的静态内容,函数还能接受到表达式的返回值
const name = 'tom'
const gender = true
function myTagFunc (strings, name, gender) {
    console.log(strings, name, gender) // ['hey, ' ' is a ', '.'] tom true
    return '123'
}
const result = myTagFunc`hey, ${name} is a ${gender}.` //返回值result为带标签的模板字符串返回的值
console.log(result); // 123

字符串的扩展方法

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

参数默认值

// 原始设置默认值方式
function foo (enable) {
    // enable = enable || true; // 使用逻辑判断设置默认值,如果传入false也会使用默认值
    enable = enable === undefined ? true : enable
    console.log('foo invoked - enable')
    console.log(enable)
}
foo(true)
// 新的设置方式, 只会在没有传实参或传递了undefined时生效
// 注意:带有默认值的参数需要出现在最后,否则默认值加无法生效,例如 function foo (enable = true, bar)
function foo (enable = true) {
    console.log('foo invoked - enable')
    console.log(enable)
}
foo(true)

剩余参数

// 原始方式使用arguments
function foo () {
    console.log(arguments)
}
// 剩余参数方式, 只能出现在形参列表最后,只能使用一次
function foo (first, ...args) {
    console.log(args)
}
foo(1, 2, 3)

展开数组

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

箭头函数

// 传统函数
function inc (number) {
    return number + 1
}
// 箭头函数
const inc = n => n + 1
const inc = (n, m) => {
    return n + 1 
}
console.log(inc(100))

// 箭头函数与this
const person = {
    name: 'tom',
    // sayHi: function () {
       // console.log(`hi, my name is ${this.name}`)
    // }
    sayHi: () => {
        console.log(`hi, my name is ${this.name}`)
    },
    sayHiAsync: function () {
        setTimeout(function () {
            console.log(this.name) // undefined
        }, 1000)
        setTimeout(() => {
            console.log(this.name) // tom
        }, 1000)
    }
}
person.sayHi() // 普通函数打印hi, my name is tom/箭头函数打印hi, my name is undefined

对象字面量增强

const bar = 'aaa'
const obj = {
    foo: 123,
    // bar: bar // 传统方式
    bar // 可直接省略,增强后写法
    method1: function () {
        console.log('method111')
    }, // 传统方式
	method1() {
        console.log('method111')
    }, // 增强后写法
    [Math().random]: 123 // 计算属性,[表达式]
} 

对象扩展方法

  • Object.assign():将多个源对象中的属性复制到一个目标对象中
const source1 = {
    a: 123,
    b: 123
}
const target = {
    a: 456,
    c: 456
}
const result = Object.assign(target, source1)
console.log(target) // { a: 123, c: 456, b: 123 }
console.log(result === target) // true, 返回值就是传入的第一个对象
  • Object.is():判断两个值是否相等
// 0 == false // true
// 0 === false // false
// +0 === -0 // true
// NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // false

Object.definePropertyProxy

const person = {
    name: 'jack',
    age: 29
}
const personProxy = new Proxy(person, {
    get (target, property/*代理目标对象,目标属性*/) {
        console.log(target, property)
        return 100
        // return property in target ? target[property] : 'default' // get的内部正常逻辑
    },
    set (target, property, value/*代理目标对象,目标属性,目标属性值*/) {
        console.log(target, property, value)
        // 一般逻辑,判断要设置某个属性的值是否符合条件,符合则相应设置,不符合则抛出错误
        if(property === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError(`${value} is not an int`)
            }
        }
        // target[property] = value
    }
})
personProxy.gender = true // { name: 'jack', age: 29 } gender true
console.log(personProxy.name) // { name: 'jack', age: 29 } name 100
  1. defineProperty只能监视属性的读写
  2. Proxy能监视到更多操作,例如:delete操作或对象中方法调用等
  3. Proxy更好的支持数组对象的监视,例如:重写数组的操作方法
  4. Proxy是以非侵入的方式监管了对象的读写

Reflect

Reflect属于一个静态类,所以不能通过new的方式去创建一个实例。其内部封装了一系列对对象的底层操作,Reflect成员方法就是Proxy处理对象的默认实现。

const obj = {
    foo: '123',
    bar: '456'
}
const proxy = new Proxy(obj, {
    get (target, property) {
        console.log('watch logic')
        return Reflect.get(target, property)
    }
})
console.log(proxy.foo) 
// watch logic
// 3
console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))
// Reflect统一提供一套用于操作对象的API
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))

Promise: 一种更优的异步编程解决方案,通过链式调用解决了传统异步编程中回调函数嵌套过深的问题

class、静态方法、类的继承

// 通过构造函数和原型链来实现在各个实例间共享一些成员
function Person (name) {
    this.name = name
}
Person.prototype.say = function () {
    console.log(`hi, my name is ${this.name}`)
}
// 使用class实现
class Person {
    constructor(name) {
        this.name = name
    }
    say () {
        console.log(`hi, my name is ${this.name}`)
    }
    // 静态方法
    static create (name) {
        return new Person(name)
    }
}
const p = new Person('tom')
p.say()
const tom = Person.create('tom')
tom.say()
// extends 继承
class Student extends Person {
    construtor (name, number) {
        super(name)
        this.number = number
    }
    hello () {
        super.say()
        console.log(`my school number is ${this.number}`)
    }
}
const s = new Student('jack', '100')
s.hello()
// hi, my name is jack
// my school number is 100

Set、Map

// Set 数据结构
const s = new Set()
s.add(1).add(2).add(3).add(4).add(2)
console.log(s) // Set { 1, 2, 3, 4 }
s.forEach(i => console.log(i)) // 1 2 3 4
for (let i of s) {
    console.log(i)
}
console.log(s.size) // 4,size与数组中的length是一个道理
console.log(s.has(100)) // false
console.log(s.delete(3)) // true
console.log(s) // Set { 1, 2, 4 }
s.clear()
console.log(s) // Set {}
// 数组去重
const arr = [1, 1, 2]
const result = Array.from(new Set(arr))
console.log(result) // [1, 2, 3, 4]
// Map 数据结构
// 在对象字面量中以布尔值和对象作为键都会被转化为字符串, Map就是解决这种问题的
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj)) // ['123', 'true', '[object Object]']
// Map
const m = new Map()
const tom = { name: 'tom' }
m.set(tom, 90)
console.log(m) // Map { { name: 'tom' } => 90 }
console.log(m.get(tom)) // 90
// m.has()  m.delete()  m.clear()  m.forEach()

Symbol

// Symbol 数据类型
// shared.js ================================
const cache = {}
// a.js =====================================
cache['foo'] = Math.random()
// b.js =====================================
cache['foo'] = '123'
console.log(cache) // { foo: '123' }
// 为了解决维护对象中键会重复导致覆盖的问题,出现了Symbol这个独一无二的数据类型
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // symbol

console.log(Symbol() === Symbol()) // false
// Symbol允许传入字符串作为值的描述文本以作区分
console.log(Symbol('foo')) // foo
console.log(Symbol('bar')) // bar
console.log(Symbol('baz')) // baz
const obj = {
    [Symbol()]: 123
}
console.log(obj) // { [Symbol()]: 123 }
// Symbol.for静态方法, 传入for方法中的值会自动转换成字符串
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
const obj = {
    [Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) // [object XObject]此时的obj的toString标签就是XObject
// 通过for in和 Object.keys()都无法获取的到Symbol类型的属性名,JSON.stringify序列化也会忽略掉Symbol
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol()] 获取到的全是Symbol类型的属性名

可迭代接口(Iterable)

  1. 实现Iterable接口就是for...of的前提
  2. 内部必须挂载一个iterator方法,这个方法返回一个带有next方法的对象
  3. 不断调用next方法就可以实现对内部所有数据的遍历
// 迭代器(Iterator)
const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
console.log(iterator.next()) // { value: 'foo', done: false }
console.log(iterator.next()) // { value: 'bar', done: false }
console.log(iterator.next()) // { value: 'baz', done: false }
console.log(iterator.next()) // { value: undefined, done: true }
// 实现可迭代接口(Iterable)
const obj = {
    store: ['foo', 'bar', 'baz'],
    [Symbol.iterator]: function () {
        let index = 0
        const self = this
        return {
            next: function () {
                const result = {
                    value: self.store[index],
                    done: index >= self.store.length
                }
                index++
                return result
            }
        }
    }
}
for (const item of obj) {
    console.log(item) // foo bar baz
}

迭代器模式

const todos = {
    life: ['吃饭', '睡觉', '打豆豆'],
    learn: ['语文', '数学', '外语'],
    work: ['喝茶'],
    
    each: function(callback) {
        const all = [].concat(this.life, this.learn, this.work)
        for(const item of all) {
            callback(item)
        }
    },
    [Symbol.iterator]: function () {
        const all = [...this.life, ...this.learn, ...this.work]
        let index = 0
        return {
            next: function () {
                return {
                    value: all[index],
                    done: index++ >= all.length
                }
            }
        }
    }
}

生成器

// Generator 函数
function * foo () {
    console.log('jack')
    return 100
}
const result = foo() // jack
console.log(result.next()) // { value: 100, done: true } 说明生成器函数也是实现了迭代器模式
function * foo () {
    console.log('1111')
    yield 100
    console.log('2222')
    yield 200
    console.log('3333')
    yield 300
}
const generator = foo()
console.log(generator.next())
// 1111
// { value: 100, done: false }
console.log(generator.next())
// 2222
// { value: 200, done: false }
console.log(generator.next())
// 3333
// { value: 300, done: false }

ES2016及ES2017

// 数组的includes方法
const arr = [1, 2, 3];
arr.includes(1) // true
// Object.values, Object.entries
const obj = {
    foo: 'value1',
    bar: 'value2',
}
Object.values(obj) // ['value1', 'value2']
Object.entries(obj) // [['foo', 'value1'], ['bar', 'value2']]
// Object.getOwnPropertyDescriptors
const p1 = {
    firstName: 'Lei',
    lastName: 'Niu',
    get fullName () {
        return this.firstName + ' ' + this.lastName
    }
}
const descriptors = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'jack'
console.log(p2.fullName) // jack Niu
// String.prototype.padStart / String.prototype.padEnd
const books = {
    html: 5,
    css: 16,
    javascript: 128
}
for(const [name, count] of Object.entries(books)) {
    console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}
// html------------|005
// css-------------|016
// javascript------|128
// Async/await Promise的语法糖,后续会在异步编程篇中详细说明

就写到这里吧,只是挑出一部分ES6+的新功能及语法做一些记录,并没有全部写出来,支持原创,请勿抄袭