ECMAScript新特性

ECMAScript新特性

技术杂谈小彩虹2021-07-08 10:48:2160A+A-

一、 ES2015(es6)

1. let与块级作用域

console.log(a)//undefined
var a = 1

let 声明变量,但不存在变量提升

console.log(a)// a is not defined
let a = 1

js包括全局作用域函数作用域块级作用域let和const只能在块级作用域内部被访问(内部也是闭包机制)。let的提出要求我们先声明在使用变量!

for (var i = 0; i < 3; i++) {
    var i = 'foo' //(1)
    let i = 'foo' // (2)
    console.log(i) //(1)只打印一个foo 因为i混淆了 (2)打印三个foo
}

在熟悉不过的问题

for (var i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i)//3 3 3
    })
}

解决

for (let i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i)//0 1 2
    })
}

2. const

const name = 'lcj'
name = 'xyz'//error
const name
name = 'xyz'//error

const必须声明和赋值在一起写const name = 'lcj' const定义的常量一旦别定义就不可修改

const obj = {}
obj = { x: 1 }//error
const obj = {}
obj.name = 'xyz'//可以

const可以修改常量属性,但不能改变内存指向!

合理的声明规范:主用const、配合let、不用var!

3. 数组的解构

const arr = [100, 200, 300]

const [a, b, c] = arr
console.log(a, b, c)//100, 200, 300

//想要取最后一位,前面别忘记,,
const [, , d] = arr
console.log(d)// 300

//...解构只能用在数组最后一个位置
const [e, ...rest] = arr
console.log(rest)//[ 200, 300 ]

//从第一个开始赋值
const [f] = arr
console.log(f)//100

//数组项数多余实际数组项数,找不到返回undefined
const [g, h, i, j] = arr
console.log(j)//undefined

//数组中没有就拿默认值
const [k, l, m, n = '123'] = arr
console.log(n)//123

const path = 'foo/bar/baz'
const [, rootdir] = path.split('/')
console.log(rootdir)//bar

4. 对象的解构

数组的解构是根据下标去提取,对象的解构是根据属性名去提取

const obj = { name: 'lcj', age: 18 }
const { name } = obj
console.log(name)//lcj

产生一个问题,当我们利用对象解构取name和声明一个name冲突时,怎么办

const obj = { name: 'lcj', age: 18 }
const { name } = obj
const name = 'abc'
console.log(name)//error

解决办法:给对象属性重命名

const obj = { name: 'lcj', age: 18 }
const { name: myName = 'daisy' } = obj
const name = 'abc'
console.log(name)//abc
console.log(myName)//lcj

const { name: myName = 'daisy' } = obj【= 'daisy'】这里是设置默认值。name对象obj中的变量名,myName是我们重新的命名。打印myName可以拿到结果。

const { log } = console
console.log(log)//[Function: bound consoleCall]
log('123')//123

5. 模版字符串

const str = `hello world`
console.log(str)//hello world

//想要在模版字符串中输出`,使用转义字符\
const str2 = `hello ,this is a \`string`
console.log(str2)//hello ,this is a `string

//可以换行输入,也能正常输出
const str3 = `hello 
我换行了`
console.log(str3)//hello (换了行)我换行了

//可以赋值
const name = 'lcj'
const str4 = `hey,${name}`
console.log(str4)//hey,lcj

//可以进行基本计算
const str5 = `hey,${1 + 2}`
console.log(str5)//hey,3

6. 模版字符串标签函数

const str = console.log`hello world`
console.log(str)//[ 'hello world' ]

定义模版字符串之前添加一个标签,如:【const str = console.loghello world】,此处tag是个标签函数(一个特殊的函数),打了标签就执行这个函数

const name = 'lcj'
const gender = false
function myTag(arr, name, gender) {
    console.log(arr)//[ 'hey,', ' is a ', '.' ]
    let sex = gender ? 'man' : 'woman'
    return arr[0] + name + arr[1] + sex + arr[2]
}
const res = myTag`hey,${name} is a ${gender}.`
console.log(res)//hey,lcj is a woman.

7. 字符串的扩展方法

startsWith:判断是否以开头,endsWith:判断是否以结尾,includes:判断是否包含

const msg = `I am a girl.`
console.log(msg.startsWith('I'))//true
console.log(msg.startsWith('a'))//false
console.log(msg.endsWith('.'))//true
console.log(msg.endsWith('l'))//false
console.log(msg.includes('am'))//true
console.log(msg.includes('am ab'))//false

