ES 新特性

ECMAScript

ECMAScript也是一门脚本语言,一般缩写为ES,通常被看作为JavaScript的标准化规范,实际上JavaScript是ECMAScript的扩展语言。

ECMAScript只提供了最基本的语法(停留在语言层面),我们使用的JavaScript实现了ECMAScript语言的标准,并在这个基础之上,做了一些扩展。

浏览器环境中的JavaScript组成,可以看做是ECMAScript + Web 提供的 APIs

  • 浏览器环境中的JavaScript组成

Node环境中的JavaScript组成, 就是ECMAScript + Node APIs


Node环境中的JavaScript组成

JavaScript语言本身指的就是ECMAScript,从2015年开始ES就保持每年一个版本的迭代,伴随着新版本的迭代,很多新特性陆续出现,其中ES2015里面包含着和之前版本有很大的不同新功能。ES2015迭代时间过长、发布内容特别多。从ES2015之后,ECMAScript就不再用版本号来命名,开始按照发行年份命名,由于这个决定是在ES2015发行的过程之中定的,很多人已经习惯将ES2015称为ES6.

市面上的浏览器也在不断支持ECMAScript的新特性,学习这些新特性很有必要。


ECMAScript版本

ECMAScript 2015(ES2015)

  • 相比于ES5.1的变化比较大
  • 自此,标准命名规则发生变化
    http://www.ecma-international.org/ecma-262/6.0/

下面我们来说一些主要的ES2015的新特性,可以分为四大类:

  • 解决原有语法上的一些问题或者不足 (let、const)
  • 对原有语法进行增强 (数组对象解构、模板字符串、字符串扩展方法、... 运算符、箭头函数、对象字面量增强)
  • 全新的对象、全新的方法、全新的功能(Proxy、Reflect、Promise)
    *全新的数据类型和数据结构 (Reflect)

let 与块级作用域

作用域-- 指的就是代码中的一个成员能够起作用的范围。
在ES2015之前,ES中只有两种作用域

  • 全局作用域
  • 函数作用域
  • 块级作用域 (ES2015新增)就是我们代码中用一对花括号所包裹起来的范围,例如if语句, for语句中的花括号,都会产生块

以前块没有独立的作用域,这就导致我们在块中定义的变量,外部也能访问到。对于复杂的代码来说是非常不安全的。
我们可以只用let声明块级变量

let声明不会有变量提升

console.log(year)
// 报错
let year = 2021

console.log(foo)
// undefined
var foo = 'abc'

const 常量

const在声明时必须设置一个初始值。不能将声明和赋值分开


const

const声明后不允许修改内存地址

const obj = {}
// 可以修改属性
obj.name = 'uamru'

// 会修改内存地址--报错
obj = {}

数组的解构

快速提取成员

const arr = [100, 200, 300]
const [foo, bar, baz] = arr
console.log(foo, bar, baz)
// 100, 200, 300

// 只获取第三个成员
const [, , three] = arr
console.log(three)
//300

// 获取从当前位置到最后一个成员
const [one, ...rest] = arr
console.log(rest)
//[ 200, 300 ]

// 结构数量小于数组的数量(会从第一个位置开始提取)
const [one] = arr
console.log(one)
// 100

//解构数量大于数组的长度 (得到undefined)
const [one, two, three, four] = arr
console.log(four)
// undefined

// 设置默认值(使用 =)
const [one, two, three, four = 'default value'] = arr
console.log(four)
// default value

// 例子:拆分字符串
const path = '/a/b/c'
const [, dir] = path.split('/')
console.log(dir)
// a

对象的解构

const obj = { name: 'umaru', age: 16}
let { name } = obj
console.log(name)
// umaru

// 对属性重命名,解决名称冲突
const name = 'tom'
const { name: newName} = obj
console.log(newName)

// 添加默认值
const { name: newName = 'default value'} = obj
console.log(newName)
// umaru

模板字符串

// 传统定义字符串需要使用单引号、双引号
const str = 'hello es2015, this is a string'

// 模板字符串 
// 支持直接换行
const str2 = `hello es2015,
this is a \`string\``
console.log(str2)
// hello es2015,
// this is a `string`

// 支持模板插值
// ${} 可以嵌入任何js标准语句 ${ 1 + 1 },语句的返回值 最终会被输出到插值表达式存在的位置
const name = 'umaru'
const msg = `hey, ${name}`
console.log(msg)
// hey, umaru

带标签的模板字符串

可以对字符串内容进行加工

