我的笔记--ECMAScript 新特性

ECMAScript 概述

ECMAScript 是 JavaScript的语言本身
通常看作JavaScript的标准化规范
时间上JavaScript是ECMAScript的扩展语言
ECMAScript只提供了最基本的语法(约定了代码该怎么编写,停留在语言上)

ES2015 概述

  • 解决原有语法上的一些问题或者不足
  • 对原有语法进行增强
  • 全新的对象、全新的方法,全新的功能
  • 全新的数据类型和数据结构

ES2015 let 与块级作用域

在ES2015之前,ES中只有两种作用域
全局作用域
函数作用域
ES2015增加了
块级作用域
块 指 {} 里面的内容
以前块没有独立的作用域

if (true) {
	var foo = 'zce'
}
console.log(foo)	// zce

let 有块级作用域

if (true) {
	let foo = 'zce'
}
console.log(foo)	// foo is not defined

适合用于计时器,只打印三次,因为外层声明i后,内层又声明了一次,是使用 var 声明没有是块级作用域内的成员,而是全局成员,内层i会覆盖外层i,所以内层循环完过后i=3,外层拿到的是全局的i,等于3

for (var i = 0; i < 3; i++) {
	for (var i = 0; i < 3; i++) {
		console.log(i)
	}
	console.log('内存结束 i = ' + i)
}

let 会避免这种问题,let有块级作用域

```javascript
let 会避免这种问题,let有块级作用域
for (let i = 0; i < 3; i++) {
	for (let i = 0; i < 3; i++) {
		console.log(i)
	}
	console.log('内存结束 i = ' + i)
}

访问循环的计数器,不管打印哪个的都是3,因为这里打印的始终都是全局作用域的i,在循环执行完过后i累计到 3

var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
	elements[i].onclick = function () {
		console.log(i)	// 3
	}
}
elements[2].onclick()

没有let块级作用域以前可以用 闭包来解决,闭包解决函数作用域摆脱全局作用域产生的影响

var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
	elements[i].onclick = (function (i) {
		return function () {
			console.log(i)
		}
	})(i)
} 
elements[0].onclick()

let 块级作用域解决这个,内部也是用闭包机制

var elements = [{}, {}, {}]
for (let i = 0; i < elements.length; i++) {
	elements[i].onclick = function () {
		console.log(i)	// 3
	}
}
elements[2].onclick()

let 两层嵌套作用域,互不影响,内部的局部变量,外部计算器就是外部循环的块产生的局部变量

for (let i = 0; i < 3; i++) {
	let i = 'foo'
	console.log(i)
}

// 等同于

let i = 0

if(i <3) {
	let i = 'foo'
	console.log(i)
}

i++
if(i <3) {
	let i = 'foo'
	console.log(i)
}

i++
if(i <3) {
	let i = 'foo'
	console.log(i)
}

let 和 var 还有一个很大的区别
let 不会变量提升
var 会变量提升

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


console.log(foo)	//  Cannot access 'foo' before initialization
let foo = 'zce'

ES2015 const

const 声明一个只读的 恒量/常量
特点在 let 基础上多了 只读
只读指的是变量声明过后就不允许再修改

const arr = [100, 200, 300]
const name = 'zce'
name = 'jack'	// TypeError: Assignment to constant variable.

const是恒量在声明的时候必须要设置一个初始值

const name
name = 'zce'	// SyntaxError: Missing initializer in const declaration

const 修改的成员不能被修改只是说不允许在声明了过后重新去指向一个新的内存地址,并不是说不允许修改恒量中的属性成员

const obj = {}
obj.name = 'zce'

const arr = []
arr[0] = 1

最佳实践:不用 var,主用 const, 配合 let
默认使用const 是让我们更明确声明的这些变量是不是允许被修改

ES2015 数组的解构

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

如果只是想获取某个位置的成员,把不要的成员去掉,但是要保留逗号,

const [, , baz] = arr
console.log(baz)	// 300

在解构位置前面添加三个点 …,表示提取从当前位置开始往后的所有成员,最终所有的结果会放在一个数组当中,(注意:这种三个点的用法只能在使用的最后一个成员中用)

const [foo, ...rest] = arr
console.log(rest)	// [200, 300]

解构的成员比被解构的成员多,最后会提取到 undefined

const [foo, bar, baz, name] = arr
console.log(name)	// undefined

如果想给提取的成员赋给默认值也是可以的

const [foo, bar, baz = 123, name ='default value'] = arr
console.log(baz, name)	// 300 default value

ES2015 对象的解构

对象的解构根据属性名解构提取

const obj = {name: 'zce', age: 18}

const {name} = obj
console.log(name)	// zce

对象解构基本和数组解构是一致的,不过有一个特殊的地方:解构的变量名同时又是用来匹配被解构的对象当中的属性名的,当前作用域中有同名的成员就会产生冲突,解决这个问题可以重命名

const name = 'tom'
const {name} = obj
console.log(name)	// SyntaxError: Identifier 'name' has already been declared
==const name = 'tom'
const {name: objName} = obj
console.log(objName)	// zce

常用的场景

const {log} = console
log('foo') 	// foo
log('bar')	// bar
log('baz')	//baz

ES2015 模板字符串

const str = `hello es2015, this is a \`string\``
console.log(str)	// hello es2015, this is a `string`