8. 参数默认值

*** 参数存在那么就正常使用,不存在就默认改成true ***
function foo(enable) {
    enable = enable || true //(1)
    enable = enable === undefined ? true : enable//(2)
    console.log(enable)
}
foo()//true
foo(false)//(1)true (2)false

上面使用短路运算,但是foo(false)依然返回了true,所以不正确,应该改成(2),es6新写法

function foo(enable = true) {
    console.log(enable)
}
foo()//true
foo(false)//false

9. 剩余参数

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

未知参数个数的时候,我们使用arguments获取参数,它其实是个伪数组

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

...arg只能出现在形参的最后一位,只能使用一次.以数组形式,从当前位置接收所有参数

function foo(a, ...arg) {
    console.log(a)//1
    console.log(arg)//[ 2, 3, 4 ]
}
foo(1, 2, 3, 4)

10. 展开数组

const arr = ['foo', 'bar', 'abc']
console.log.apply(console, arr)//foo bar abc【原来写法,利用apply】
console.log(...arr)//foo bar abc 

...自动把数组中的成员依次传递到参数列表中

11. 箭头函数

箭头函数代码更简短易读,箭头左边是参数的列表,右边是函数体

const inc = (m,n)=>m+n

12. 箭头函数与this

箭头函数不会改变this指向

const person={
  name:'tom',
  //*** 原来写法 ***
  // sayHi:function(){
  //   console.log(`hi,my name is ${this.name}`)//hi,my name is tom
  // }
  // *** es6 ***
  sayHi:()=>{
    // console.log(this)//{}
    console.log(`hi,my name is ${this.name}`)//hi,my name is undefined
  },
  sayHiAsync:function(){
  //*** 原来写法 ***
    // const that = this
    // console.log(this)//{name: 'tom',sayHi: ...}
    // setTimeout(function(){
    //   console.log(this)//Timeout {...}
    //   console.log(this.name)//undefined
    //   console.log(that.name)//tom
    // },1000)
  // *** es6 ***
    setTimeout(()=>{
      console.log(this.name)//tom
    })
  }
}
person.sayHi()
person.sayHiAsync()

箭头函数中,没有this的机制,所以this和方法外面的this一致

13. 对象字面量的增强

const bar = '123'
const obj = {
  name:'lcj',
  //bar:bar //传统写法
  bar, //现在可以省略:和变量名
  //method:function(){//传统写法
  //    console.log(123456)
  //}
  method(){
      console.log(this)//{ name: 'lcj', bar: '123', method: [Function: method] }//this指向当前对象
      console.log(123456)
  }
  //es6动态添加属性名
  [Math.random()]:1,
  [1+2]:2
  [bar]:3
}
console.log(obj)//{ name: 'lcj', bar: '123', method: [Function: method] }
obj.method()//123456

//*** 之前动态添加属性名 ***
obj[Math.random()]=1

14. Object.assign

将多个源对象中的属性复制到一个目标对象中,如果对象对象中有相同的属性,源对象的属性会覆盖目标对象的属性。

const s1 ={
  a:123,
  b:123
}
const s2 ={
  b:567,
  d:567
}
const target = {
  a:456,
  c:456
}
const res = Object.assign(target,s1)
console.log(res)//{ a: 123, c: 456, b: 123 }
const res2 = Object.assign(target,s1,s2)
console.log(res2)//{ a: 123, c: 456, b: 567, d: 567 }
console.log(res === target)//true 说明是一个

后面覆盖前面的!

function func(obj){
 obj.name = 'func obj'
 console.log(obj)//{ name: 'func obj' }
}
const obj = {name:'global obj'}
func(obj)
console.log(obj)//{ name: 'func obj' }

上面题由于obj引用的是同一个内存地址,所以在内部更改了属性值,外部也跟着更改了,如何只在内部更高呢?内部生成一个全新的属性!

function func(obj){
 const funcObj = Object.assign({},obj)
 funcObj.name = 'funcObj obj'
 console.log(funcObj)//{ name: 'funcObj obj' }
}
const obj = {name:'global obj'}
func(obj)
console.log(obj)//{ name: 'func obj' }

Object.assign参数是对象,如果不是对象会先转为对象,对于无法转成对象的报错。

Object.assign(undefined)//error
Object.assign(null)//error