// 高级用法
// 带标签的模板字符串,定义模板字符串之前,去添加一个标签,这个标签是一个特殊的函数,使用标签就是调用这个函数
// const str = tag`hello world`
// 使用标签是console.log
const str = console.log`hello world`
// [ 'hello world' ]

const name = 'umaru'
const gender = true

// 定义一个标签函数
function myTagFunc(strings, name){
    // 表达式静态分割的内容
    console.log(strings)
    //[ 'hey, ', '.' ]
    console.log(name)
    // umaru
    return '123'
}
const msg = myTagFunc`hey, ${name}.`
// 返回值 就是这个标签函数的返回值
console.log(msg)
// 123

示例:对字符串内容进行加工

const name = 'umaru'
const gender = true
// 定义一个标签函数
function myTagFunc(strings, name, gender){
    // 表达式静态分割的内容
    console.log(strings)
    // [ 'hey, ', ' is a ', '.' ]

    // 我们定义的true不是很好理解,可以对这个值做加工
    gender = gender ? 'man' : 'woman'
    return strings[0] + name + strings[1] + gender + strings[2]
}
const msg = myTagFunc`hey, ${name} is a ${gender}.`
// 返回值就是这个标签函数的返回值
console.log(msg)
// hey, umaru is a man.

字符串的扩展方法

  • includes(searchString, position) 判断字符串是否包含指定的子字符串
  • startsWith(searchString, position) 判断字符串是否以指定的子字符串开头
  • endsWidth(searchString, position) 判断字符串是否以指定的子字符串结尾
    参数解析:
    (1).searchString:必需,规定要搜索的子字符串。
    (2).position:可选,规定在str中搜索searchString的结束位置,默认值为str.length,也就是字符串结尾处。
const message = 'Error: foo is not defined.'
console.log(
    message.startsWith('Error'),
    message.endsWith('.'),
    message.includes('foo')
)
// true true true

函数参数默认值

// 之前
function foo(enable) {
    // 通过代码设置默认值
    enable = enable === undefined ? true : enable
    console.log('foo invoke - enable: ')
    console.log(enable)
}
foo()
// true

// ES2015
// 使用 = 设置默认值
// 带默认值的参数要放在最后
function foo2(bar, enable = true) {
    // 通过代码设置默认值
    console.log('foo invoke - enable: ')
    console.log(enable)
}
foo(false)
// false
foo()
// true

剩余参数

比如我们console.log 可以接受任意多个参数,并且将这些参数打印出来

对于未知数量的参数,以前我们都是使用arguments这个对象去接受,arguments是个伪数组。

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

// ES2015可以使用...
function foo(...args){
    console.log(args)
    //[ 1, 2, 3, 4 ]
}
foo(1, 2, 3, 4)

关于... 运算符

在ES2015中有一个 ... 运算符用于操作数组,有两种层面

  • 1、第一个叫做 展开运算符(spread operator),作用是和字面意思一样,就是把东西展开。可以用在array和object上都行
// joining arrays
const odd = [1, 3, 5 ];
const nums = [2 ,4 , 6, ...odd];
console.log(nums); // [ 2, 4, 6, 1, 3, 5 ]

// 可以使用spread运算符在另一个数组内的任何位置插入数组。
const odd = [1, 3, 5 ];
const nums = [2, ...odd, 4 , 6];
console.log(nums)  //[ 2, 1, 3, 5, 4, 6 ]

// 还可以将扩展运算符与ES6解构表示法结合使用:
const { a, b, ...z } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a) // 1
console.log(b) // 2
console.log(z) // { c: 3, d: 4 }
    1. 第二个叫做 剩余操作符(rest operator),是解构的一种,意思就是把剩余的东西放到一个array里面赋值给它。一般只针对array的解构
function foo(...args){
    console.log(args)
}
foo(1, 2, 3, 4)

箭头函数 Arrow Function

const inc = n => n+1

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

// 箭头函数和this
const person = {
    name: 'umaru',
    sayHi: function() {
        // 传统函数this指向调用者
        console.log(`hi, my name is ${this.name}`)
        // => hi, my name is umaru
    },
    sayHi2: () => {
        // 传统函数this指向调用者
        console.log(`hi, my name is ${this.name}`)
        // => hi, my name is undefined
    },
    sayHiAsync: function(){
        setTimeout(function() {
            // 这个函数在setTimeOut里面会被全局作用域调用
            console.log(this.name)
            // => undefined
        }, 1000)
    },
    // 借助闭包获取this
    sayHiAsync2: function(){
        const _this = this;
        setTimeout(function() {
            // 这个函数在setTimeOut里面会被全局作用域调用
            console.log(_this.name)
            // => umaru
        }, 1000)
    },
    sayHiAsync3: function(){
        // 使用箭头函数就不用这么麻烦,指向的还是当前作用域
        setTimeout(() => {
            console.log(this.name)
            // => umaru
        }, 1000)
    }
}
person.sayHi()
person.sayHi2()
person.sayHiAsync()
person.sayHiAsync2()
person.sayHiAsync3()

