ECMAScript新特性

121 阅读5分钟

JavaScript是ECMAScript的扩展语言,ECMAScript只定义了基本的语法

在浏览器环境中JavaScript包括ECMAScript和WebAPI提供的Bom和Dom

在node环境中JAVAScript包括ECMAScript和node提供的API

ES6特指的是EMCAScript2015版本,之后也将一些后面版本发布泛指为ES6

ECMASrcipt2015有4个类型的改动

  • 解决原有语法的一些不足和问题
  • 对原有语法进行扩展
  • 全新的对象、功能、语法
  • 全新的数据类型、数据结构

let和块级作用域

ES6之前,只有两种作用域 全局作用域和函数作用域,ES6增加了块级作用域,用let声明的变量就是块级作用域

for (var i = 0;i < 3; i++) {
    // 关键是在内层循环中i声明为块级作用域限制在内层循环中
    // 不影响外层的i 内层的i和外层的i不是同一个变量
    for (let i = 0;i < 3; i++) {
        console.log(i)
    }
}

var element = [{}, {}, {}]
for (var i = 0;i < element.length; i++) {
    element.onClick = (function (i) {
        // 闭包用函数作用域记录了i的值
        // 访问的i是循环中闭包引用的i 不是同一个
        return function () {
            console.log(i)
        }
    })(i)
}
var element = [{}, {}, {}]
for (let i = 0; i < element.length; i++) {
    // i是块级作用域 因为绑定的函数访问到i 所以i是当前循环中i的值
    element.onClick = function () {
        console.log(i)
    }
}

for (let i = 0; i < 3; i++) {
    // 循环体声明的是内层作用域 独立开来的
    let i = 'foo'
    // 会输出3次 因为for循环内容有两层作用域
    // 声明for循环的i 和循环内容的i不是同一个
    console.log(i)
}
let i = 0
if (i < 3) {
    let i = 'foo'
    console.log(i)
}
i++

// let声明的变量不会发生变量声明提升
console.log(a)
let a = 'foo' // 会报错

const

声明变量后不可以再被修改,声明时就需要给初始值,相当于在let上面增加了只读的属性,只读是指变量指向的内存地址不应该再被修改指向

数组解构

通过数组解构的方式可以快速简单的将数组中对应的元素赋值给对应的变量

const [a1, b1, c1] = [1, 2, 3]
// a1 = 1  b1 = 2  c1 = 3
// 获取到对应下标的元素
const [, , c3] = [1, 2, 3]
// c2 = 3
// 超出数组长度获取到undefined
const [a3, b3, c3, d3] = [1, 2, 3]
// a3 = 1  b3 = 2  c3 = 3  d3 = undenfined
// 可以设置默认值
const [a4, b4, c4 = 123, d4 = 456] = [1, 2, 3]
// a4 = 1  b4 = 2  c4 = 3  d4 = 456
// ... 可以获取剩余的值 是一个数组 只能出现在解构的最后
const [a5, b5, ...rest] = [1, 2, 3]
// a5 = 1  b5 = 2  rest = [3]
// 快速获取字符串拆分后的一项
const [, userName] = 'hello,tom,haha'.split(',')
// userName = tom

对象解构

和数组解构一样,可以快速获取对象的属性,用法上有一些不同

// 通过属性名匹配对象的属性 没有先后顺序 没有匹配到就是undefined 可以设置默认值
const { name = 'jack' } = { name: 'tom' }
// 可以通过别名来命名新赋值的变量 避免重名的情况 用:
// 匹配名: 新的别名
const { name: objName = 'jack' } = { name: 'tom' }

模板字符串

模板字符串支持换行,和插值表达式,花括号里面可以是任意js代码,最终显示代码的返回值

模板字符串可以定义标签,就是调用这个函数

const name = 'tom;
const str `hello 
${ name }`
// 模板标签函数
// 将模板字符串作为数组传入
console.log`hellow,world`
// [ 'hello,world' ]
const gender = 1
// 后面参数可以接收到插值表达式对应的值
function mytagFun (strings, name, gender) {
    // strings 就是模板字符串按照插值表达式分割后的数组
    // strings = [ 'hello ', ' is a ' ]
    // name = 'tom'  gender = true
    // 可以做一些处理
    const sex = gender === 1 ? 'man' : 'woman'
    // 返回值就是模板字符串的结果
    return strings[0] + name + string[1] + sex
}
const result = mytagFun`hello ${name} is a ${gender}`
// result = 'hello tom is a man'

函数参数默认值

参数默认值只能出现在参数最后,可以连续多个默认值

默认值只在参数不传或者为undefined才有效
function foo (value = true) {
    console.log(value)
}
foo(1) // 1
foo() // true 默认值
foo(undefined) // true 默认值
foo(false) // false

函数剩余参数

...操作符 rest 用法 接收剩余的参数,是个数组,只能出现在参数最后,只能出现一次

function foo (value, ...args) {
    console.log(args)
}
foo (1, 2, 3, 4, 5)
// [2, 3, 4, 5]

展开数组

...操作符 spread 用法

