Set数据结构
Set与Array是十分相似的,不过Set不允许值重复
const s = new Set()
s.add(1).add(2).add(3).add(4).add(1) // add返回的还是set类型,所以可链式调用
console.log(s) // => Set [1,2,3,4] 重复的1会被忽略掉
// 依然可以使用forEach等数组循环方法
s.forEach(i => console.log(i))
// 一些其他的set方法,某些与Reflect较相似
console.log(s.size)
console.log(s.has(100))
console.log(s.delete(3))
s.clear() // 清除全部内容
// 应用场景:数组去重
const arr = [1, 2, 1, 3, 4, 1]
// Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。
// const result = Array.from(new Set(arr))
const result = [...new Set(arr)] // 这两种方法都可
console.log(result)
Map数据结构
Map与对象是十分相似的,本质都是健值对集合,Map类型可以映射两个任意类型的数据类型
const obj = {}
obj[true] = 'value'
obj[123] = 'value'
obj[{ a: 1 }] = 'value'
console.log(Object.keys(obj)) // => ["123", "true", "[object Object]"]
// obj的key都会被强制toString转化成string形式
// Map类型可以映射两个任意类型的数据类型
const m = new Map()
const tom = { name: 'tom' }
m.set(tom, 90)
console.log(m) // => { { name: 'tom' } => 90}
// 如果m[tom] 还是会"[object Object]"
console.log(m.get(tom))
// m.has()
// m.delete()
// m.clear()
// 依然可以遍历
m.forEach((value, key) => {
console.log(value, key)
})
Symbol
一种全新的数据类型
// 场景:扩展对象,属性名冲突问题
// main.js ====================================
const cache = {};
// a.js =========================================
cache['foo'] = Math.random();
// b.js =========================================
cache['foo'] = '123';
console.log(cache); // => {foo: 123}
比如在main 中有一个缓存对象,a.js与b.js都会向其中添加属性,较容易就造成覆盖,从前的解决办法就是约定a文件就以 a_foo开头,但是人为约定并不一定都遵守,所以出现了Symbol
let a = Symbol('foo') // 声明一个Symbol类型,foo只是给他加的名字,方便辨认
console.log(Symbol('foo') === Symbol('foo')) // 每次通过Symbol创建的值一定是唯一的,不会重复
// obj除了字符串外,还有Symbol类型
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // => {Symbol(): "123", Symbol(): "456"}
Symbol 模拟实现私有成员
// a.js ======================================
const name = Symbol()
const person = {
[name]: 'zce',
say () {
console.log(this[name])
}
}
// 只对外暴露 person
// b.js =======================================
// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say()
Symbol最主要的作用就是为对象添加一个独一无二的属性名
如果想在全局复用一个相同的Symbol
// 全局注册表
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
const s3 = Symbol('foo')
console.log(s1 === s2) // => true
console.log(s2 === s3)
// 通过Symbol.for 可以复用相同的Symbol
const obj1 = {}
console.log(obj1.toString()) // =>[object Object]
const obj2 = {
[Symbol.toStringTag]: 'XXObject'
};
console.log(obj2.toString()) // =>[object XXObject]
// 这种Symbol,在后面我们为对象实现迭代器中会经常使用
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
for (var key in obj) {
console.log(key) // 通过这两种方式都无法拿到Symbol类型的属性名
}
console.log(Object.keys(obj)) // 通过这两种方式都无法拿到Symbol类型的属性名
console.log(JSON.stringify(obj)) // Symbol也会被忽略掉
所以Symbol很适合用来实现私有属性名
// 当然也有方法获取Symbol属性
console.log(Object.getOwnPropertySymbols(obj))
// => [Symbol()]
for...of循环
for 适合遍历普通数组(array,set)
for in 适合遍历健值对(object,map)
还有一些对象方法,例如forEach
这些遍历方式都有些局限性,于是ES2015引入了for...of循环,它以后会成为遍历所有数据结构的统一方式
const arr = [100, 200]
for (const item of arr) {
console.log(item) // => 100 200 不同于for in拿到下标
}
// for...of 循环可以替代 数组对象的 forEach 方法
arr.forEach(item => {
console.log(item)
})
for (const item of arr) {
console.log(item)
if (item > 100) {
break
}
}
// forEach 无法跳出循环,必须使用 some 或者 every 方法
arr.forEach() // 不能跳出循环
// 遍历 Set 与遍历数组相同
const s = new Set(['foo', 'bar'])
for (const item of s) {
console.log(item) // =>'foo' 'bar'
}
// 遍历 Map 可以配合数组结构语法,直接获取键值
const m = new Map()
m.set('foo', '123')
m.set('bar', '345')
for (const [key, value] of m) {
console.log(key, value)
}
// 普通对象不能被直接 for...of 遍历
const obj = { foo: 123, bar: 456 }
for (const item of obj) { // => obj is not iterable (obj对象不可被迭代)
console.log(item)
}
上面明明说了所有数据结构,那普通对象为啥会报错,所不可被迭代呢?
可迭代接口
为了给各种各样的数据结构,统一遍历方式,ES2015提供了一个Iterable接口。
接口,可以把它理解为一种规格标准,比如任意一种数据类型都有toString方法,那就是因为他们都实现了统一的规格标准,那在编程中,更专业的说法就是,他们都实现了统一的接口。
Symbol.Iterator是Symbol类型提供的一个常量
实现Iterable接口,就是for...of的前提
所有可以直接被for...of遍历的数据类型,它都必须要实现Symbol.Iterator的接口,也就是在他的原型对象上必须要挂载一个Iterator,Iterator对象上有一个next方法,调用next方法,就可以对数据内部所有数据的遍历,这也就是for...of循环的工作原理
// 迭代器(Iterator)
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}
实现可迭代接口
所以对象上也实现了Iterator接口,那么就可以遍历
const obj = {
[Symbol.iterator]: function () { // 用计算属性给obj挂一个Symbol.iterator,它是一个函数
return { // 这个函数返回一个对象(iterator)
next: function () { // 该对象有一个next方法
return { // 调用这个方法,会返回 {value: xxx, done: xxx} 格式的对象(iterationResult)
value: 'zh',
done: true // 代表迭代是否结束
}
}
}
}
}
// 试试迭代store
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
}
}
}
}
for (const item of obj) {
console.log('循环体', item)
}
迭代器模式
迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
// 迭代器设计模式
// 场景:你我协同开发一个任务清单应用
// 我的代码 ===============================
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
// 提供统一遍历访问接口
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback(item)
}
},
// 提供迭代器(ES2015 统一遍历访问接口)
[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)
}
生成器
避免异步编程中回调嵌套过深,提供更好的异步编程方案
// Generator
function * foo () { // 加*代表生成器函数
console.log('zh')
return 100
}
const result = foo()
console.log(result) // =>foo {<suspended>} foo并没有执行
console.log(result.next()) // => 调用他的next方法才执行 输出{value: 100, done: true}
惰性执行,抽一下动一下
function * foo () {
console.log('1111')
yield 100 // 类似return
console.log('2222')
yield 200
console.log('3333')
yield 300
}
const generator = foo()
console.log(generator.next()) // 第一次调用,函数体开始执行,遇到第一个 yield 暂停
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
console.log(generator.next()) // 。。。
console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到 undefined
Generator 应用
// 案例1:发号器(自增)
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value)
console.log(idMaker.next().value)
// 案例2:使用 Generator 函数实现 iterator 方法
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for (const item of all) { // 这里有个疑问,为什么没有next
yield item
}
}
}
for (const item of todos) {
console.log(item)
}
ES Modules
语言层面的模块化标准 (import、export)
ES2016
// ECMAScript 2016
// Array.prototype.includes -----------------------------------
const arr = ['foo', 1, NaN, false]
// 找到返回元素下标
console.log(arr.indexOf('foo'))
// 找不到返回 -1
console.log(arr.indexOf('bar'))
// 无法找到数组中的 NaN
console.log(arr.indexOf(NaN))
// 直接返回是否存在指定元素
console.log(arr.includes('foo'))
// 能够查找 NaN
console.log(arr.includes(NaN))
// 指数运算符 ---------------------------------------------------
// console.log(Math.pow(2, 10))
console.log(2 ** 10)
ES2017
// ECMAScript 2017
const obj = {
foo: 'value1',
bar: 'value2'
}
// Object.values -----------------------------------------------------------
// keys返回 健的数组,values返回 值的数组
console.log(Object.values(obj)) // => ["value1", "value2"]
// Object.entries ----------------------------------------------------------
// entries返回 健值对的数组
console.log(Object.entries(obj))
// 这样就可以直接使用for of遍历普通对象
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
// 普通对象转化成Map类型对象
console.log(new Map(Object.entries(obj)))
// 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)
// }
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,2,3,4,] 最后的逗号在代码层面不会报错
// function foo (
// bar,
// baz,
// ) { }
// Async Await ---------------------------------------------------------------