模板字符串有用的新特性:
支持换行

const str = `hello es2015, 
this is a \`string\``
console.log(str)	// hello es2015,
// this is a `string`

支持通过插值表达式的方式在字符串中嵌入对应的数值

const name = 'tom'
const msg = `hey, ${name}`
console.log(msg)	// hey, tom

不仅可以嵌入变量还可以嵌入任何标准的js语句

const name = 'tom'
const msg = `hey, ${name} --- ${1 +2} --- ${Math.random()}`
console.log(msg)	// hey, tom --- 3 --- 0.07959079270219993

ES2015 带标签的模板字符串

标签函数的作用就是对模板字符串的加工

const name = 'tom'
const gender = true

function myTagFunc (strings, name, gender) {
	// console.log(strings, name, gender)
	// return '123'
	const sex = gender ? 'man' : 'woman'
	return strings[0] + name + strings[1] + sex +strings[2]
}
      
const result = myTagFunc`hey, ${name} is a ${gender}.`
console.log(result)	// hey, tom is a man.

ES2015 字符串的扩展方法

const message = 'Error: foo is not defined.'

console.log(
	// message.startsWith('Error')	// true
	// message.endsWith('.')	// true
	message.includes('foo')	// true
)

ES2015 参数默认值

带有默认值的参数要放在最后

function foo (enable) {
	// enable = enable || true
	enable = enable === undefined ? true : enable
	console.log('foo invoked - enable: ')	// foo invoked - enable:
	console.log(enable)	// false
}
==function foo (bar, enable = true) {
	// enable = enable || true
	enable = enable === undefined ? true : enable
	console.log('foo invoked - enable: ')	// foo invoked - enable:
	console.log(enable)	// false
}

foo(false)

ES2015 剩余参数

…args只可以出现在形参的最后一个

function foo () {
	console.log(arguments)
}

foo(1, 2, 3, 4)		// [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }
==// ...args 可以取代 arguments
function foo (...args) {
	console.log(args)
}

foo(1, 2, 3, 4)		// [ 1, 2, 3, 4 ]     

function foo (first, ...args) {
	console.log(args)
}

foo(1, 2, 3, 4)		// [ 2, 3, 4 ]     

// console.log(1, 2, 3, 4)	// 1 2 3 4

ES2015 展开数组

把数组中的每一个传递给 console.log方法

const arr = ['foo', 'bar', 'baz']

console.log(
	arr[0],
	arr[1],
	arr[2]
	)	// foo bar baz
==》
数组个数太多用 apply
console.log.apply(console, arr)	// foo bar baz
==ES2015
console.log(...arr)	// foo bar baz   

ES2015 箭头函数

使用箭头函数极大简化了回调函数的编写

// function inc (number) {
// 	return number + 1
// }

// const inc = n => n + 1

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

// const inc = (n, m) => {
// 	console.log('inc invoked')
// 	return n + 1
// }

// console.log(inc(100))

const arr = [1, 2, 3, 4, 5, ,6, 7]

arr.filter(function (item) {
	return item % 2
})

arr.filter(i => i % 2)

ES2015 箭头函数与this

箭头函数中没有 this的机制不会改变this的指向,就是说在箭头函数外面this是什么,在里面拿到的就是什么