其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。下面代码中,v1、v2、v3分别是字符串、布尔值和数值,结果只有字符串合入目标对象(以字符数组的形式),数值和布尔值都会被忽略。这是因为只有字符串的包装对象,会产生可枚举属性。

const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

常见用途

1.为对象添加属性--通过Object.assign方法,将x属性和y属性添加到Point类的对象实例。

class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

2.对象添加方法

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};

3.克隆对象

function clone(origin) {
  return Object.assign({}, origin);
}

采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

4.合并多个对象

const merge = (target, ...sources) => Object.assign(target, ...sources);

5.为属性指定默认值

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

DEFAULTS对象是默认值,options对象是用户提供的参数。Object.assign方法将DEFAULTS和options合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值。

15. object.is

用来判断两个值是否相等。之前我们判断两个值是否相等使用==(会自动转换数据类型)或者===(三等严格比较)

//==会进行类型转换
console.log(0==false)//true
//===严格,但是无法分辨+0和-0,NAN
console.log(0===false)//false
console.log(+0===-0)//true

// console.log(NAN === NAN)//error
//Object.is可以分辨+0和-0,NAN(但这里认为NaN===NaN,将NAN当做一种特别的数据类型了)
console.log(Object.is(+0,-0))//false
console.log(Object.is(NaN,NaN))//true

16. Proxy

vue3.0之前使用Object.defineProperty为属性添加读写

const person = {
  name:'lcj',
  age:18
}
const personProxy = new Proxy(person,{//为person创建代理对象,(代理的目标对象,代理的处理对象)
  get(target,property){//监视属性读取访问(代理的目标对象,外部访问属性的属性名)
    // console.log(target,property)//{ name: 'lcj', age: 18 } name
    return property in target?target[property]:'default'//作为外部访问属性得到的结果
  },
  set(target,property,value){//代理目标对象,我们要写入的名称,我们要写入的值
    if(property==='age'){//此处可以判断某些属性,是否符合条件
      if(!Number.isInteger(value)){
        throw new TypeError(`${value} is not int`)
      }
    }
    target[property] = value
  }
})
console.log(personProxy.name)//100 //lcj 
console.log(personProxy.hobby)//default
personProxy.gender = true
console.log(personProxy)//{ name: 'lcj', age: 18, gender: true }

17. Proxy与defineProperty

  • defineProperty只能监视对象的读写,Proxy更强大!(省略部分与例子16一样)
const personProxy = new Proxy(person,{
  //此处省略...
  //代理目标对象,所要删除属性名称
  deleteproperty(target,property){
    console.log('delete ',property)
    delete target[property]
  }
})
console.log(personProxy.name)//lcj
personProxy.gender = true
console.log(personProxy)//{ name: 'lcj', age: 18, gender: true }
delete personProxy.age
console.log(personProxy)//{ name: 'lcj', gender: true}

image.png

  • proxy更好的支持数组对象的监听,重写数组的操作方法。思路:通过自定义的方法,覆盖掉数组原型上的push、pop等方法,以此来劫持这个方法调用的过程。

下面使用proxy对象监视数组

const list = []
const listProxy = new Proxy(list,{
  set(target,property,value){
    console.log('set',property,value)//
    target[property] = value
    return true //表示设置成功
  }
})
// proxy 内部会根据push操作推算出来他所处的下标
listProxy.push(100)//set 0 100(0是数组当中的下标,100是0这个下标所对应的值) 
listProxy.push(200)//set 1 200
  • proxy以非侵入的方式监管了对象读写

下图为defineProperty写法:proxy写法更合理,proxy写法如上面例子 image.png

18. Reflect

Reflect属于一个静态类,不能通过 new Reflect() 的方式去构建一个实例对象,只能去调用这个静态类中的一些方法,如:Reflect.get(),和js中的Math对象一样。Reflect内部封装了一系列对对象的底层操作。原有14个方法,废弃了一个,剩下13个方法名与proxy当中处理对象里面的方法名完全一致,其实Reflect成员方法就是Proxy处理对象方法内部的默认实现。

const obj ={
  foo:'123',
  bar:'456'
}
const proxy = new Proxy(obj,{
  get(target,property){
    console.log('watch ')//watch
    return Reflect.get(target,property)
  }
})
console.log(proxy.foo)//123

统一提供了一套用于操作对象的API

