ES

116 阅读7分钟

ECMAScript

缩写ES

  • JavaScript是ES的扩展语言

  • ECMAScript只提供了最基本的语法,只停留在语言层面

  • JavaScript语言本身指的就是ECMAScript

ES2015

概述

ES6

asnyc2017标准

准备

yarn init
code 001.js
yarn add nodemon --dev
yarn nodemon 001.js 
//监视脚本文件

let与块级作用域

  • 全局作用域

  • 函数作用域

  • 块级作用域 {}

    • let 在所声明代码块外部无法访问

    • 循环计数器 嵌套循环 是两层嵌套的独立作用域

      var i重复 内部拿到的只是外部的i

      for(var i ...)
      	for(let i ...)
      		console.log(i)
      

      var 全局作用域 闭包也可以摆脱影响

      for(let i = 0;i < 3;i ++){
      	let i = 'foo'
      	console.log(i)
      	//互不影响
      }
      /===
      let i = 0
      i++
      if(i < 3){
          let i = 'foo'
          console.log(i)
      }
      
      • 原有变量声明的提升,不报错,undefined

        let被改正

      console.log(foo)
      var foo = 'zce'
      

const

恒量常量 只读

只要没有修改指向的内存地址,都是被允许的

特性与let相同

const u = {}
u.hi = '1'//√
u = {}//×

主用const,配合let,不用var

数组的解构

const arr = [1,2,3]
const [foo, bar, baz] = arr

const [foo=3, ...rest] = arr
//提取当前位置往后的所有成员,只能在解构位置最后一个成员上使用

对象的解构

const obj = {name:'zce', age: 18}
const name = 'tom'
const {name: objName='jack'} = obj
console.log(objName)//zce

可以简化代码的编写,体积减小

const { log } = console
log('foo')
log('1')

模板字符串字面量

const str = \`nihao\`nihaonihao`
const mag = `hey, ${name}`

带标签的模板字符串

fuction myTagFunc (string, name, gender){
    console.log(string, name, gender)
    return '123'
}//按照表达式分割的静态的内容,结果是一个数组
const str = myTagFunc`hey,${name} is a ${gender}`
console.log(str)//123
//tag是函数,作为一个模板标签
const arr = console.log`world`

字符串的扩展方法

  • includes()中间包含xx
  • startsWith()判断开头是xx
  • endsWith()判断结尾是xx

参数默认值

没有在传递参数或者传递undefined时使用的一个值

function foo(enable){
    enable = enable === undefined ? true : enable
}
//==
function foo(a, enable = true){
    //一定要出现在参数列表的最后
}
foo()

剩余参数

新增了...

取代argement

出现在最后一位,只能出现一次

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

展开数组

arr = [1,2,3,4]
console.log(...arr)

箭头函数

const omc = n => n + 1
//Fira Code
const arr = [1,2,3,4,5,7]
arr.filter(i => i % 2)

箭头函数与this

  • 没有this的机制,不会改变this的指向,它始终指向的都是当前作用域里的this

  • this在外面是什么,在里面就是什么

  • 在普通函数中,this始终会指向调用它的对象,箭头函数不然

    const person = {
        name : 'tom',
        sayHi : () => {
            console.log(`hi,${this.name}`)
            //在外面this是undefined
        },
        sayHiAsync:function(){
            const _this = this
            //使用了闭包
            setTimeout(function(){
                console.log(_this.name)
            },1000)
        },
        sayHiAsync1:function(){
            setTimeout(() => {
                console.log(this.name)
                //this就是sayHiAsync1作用域中的this
            },1000)
        }
    }
    person.sayHi()
    person.sayHiAsync1()
    

对象字面量的增强

计算属性名

const obj = {
	foo:2,
	bar,
	[Math.random()]:123
}

Object.assign

  • 多个源对象的属性复制到一个目标对象中

    const a = {}
    const b = {}
    const result = Object.assign(a,b)
    //a === b
    const result = Object.assign(source3,source1,source2)
    //复制的是source2
    
  • 也可以解决两个对象直接相等改变前者指向地址的问题

    const funcObj = Obj.assign({},obj)
    

Object.is(对象扩展方法)

==自动转换数据类型

===严格比较

Object.is(+0,-0)
Object.is(NaN,NaN)

Proxy代理对象

Object.defineProperty捕获到对象读写的过程。Vue3.0前实现数据响应,完成双向数据绑定

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

const p = new Proxy(person, {
    get(target, property) {
        //目标对象,访问的属性名
        return property in target ? target[property] : 'default'
        //访问属性值,先判断对象中是否存在该属性名
    },//监视属性访问
    set(target, property, value) {
        //代理目标对象,要写入的属性名称,写入的属性值
        console.log(target, property, value)
        //加入数据校验
        if(property === 'age'){
            if(!Number.isInteger(val)){
                throw new TypeError(`1111`)
            }
        }
        target[property] = value

    }//监视属性设置
})