对象字面量增强

const bar = '345'
const obj = {
  foo: 123,
  //bar: bar
  //变量名相同就可以省去
  bar,
//   method1: function() {
//       console.log('method111')
//   }
    method1() {
        console.log('method111')
        // method111
        // this就是当前对象
        console.log(this)
        //  foo: 123, bar: '345', method1: [Function: method1] }
    },
    // es2015可以直接使用动态属性
    // 计算属性名
    [Math.radom()]: 123
  
}

console.log(obj)
// { foo: 123, bar: '345', method1: [Function: method1] }
obj.method1()

对象扩展方法

Object.assign(target, ...sources)
参数:
target:目标对象
sources:源对象
用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后面的源对象的属性将类似地覆盖前面的源对象的属性。

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

可以使用Object.assign复制对象,不会修改到原对象

Object.is(value1, value2)
Object.is() 方法判断两个值是否为同一个值。如果满足以下条件则两个值相等:

  • 都是undefined
  • 都是null
  • 都是true或false
  • 都是相同长度的字符串且相同字符串按相同顺序排序
  • 都是相同对象(意味着引用地址相同)
  • 都是数组且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都是 非零而且非NaN且为同一值

与 == 运算不同。 == 运算在判断相等前会对两边的变量(如果不是同一类型)进项强制转换。而Object.is 不会强制转换两边的值

与 === 运算页不同, === 运算符将数字 -0和+0视为相等,而将Number.NaN与NaN视为不相等。

Proxy

为对象设置代理器,可以轻松监视到对象的读写
在ES2015之前,我们可以使用Object.defineProperty捕获到对象的读写过程,Vue2就是使用这个方法进行双向绑定。

// Proxy对象
const person = {
    name: `umaru`,
    age: 16
}

const personProxy = new Proxy(person, {
    get(target, property){
        return property in target ? target[property] : undefined
    },
    set(target, property, value) {
        if(property === 'age') {
            if(!Number.isInteger(value)){
                throw new TypeError(`${value} is not an int`)
            }
        }
        target[property] = value
    }
})
personProxy.age = '111'
// 抛出异常 TypeError: 1111 is not an int
personProxy.age = 111
personProxy.gender = true
console.log(personProxy.name)
// => umaru
console.log(personProxy.XXX)
// => undefined

Proxy对比Object.defineProperty

1、defineProperty只能监视到属性的读写,Proxy能监视到更多对象操作,例如delete和对对象当中方法的调用

const person= {
    name: 'uamru',
    age: 16
}

const personProxy = new Proxy(person, {
    deleteProperty(target, property){
        console.log('delete', property)
        // => delete age
        delete target[property]
    }
})

delete personProxy.age
console.log(person)
// => { name: 'uamru' }
Proxy

2、Proxy更好的支持数组对象的监视
常见的一种方式,重写数组的操作方法,以此去劫持对应方法的调用过程。

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

listProxy.push(100)

3、Proxy是以非侵入的方式监管了对象的读写。一个已经定义好的对象,我们不需要对对象本身去做任何操作,就可以监视到内部成员的读写。而object.defineProperty需要特殊的方式单独定义对象当中需要监视的属性。

const person = {}
Object.defineProperty(person, 'name', {
    get(){
        console.log('name 被访问')
        return person._name
    },
    set(value){
        console.log('name 被设置')
        person._name = value
    }
})
Object.defineProperty(person, 'age', {
    get(){
        console.log('age 被访问')
        return person._age
    },
    set(value){
        console.log('age 被设置')
        person._age = value
    }
})
person.name = 'umaru'
console.log(person.name)


// Proxy 方式更为合理
const person2 = {
    name: 'umaru',
    age: 16
}

const personProxy = new Proxy(person2, {
    get(target, property){
        return property in target ? target[property] : undefined
    },
    set(target, property, value){
        target[property] = value
    }
})
personProxy.name = 'jack'
console.log(person2)

Reflect