const arr = [1, 2, 3]
// 将数组每一项传入调用
console.log.apply(console, arr) // 1 2 3
// 展开数组 按次序传入方法中
console.log(...arr) // 1 2 3

箭头函数

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

const person = {
    name: 'tom',
    sayHi: function () {
        console.log(this.name)
    },
    sayHello: () => {
        console.log(this.name)
    },
    sayHiWait: function () {
        setTimeout(() => {
            console.log(this.name)
        }, 1000)
    }
}
person.sayHi()
person.sayHello()
person.sayHiWait()

对象字面量增强

const name = 'tom'
const obj = {
    name, // 等价于 name: name 同名是可以简写
    methods () { // 等价于 methods: function () {}
        console.log(this) // 指向obj
    },
    // 计算属性名 方括号内可以是任意js表达式
    [1 + 1]: 123 // 等价于 2: 123
}
// 计算属性名等价于  obj[1 + 1] = 123

Objece.assign

将多个源对象的属性复制到目标对象中,遇到有相同就要源对象属性覆盖目标对象中,返回目标对象,第一个参数就是目标对象,后面参数是源对象

const source1 = {    a: 123,
    b: 456
}
const target = {
    a: 456,
    c: 789
}
const result = Object.assign(target, source1)
console.log(target) // { a: 123, b: 456, c: 789 }
console.log(target === result) // true

Proxy

为对象定义代理

const person = {
    name: 'tom',
    age: 20
}
// 第一个参数是被代理的对象 第二个参数是代理的配置对象
const personProxy = new Proxy(person, {
    get: (target, property) {
        // target 访问的对象 property 访问的值
        // get的返回值就是外部访问这个属性得到的值
        // 判断这个属性是不是存在
        property in target ? target[property] : undefined
    },
    set: (target, property, value) {
        // 可以在这个为设置值之前增加校验
        if (property === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError(`${value} in not an int`)
            }
        }
        return true
    }
})

Object.definedProperty只能监听到对象的读写

Proxy可以监听到对象更多的操作,还可以对数组进行监听

const list = []
const proxyList = new Proxy(list, {
    set (target, property, value) {
        console.log(target, property, value)
        target[property] = value
        return true
    }
})
proxyList.push(1)
proxyList[1] = 10

Proxy是以非侵入的方式进行对象的监听,可以对一个现有对象进行监听,不需要在原来对象上面增加监听操作,是监听整个对象

Object.definedProperty监听读写需要在原来对象上进行额外操作,并且要对每一个要监听的属性进行操作

Reflect

提供一种统一的对象操作方法,属于一个静态类,不能用new创建实例,只能调用静态方法

目的是提供一套统一对象操作方法,解决之前对象操作中多种语法的不统一

Reflect的成员方法是Proxy处理对象对应方法的默认实现,当创建Proxy代理时,没有自定义的代码方法都会默认用Reflect里面对应的方法

const person = {
    name: 'tom'
}
const personProxy = new Proxy(person, {
    get (target, property) {
        // 自定义时除了添加的逻辑 最后应该用回Reflect.get(target, property)
    }
    // 其他没有自定义的方法都是用Reflect里面对象的方法
    // 例如 set 就要 Reflect.set(target, proerty, value)
})

class

// 构造函数
function Person (name) {
    this.name = name
}
// 公共属性
Person.prototype.say = function () {
    console.log(this.name)
}
// class 定义
class Person {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(this.name)
    }
}

静态方法

class Person {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(this.name)
    }
    // 静态方法
    static create (name) {
        return new Person(name)
    }
}
const p = Person.create('tom')

继承

class Person {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(this.name)
    }
}
class Student extends Person {
    constructor (name, number) {
        super(name)
        this.number = number
    }
    hello () {
        super.say()
        console.log(`hello, ${this.number}`)
    }
}
const tom = new Student('tom', 100)
tom.hello()

Set

元素不会重复

const s = new Set()
// add() 返回是Set实例 可以链式调用
// add重复的值会被忽略
s.add(1).add(2).add(3).add(2)
// 遍历
s.forEach(i => console.log(i))
for (let i of s) console.log(i)
// 大小
console.log(s.size)
// 判断是否存在某个值
s.has(1)
// 删除 删除成功返回true
s.delete(2)
// 清空
s.clear()
// 数组去重
const arr = [1, 2, 3, 4]
const result = Array.from(new Set(arr))

Map

类似于对象,区别是Map的键可以是任意类型数据

const m = new Map()
const tom = { name: 'tom' }
// 不同于对象设置键 Map的键为对象时是原来的对象 不是对应的toString方法结果
// 设置
m.set(tom, 90)
// 获取
m.get(tom)
// 判断是否存在
m.has(tom)
// 删除
m.delete(tom)
// 清空
m.clear()
// 遍历
m.forEach((value, key) => {
    console.log(value, key)
})

Symbol

新的数据类型,特点是通过Symbol创建的值是独一无二,对象的属性名可以是字符串和Symbol两种类型,传入的值是描述文本

