Symbol
是ES6
新增的原始数据类型,表示独一无二的值。ES5
的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6
引入Symbol
的原因。
ES5
的原始数据类型:string
、number
、boolean
、null
、undefind
所以目前有 6 个原始数据类型
**Symbol**
主要是为了解决对象属性重名的问题!!!**Symbol**
是通过**Symbol()**
函数返回的,但是该函数不是构造函数!!!
基础使用
基础用法:
console.log(Symbol()); // Symbol()
console.log(Symbol() == Symbol()); // false
console.log(typeof Symbol()); // symbol
不能进行赋值属性:
let s1 = Symbol();
s1.a = "a";
console.log(s1.a); // undefind
Symbol()
方法可以传递一个参数,表示对Symbol
的描述:
let s1 = Symbol("s1");
console.log(s1); // Symbol(s1)
Symbol()
的值是字符串,标识名会转换为字符串:
let obj = { a: 1 };
let s1 = Symbol(obj);
console.log(s1); // Symbol([object Object])
使用undefined
和null
:
console.log(Symbol(undefined)); // Symbol(),undefined 表示空的,和没有传参一样
console.log(Symbol(null)); // Symbol(null)
symbol
不能强制转换为number
数据类型:
let s1 = Symbol();
console.log(String(s1)); // Symbol()
console.log(Boolean(s1)); // true
console.log(Number(s1)); // Cannot convert a Symbol value to a number
symbol
可以使用!
转换为布尔值:
let s1 = Symbol();
console.log(!s1); // false
访问symbol
数据的原型:
let s1 = Symbol();
console.log(Object.getPrototypeOf(s1));
举例:
let name = Symbol();
let person = {};
// person.name = "张三"; // 无效的,不会使用上面的 Symbo
person[name] = "张三"; // 必须使用 [] 表达式
console.log(person); // {Symbol(): '张三'}
// =======分割=======
let name = Symbol();
let eat = Symbol();
let person = {
[name]: "张三",
[eat]() {
console.log(this[name] + "is eat");
},
};
person[eat](); // 张三is eat
方法
Symbol.for()
有时,我们希望重新使用同一个Symbol
值,Symbol.for()
方法可以做到这一点。
它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol
值。如果有,就返回这个Symbol
值,否则就新建一个以该字符串为名称的Symbol
值,并将其注册到全局。
let s1 = Symbol("foo");
let s2 = Symbol("foo");
let s3 = Symbol.for("foo");
let s4 = Symbol.for("foo");
console.log(s1 === s2); // false
console.log(s3 === s4); // true
console.log(s1 === s4); // false
Symbol.for()
与Symbol()
这两种写法,都会生成新的Symbol
。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.keyFor()
该方法返回一个已登记的Symbol
类型值的key
(描述名),但是只能获取Symbol.for()
返回的symbol
let s = Symbol("foo");
let s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s)); // undefined
console.log(Symbol.keyFor(s1)); // foo
遍历
symbol
的值是不能进行遍历的。
let a = Symbol();
let b = Symbol();
const obj = {
[a]: "a",
[b]: "b",
};
console.log(obj); // {Symbol(): 'a', Symbol(): 'b'}
console.log(Object.keys(obj)); // []
for (const key in obj) {
console.log(key); // 空的
}
let newObj = Object.assign({}, obj); // 但是可以进行合并
console.log(newObj); // {Symbol(): 'a', Symbol(): 'b'}
所以Object
提供了一个遍历Symbol
的方法Object.getOwnPropertySymbols()
,该方法只能遍历symbol
的值。
let a = Symbol();
let b = Symbol();
const obj = {
[a]: "a",
[b]: "b",
"c": "c"
};
// 只遍历对象的 Symbol 值
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol()]
iterator 迭代对象
:::info
迭代器的概念:对数据结构的读取的一种方式,有序的、连续的基于拉取的一种消耗数据的组织方式;
:::
Symbol()
函数的构造器上有很多的属性,属性对应部署了相关的方法:
let s = Symbol();
console.log(Object.getPrototypeOf(s));
而我们之前使用的instanceof
来判断对象是否是构造函数实例背后就是使用hasInstance
方法来实现的。
而我们主要学习的是iterator: Symbol(Symbol.iterator)
迭代方法。
以数组为例子,我们看看数组的原型:
可以看到数组的原型上有个Symbol(Symbol.iterator)
方法。
接着我来执行一下:
let arr = [1, 2, 3, 4];
let iter = arr[Symbol.iterator];
console.log(iter); // ƒ values() { [native code] }
因为
Symbol
返回是独一无二的标识,所以我们不能通过arr[Symbol("Symbol.iterator")]
去访问,这样是访问一个新的Symbol
标识,所以我们要直接访问Symbol
函数上的iterator
属性来访问迭代对象。
可以看到只返回了一个函数的标识,那我们来执行它:
let arr = [1, 2, 3, 4];
let iter = arr[Symbol.iterator](); // 获取迭代器接口
console.log(iter); // Array Iterator {} // 返回一个迭代空对象
里面有个next
方法,我们去执行它:
let arr = [1, 2, 3, 4];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // {value: 1, done: false}
console.log(iter.next()); // {value: 2, done: false}
console.log(iter.next()); // {value: 3, done: false}
console.log(iter.next()); // {value: 4, done: false}
console.log(iter.next()); // {value: undefined, done: true}
可以看到我们每次调用next
方法都会返回数组的value
和表示是否完成的done
。
JavaScript
中的数据结构有:object
、array
、类数组、Map
、Set
、weakMap
、weakSet
、typeArray
(二进制数据的缓存区)
其中object
没有iterator
的接口(方法),且数据的key
不是有序的、连续的,所以不能进行迭代。
如果想要迭代以上数据结构(除object
)的值的该怎么办呢?ES6
对部署了iterator
接口的数据类型提供了一种统一的方式进行迭代,那就是for...of...
for...of...
本身也是调用iterator.next()
方法然后进行遍历。
let arr = [1, 2, 3];
// 迭代相应的数据结构
for (const iterator of arr) {
console.log(iterator); // 1 2 3
}
// for...in... 遍历数组
for (const key in arr) {
console.log(key); // 0 1 2
}
let obj = {
name: "张三",
age: 20,
};
// 对象是不能迭代
for (const iterator of obj) {
// obj is not iterable
// 因为没有部署 iterable 接口
// 如果想要迭代对象,必须给对象部署 iterable 接口
console.log(iterator);
}
根据以上的分解步骤,我们可以模拟一个iterator
的接口:
let arr = [1, 2, 3];
function makeIterator(array) {
let nextIndex = 0;
// 返回一个对象,对象里有 next 方法
return {
next() {
// 如果没有迭代完成
// 返回 {value:xxx, done:false}
return nextIndex < array.length
? {
value: array[nextIndex++],
done: false,
}
: {
value: undefined,
done: true,
};
// 如果迭代完成
// 返回 {value:undefined, done:true}
},
};
}
let ite = makeIterator(arr);
console.log(ite.next()); // {value: 1, done: false}
console.log(ite.next()); // {value: 2, done: false}
console.log(ite.next()); // {value: 3, done: false}
console.log(ite.next()); // {value: undefined, done: true}
最后给对象新增一个iterator
的接口:
let obj = {
start: [1, 2, 3, 4, 5],
end: [10, 20, 30],
[Symbol.iterator]() {
let index = 0,
list = [...this.start, ...this.end],
len = list.length;
return {
next() {
if (index < len) {
return {
value: list[index++],
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
},
};
},
};
for (const iterator of obj) {
console.log(iterator); // 1 2 3 4 5 10 20 30
}
// 部署了迭代器接口后,能够使用数组的拓展运算符
let arr = [...obj];
console.log(arr); // [1, 2, 3, 4, 5, 10, 20, 30]
:::danger
⚠️ 注意
部署了迭代器接口后,能够使用数组的拓展运算符。
:::