const person = {
	name: 'tom',
	// sayHi: function () {
	// 	console.log(`hi, my name is ${this.name}`)	// hi, my name is tom
	// }
	sayHi: () => {
		console.log(`hi, my name is ${this.name}`)	// hi, my name is undefined
	}
}

person.sayHi()

在setTimeout里面的this放在全局作用域上面调用所以拿不到当前对象里面的this,拿到的应该是全局对象,解决这个问题一般定义一个 _this = this

const person = {
	name: 'tom',
	sayHiAsync: function () {
		const _this = this
		setTimeout(function () {
			console.log(this.name)	// undefined
			console.log(_this.name)	// tom
		}, 1000)
	}
}

person.sayHiAsync()

如果是箭头函数就没有这个问题,因为箭头函数的this始终指向当前作用域的this

const person = {
	name: 'tom',
	sayHiAsync: function () {
		// const _this = this
		// setTimeout(function () {
		// 	console.log(this.name)	// undefined
		// 	console.log(_this.name)	// tom
		// }, 1000)
		setTimeout(() => {
			console.log(this.name)	// tom
		}, 1000)
	}
}

person.sayHiAsync()

ES2015 对象字面量的增强

const bar = '345'
const obj = {
	foo: 123,
	// bar: bar	// 传统
	bar,	// ES2015 当变量名和属性名一致的时候可以省略 : bar,效果与上面的一致
	// method1: function () {
	// 	console.log('method111')	// { foo: 123, bar: '345', method1: [Function: method1] }
	// }
	method1 () {
		console.log('method111')
		console.log(this)	// { foo: 123, bar: '345', method1: [Function: method1] }
	},
	[Math.random()]: '123'	// 计算属性名,加方括号[]就可以用计算属性了
}

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

ES2015 Object.assign

将多个源对象中的属性复杂到一个目标对象中
作用:用后面对象的属性去覆盖第一个对象,返回值就是第一个对象

const source1 = {
	a: 123,
	b: 123
}

const source2 = {
	b: 789,
	d: 789
}
const target = {
	a: 456,
	c: 456
}

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

修改了函数内部的属性,函数外部的属性也发生了改变,可用Object.assign 赋给一个全新的变量

function func (obj) {
	// 写法一:
	// obj.name = 'func obj'
	// console.log(obj)	// { name: 'func obj' }
	// 写法二: 用 Object.assign 赋给一个全新的变量
	const funcObj = Object.assign({}, obj)
	funcObj.name = 'func obj'
	console.log(funcObj)	// { name: 'func obj' }
}

const obj = {name: 'global obj'}
// 修改了函数内部的属性,函数外部的属性也发生了改变,如写法一结果如下
func(obj)	
console.log(obj)	// 写法一结果: { name: 'func obj' }
// 写法二结果: { name: 'global obj' }

ES2015 Object.is

Object.is 判断两个值是否相等
== 运算符会在比较之前自动转换数据类型
=== 严格对比两者之间的数值是否相等,对数字0的正负值是没有办法区分的

console.log(
	// // == 运算符会在比较之前自动转换数据类型
	// 0 == false	// true
	// === 严格对比两者之间的数值是否相等,对数字0的正负值是没有办法区分的
	// +0 === -0	// true
	// NaN === NaN	// false
	// Object.is(+0, -0)	// false
	Object.is(NaN, NaN)	// true
)

ES2015 Proxy 代理

Proxy 代理 可以理解为门卫
get 方法监视数据的访问
set 方法监视设置属性

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

const personProxy = new Proxy(person, {
	// get 方法监视数据的访问
	get (target, property) {
		// 内部逻辑先去判断代理对象当中是否存在这个属性,如果存在则返回对应的值如果不存在则返回undefined 或 default
		return property in target ? target[property] : 'default'
		// console.log(target, property)
		return 100
	},
	// set 方法监视设置属性
	set (target, property, value) {
		// 内部逻辑为代理目标设置正确的值,可以先做一些数据校验
		if (property === 'age') {
			if (!Number.isInteger(value)) {
				throw new TypeError(`${value} is not an int`)
			}
		}
		target[property] = value
		// console.log(target, property, value)
	}
})