const s = new Symbol()
console.log(new Symbol() === new Symbol()) // false
console.log(new Symbol('abc') // Symbol(abc)
let obj = {
    [Symbol()]: 123,
    [Symbol()]: 456
}
console.log(obj) // { [Symbol]: 123, [Symbol]: 456 }

Symbol 还可以用来创建私有成员

const name = Symbol()
const person = {
    [name]: 'tom'
    say () {
        console.log(this[name])
    }
}
// 外部访问不到name这个变量名对应的Symbol值 所以就访问不到person里面用name变量存储Symbol值得元素

Symbol.for() 可以实现描述文本和Symbol值得关联,Symbol.for()传入的值是会转换为字符串

console.log(Symbol() === Symbol()) // false
const s1 = Symbol.for(123) // 用字符串关联 会转换为字符串 '123'
const s2 = Symbol.for('123')
console.log(s1 === s2)
const obj = {
    name: 'tom',
    [Symbol()]: 123
}
// 这些方法访问不到Symbol变量名
for (let key in obj) {
    console.log(key)
}
console.log(Object.keys(obj)
console.log(JSON.stringify(obj))
// 只能访问到Symbol的值
console.log(Object.getOwnPropertySymbol(obj))

for of

作为遍历所有数据结构的统一方式

const arr = [1, 2, 3, 4]
// for of 方式可以用break终止
for (const item of arr) {
    console.log(item)
    if (item > 3) {
        break
    }
}
// 数组forEach没有办法终止循环
arr.forEach(item => console.log(item))
// 遍历Set
const s = new Set([1, 2, 3])
for (const item of s) console.log(s)
// 遍历Map
const m = new Map()
m.set('123', 123)
m.set('foo', 456)
for (const [key, value] of m) {
    console.log(key, value) // '123' 123  'foo' 456}

可迭代接口Iterable

ECMAScript为了提供统一的数据类型遍历方式,规定了实现可迭代接口Iterable的数据格式才可以被for of 遍历

实现Iterable接口的数据类型都有一个[Symbole.Iterable]的属性的方法,返回一个迭代器对象,返回的迭代器对象有next方法,next方法返回一个对象,对象有value和done属性,通过next让指针对象向一个数据移动获取值返回来实现遍历

const s = new Set('a', 'b', 'c')
const iterable = s[Symbol.iterator]() // 获取到迭代器对象// 可以用while来实现迭代
console.log(iterable.next()) // { value: 'a', done: false }
console.log(iterable.next()) // { value: 'b', done: false }
console.log(iterable.next()) // { value: 'c', done: false }
console.log(iterable.next()) // { value: undefined, done: true }
console.log(iterable.next()) // { value: undefined, done: true }

获取迭代器对象,调用next来遍历,是for of 的实现原理

实现Iterable迭代接口

const obj = {
    store: [1, 2, 3],
    // obj实现Iterable接口规范 Iterable: 规定里面有一个返回Iterator迭代器的方法
    [Symbol.iterator]: function () {
        let index = 0 // 保存下标
        const self = this
        // 这里返回了一个迭代器对象 Iterator: 规定里面要有一个迭代的next方法
        return {
            // next 返回对象 IterationResult:
            // 规定要一个value属性表示迭代的数据 和 done表示迭代是否结束            next: function () {
                const result = {
                    value: self.store[index],
                    done: index >= self.store.length
                }
                index++
                return result
            }
        }
    }
}
for (const item of obj) {
    console.log(item)
}

迭代器模式

提供一个统一的迭代接口,外部要遍历数据时,不需要关心数据的数据结构,只需要调用数据的迭代接口,避免的数据使用者和数据结构强耦合,后面数据结构发生变化时,不需要修改使用者获取数据的方式,只需要修改数据内部迭代器方式

const todos = {
    work: ['工作1', '工作2'],
    learn: ['语文', '数学', '英语'],
    life: ['吃饭', '喝水', '睡觉'],
    // 统一的迭代器接口  外部不需要关心怎么实现
    [Symbol.iterator]: function () {
        const all = [...this.work, ...this.learn, ...this.life]
        let index = 0
        return {
            next: function () {
                return {
                    value: all[index],
                    done: index++ >= all.length
                }
            }
        }
    }
}

// 只需要用for of调用统一的迭代器遍历 就算todos里面的数据结构变化也不影响这里的遍历
for (const item of todos) {
    console.log(item)
}

生成器Generator

除了在异步编程中使用还可以结合迭代器来简化迭代器的代码,因为都返回一个对象,对象有value和done

const todos = {
    work: ['工作1', '工作2'],
    learn: ['语文', '数学', '英语'],
    life: ['吃饭', '喝水', '睡觉'],
    // 生成器Generator会返回一个有next方法的对象 和 iterator需要的返回一样
    [Symbol.iterator]: function * () {
        const all = [...this.work, ...this.learn, ...this.life]
        // 用数组本身的iterator迭代器
        for (const item of all) {
            // yield 会在调用next后返回一个对象
            // item是数组本身iterator迭代器返回的iterationResult
            // 再通过yield返回出去
            yield item
        }
    }
}

for (const item of todos) {
    // for of 也是调用返回对象的next方法
    console.log(item)
}