统一对象的操作方式

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)
// 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)

const obj = {
    foo: '123',
    bar: '456'
}
// 传统操作对象,我们要用到各种操作符、对象的方法(in、delete、Object.keys等)
console.log('name' in obj)
console.log(delete obj['age'])
console.log(Object.keys(obj))
// =>
// false
// true
// [ 'foo', 'bar' ]

console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))
// =>
// false
// true
// [ 'foo', 'bar' ]

ES2015 Promise

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

class类

// 使用function创建对象
function Person2(name) {
    this.name = name
}
// 使用原型添加方法
Person2.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}`)
    }
}
const p = new Person('umaru')
p.say()

实例方法vs静态方法

ES2015中新增添加静态成员的static关键词
实例:添加一个create静态方法

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 = Person.create('umaru')
p.say()

类的继承 extends

class Person {
    constructor(name){
        this.name = name
    }
    say() {
        console.log(`hi, my name is ${this.name}`)
    }
    static create(name){
        return new Person(name)
    }
}

class Student extends Person {
    constructor (name, number){
        // super指向父类
        // 调用它就相当于调用父类构造函数
        super(name)
        this.number = number
    }
    hello(){
        super.say()
        console.log(`my school number is ${this.number}`)
    }
}

const s = new Student('umaru', 1)
s.hello()

Set 数据结构

ES2015提供了新的数据结构Set,它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set 本身是一个构造函数,调用构造函数用来生成 Set 数据结构。
Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来进行数据初始化。

let s = new Set([1, 2, 3, 4, 4]); 
console.log(s)
// => Set(4) { 1, 2, 3, 4 }

// 使用add链式调用
// 添加已有的值,这个值会被忽略掉
s.add(7).add(5).add(1)
console.log(s)
// => Set(6) { 1, 2, 3, 4, 7, 5 }

s.forEach( i => console.log(i))

for(i of s){
    console.log(i)
}

// size
console.log('size',s.size)
// => size 6

// has
console.log(s.has(100))
// => false

// delete
console.log(s.delete(3))
// => true
console.log(s)
// => Set(5) { 1, 2, 4, 7, 5 }

// 清除
s.clear()
console.log(s)
// => Set(0) {}

// 常见使用方式,为数组去重
const arr = [1, 2, 3, 4, 3, 6]
const result = new Set(arr)
console.log(result)
// => Set(5) { 1, 2, 3, 4, 6 }

// 如果我们想要再得到一个数组,可以使用Array.from
// const result2 = Array.from(new Set(arr))
// 或者使用...展开
const result2 = [...new Set(arr)]
console.log(result2)
// => [ 1, 2, 3, 4, 6 ]

Map数据结构

一个 Map的键可以是任意值,包括函数、对象或任意基本类型。
Map 中的 key 是有序的。因此,当迭代的时候,一个 Map 对象以插入的顺序返回键值。

// Object
const obj = {}

obj[true] = 'value'
obj[123] = 'value'
obj[{a:1}] = 'value'
console.log(Object.keys(obj))
// 如果添加的键不是字符串,内部会将他们toString的结果当做键值
// => [ '123', 'true', '[object Object]' ]
// 加入我们如果想用对象作为键值,那这样转换过后都会变成一样的值,就会有问题

// Map
const m = new Map()
const tom = {name: 'tom'}
m.set(tom, 90)
console.log(m)
// => Map(1) { { name: 'tom' } => 90 }

// m.has() 判断某一个键是否存在
// console.log(m.has('2222'))
// m.delete() 删除某一个键
// console.log(m.delete('222'))
// m.clear() 清空map

// 遍历map
m.forEach((value, key) => {
    console.log(value, key)
    // => 90 { name: 'tom' }
})

Symbol

一种全新的原始数据类型,在ES2015之前,对象的属性都是字符串,如果字符串重复,就会产生冲突。

一个具有数据类型 “symbol” 的值可以被称为 “符号类型值”。在 JavaScript 运行时环境中,一个符号类型值可以通过调用函数 Symbol() 创建,这个函数动态地生成了一个匿名,唯一的值。Symbol类型唯一合理的用法是用变量存储 symbol的值,然后使用存储的值创建对象属性。

const s = Symbol()
console.log(s)
// => Symbol()
console.log(typeof s)
// => symbol

console.log(Symbol() === Symbol())
// => false
console.log(Symbol('ONE'))
console.log(Symbol('TWO'))
console.log(Symbol('THREE'))
// => Symbol(ONE)
// Symbol(TWO)
// Symbol(THREE)

// 解决对象属性名不重复
const obj = []
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
//  => [ [Symbol()]: '123', [Symbol()]: '456' ]


// 使用Symbol模拟对象私有成员
const name = Symbol()
const person = {
    [name]: 'umaru',
    say() {
        console.log(this[name])
    }
}

// 在外部就只能调用普通名称的成员
console.log(person[Symbol()])
// undefined
person.say()

Symbol每次创建都是一个新的值,如果想要使用同一个值,可以使用全局变量去复用一个Symbol的值,也可以使用Symbol静态方法for方法来实现。
这个方法内部维护了一个全局的注册表,为字符串和Symbol值提供了一一对应的方法。如果我们传入的不是字符串,这个方法内部会自动把他转换为字符串。这会导致我们传入bool类型的true和字符类型的true相等。

const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1===s2)
// => true

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

toStringTag

// 传统定义一个对象,如果打印对象的toString方法会得到[object object],
// 我们把这样的字符串叫做对象的字符串标签

// 如果我们想要更改对象的toString标签,需要在对象中添加一个成员来标识
// 如果使用字符串去添加这种标识符,就有可能跟内部的成员产生冲突,
// ES要求我们使用Symbol值去使用这样一个接口
// toStringTag内置常量
const obj = {
    [Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString())
//  => [object XObject]

获取对象中的symbol:getOwnPropertySymbols

const obj = {
    [Symbol()]: 'symbol value',
    foo: 'normal value'
}
// 以下方法都获取不到Symbol值
for(let key in obj){
    console.log(key)
    // => foo
}
console.log(Object.keys(obj))
// => [ 'foo' ]
console.log(JSON.stringify(obj))
// => {"foo":"normal value"}

// 通过getOwnPropertySymbols获取
console.log(Object.getOwnPropertySymbols(obj))
// => [ Symbol() ]

for...of

ES2015引入for...of作为遍历所有数据结构的统一方式。

使用:遍历数组、Map对象
for...of 可以用break跳出循环

// for...of 使用break跳出循环
const arr = [100, 200, 300, 400]
for(item of arr){
    console.log(item)
}
// 100
// 200
// 300
// 400


for(let item of arr){
    if(item > 200) break;
    console.log(item)
}
// 100
// 200

// 遍历map
const m = new Map()
m.set('one','123')
m.set('two','456')
m.set('three','789')

for(const item of m){
    console.log(item)
}
// 数组形式获取键和值
// [ 'one', '123' ]
// [ 'two', '456' ]
// [ 'three', '789' ]


// 使用数组结构获取key,value
for(const [key, value] of m){
    console.log(key, value)
}
// one 123
// two 456
// three 789

使用for...of遍历Object会报错

const obj = {one: 123, two: 456}
for(const item of obj){
    console.log(item)
}
//for(const item of obj){
                  ^
//TypeError: obj is not iterable

我们前面说它可以遍历说有数据结构,这是为什么呢?

ES中表示有结构的数据类型越来越多,像Array、Object、Set、Map等,为了给各种各样的数据结构提供统一遍历方式,ES2015提供了Iterable接口,意思是可迭代的接口。
实现Iterable接口就是使用for...of遍历的前提。

迭代器(Iterator)

实现Iterable接口的对象,里面都有一个迭代器对象 通过调用 Symbol.iterator(他是一个函数)方法获取

image.png

Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被for...of循环使用。

当需要对一个对象进行迭代时(比如开始用于一个for..of循环中),它的@@iterator方法都会在不传参情况下被调用,返回的迭代器用于获取要迭代的值。

const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()
// 使用next()方法遍历
console.log(iterator.next())
// => { value: 'foo', done: false }

自定义迭代器

const obj = {
    store: ['foo', 'bar', 'baz'],
    // 使用计算属性名 添加Symbol.iterator
    [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)
}

迭代器模式(设计模式)

对外提供统一遍历接口,让外部不用关心内部结构

ES2015生成器 Generator

生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议。

定义生成器 就是在普通的函数function后加一个*,这个函数就变成了一个生成器函数

// 普通函数
function foo(){
    console.log('umaru')
    return 100
}
const result = foo()
console.log(result)
// umaru
// 100



// 生成器函数
function * foo2(){
    console.log('umaru')
    return 100
}
const result2 = foo2()
console.log(result2)
// Object [Generator] {}

// 调用next方法,函数体才开始执行
console.log(result2.next())
// umaru
// { value: 100, done: true }

生成器函数与yield
调用生成器的next方法,返回一个由 yield表达式生成的值。

function * one() {
    console.log(1111)
    yield 100
    console.log(2222)
    yield 200
    console.log(3333)
    yield 300
}

const generator = one()

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 }

console.log(generator.next())
// { value: undefined, done: true }

生成器应用

// Generator 应用
// 案例1 发号器
function * createIdMaker() {
    let id = 1
    // 不用担心死循环,调用next才会执行
    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)

// 案例2 用生成器去实现iterator方法
// const todos = {
//     life: ['吃饭','睡觉','打豆豆'],
//     learn: ['语文','数学','外语'],
//     work: ['演讲'],
//     [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
//                 }
//             }
//         }
//     }
// }

const todos = {
    life: ['吃饭','睡觉','打豆豆'],
    learn: ['语文','数学','外语'],
    work: ['演讲'],
    [Symbol.iterator]: function * () {
        const all = [...this.life, ...this.learn, ...this.work]
        for(const item of all){
            yield item
        }
    }
}
// 测试iterator
const it = todos[Symbol.iterator]()
console.log(it.next())
// { value: '吃饭', done: false }
console.log(it.next())
// { value: '睡觉', done: false }

for(const item of todos){
    console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 外语
// 演讲

ES Modules

ESMAScripit 2016

Array.property.includes

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

// includes可以直接判断数组中是否又该元素
// includes可以匹配 NaN
console.log(array.includes('foo'))
// true
console.log(array.includes(NaN))
// true

指数运算符

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

console.log(2 ** 10)
// 1024

ECMAScript2017

对象的扩展方法

// ECMAScript2017
const obj = {
    one: 'value1',
    two: 'value2'
}
// Object.values -------------
// 返回对象中所有值组成的数组
console.log(Object.values(obj))
// => [ 'value1', 'value2' ]

// Object.entries ------------
// 拿到key、value组成的数组
console.log(Object.entries(obj))
// => [ [ 'one', 'value1' ], [ 'two', 'value2' ] ]

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

// 将对象转换成map
console.log(new Map(Object.entries(obj)))
// => Map(2) { 'one' => 'value1', 'two' => 'value2' }


// Object.getOwnPropertyDescriptors --------------
// 获取对象当中属性描述的完整信息

const p1 = {
    firstName: 'li',
    lastName: 'lei',
    get fullName(){
        return this.firstName + ' ' + this.lastName
    }
}
console.log(p1.fullName)
// => li lei
const p2 = Object.assign({}, p1)
p2.firstName = 'zzz'
console.log(p2)
// { firstName: 'zzz', lastName: 'lei', fullName: 'li lei' }
// Object.assign 把fullName当成普通的对象去复制了

// 此时可以使用getOwnPropertyDescriptors
const descriptors= Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
// {
//     firstName: { value: 'li', writable: true, enumerable: true, configurable: true },
//     lastName: {
//       value: 'lei',
//       writable: true,
//       enumerable: true,
//       configurable: true
//     },
//     fullName: {
//       get: [Function: get fullName],
//       set: undefined,
//       enumerable: true,
//       configurable: true
//     }
//   }

const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'hhhh'
console.log(p3)
// => { firstName: 'hhhh', lastName: 'lei', fullName: [Getter] } 
console.log(p3.fullName)
// => hhhh lei

字符串填充

// 字符串填充
// String.prototype.padStart / String.prototype.padEnd --------------------
const books = {
    html: 5,
    css: 10,
    javascript: 128
}

for(const [name, count] of Object.entries(books)){
    console.log(`${name.padEnd(16, '-')} | ${count.toString().padStart(3, '0')}`)
}
// html------------ | 005
// css------------- | 010
// javascript------ | 128

// 在函数参数中添加尾逗号 ------------------------------
// 没有实际功能
function foo(bar, baz,) {
    
}
const arr = [
    100,
    200,
    300,
]

Async/Await
在async/await之前,我们有三种方式写异步代码

  • 嵌套回调
  • 以Promise为主的链式回调
  • 使用Generators
    async/await相比较Promise 对象then 函数的嵌套,与 Generator 执行的繁琐(需要借助co才能自动执行,否则得手动调用next() ), Async/Await 可以让你轻松写出同步风格的代码同时又拥有异步机制,更加简洁,逻辑更加清晰。

被async修饰的函数是异步函数,异步函数就是代码执行起来不会阻塞后面后面代码的进程.

你可能感兴趣的:(ES 新特性)