personProxy.age = 100
personProxy.gender = true
console.log(personProxy.name)	// name
console.log(personProxy.xxx)	// default

ES2015 Proxy 对比 Object.definedProperty()

Proxy 更为强大一些,表现为:
1、Object.definedProperty() 只能监视属性的读写,而Proxy 能够监视到更多对象操作,例如 delete操作,或者是对对象当中方法的调用等等

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

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

delete personProxy.age
console.log(person)	// { name: 'zce' }

2、Proxy 更好的支持数组对象的监视,Object.definedProperty() 是通过重写数组的操作方法,如 push(),pop(), shift(), unshift() 等
如何用 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.definedProperty()要求特定方式单独去定义对象中需要被监视的属性,对于一个已经存在的对象要想去监视它的属性需要做很多额外的操作

ES2015 Reflect

Reflect 是ES2015 中统一的对象操作API
Reflect 属于一个静态类,不能用 new Reflect() 去构建实例对象,只能用 Reflect. 去调用一些方法,如 Reflect.get()
Reflect 内部封装了一系列对对象的底层操作(Reflect成员方法有14个,摒弃了1个,还剩下13个)
Reflect成员方法就是Proxy处理对象的默认实现
Reflect 的价值:提供了统一提供一套用于操作对象的API

// 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 = {
	name: 'zce',
	age: 18
}

// console.log('name' in obj)	// 判断对象中是否存在某个属性 true
// console.log(delete obj['age'])	// 删除某个属性 true
// console.log(Object.keys(obj))	// 获取对象中所有的属性名 [ 'name' ]

console.log(Reflect.has(obj, 'name'))	// 判断对象中是否存在某个属性 true
console.log(Reflect.deleteProperty(obj, 'age'))	// 删除某个属性 true
console.log(Reflect.ownKeys(obj))	// 获取对象中所有的属性名 [ 'name' ]

ES2015 Promise

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

ES2015 class类

// function Person (name) {
// 	this.name = name
// }

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

class Person {
	constructor (name) {
		this.name = name
	}

	say () {
		console.log(`hi, my name is ${this.name}`)
	}
}

const p = new Person('tom')
p.say()	// hi, my name is tom

ES2015 静态方法
实例方法 vs. 静态方法
实例方法通过这个类型构造的对象去调用
静态方法是通过类型本身去调用
ES2015中新增加添加静态成员的static 关键词
静态方法是挂载到类型上面的,所以说在静态方法内部this不会指向某个实例的对象而是当前的类型

class Person {
	constructor (name) {
		this.name = name
	}

	say () {
		console.log(`hi, my name is ${this.name}`)
	}

	static create (name) {
		return new Person(name)
	}
}

const tom = Person.create('tom')
tom.say()	// hi, my name is tom

ES2015 类的继承 extends

用class extends实现的继承

在这里插入代码片class Person {
	constructor (name) {
		this.name = name
	}

	say () {
		console.log(`hi, my name is ${this.name}`)
	}
}

