Hello
clarencec / javascript-es6plus-learn Goto Github PK
View Code? Open in Web Editor NEWJavaScript ES6 知识点与学习笔记
JavaScript ES6 知识点与学习笔记
Map 也是在 ES6 里面新的数组类型。在 ES6 以前 Object 本质上都是健值对的集合,在键值定义上只能使用字符串做键值。其它类型作为键值都会自动转为字符串。
const data = {}
const element = document.getElementById('myDiv')
data[element] = 'metadata'
data['[object HTMLDivElement]'] // 'metadata'
而 Map 也是类似于对象的键值对集合,但是键值有类型不只限于字符串,其它类型都可以当作键。
const m = new Map()
const o = {p: 'Hello World'}
m.set(o, 'content')
console.log(m) // Map(1) {{…} => "content"}
m.has(o) // true
m.delete(o) // true
m.has(0) // false
构建 Map 时,可以接受一个数组作为参数,或者任何具有 Iterator 接口的数据结构。在构建时传入的数组必须是键值对,不然会报错。
const mapArr = new Map([3, 6])
// Uncaught TypeError: Iterator value 1 is not an entry object
const map = new Map([
['name', 'cccc'],
['title', 'Author']
])
map.has('name') // true
map.get('name') // 'cccc'
// 同一个键值会覆盖
map.set('name', 'abc')
map.set('name', 'clarence')
map.get('name') // 'clarence'
map.get('num') // undefined
// 键值对象是通过内存地址来表示唯一的。
map.set(['a'], 666)
map.get(['a']) // undefined 看似相同对象其实是不同内存地址的。
对于基础类型的值,只要两个值严格相等 Map 就会视为一个同一个键。
const map = new Map()
map.set(-0, 123)
map.get(+0) // 123
map.set(undefined, 3)
map.set(null, 4)
console.log(map) // Map(3) {0 => "abc", undefined => 3, null => 4}
size
Map 集合的长度。set(key, value)
添加 Map 集合的值。可以链式调用,因为 set 方法返回的是 Map 对象。const m = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c')
get(key)
获取 Map 集合里面的 。当前没有键值,即会返回 undefined.has(key)
判断当前 Map 集合里面有没有该键,返回 Boolean。delete(key)
删除 Map 键的键值对。clear()
删除全部 Map 的成员,没有返回值。keys()
: 返回键名的可遍历器。values()
: 返回键值的可遍历器。entries()
: 返回Map键值对的可遍历器。forEach(function, this)
: 遍历Map所有成员,和 Array.prototype.forEach 很像。forEach
的第二个参数可以用来绑定外部对象。const map = new Map([
['F', 'no'],
['T', 'Yes'],
['name', 'cc']
])
map.keys() // MapIterator {"F", "T", "name"}
for(let key of map.keys()) {
console.log(key)
}
// 'F'
// 'T'
// 'name'
map.values() // MapIterator {"no", "Yes", "cc"}
for(let value of map.values()) {
console.log(value)
}
// 'no'
// 'Yes'
// 'cc'
map.entries() // MapIterator {"F" => "no", "T" => "Yes", "name" => "cc"}
for(let item of map.entries()) {
console.log(item)
}
// ["F", "no"]
// ["T", "Yes"]
// ["name", "cc"]
// 等同于
for (let item of map) {
console.log(item)
}
map.forEach((value, key) => {
console.log(value, key)
})
// no F
// Yes T
// cc name
有很多时候,Set 和 Map 集合操作遍历和过滤的方法不够,比如没有 map
和filter
方法,可以通过把 Set 和 Map 转换成 Array,再操作。
const map1 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c')
const map3 = new Map(
[...map1].filter(([k, v]) => k < 3)
)
console.log(map3) // Map(2) {1 => "a", 2 => "b"}
WeakMap 的作用和 Map 有相同的键值集合,但它们还是有不一样的地方的。
const map = new WeakMap()
map.set(1, 2) //Uncaught TypeError: Invalid value used as weak map key
map.set(Symbol(), 2) // Uncaught TypeError: Invalid value used as weak map key
map.set(null, 2) // Uncaught TypeError: Invalid value used as weak map key
因为 WeakMap 也是没办法知道垃圾回收机制什么时候回收,所以也没办法遍历操作的,只有简单的四个方法。
使用场景主要是内存回收机制的时候减少手动清空的机会,减少内存泄漏的风险。
在ES6以前,js里面只支持 Array 数组,数据保存成数组都是以 Array 形式,在 ES6后,多了两类类型 Set 和 Map。
Set 的数据结构类似于数组,但是成员的值都是唯一的,没有重复的值。
const s = new Set()
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x))
for (let i of s) { //能用 for ...of 遍历 set 数组。
console.log(i)
}
Set
结构里面添加成员,是通过 add()
方法向里面添加的,并且 Set 结构不会添加重复的值。
Set
函数可以接受一个数组作为参数新建对象。
const set = new Set([1, 2, 3, 4, 4])
[...set] // [1, 2, 3, 4]
set.size // 4
const set = new Set(document.querySelectorAll('div'))
Set 数据数组长度是通过 set.size
来获取,和 Array.length
不一样。
可以通过 Set 数据类型进行数组去重。
[...new Set(array)]
[...new Set('abaccc')].join('') // 'abc'
Set
数据类型内部的值,不会发生类型转换。其实 Set
是一个构造函数,
Set.prototype.constructor
: 构造函数,指回 Set
函数。Set.prototype.size
: 返回 Set
实例的成员总数。add(value)
: 添加值到 Set 数组里面。delete(value)
: 删除某个值,返回Boolean值,表示删除是否成功。has(value)
: 返回一个布尔 值,表示该值是否为 Set
的成员。clear()
: 清除所有成员,没有返回值。Set
可以通过 Array.from
把数据转为数组。const items = new Set(['a', 4, 78])
const array = Array.from(new Set(array))
keys()
: 返回键名的遍历器values()
: 返回键值的遍历器entries()
: 返回键值对的遍历器forEach()
: 使用回调函数遍历每个成员由于 Set
结构没有键名,只有键值,所以 keys
方法和 values
方法的行为完全一致,都是只返回键值。
let set = new Set(['red', 'green', 'blue'])
for (let item of set.keys()) {
console.log(item) // red green blue
}
for (let item of set.values()) {
console.log(item) // red green blue
}
for (let item of set.entries()) {
console.log(item) //['red', 'red'] ['green', 'green'] ['blue', 'blue']
}
set.forEach((value, key) => console.log(key + ':' + value))
// red:red
// green:green
// blue:blue
WeakSet 结构和 Set 很相似,但是他们还是有区别的
构造 WeakSet 和 Set 一样,需要通过 new 来造建函数,并且可以接受一个数组或者类似数组的对象作为参数。 任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数, 并且添加进 WeakSet 的成员也只能唯一,没有重复的对象成员,根据对象地址指针来判断。
const ws = new WeakSet()
const a = [[1, 2], {name: 'c', age: 28}]
const ws = new WeakSet(a) // 直接传数组作为参数,会自动把数组里面的对象转为 WeakSet 的实员对象。
const ws1 = new WeakSet([2,3]) // 如果数组里面是基础类型不是对象,一样会报错 Uncaught TypeError: Invalid value used in weak set
但是 WeakSet 没有 size 属性,没办法遍历它的成员。
const ws = new WeakSet()
const obj = {}
const foo = {}
ws.add(window)
ws.add(obj)
ws.has(window) // true
ws.has(foo) // false 判断是根据内存地址来判断的。
一个数据结构只要部署了 Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用 for...of
循环遍历它的成员了。
const arr = ['red', 'green', 'blue']
for (let v of arr) {
console.log(v)
}
// red green blue
const obj = {}
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr)
for(let v of obj) {
console.log(v)
}
// red green blue
把 arr
数据的 Symbol.iterator
接口绑定arr 对象后,重新接到 obj
,返回一样是 arr
的数据。
for..in
和 for...of
的对比for...in
是遍历集合的键名 keys 值for...of
是遍历集合的键值 values 值var arr = ['a', 'b', 'c', 'd']
for (let a in arr) {
console.log(a) // 0 1 2 3
}
for (let a of arr) {
console.log(a) // a b c d
}
for...of
只会遍历集合的具有数字索引的属性,只能遍历带有 Interator 接口的集合。for...in
会遍历全部可以遍历的属性,和 for...in
能遍历对象,和prototype原型链上面的属性也能遍历到。
let arr = [3, 5, 7]
arr.foo = 'hello'
for (let i in arr) {
console.log(i) // '0', '1', '2', 'foo'
}
for (let i of arr) {
console.log(i) // 3 5 7
}
在ES6 中引入了 Sybmol 对象,表未独一无二的值,他是新的基础数据类型,和undefined
、null
、Boolean
、String
、Number
、Object
成为第七种基础类型,属性名现在可以有两种类型:字符串、和 Symbol
,对象属性名字唯一能有效防止属性名的冲突。
Symbol
创建 Symbol
时是调用 Symbol
函数生成,不需要 new
,一般传入字符串以表示对生成 symbol 对象的描述。如果相同的字符串生成的Symbol 也不会相同,因为Symbol 是的值是唯一的。
let s = Symbol()
typeof s // "symbol"
let s1 = Symbol('s')
let s2 = Symbol('s')
s1 === s2 // false
如果创建 Symbol
函数时,传的是一个对象,就会先调用该对象的 toString
方法转为字符串,再生成 Symbol 的值
const obj = {
toString() {
console.log('toString')
return 'abc'
},
valueOf() {
console.log('valueOf')
return '123'
}
}
const sym = Symbol(obj)
// toString
console.log(sym) // Symbol(abc)
Symbol
转换成
symbol
不能隐式转换,只能显示转换let sym = Symbol('My symbol')
"your symbol is " + sym
`your symbol is ${sym}` // TypeError
String() // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
toString
方法调用可以使用转换后类型错误。Number(sym) // TypeError
sym + 2 // TypeError
let sym = Symbol()
let sym1 = Symbol('text')
Boolean(sym) // true
!sym // false
要注意symbol
作为属性值名,访问或取值是都不能用点运算符,点运算符当作变量操作。
let mySymbol = Symbol('a')
let a = {}
a[mySymbol] = 'Hello' // method1
let a = { // method2
[mySymbol]: 'Hello'
}
Object.defineProperty(a, mySymbol, { value: 'Hello' }) // method3
console.log(a[mySymbol]) // Hello
console.log(a.mySymbol) // undefined
// symbol 作为函数名
let s = Symbol()
let obj = {
[s]: function (arg) {
console.log(arg)
}
}
// or
let obj = {
[s](arg) {
console.log(arg)
}
}
obj[s](123) // 123
const log = {}
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
}
console.log(log.levels.DEBUG, 'debug message')
console.log(log.levels.INFO, 'info message')
正常遍历访问属性性的方法对 symbol
都不太适用。可以使用新提供的 Object.getOwnPropertySymbols
来返回 Symbol
数组。
const obj = {}
let a = Symbol('a')
let b = Symbol('b')
obj[a] = 'Hello'
obj[b] = 'World'
console.log(obj) // {Symbol(a): "Hello", Symbol(b): "World"}
// for...in 不执行
for(prop in obj) {
console.log(prop)
}
// for...of
for(prop of obj) {
console.log(prop) // TypeError: obj is not iterable
}
// Object.keys 不会自动转换内部属性名为字符串
Object.keys(obj) // []
// JSON.stringify
JSON.stringify(obj) // {}
// Object.getOwnPropertyNames
Object.getOwnPropertyNames(obj) // []
// Object.getOwnPropertySymbols
Object.getOwnPropertySymbols(obj) // [Symbol(a), Symbol(b)]
一个新的API,Reflect.ownKeys
可以返回所有类型的键名。
let obj = {
[Symbol('key')]: 1,
enum: 2,
nonEnum: 3
}
console.log(Reflect.ownKeys(obj)) // ["enum", "nonEnum", "Symbol(my_key)"]
symbol
属性不会被常规方法遍历,定义对象非私有方法。let size = Symbol('size')
class Collection {
constructor() {
this[size] = 0
}
add(item) {
this[this[size]] = item
this[size]++
}
stateic sizeOf(instance) {
return instance[size]
}
}
let x = new Collection()
Collection.sizeOf(x) // 0
Object.keys(x) // ['0']
Object.getOwnPropertyNames(x) // ['0']
Object.getOwnPropertySymbols(x) // [Symbol(size)]
Symbol.for()
和 Symbol.keyFor()
和 Symbol()
的区别Symbol.for()
接收一个字符串参数值,如果已经存在这个字符串的 Symbol 值,则登记该值,并返回相同的 Symbol 值,否则新建一个 该字符串的 Symbol
值.而 Symbol()
每次新建都会返回不同的 Symbol 值,就算 字符串一样.let s1 = Symbol.for('foo') // 创建 Symbol('foo')
let s2 = Symbol.for('foo') // 获取 Symbol('foo')
s1 === s2 // true
let s3 = Symbol('foo')
let s4 = Symbol('foo')
s3 === s4 // false
Symbol.keyFor()
检查 Symbol.for
登记后的值。let s1 = Symbol.for('SymbolText')
Symbol.keyFor(s1) // SymbolText
let s2 = Symbol('SymbolText1')
Symbol.keyFor(s2) // undefined
ES6 后暴露了11个语言内部的Symbol
使用方法出来.
Symbol.hasInstance
是一个对象内部的属性,指向一个内部的使用方法,当其它对象调用 instanceof
运算符的时候,就会调用其方法.class Even {
static [Symbol.hasInstance](obj) {
return Number(obj) % 2 === 0 // 判断是否偶数
}
}
1 instanceof Even // false
2 instanceof Even // true
12345 instanceof Even // false
Symbol.isConcatSpreadable
是一个对象内的属性设置开关,是一个 Boolean 值,如果设置为 false 就不会再在Array.prototype.concat()
方法时把数组展开。let arr1 = ['c', 'd']
let arr = ['a', 'b']
arr.concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
arr1[Symbol.isConcatSpreadable] = false
arr.concat(arr1, 'e') // ['a','b',['c','d'],'e']
arr1[Symbol.isConcatSpreadable] = true
arr.concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
对像的 Symbol.species
属性,指向一个构造函数,创建衍生时会使用到该属性。在不修改 Symbol.species
的情况下等同于下面的操作。
class T1 extends Array {
static get [Symbol.species]() {
return this
}
}
自定义修改 Symbol.species
操作:
class T1 extends Array {}
class T2 extends Array {
static get [Symbol.species] () {
return Array
}
}
const ObjT1 = new T1()
const ObjT2 = new T2()
ObjT1 instanceof Array // false
ObjT2 instanceof Array // true
Symbol.match
属性,指向一个函数. String.match()
一般用来匹配字符串正则表达式。class MyMatcher (
[Symbol.match](string) {
return 'hello world'.indexOf(string)
}
)
'e'.match(new MyMatcher()) // 1
Symbol.replace
属性,指向 String.prototype.replace
方法。const x = {}
x[Symbol.replace] = (...s) => console.log(s)
'Hello'.replace(x, 'World') // ['Hello', 'World']
Symbol.search
属性指向 String.prototype.search
方法。用来查找正则表达式的位置。String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this) 的调用
class MySearch {
constructor(value) {
this.value = value
}
[Symbol.search] (string) {
return string.indexOf(this.value)
}
}
'foorbar'.search(new MySearch('bar')) // 4
对象的 Symbol.split
属性,指向方法 String.prototype.split
。
String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)
Symbol.iterator
属性,指向该对象的默认遍历器方法,对对象进行 for...of
循环时,会调用 Symbol.iterator
方法。比较重要可以通过 Symbol.iterator
重定义遍历方法。class Collection {
*[Symbol.iterator] () {
let i = 0
while(this[i] ! == undefined) {
yield this[i]
++i
}
}
}
let myCollection = new Collection()
myCollection[0] = 1
myCollection[1] = 2
myCollection[2] = 'text'
for (let value of myCollection) {
console.log(value) // 1 2 hello
}
Symbol.toPrimitive
属性,也是指向一个方法。该对象被转为原始类型的值时会调用这个方法。Symbol.toPrimitive
自定义方法时,会传一个字符串参数 hint
进去,表示当前运行的模式。有以下三种模式:let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123
case 'string':
return 'str'
case 'default':
return 'default'
default:
throw new Error()
}
}
}
2 * obj // 246
'Hello' + obj // Hellodefault
obj == 'default' // true
obj === 'default' // false
String(obj) // 'str'
Symbol.toStringTag
属性,指向一个方法,调用 Object.prototype.toString
方法时, 可以重新定义返回的字符串中的值.class Collection {
get [Symbol.toStringTag] () {
return 'xxx'
}
}
let x = new Collection()
Object.prototype.toString.call(x) // [Object xxx]
Symbol.unscopables
属性,指向一个对象,该对象指定了 with
关键字,哪些属性会被 with
环境排除。 with
是比较少使用的 JavaScript 关键词,用为将某个对象添加到作用域连的顶部,然后在这作用域下面运行。var a, x, y
var r = 10
with (Math) {
a = PI * r * r
x = r * cos(PI)
y = r * sin(PI / 2)
}
class MyClass {
foo() {return 1}
}
var foo = function() {return 2}
with (MyClass.prototype) {
foo() // 1
}
// 通过 unscopable 修改当前作用域,使 `with` 语法块不会在当前作用域寻找`foo` 属性,指向外部 `foo` 。
class MyClass {
foo() { return 1 }
get [Symbol.unscopables] () {
return { foo: true }
}
}
var foo = function() { return 2 }
with (MyClass.prototype) {
foo() // 2
}
async 函数是什么?可以理解为他就是 Generator 函数的语法糖。
下面分别使用 Generator
和 Async
来异步读取两个文件。这其实发现 Async 和 Generator 很像。
const fs = require('fs')
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, funcition(error, data) {
if (error) return reject (error)
resolve(data)
})
})
}
// 使用 Generator 函数
const gen = function* () {
const f1 = yield readFile('/etc/fstab')
const f2 = yield readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
gen.next()
gen.next()
// 使用 Async
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab')
const f2 = await readFile('/etc/shells')
console.log(f1.toString())
console.log(f2.toString())
}
asyncReadFile()
需然 async
和 Generator
很像,但是还是有不同的。
async
函数不需要执行器,直接调用可以会自动执行最后结果。不像 Generator
函数需要调用 next 方法。async
表示函数里有异步操作, await
表示紧跟后面的表达式需求等待结果。Generator
yield 命令后面只能 是 Thunk 函数或者 Promise 对象,async
的 await 后面,可以接 Promise 对象和各原始类型的值(原始类型的值会自动转换为 Promise.resolved 形式)Generator
返回的是 Iterator 集合需要手动遍历,async
返回的是 Promise 对象。async 函数会返回一个 Promise 对象,可以使用 then
方法 添加回调函数。当函数执行的时候,遇到 await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async function timeout(ms) {
await new Promise ((resolve) => {
setTimeout(resolve, ms)
})
}
async function asyncPrint(value, ms) {
await timeout(ms)
console.log(value)
}
asyncPrint('hello world', 300)
async 函数运行的过程中会返回一个 Promise 对象, 如果 async 函数内部有 return
的值,会成为 then
函数的参数。如果 async 函数里面抛错误,会被外层的 catch
接受到。async 函数内部的状态必须等到所有 await 命令后 Promise 对象执行完,才会发生async 函数状态的改变,除非遇到 return 或者错误。
async function f() {
throw new Error('Error Msg')
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
如果 await
后面不是一个 Promise 对象,而是基础类型,则会直接返回基础类型。
如果 await
后面是一个 thenable
对象[即定义了 then 方法的对象], 则会等同于 Promise 对象,调用对象里面的 then
方法。
class Sleep {
constructor(timeout) {
this.timeout = timeout
}
then(resolve, reject) {
const startTime = Date.now()
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
)
}
}
(async() => {
const actualTime = await new Sleep(1000)
console.log(actualTime)
})()
在 async 错误处理中,如果 async 函数里面其中一个 await 异步操作错误返回 Promise.reject,整个 async 返回的 Promise 都会返回 reject,所以如果其中一个 await 错误就不能继续执行后面的 await 了。所以如果不想整个 async 函数都处于 reject 状态,需要处理内部 await 的错误处理.
async function main() {
try {
const val1 = await firstStep()
}
catch (err) {
console.error(err)
}
const val2 = await secondStep(val1)
const val3 = await thirdStep(val1, val2)
console.log('Final: ', val3)
}
Promise.catch
来处理 await 错误.async function f() {
await Promise.reject('ErrorMsg')
.catch(e => console.log(e))
return await Promise.resolve('hello world')
}
f().then(v => console.log(v))
// ErrorMsg
// hello world
await
中断时整个 async 也会中断,起不到执行下一个 async
效果。async function main() {
try {
const val1 = await firstStep()
const val2 = await secondStep(val1)
const val3 = await thirdStep(val1, val2)
}
catch (err) {
console.error(err)
}
console.log('Final: ', val3)
}
1. async 函数内部每个 await
最好每个后面都捕获错误对象。
async function myFunction() {
try {
await somethingThatReturnAPromise()
} catch (err) {
console.log(err)
}
}
// or
async function myFunction() {
await somethingThatReturnsAPromise()
.catch((err) => {
console.log(err)
})
}
2. 多个 await
命令后面的异步操作,如果不存在继发关系,最好同时触发异步。
let [foo, bar] = await Promise.all([getFoo(), getBar()])
// or 提前运行 Promise 函数
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise
3. await 只能用在 async 函数中,用在其它普通函数就会报错。
4. async 函数可以保留运行堆栈。
如果b或c函数报错, 错误堆栈将包括 a(),a是暂停执行, 上下文环境都保存着。
const a = async () => {
await b()
c()
}
有很多时候总觉得 async 被滥用,把 async 当成是 Promise 的使用,这其实是对 async 函数的不理解。因为主要是 async 是 Generator
的语法糖,是用来管理批量执行异步函数的。主要是以下三种场景。
async
await
写法会更为简洁。function getUrls() {
let url3
getUrl()
.then((url1) => {
getUrl(url1)
.then(url2 => {
getUrl(url2)
.then(url3 => {
url3 = url3
})
})
})
return url3
}
async function getUrls() {
let url1 = await getUrl()
let url2 = await getUrl(url1)
let url3 = await getUrl(url2)
return url3
}
Promise.all
外还可以使用 async
await
async function getABC() {
let results = await Promise.all([ getValueA, getValueB, getValueC])
return results
}
async function getABC() {
let valueA = getValueA()
let valueB = getValueB()
let valueC = getValueC()
let a = await valueA
let b = await valueB
let c = await valueC
return [a, b, c]
}
继发和批量执行同时在执行也是没问题的,使用 async
可以加构自动化。
TC39 提案分为以下几个阶段:
遍历器 (Iterator) 的主要想法是提供一种接口或者机制为 JavaScript,统一处理各种不同的数据结构。在 ES6 后,JavaScript 有共有了四种集合 Array (数组) 、对象 (Object)、Map 、Set都是 可以使用 Iterator 形式访问的,ES6 新提供的 for...of
可以遍历有 Iterator 机制的所有集合。
下面模拟一下 Iterator 实现,返回一个指针对象,对象有一个方法 next
,每次调用 next
,都会返回当前成员的信息,返回的信息有两个属性 value
当前成员的值, done
一个标志遍历是否结束的值是一个 Boolean 类型的。
var it = makeIterator(['a', 'b'])
it.next() // { value: 'a', done: false }
it.next() // { value: 'b', done: false}
it.next() // { value: undefined, done: true}
function makeIterator(array) {
var nextIndex = 0
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true}
}
}
}
// 对于遍历器来说 `done: false` 和 `value: undefined` 属性都是可以省略的,所以上面的方法可以简写成下面的形式
function makeIterator(array) {
let nextIndex = 0
return {
next: function() {
return nextIndex < array.length ?
{ value: array[nextIndex++] } :
{ done: true }
}
}
}
可以看出其实上面的数据是通过函数内部方法对应集合出来的,与集合数据一一对应,其实也可以直接从 Iterator 接口模拟出自定义的数据结构,把集合和遍历出来的结果分离。
var it = idMarker()
it.next().value // 0
it.next().value // 1
it.next().value // 2
function idMaker() {
var index = 0
return {
next: function () {
return { value: index++, done: index > 10? true : false}
}
}
}
在ES6 中,暴露了一部分Symbol 属性方法,Symbol.iterator
也是其中之一。当遍历一个数据结构时,只要配置有 Symbol.iterator
属性,并且返回的对象具有 next
方法,调用 next
方法,会 返回具有 value
和 done
两个属性的对象特征,就被认为是可遍历的。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
}
}
}
}
}
原生具备 Iterator 接口的数据结构有下面这些:
才发现原来 Object 对象并不是原生拥有可遍历接口的,原因是对象保存的属性不是线性的,任何非线性的数据结构,部署遍历器接口,等于部署一种线性转换。
下面尝试把 Object 对象添加 Iterator 接口
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined}
}
}
var rangeObject = new RangeIterator(0, 5)
// 如果 value 不给变量定义词 let 或 var 会,变成全局变量
for(value of rangeObject) {
console.log(value); // 0 1 2 3 4
}
Symbol.iterator
方法。let set = new Set().add('a').add('b').add('c')
let [x, y] = set // x='a'; y='b'
let [first, ...rest] = set
// first='a'; rest=['b','c']
var str = 'hello'
[...str] // ['h', 'e', 'l', 'l', 'o']
let arr = ['b', 'c']
['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
let generator = function* () {
yield 1;
yield* [2, 3, 4];
yield 5;
}
var iterator = generator()
iterator.next() // { value: 1, done: false}
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false}
iterator.next() // { value: 4, done: false}
iterator.next() // { value: 5, done: false}
iterator.next() // { value: undefined, done: true}
4.其它任何接受数组作为参数和场合,都可以使用 Iterator 接口.
var someString = "Hi"
[...someString] // ["H", "i"]
var iterator = someString[Symbol.iterator]
iterator.next() // { value: "H", done: false }
iterator.next() // { value: "i", done: false}
iterator.next() // { value: "undefined", done: true}
可以利用 Symbol.iterator
方法改写,遍历器的行为。
var str = new String('hi')
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false
return { value: "bye", done: false }
} else {
return { done: true }
}
},
_first: true,
}
}
[...str] // ["bye"]
str // 'hi'
return
一般用来处理遍历时出现的错误。
throw
一般用来配合 Generator 函数使用。一般遍历器使用不到这个方法。
像下面这个异步读取文件的函数,在遍历器对象配置了 return
函数,当for...of
遇到出错或者 break
时,会立刻清理或释放 file 资源。
function readLinesSync(file) {
return {
[Symbol.iterator]() {
next() {
return { done: false }
},
return () {
file.close()
return { done: true }
}
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.