const obj = {
  name:'lcj',
  age:18,
  sex:'woman'
}
// console.log('name' in obj)//true
// console.log(delete obj['age'])//true
// console.log(Object.keys(obj))//[ 'name', 'sex' ]

console.log(Reflect.has(obj,'name'))//true
console.log(Reflect.deleteProperty(obj,'age'))//true
console.log(Reflect.ownKeys(obj))//[ 'name', 'sex' ]

image.png
相关链接:MDN(Reflect)

19. Promise

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

20. class类

es6之前是通过定义函数和函数的原型对象来定义类,想要定义一个Person类型,先定义Person函数作为这个类型的构造函数,构造函数中通过this访问实例对象,如果需要在实例之间共享一些成员,需要借助prototype(原型)去实现

function Person(name) { //
    this.name = name
}
Person.prototype.say = function () {
    console.log(`hi,my name is ${this.name}`)
}
let me = new Person('lcj')
me.say() //hi,my name is lcj

es6使用class关键词去声明一个类型

class Person {
    constructor(name) {//构造器
        this.name = name
    }
    say() {
        console.log(`hi,my name is ${this.name}`)
    }
}
let me = new Person('lcj')
me.say()//hi,my name is lcj

21. 静态方法

类型当中的方法分为实例方法和静态方法。实例方法需要这个类型构造的实例对象来调用,静态方法之间通过类型本身去调用。以前我们实现静态方法,我们直接在构造函数对象上去挂载方法(js中函数也是对象,可以挂载属性和方法)。es6中新增添加静态成员的static关键词

class Person {
    constructor(name) {//构造器
        this.name = name
    }
    say() {
        console.log(`hi,my name is ${this.name}`)
    }
    static create(name) {
        return new Person(name)
    }
}
let me = new Person('lcj')
me.say()//hi,my name is lcj
const tom = Person.create('tom') //create是静态方法
tom.say()//hi,my name is tom

22. 类的继承

extends实现继承,super始终指向父类,调用他就是调用父类的构造函数

class Student extends Person {
    constructor(name, id) {
        super(name)
        this.id = id
    }
    hello() {
        super.say()
        console.log(`my schoolId is ${this.id}`)//my schoolId is 100
    }
}
const s = new Student('xiaoming', '100')
s.say()//hi,my name is xiaoming
s.hello()//hi,my name is xiaoming

23. Set

Set数据结构,类似于数组,可以理解为集合,但是Set内部成员不可以重复

const s = new Set()

//add 返回集合对象本身,可以链式调用添加
s.add(1).add(2).add(3).add(4).add(2)
console.log(s)//Set { 1, 2, 3, 4 }【重复的值会被忽略调】

//使用forEach遍历数组
s.forEach(i => console.log(i))// 1 2 3 4

//使用for..of(es6)遍历数组
for (let i of s) {
    console.log(i)// 1 2 3 4
}

//打印数组长度
console.log(s.size)//4

//判断是否含有某个值,有的话返回true否则false
console.log(s.has(100))//false

//判断是否删除了某个值,删除成功返回true否则false
console.log(s.delete(3))//true
console.log(s)//Set { 1, 2, 4 }

//清空数组
s.clear()
console.log(s)//Set {}

//常见用法,数组去重
const arr = [1, 2, 3, 1, 4, 3, 4, 5]
const res = new Set(arr)
console.log(res)//Set { 1, 2, 3, 4, 5 }

//Array.from将set结构数组转为真正数组
const res2 = Array.from(new Set(arr))
console.log(res2)//[ 1, 2, 3, 4, 5 ]

//...将set结构数组转为真正数组
const res3 = [...new Set(arr)]
console.log(res3)//[ 1, 2, 3, 4, 5 ]

24. Map

Map数据结构,类似于对象,本质上都是键值对集合。对象结构中的键名只能是字符串形式。

const obj = {}
obj[true] = 'val1'
obj[123] = 'val2'
obj[{ a: 1 }] = 'val3'
console.log(obj)//{ '123': 'val2', true: 'val1', '[object Object]': 'val3' }

console.log(obj[{}])//val3
console.log(obj['[object Object]'])//val3

我们设置的键并不是字符串,但是打印显示已经被转成了字符串(toString())作为键。而且上面可以看到不同的键名,打印出相同结果,这不合理。 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

//判断是否存在
console.log(m.has(tom))//true

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