class Student extends Person {
	constructor (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

ES2015 Set

Set 数据结构,因为可以返回数据本身所以可以链式调用

const s = new Set()

s.add(1).add(2).add(3).add(4).add(2)	// 如果在这个链式添加了之前已经存在的值那所添加的这个值会被忽略掉
console.log(s)	// Set(4) { 1, 2, 3, 4 }

s.forEach(i => console.log(i))	// 1
// 2
// 3
// 4

for (let i of s) {
	console.log(i)	// 1
// 2
// 3
// 4
}

console.log(s.size)	// 4 ,判断数组长度

console.log(s.has(100))	// 数组中是否包含某个值 false

console.log(s.delete(3))	// 删除数组某个值, true
console.log(s)	// Set(3) { 1, 2, 4 }

s.clear()	//清除当前集合中的所有成员
console.log(s)	// Set(3) { 1, 2, 4 }

// 去重
const arr = [1, 2, 1, 3, 4, 1]
// const result = new Set(arr)
// console.log(result)	// Set(4) { 1, 2, 3, 4 }

// const result = Array.from(new Set(arr))	//用 Array.from 再次转换成数组
// 或者用
const result = [...new Set(arr)]
console.log(result)	// [ 1, 2, 3, 4 ]

ES2015 Map

Map 数据结构
原来设置的 布尔值,数组,对象都被转换成了字符串,也就是说如果添加的对象的键不是字符串内部会转换成字符串(将toString的结果转为键)

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(1) { { name: 'tom' } => 90 }
console.log(m.get(tom))	// 90

console.log(m.has(tom))	// 判断是否有某个键 true
// m.delete(tom)	// 删除某个键
// console.log(m)	// Map(0) {}
m.clear()	//清空所有的键值
console.log(m)	// Map(0) {}

遍历

// 遍历
const m = new Map()

const tom = {name: 'tom'}

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

ES2015 Symbol

一种全新的原始数据类型
目前最主要的主要监视为对象添加独一无二的属性名

// // shared.js =======================================

// const cache = {}

// // a.js ============================================

// cache['foo'] = Math.random()



// // b.js ============================================

// cache['foo'] = 123

// console.log(cache)	// { foo: 123 }ean exit - waiting for changes before restart

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

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


// console.log(Symbol('foo'))	// Symbol(foo)
// console.log(Symbol('bar'))	// Symbol(bar)
// console.log(Symbol('baz'))	// Symbol(baz)

// 从ES2015 开始对象的属性名可以用 Symbol,所以从ES2015 开始对象属性名可以是 string 和 Symbol
// const obj = {}
// obj[Symbol()] = '123'
// obj[Symbol()] = '456'
// console.log(obj)	// { [Symbol()]: '123', [Symbol()]: '456' }

// const obj = {
// 	[Symbol()]: 123
// }
// console.log(obj)	// { [Symbol()]: 123 }

// a.js ===============================================

const name = Symbol()
const person = {
	[name]: 'zce',
	say () {
		console.log(this[name])
	}
}

// b.js ==============================================

// person(Symbol())
person.say()	// zce

截止到ES2019一共定义了 7 种数据类型(未来还会有 BigInt用于存放更长的数字)

Symbol 补充

每次调用 Symbol 结果得到的都是一个全新的值

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

如果在全局复用一个相同的Symbol值,可以使用全局变量的方式实现,或者使用Symbol提供的一个静态方法去实现
Symbol 的 for方法,这个方法可以接收一个字符串作为参数,相同的字符串一定会返回相同的Symbol的值,这个方法维护了全局的注册表,为字符串和Symbol的值提供了一个一一对应的关系

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

要注意的是:在这个方法内部维护的是字符串和Symbol之间的对应关系,如果传入的不是字符串,就会自动转为字符串

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

const obj = {
	[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString())	// [object XObject]

Symbol 作为对象属性名在 for in循环是无法拿到的,通过Object.keys也是无法获取到 Symbol() 的属性名的,JSON.stringify去序列化字符串 Symbol() 的属性也会被忽略掉,这些特性使得 Symbol() 特别适合作为对象的私有化属性

const obj = {
	[Symbol()]: 'symbol value',
	foo: 'normal value'
}

for (var key in obj) {
	console.log(key)	// foo
}
console.log(Object.keys(obj))	// [ 'foo' ]
console.log(JSON.stringify(obj))	// {"foo":"normal value"}

Object.getOwnPropertySymbols 获取 symbol 类型的属性名,Object.getOwnPropertySymbols 类似于 Object.keys 方法,不同的是 Object.keys 方法 只能获取到对象中的字符串属性名,而 Object.getOwnPropertySymbols 获取到的全是 symbol 类型的属性名

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

ES2015 for… of 循环

for 比较适合遍历普通的数组
for…in 循环比较适合遍历键值对
一些对象(函数式)的遍历方法:forEach
这些遍历方式都有一定的局限性
ES2015 引入了全新的 for…of 循环,这种遍历方式以后会作为遍历所有数据结构的统一方式
for…of 拿到的是数组当中的每个元素而不是下标,for…in拿到的是数组当中的每个元素的下标而不是元素

for (const item of arr) {
	console.log(item)	// 100 200 300 400
}

for…of 可以取代 forEach,

arr.forEach (item => {
	console.log(item)	// // 100 200 300 400
})

而且for…of相比于 forEach,可以使用 break 关键词随时去终止循环

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

arr.forEach 不能跳出循环,使用arr.some 返回 true, arr.every 返回 false 都可以用来跳出(终止)遍历

arr.forEach ()	// 不能跳出循环
arr.some()
arr.every()

伪数组也可以用 for…of 遍历
ES2015当中的 Set 和 Map 对象 可以用 for…of 遍历
遍历 Map 每次得到的还是数组成员,而且这个结果数组还是两个成员,这两个成员是当前被遍历的键和值

const s = new Set(['foo', 'bar'])

for (const item of s) {
	console.log(item)	// foo bar
}

const m = new Map()
m.set('foo', '123')
m.set('bar', '345')

// 遍历 Map 每次得到的还是数组成员,而且这个结果数组还是两个成员,这两个成员是当前被遍历的键和值
for (const item of m) {
	console.log(item)	// [ 'foo', '123' ] [ 'bar', '345' ]
}

// ==》

for (const [key, value] of m) {
	console.log(key, value)	// foo 123 bar 345
}

for…of无法遍历最普通的对象

const obj = {foo: 123, bar: 456}

for (const item of obj) {
	console.log(item)	// obj is not iterable,obj对象是不可被迭代的
}

ES2015 可迭代接口 Iterablae

上一节说for…of 循环是一种数据统一遍历方式,但是经过尝试发现它只能遍历数组之类的数据结构,对于普通对象如果直接遍历它会报出一个错误:obj is not iterable,obj对象是不可被迭代的
原因是:ES中能够表示有结构的数据类型越来越多,为了给各种各样的数据结构提供统一遍历方式,ES2015 提供了 Iterable 接口,可迭代的,可理解为一种规格标准,实现了一种接口,实现了 Iterable 接口就是 for…of 的前提
可以被 for…of 遍历的数组[],new Set(), new Map() 都有Symbol(Symbol.iterator),且方法都是iterator
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在迭代器当中内部应该是维护了一个数据指针,每调用一次next(), 这个指针都会往后移一位,done表示的应该是所有的数据是否被遍历完了
在迭代器当中内部应该是维护了一个数据指针,每调用一次next(), 这个指针都会往后移一位,done表示的应该是所有的数据是否被遍历完了

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 }
console.log(iterator.next())	// { value: undefined, done: true }

ES2015 实现可迭代接口 Iterable

了解 for…of 原理过后,就应该理解为什么 for…of 可以作为遍历所有数据结构的统一方式了,因为它内部去调用被遍历对象的 iterator 方法得到一个迭代器从而去遍历所有的数据,这也是 Iterable 接口约定的内容,换句话 说,对象实现了 Iterable 接口就也可以用 for…of 遍历

// // Iterable
// const obj = {
// 	[Symbol.iterator]: function () {
// 		// Iterator
// 		return {
// 			// 迭代的next方法
// 			next: function () {
// 				// IterationResult
// 				return {
// 					value: 'zce',	// 值可以是任意类型
// 					done: true	// 迭代有没有结束
// 				}
// 			}
// 		}
// 	}
// }

// Iterable
const obj = {
	store: ['foo', 'bar', 'baz'],

	[Symbol.iterator]: function () {
		// Iterator
		let index = 0
		const self = this

		return {
			// 迭代的next方法
			next: function () {
				// IterationResult
				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
}

ES2015 迭代器模式 Iterator

迭代器的作用:对外提供遍历统一接口,让外部不用再去关心数据内部的结构是怎样的

// 场景:你我协同开发一个任务清单应用

// 我的代码 =========================================
// 设计一个存放所有任务清单的对象

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
				}
			}
		}
	}
}


