Symbol

使用方法

自ES2015起,symbol成为了一种新的原生类型,就像number和string一样

symbol类型的值通过Symbol构造函数创建,可以传递参数(string|number|undefined)作为唯一标识

两个symbol即使构造时传递的参数相同,它们也是不同的(主要是内存地址不同),因为symbol就是唯一标识

如果使用Symbol.for()创建symbol,它就会查找当前所有symbol值,如果有重名symbol(字面上),则复用;没有则新建

1
2
3
// true,因为symbol会在全局查找有没有注册过这个key(此处是"yajue")
// 如果注册过,会直接把已注册的symbol拿来(地址相同),没有就创建
console.log(Symbol.for("yajue") == Symbol.for("yajue"))

可以把symbol理解成分别创建两个变量,它们字面量看似相同,实则因地址不相同,所以两个变量不相同,这样的引用类型变量

Symbol的应用场景

symbol诞生的目的就是作为对象键的唯一值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let obj = {
name:0,
a:1,
b:2,
c:3,
d:4,
e:5,
// 如果使用JS,下面的name会覆盖上面的name
// name:"yajue"
}

let name1:symbol = Symbol("name")
let name2:symbol = Symbol("name")
let obj2 = {
[name1]:0,
a:1,
b:2,
c:3,
d:4,
e:5,
// 因为每个symbol都是唯一值,所以两个Symbol("name")看上去重名,但实际上没有重名
[name2]:"yajue"
}

遍历对象键的方法取得symbol键的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// for in中读不到symbol
for (let key in obj2) {
console.log(key) // 依次是"a" "b" "c" "d" "e"
}

// Object.keys也读不到symbol
console.log(Object.keys(obj2)) // ->[ 'a', 'b', 'c', 'd', 'e' ]

// Object.getOwnPropertyNames还是读不到symbol
console.log(Object.getOwnPropertyNames(obj2)) // ->[ 'a', 'b', 'c', 'd', 'e' ]

// Object.getOwnPropertySymbols可以读到symbol,但读不到字符串键
console.log(Object.getOwnPropertySymbols(obj2)) // ->[ Symbol(name), Symbol(name) ]

// Reflect.ownKeys,终于能同时读到字符串键和symbol了!!!
console.log(Reflect.ownKeys(obj2)) // ->[ 'a', 'b', 'c', 'd', 'e', Symbol(name), Symbol(name) ]

生成器和迭代器

生成器和迭代器的用法其实一样,就是写法不同

生成器

带星号*的函数就是一个生成器(generator),自带关键字yield用于返回迭代值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.生成器generator 带星号*的函数就是一个生成器
function* gen() {
// 关键字yield,后跟一个返回值,同步或异步都可以
yield Promise.resolve("yajue")
yield "yjsp"
yield 114514
yield Promise.resolve(1919810)
}

const omg = gen()

// 使用生成器返回值的next()方法,可以获得一个对象
console.log(omg.next()) // ->{ value: Promise { 'yajue' }, done: false }
console.log(omg.next()) // ->{ value: 'yjsp', done: false }
console.log(omg.next()) // ->{ value: 114514, done: false }
console.log(omg.next()) // ->{ value: Promise { 1919810 }, done: false }
console.log(omg.next()) // ->{ value: undefined, done: true }
// 1.next()会用value键返回各yield后跟的返回值
// 2.即使yield返回Promise,next()总是按照先后顺序取值
// 3.done键表示“是否能继续迭代”,它在yield返回次数+1次调用next()时才为false

迭代器

使用迭代器可以遍历可迭代对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 这些类型的身上都藏着迭代器
let set:Set<number> = new Set([1,1,4,5,1,4,1,9,1,9,8,1,0,3,6,4,3,6,4])
let map:Map<any,any> = new Map()
map.set("omg",1)
map.set("foo",2)
map.set("bar",3)
let arr:number[] = [1,2,3,5,7,11,13,17,19,23,29]
function args(...args:string[]) {
// arguments是一个类数组对象,没有那些数组方法
each(arguments)
}

// 这是个可迭代可迭代对象的函数:P
const each = (value:any)=> {
// 成员Symbol.iterator是个函数,调用它就能得到迭代器
let It:any = value[Symbol.iterator]()
let next:any = {done:false}
// done为true时才停止执行
while(!next.done) {
next = It.next()
// done为false时打印value值
if(!next.done) {
console.log(next.value)
}
}
}

each(set) // 依次是1 4 5 9 8 0 3 6
each(map) // 依次是[ 'omg', 1 ] [ 'foo', 2 ] [ 'bar', 3 ]
each(arr) // 依次是1 2 3 5 7 11 13 17 19 23 29
args("NYN","ICG","SZ","milk","flour") // 依次是"NYN" "ICG" "SZ" "milk" "flour"

// 迭代器的语法糖:对可迭代对象使用for of
// 记得不要给对象用for of,因为普通对象不可迭代(没有Symbol.iterator)
for(let value of set) {
console.log(value) // 依次是1 4 5 9 8 0 3 6
}

// -----------------------------------------

// 数组解构的底层原理也是调用Symbol.iterator
let [a,b,c] = [1,1,4]
let d = [5,1,4]
let copy = [...d]
console.log(a,b,c) // ->1 1 4
console.log(d) // ->[ 5, 1, 4 ]

// 只要在对象里实现Symbol.iterator,就能让对象可迭代
let obj = {
max:5,
current:0,
[Symbol.iterator]() {
return {
max:this.max,
current:this.current,
next() {
if(this.current == this.max) {
return {
value:undefined,
done:true
}
} else {
return {
value:this.current++,
done:false
}
}

}
}
}
}
// 对象居然可以迭代辣!
for(let value of obj) {
console.log(value) // 依次是0 1 2 3 4
}
// 也可以数组解构辣!
let x = [...obj]
console.log(x) // ->[ 0, 1, 2, 3, 4 ]