//删除
console.log(m.delete(tom))//true
console.log(m)//Map {}

//清空所有键值
console.log(m.clear)//[Function: clear]
console.log(m)//Map {}

与对象最大区别:可以使用任意类型的值作为键名,而对象只能用字符串作为键名

25. Symbol

一种全新的原始数据类型

const cache = {}
//----- a.js ----
cache['foo'] = Math.random()
//----- b.js ----
cache['foo'] = 123
console.log(cache)

上面两个文件改了相同的地方(cache['foo'])的值,这时候访问就会出现不正确,以前的解决办法是不同的文件带自己的独名,如a.js文件里面cache['a_foo'],a.js文件里面cache['b_foo'].这样只是暂时规避了问题,并没有解决。我们可以使用Symbol解决,Symbol表示一个独一无二的值。

const s = Symbol()
//Symbol()函数创建symbol类型
console.log(s)//Symbol()

//symbol是原始数据类型
console.log(typeof s)//symbol

//每次创建都是一个新的
console.log(Symbol() === Symbol())//false 

//可以接收一个参数判断是哪个Symbol来区分
console.log(Symbol('foo'))//Symbol(foo)
console.log(Symbol('foo2'))//Symbol(foo2)
console.log(Symbol('foo3'))//Symbol(foo3)
// 对象属性名可以是string和symbol两种类型
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = 'abc'
console.log(obj)//{ [Symbol()]: '123', [Symbol()]: 'abc' }
const obj2 = {
    [Symbol()]: '123'
}
console.log(obj2)//{ [Symbol()]: '123' }

因为每次生成Symbol都是一个新的,所以可以避免重复键名问题

// a.js
const name = Symbol()
const person = {
    [name]: 'lcj',
    say() {
        console.log(this[name])
    }
}
// b.js
person.say()//lcj

最主要作用:为对象添加独一无二的属性名,截止es2019共有7种数据类型, 将来会有BigIn类型存放更长的数字。

26. Symbol补充

console.log(Symbol() === Symbol())//false

因为每次生成Symbol都是一个新的,那么怎么复用呢?

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

Symbol.for,symbol 内部维护了一个全局注册表,为字符串和Symbol值提供了一个一一对应的关系,传入的不是字符串,内部会把他转成字符串

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

Symbol类型中提供了许多内置的Symbol常量,用来去作为内部方法的标识,这些标识符可以让自定义对象去实现一些js当中内置的接口

console.log(Symbol.iterator)//Symbol(Symbol.iterator)
console.log(Symbol.hasInstance)//Symbol(Symbol.hasInstance)
const obj = {}
console.log(obj.toString())//[object Object]

const obj1 = {
    //避免和内部成员重复
    [Symbol.toStringTag]: 'XObject'
}
//obj的toString()标签
console.log(obj1.toString()) //[object XObject]

Symbol作为对象属性名,特别适合作为对象的私有属性,常规访问无法访问到,只能访问到字符串类型属性名属性

const obj2 = {
    [Symbol()]: 'symbol value',
    foo: 'normal value'
}
for (let key in obj2) {
    console.log(key)//foo
}
console.log(Object.keys(obj2))//[ 'foo' ]
console.log(JSON.stringify(obj2))//{"foo":"normal value"}

获取Symbol()属性名

console.log(Object.getOwnPropertySymbols(obj2))//[ Symbol() ]

Object.keys获取字符串类型属性名,getOwnPropertySymbols获取symbol类型属性名

27. for...of循环

js中for适合遍历普通数组, for..in 适合遍历键值对, 还有一些对象遍历方法,如 forEach,for ... of 作为遍历所有数据结构的统一方式

const arr = [100, 200, 300, 400]
for (const i of arr) {
    console.log(i)//100 200 300 400
}
arr.forEach(i => {
    console.log(i)//100 200 300 400
})

for...of 可以随时终止循环,forEach不能终止遍历,some返回true终止遍历,every返回false终止遍历

for (const item of arr) {
    console.log(item)//100 200
    if (item > 100) {
        break;//可以随时终止循环
    }
}
// arr.forEach()//不能终止遍历
// arr.some()//返回true终止
// arr.every()//返回false终止

遍历返回-set返回值

const s = new Set(['foo', 'bar'])
for (const i of s) {
    console.log(i)//foo bar
}

遍历返回-map返回键值对,const [key, value] of m拆解开