// 你的代码 =========================================
// 将全部任务对象罗列到页面上

// for (const item of todos.life) {
// 	console.log(item)	// 吃饭 睡觉 打豆豆
// }

// for (const item of todos.learn) {
// 	console.log(item)	// 语文 数学 外语
// }

// for (const item of todos.work) {
// 	console.log(item)	// 喝茶
// }

todos.each(function (item) {
	console.log(item)	// 吃饭 睡觉 打豆豆 语文 数学 外语 喝茶
})

console.log('------------------')

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

ES2015 生成器 Generator

避免异步编程中嵌套过深产生的问题,提供更好的异步编程解决方案
在普通函数的 function 后面 添加 * 就是 Generator 函数
生成器其实也实现了 iterator 接口(也就是迭代器接口)的协议

function * foo () {
	console.log('zce')
	return 100
}

const result = foo()
console.log(result)	// 打印一个生成器对象, Object [Generator] {}

console.log(result.next())	// zce { value: 100, done: true }
// 生成器其实也实现了 iterator

yield 类似 return,但是yield并不会结束掉函数的执行
生成器函数会自动帮我们返回一个生成器对象,调用这个对象的 next() 方法才会让这个函数的函数体开始执行,执行过程中一旦遇到了 yield 关键词,执行就会被暂停下来,yield 的值将会作为 next() 的结果返回
如果继续调用生成器对象的 next() ,函数就会从暂停的位置继续开始执行,周而复始,直到这个函数结束,next() 所返回的 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 }
console.log(generator.next())	// { value: undefined, done: true }