p.gender = true
console.log(p.name)

Proxy VS. defineProperty

  • defineProperty只能监视属性的读写,Proxy 能够监视到更多对象操作

    //Proxy对象
    deleteProperty(target,property){
    	delete target[property]
    }//监听删除操作
    
    
    handler方法触发方式
    get读取
    set写入
    hasin操作符
    deletePropertydelete操作符
    getPrototypeOfObject.getPrototypeOf()
    setPrototypeOfObject.setPrototypeOf()
    apply调用一个函数
    construct用new调用一个函数
  • Proxy更好的支持数组对象的监视

    以往重写数组的操作方法

    const list = []
    
    const listProxy = new Proxy(list,{
        set(target, property, value){
            console.log(target, property, value)
            //目标对象,数组长度,传值
            target[property] = value
            return true
        }
    })
    
    listProxy.push(100)
    
  • Proxy是以非侵入的方式监管了对象的读写

Reflect

  • 内部封装了一系列对对象的底层操作(13个)

  • Reflect成员方法就是Proxy处理对象的默认实现

    如果在内部没有定义set get等方法,就默认调用了Reflect同名方法

    ...
    get(target, property){
    	return Reflect.get(target, property)
    }
    
  • 意义:提供了统一一套用于操作对象的API

    const obj = {
        name : 'hi',
        age:1
    }
    
    // console.log('name' in obj)
    // console.log(delete obj['age'])
    // console.log(Object.keys(obj))
    //即将废弃
    
    console.log(Reflect.has(obj, 'name'))
    console.log(Reflect.deleteProperty(obj, 'age'))
    console.log(Reflect.ownKeys(obj))
    
    
    
  • new Reflect()本身不可以对数据进行拦截

Promise

  • 一种更优的异步编程解决方案
  • 解决了传统异步编程中回调函数嵌套过深的问题

class 类

  • 以前定义函数和定义函数的原型对象prototype去实现的类型
function Person (name) {
    this.name = name
}//构造函数

Person.prototype.say = function () {
    //对象方法
}
  • 通过class

    class Person {
        constructor (name) {
            this.name = name
        }
        say() {
            console.log(`hi`)
        }
    }
    
    const p = new Person('tom')
    p.say()
    

静态方法(static)

  • 实例方法 vs. 静态方法

  • 新增了添加静态成员的static关键词

    需要注意this

    class Person {
        constructor (name) {
            this.name = name
        }
        say() {
            console.log(this)
            console.log(`hi`)
        }
        static create (name) {
            console.log(this)
            //this = Person { name: 'tom' }
            return new Person(name)
        }
        //静态方法挂载到类型上,所以静态方法内部的this就不会指向某,而是当前的类型
        //this = [class Person]
    }
    const tom = Person.create('tom')
    tom.say()
    

类的继承(extends)

以前用原型

class Student extends Person {
    constructor(name ,number){
        super(name)//调用了弗雷的构造函数
        this.number = number
    }
    hello(){
        super.say()
    }
}
  • 子类构造器中super关键字前面不能出现this关键字

Set(集合)

  • 内部成员不允许重复,重复添加会忽略

    const s = new Set()
    s.add(1).add(2).add(3).add(4)
    //返回集合本身,链式调用
    

    add forEach size(length) have delete clear

  • 作用:为数组元素去重

const arr = [1,2,3,4,2,3,1]
//const result = new Set(arr)是一个对象
const result = Array.from(new Set(arr))
const result = [...new Set(arr)]
//都可以得到一个数组

Map

和对象类似,本质上是键值对集合,但键只能是字符串类型,

const obj = {}
obj[123] = '?'
obj[{a:1}] = '!'

Map可以解决这样的问题

const m = new Map()
const tom = {name : 'tom'}
m.set(tom, 90)
console.log(m.get(tom))
//键值是什么都可以

has delete clear forEach