const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const i of m) {
    console.log(i)//[ 'foo', '123' ] [ 'bar', '345' ]
}
for (const [key, value] of m) {
    console.log(key, value)//foo 123 ;bar 345
}

出现问题了,下面代码报错,可见只能遍历数组形式,如果是对象会报错,看【28 可迭代接口】

const obj = { foo: 123, bar: 456 }
for (const item of obj) {//Error:obj is not iterable
    console.log(item)
}

28. 可迭代接口

上面说到 for...of 数据的统一遍历方式,es中能够表示有结构的数据类型越来越多,为了给各种各样的数据类型提供统一的遍历方式,es6提出Iterable接口,实现Iterable(可迭代的)接口就是for...of 的前提 image.png

image.pngimage.png 看数组原型对象__proto__,里面有个Symbol(Symbol.iterator)方法,三个可以被for...of遍历的都有这个方法,这个方法的名字叫iterator。iterator约定我们对象当中必须要挂载iterator方法。

image.png 总结:所以可以直接被for...of遍历的数据都需要实现iterator接口,它的内部必须要挂载iterator的方法,这个方法需要返回一个带有next方法对象,不断调用next返回数据

const set = new Set(['foo', 'bar', 'baz'])
const iterator = set[Symbol.iterator]()//得到这个对象的迭代器,调用迭代器的next方法得到数据
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 }
console.log(iterator.next())//{ value: undefined, done: true }

29. 实现可迭代接口

基础实现

const obj = {//实现可迭代接口,内部有[Symbol.iterator]
    [Symbol.iterator]: function () {
        return {//实现迭代器,实现用于迭代的next
            next: function () {
                return {
                    value:'lcj',
                    done:true
                }
            }
        }
    }
}
for (const i of obj) {
    console.log(i)//循环体没有执行,因为done: true
}

改装

const obj = {//实现可迭代接口,内部有[Symbol.iterator]
    store: ['foo', 'bar', 'baz'], //创建一个数组
    [Symbol.iterator]: function () {
        let index = 0 //记录下标
        const self = this //存this
        return {//实现迭代器,实现用于迭代的next
            next: function () {
                const result = {
                    value: self.store[index],
                    done: index >= self.store.length 
                }
                index++
                return result
            }
        }
    }
}
for (const i of obj) {
    console.log(i)//foo bar baz
}

30. 迭代器模式

// 协同开发一个任务清单
const todos = {
    life: ['吃饭', '睡觉'],
    learn: ['语文', '英文']
}
for (const i of todos.life) {
    console.log(i)//吃饭 睡觉
}

todos.life todos.learn高度耦合,todos变化,使用就要变化。解决:

const todos = {
    life: ['吃饭', '睡觉'],
    learn: ['语文', '英文'],
    each: function (callback) {
        const all = [].concat(this.life, this.learn)
        for (const item of all) {
            callback(item)
        }
    }
}
todos.each(item => console.log(item))//吃饭 睡觉 语文 英文

实现可迭代接口

const todos = {
    life: ['吃饭', '睡觉'],
    learn: ['语文', '英文'],
    each: function (callback) {
        const all = [].concat(this.life, this.learn)
        for (const item of all) {
            callback(item)
        }
    },
    //实现可迭代接口
    [Symbol.iterator]: function () {
        const all = [...this.life, ...this.learn]
        let index = 0
        return {
            next: function () {
                return {
                    value: all[index],
                    done: index++ >= all.length
                }
            }
        }
    }
}
for (const i of todos) {
    console.log(i)////吃饭 睡觉 语文 英文
}

31. 生成器-Generator

避免异步编程中回调嵌套过深,提供更好的异步编程解决方案。

function* foo() {
    console.log('lcj')
    return 100
}
const result = foo()
console.log(result)//Object [Generator] {},内部也有一个next方法
console.log(result.next())// lcj ;{ value: 100, done: true }

//**** 配合 yield使用 ****
function* foo1() {
    console.log('111')
    yield 100
    console.log('222')
    yield 200
    console.log('333')
    yield 300
}
const generator = foo1()
console.log(generator.next())//111 { value: 100, done: false }
console.log(generator.next())//222 { value: 200, done: false }
console.log(generator.next())//333 { value: 300, done: false }
console.log(generator.next())//{ value: undefined, done: true }