ES2015 生成器应用 Generator

Generator 生成器最大的特点:
避免异步编程中嵌套过深产生的问题,提供更好的异步编程解决方案

Generator 应用

// 案例1: 发号器

function * createIdMaker () {
	let id = 1
	// 不需要担心死循环,每 yield 一次就会暂停
	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


// 案例2: 使用 Generator 函数实现 iterator 方法

const todos = {
	life: ['吃饭', '睡觉', '打豆豆'],
	learn: ['语文', '数学','外语'],
	work: ['喝茶'],
	[Symbol.iterator]: function * () {
		const all = [...this.life, ...this.learn, ...this.work]
		for (const item of all) {
			yield item
		}
	}
}

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

ES2015 ES Modules

ES Modules 是语言层面的模块化标准,在模块化开发的课程中详细介绍

ES2016 概述

ES2016 正式名称 ECMAScript2016,发布于2016-6,是一个小版本,仅包括两个小功能
Array.prototype.includes

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

指数运算符

console.log(Math.pow(2, 10))	// 1024
// ==> es2016
console.log(2 ** 10)	// 1024

ES2017 概述

ES2017 正式名称 ECMAScript 2017,发布于 2017-6
也是发布的一个小版本,有以下几个新增功能

  1. Array.prototype.includes
const arr = ['foo', 1, NaN, false]
console.log(arr.indexOf('foo'))	// 0
console.log(arr.indexOf('bar'))	// -1
console.log(arr.indexOf(NaN))	// -1
console.log(arr.includes('foo'))	// true
console.log(arr.includes(NaN))	// true
  1. 指数运算符
console.log(Math.pow(2, 10))	// 1024
// ==> es2016
console.log(2 ** 10)	// 1024
// ECMAScript 2017
  1. Object.values
const obj = {
	foo: 'value1',
	bar: 'value2'
}
console.log(Object.values(obj))	// [ 'value1', 'value2' ]
  1. 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
}

console.log(new Map(Object.entries(obj)))	// Map(2) { 'foo' => 'value1', 'bar' => 'value2' }
  1. Object.getOwnPropertyDescriptors, 主要是配合 ES5 新增的这种 getter, setter 使用
const p1 = {
	firstName: 'Lei',
	lastName: 'Wang',
	get fullName () {
		return this.firstName + ' ' + this.lastName
	}
}

console.log(p1.fullName)	// Lei Wang

// const p2 = Object.assign({}, p1)
// p2.firstName = 'zce'
// console.log(p2)	// { firstName: 'zce', lastName: 'Wang', fullName: 'Lei Wang' }
// // fullName 不变,出现这样的结果是因为 Object.assign 把 p1.get() 方法当作普通属性复制了,所以才会出现这种情况

const descriptors = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({}, descriptors)
p2.firstName = 'zce'
console.log(p2.fullName)	// zce Wang
// 主要是配合 ES5 新增的这种 getter, setter 使用
  1. 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, count)	// 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
}
  1. 在函数参数中添加尾逗号, 这样有两个好处,一、重新排列这个数组当中的顺序,尾部有逗号调整起来就非常容易,二、 修改一个数组中元素个数,如添加一个,这时候只需要再新建一行就可以了
function foo (
	bar,
) {

}

const arr = [
100,
200,
300,
]
  1. 最重要的一点 ES2017 增加了 Async/Await,彻底解决了异步编程中函数回调过深产生的问题,使得代码更加简洁易读, 本质上使用 promise 的语法糖
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值