symbol

  • 一种全新的原始数据类型

  • 避免对象属性名重复产生的问题

    Symbol() != Symbol()
    Symbol('foo')
    Symbol('baz')//描述文本
    ...
    
    const obj = {}
    obj[Symbol()] = '123'
    obj[Symbol()] = '456'
    //因为Symbol的值都是独一无二的,不用担心会冲突
    
  • 可以模拟实现对象的私有成员

    • 原理:外界无法获取到相同的smybol键值,则无法访问
    //a.js
    const name = Symbol()
    const person = {
        [name]:'1',
        say(){
            console.log(this[name])
        }
    }
    
    //b.js
    person.say()
    //只能通过调用对象方法访问
    
  • 为对象添加独一无二的属性名

  • 特性:

    • 唯一性 for

      Symbol('foo') != Symbol('foo')
      //与描述文本无关
      
      //for接收一个字符串作为一个参数,相同的字符串一定会返回相同的字符串的值
      const s1 = Symbol.for('foo')
      const s2 = Symbol.for('foo')
      s1 === s2
      

      它维护的是字符串和smybol之间的关系,如果传参不是字符串,会自动转换为字符串

    • 提供了很多内置的Symbol常量,作为内部方法的标识。可以去实现js中内置的接口。

      const obj = {
          [Symbol.toStringTag]: 'XObject'
          //toStringTag内置的Symbol常量
      }
      console.log(obj.toString())
      //[object object] 对象的toString标签
      

      但还是可以使用Object.getOwnPropertySymbol(obj)获取到obj中Symbol类型的属性名

for...of循环

  • 遍历所有统一方式

    for (const item of arr){
    	if(...){
            break
        }
    }
    //forEach都不会中止遍历
    

    伪数组也可

  • 但普通对象Object无法被迭代

    ES2015提供了Iterable接口,表示可迭代的。能实现Iterable接口就是for...of的前提(在内部已经实现了这个接口)

    找原型对象,有Iterable的方法,必须挂载的

    总结:需要返回带有next()方法的对象,不断调用next()方法实现对内部对象的遍历

    const set = new Set(['foo','bar','baz'])
    
    const iterator = set[Symbol.iterator]()//调用set的iterator方法,获得set的迭代器
    
    console.log(iterator.next())
    console.log(iterator.next())
    console.log(iterator.next())
    

实现可迭代接口(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
            }//向后迭代的逻辑,实现了迭代结果的接口,IterationResult
        }//实现了迭代器接口 Iterator
    }
    //挂载一个iterator方法,在这个方法里返回一个迭代器对象
}

for(const item of obj){
    console.log('???',item)
}

迭代器模式

  • 意义:对外提供遍历统一接口,外部不用再关心内部的结构。语言层面实现,适用于任何结构。

    const todos = {
        a: [1, 2, 3],
        b: [4, 5, 6],
        c: [7, 8, 9],
    
        [Symbol.iterator]: function () {
            const all = [...this.a, ...this.b, ...this.c]
            let index = 0
            return {
                next: function () {
                    return {
                        value: all[index],
                        done: index++ >= all.length
                        //防止死循环
                    }
                }//返回此次迭代的结果
            }
        }
    }
    
    for (const item of todos) {
        console.log(item)
    }
    
    

生成器(Generator)

  • 避免异步编程中回调嵌套过深问题

  • 提供更好的解决方案

  • 内部也有一个迭代器next方法

    function * foo {
    	...
        return 100
    }
    const result = foo()
    console.log(result)
    //打印出来的结构是一个Generator对象
    console.log(result.next())
    //直接调用next方法便可以遍历foo对象,foo才开始进行执行
    
  • 配合yield使用

  • 总结:

    • 生成器函数会自动返回一个生成器对象,调用它的next方法才会让这个函数体开始执行
    • 一旦中途遇到yield关键字,便会暂停执行,继续next便会继续执行

生成器应用

  1. 发号器
function * createIdMaker () {
    let id = 1
    while(true){
        yield id++
    }
}

const idMaker = createIdMaker()

console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
//简单的发号器需求
  1. 使用Generator函数实现iterator方法

    不用手动实现一个迭代器对象,直接使用循环yield去返回一个生成器的对象

    const todos = {
        a: [1, 2, 3],
        b: [4, 5, 6],
        c: [7, 8, 9],
    
        [Symbol.iterator]: function () {
            const all = [...this.a, ...this.b, ...this.c]
    //        let index = 0
    //        return {
    //            next: function () {
    //                return {
    //                    value: all[index],
    //                    done: index++ >= all.length
    //                    //防止死循环
    //                }
    //            }//返回此次迭代的结果
    //        }
            for(const item of all){
                yield item
            }
        }
    }
    
    for (const item of todos) {
        console.log(item)
    }
    

ES Modules

  • 语言层面地模块化规范

ES2016

  • 检查数组是否包含指定元素,能去查找NaN

    arr.includes()

  • 指数运算符

    Math.pow(2 ** 10)

ES2017

  • Object.value()

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

  • Object.entries()

    返回对象中所有的键值对

    for (const [key, value] of Object.entries(obj)) {
    	console.log(key, value)
    }
    
  • Object.getOwnProperyDescriptors

    完整地获取对象属性中的描述信息,配合ES中的get和set使用

  • String.prototype / String.prototype.padEnd

    字符串填充方法。用给定的字符串去填充字符串的开始和结束位置,达到指定长度为止

  • 在函数参数中添加逗号

  • Async / Await


本文首发于我的GitHub博客,其它博客同步更新。