生成器函数会自动帮我们返回一个生成器对象,调用这个函数的next这个函数才会开始执行,执行过程中一旦遇到yield关键词就会暂停下来,而且yield后面的值将会作为next的结果返回,继续调用next函数会从刚才暂停位置继续执行,直到执行完成。

32. 生成器应用

  • 发号器
function* createIdMaker() {
    let id = 1
    while (true) {
        yield id++
    }
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)//1
console.log(idMaker.next().value)//2
console.log(idMaker.next().value)//3
console.log(idMaker.next().value)//4
  • 使用Generator函数实现iterator方法

原来写法

const todos = {
    life: ['吃饭', '睡觉'],
    learn: ['语文', '英文'],
    [Symbol.iterator]: function () {
        const all = [...this.life, ...this.learn]
        let index = 0
        return {
            next: function () {
                return {
                    value: all[index],
                    done: index++ >= all.length
                }
            }
        }
    }
}

改变后

const todos = {
    life: ['吃饭', '睡觉'],
    learn: ['语文', '英文'],
    [Symbol.iterator]: function* () {
        const all = [...this.life, ...this.learn]
        for (const item of all) {
            yield item
        }
    }
}
for (const i of todos) {
    console.log(i)//吃饭 睡觉 语文 英文
}

二、 ES2016概述

相比于ES2015只增加了两处

  • 1.数组实例对象的includes方法(Array.prototype.includes),原来使用indexOf查找,但找不了NaN,includes可以,找到返回true
const arr = ['foo', 1, NaN, false]
//判断是否存在某个元素,不能查找NAN
console.log(arr.indexOf('foo'))
console.log(arr.indexOf(NaN))

console.log(arr.includes('foo'))
console.log(arr.includes(NaN))
  • 2.指数运算符 **
console.log(Math.pow(2, 10))//1024
console.log(2 ** 10)//1024

三、 ES2017概述

  • 1.Object对象的三个扩展方法

    • Object.values
      console.log(Object.values(obj)) //[ 'value1', 'value2' ]
    

    Object.values(obj)返回所有值组成的数组,Object.keys(obj)返回所有键组成的数组`

    • Object.entries
    //以数组形式返回所有键值对
      console.log(Object.entries(obj))//[ [ 'foo', 'value1' ], [ 'bar', 'value2' ] ]
      for (const [key, value] of Object.entries(obj)) {
          console.log(key, value) //foo value1;bar value2
      }
    
    //将对象转成map类型对象
      console.log(new Map(Object.entries(obj))) //Map { 'foo' => 'value1', 'bar' => 'value2'  }
    
    • Object.getOwnPropertyDescriptors
    //获取对象中属性完整信息
      const p1 = {
          firstName: 'cj',
          lastName: 'l',
          get fullName() {
              return this.firstName + ' ' + this.lastName
          }
      }
      console.log(p1.fullName)//cj l
      const p2 = Object.assign({}, p1)
      p2.firstName = 'abc'
      console.log(p2)//{ firstName: 'abc', lastName: 'l', fullName: 'cj l' }
    //此时把fullName当成一个普通属性去复制了
    

    解决

      const p1 = {
          firstName: 'cj',
          lastName: 'l',
          get fullName() {
              return this.firstName + ' ' + this.lastName
          }
      }
      const des = Object.getOwnPropertyDescriptors(p1)
      console.log(des)
      // { firstName:
      //     { value: 'cj',
      //       writable: true,
      //       enumerable: true,
      //       configurable: true },
      //    lastName:
      //     { value: 'l',
      //       writable: true,
      //       enumerable: true,
      //       configurable: true },
      //    fullName:
      //     { get: [Function: get fullName],
      //       set: undefined,
      //       enumerable: true,
      //       configurable: true } }
      const p2 = Object.defineProperties({}, des)
      p2.firstName = 'abc'
      console.log(p2.fullName)//abc l
    
  • 2.字符串padStart/padEnd(string.prototype.padStart / string.prototype.padEnd)

const books = {
    html: 5,
    css: 16,
    js: 128
}
for (const [name, count] of Object.entries(books)) {
    console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}
// html------------|005
// css-------------|016
// js--------------|128
  • 3.在函数参数中添加尾逗号,让代码管理工具更精确的找到代码变化
function foo(bar, baz, ) { }
const arr = [100, 200, 300,]
  • 4.Async/Await 彻底解决了异步编程回调地狱问题,本质promise语法糖

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们