刚学习js的时候就知道es6新增了symbol数据类型,如今工作快四年了,对于symbol也是一点也不了解,既然是这么基础的知识点,那么就一定需要重点学习一下的啦!✌🏻
symbol 是ECMAScript 6新增的一种基础数据类型。通过使用Symbol()构造函数创建唯一、不可变的实例。 主要用途是表示一个独一无二的标识符。
🚀symbol 的特点
- symbol的值是唯一且不可变的,可以用来定义不冲突的属性名或变量名。
- symbol数据类型不能与其他的数据类型进行运算;symbol可以显式转化为string类型和boolean类型,不能转化成number类型。
- symbol不能使用for...of或者for...in循环打印key值。可以使用Object.getOwnPropertySymbols()方法或Reflect.ownKeys()方法来获取。
- 使用Symbol创建symbol的时候不需要使用new关键字
🌈symbol 基本用法
当我们需要创建一个symbol类型的时候,需要使用Symbol()构造函数进行创建;因为symbol是基础数据类型,所以使用typeof可以识别symbol类型。
javascript
let s = Symbol();
console.log(typeof s) // symbol
Symbol()函数可以传入一字符串参数,作为symbol的描述,但是这个字符串参数与symbol定义或标识完全无关
javascript
let s1 = Symbol();
let s2 = Symbol();
let y1 = Symbol('zifu');
let y2 = Symbol('zifu');
console.log(s1 == s2) // false
console.log(y1 == y2) // false
📝使用全局symbol注册表
如果需要复用symbol实例,那么可以用一个字符串作为键,在全局symbol注册表中创建并复用symbol。 当你多次调用Symbol.for()
函数并传入相同的字符串键时,它会始终返回相同的Symbol值。使用某个字符串调用时,它会检查全局注册表,发现不存在对应的symbol,于是就会生产一个新的symbol实例并添加到注册表中。后续使用相同的字符串调用同样会检查注册表,发现存在该字符串对应的symbol实例就会返回该symbol实例。
javascript
let s = symbol.for('one'); // 创建新symbol
let s2 = symbol.for('one'); // 重新使用已有的symbol
console.log( s === s2 ); // true
全局注册表中的symbol必须是使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转化为字符串。同时参数也会被用作symbol的描述。
javascript
let a = Symbol.for('one')
console.log(a) // Symbol(one)
Symbol.keyFor()函数可以用来查询symbol在全局注册表中所对应的字符串;如果不存在,则返回undefined。如果传值不是一个symbol,那么会抛出typeError
javascript
let s = Symbol.for('one');
console.log(Symbol.keyFor(s)) // one
let s2 = Symbol('two');
console.log(Symbol.keyFor(s2)) // undefined
Symbol.keyFor(123) // TypeError: 123 is not a symbol
使用symbol作为对象的属性
凡是可以使用数值和字符串作为属性的地方,都可以使用symbol。这就包含了对象字面量属性和Object.defaultProperty()/Object.definedProperties()定义的属性。
javascript
// 第一种写法
let o = {
[Symbol('one')]: 'first'
};
console.log(o); // {Symbol(three): 'first'}
// 第二种写法
let o1 = {};
o1[Symbol('two')] = 'second'
console.log(o1); // {Symbol(two): 'second'}
// 第三种写法
let o2 = {};
Object.defineProperty(o2, Symbol('three'), {value: 'third'});
console.log(o2); // {Symbol(three): 'third'}
// 第四种写法
let o3 = {};
Object.defineProperties(o3, {
[Symbol('four')]: {value: 'fourth'}
});
console.log(o3); // {Symbol(four): 'fourth'}
在操作数组的时候 Object.getOwnPropertyNames() 函数会返回对象实例的常规属性数组,而不会返回对象实例的symbol属性数组;Object.getOwnPropertySymbols()函数则会返回对象实例symbol属性数组,不会返回对象实例的常规属性数组;如果要同时返回包含常规和symbol属性描述的对象,可以使用Object.getOwnPropertyDescriptors()函数;通过Reflect.ownKeys()函数会返回常规和symbol的键
javascript
let obj = {
[Symbol('one')]: 'first',
[Symbol('two')]: 'second',
three: 'third',
four: 'fourth',
};
console.log(Object.getOwnPropertyNames(obj)); // ['three', 'four']
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(one), Symbol(two)]
console.log(Object.getOwnPropertyDescriptors(obj)); //four: {value: 'fourth', ...}three: {value: 'third', ...}Symbol(one): {value: 'first', ...}Symbol(two): {value: 'second', ...}
console.log(Reflect.ownKeys(obj)); // ['three', 'four', Symbol(one), Symbol(two)]
常用的内置symbol
symbol.asyncIterator
含义:一个方法,该方法返回对象默认的AsyncInerator。由for-await-of语句使用
表示实现异步迭代器API的函数。for-await-of循环会利用这个函数执行异步迭代器操作;循环过程中,会调用Symbol.asyncIterator为键的函数,并返回实现该API的异步生成器(AsyncGenerator)
javascript
class Obj {
async *[Symbol.asyncIterator()]() {};
}
let obj = new Obj();
console.log(obj[Symbol.asyncIterator]()); // AsyncGenerator {<suspended>}
通过这个Symbol.asyncIterator 函数生成的对象通过其 next() 方法可以陆续的返回promise()实例。通过显式调用next()方法和隐式地通过异步生成器函数返回
javascript
class OBJ {
constructor(max) {
this.max = max;
this.asyncIndex = 0;
}
async *[Symbol.asyncIterator]() {
while(this.asyncIndex < this.max) {
yield new Promise((resolve) => resolve(this.asyncIndex++))
}
}
}
async function asyncCount() {
let obj = new OBJ(5);
for await(const x of obj) {
console.log(x);
}
}
asyncCount()
// 0
// 1
// 2
// 3
// 4
symbol.hasInstance
含义:一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用
这个应该不需要解释😜,想想instanceof关键字
javascript
function One() {}
let o = new One();
console.log(o instanceof One); // true
class Car {};
let c = new Car();
console.log(c instanceof Car); // true
instanceof原理:当我们使用instanceof关键字的时候,会使用Symbol.hasInstance 函数来确定关系。以 Symbol.hasInstance 为键的函数会执行同样的操作,只是操作数对调了一下
javascript
function One() {}
let o = new One();
console.log(One[Symbol.hasInstance](o)); // true
class Car {};
let c = new Car();
console.log(Car[Symbol.hasInstance](c)); // true
因为这个属性是定义在Function的原型上,所以默认在所有的函数和类上都可以调用。由于instanceof操作符会在原型链上寻找到这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重写这个函数
javascript
class Car{}
class Car1 extends Car {
static [Symbol.hasInstance]() {
return false;
};
};
let benci = new Car1();
console.log(Car[Symbol.hasInstance](benci)); // true
console.log(benci instanceof Car); // true
console.log(Car1[Symbol.hasInstance](benci)); // false
console.log(benci instanceof Car1); // false
Symbol.isConcatSpreadable
含义:作用于Array.prototype.concat()方法上,表示一个布尔值,如果是true则展开数组元素,默认的情况下展开,值是undefined
就是专门用于Array.prototype.concat()方法的,我的理解是:isConcatSpreadable表示一个开关,默认展开合并,当值为true的时候也是展开合并,为false的时候就将concat的值作为一个元素加入到数组中
js
let initial = ['张三'];
let array = ['李四'];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(array)); // ['张三', '李四']
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // ['张三', Array(1)]
array[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(array)); // ['张三', '李四']
上面的案例中可以看到当isConcatSpreadable等于undefined 或者 true的时候,合并两个数组时,是将两个数组中的元素放在了一起,当isConcatSpreadable等于false的时候,合并两个数组将数组作为一个元素合并在了一起。
Symbol.iterator
含义:一个方法,该方法返回对象默认的迭代器。由for-of语句使用。也就是表示实现迭代器API的函数
js
class Obj {
constructor(max) {
this.max = max;
this.idx = 0;
}
*[Symbol.iterator]() {
while(this.idx < this.max) {
yield this.idx++
}
}
}
function count() {
let obj = new Obj(5);
for (const x of obj) {
console.log(x)
}
}
count();
// 0
// 1
// 2
// 3
// 4
定义一个可以迭代的对象,实现Symbol.iterator方法,使用for...of等对象循环的时候就会执行Symbo.iterator重写的方法
Symbol.match
含义:一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()使用
这个方法可以重写对于正则表达式的实现,增加个性化的逻辑
js
class Matcher {
static [Symbol.match](target) {
return target.includes('abc');
}
}
console.log('abcdefg'.match(Matcher)); // true
console.log('hijklmn'.match(Matcher)); // false
Symbol.replace
含义:一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()使用
js
class Replacer {
static [Symbol.replace](target, replacement) {
return target.split('abc').join(replacement);
}
}
console.log('abcdefg'.replace(Replacer, 'zzz')) // zzzdefg
Symbol.search
含义:一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由String.prototype.search()方法使用
js
class Searcher {
static [Symbol.search](target) {
return target.indexOf('abc');
}
}
console.log('abcdefg'.search(Searcher)) // 0
Symbol.species
含义:一个函数值,该函数作为创建派生对象的构造函数
js
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
var a = new MyArray(1, 2, 3);
var mapped = a.map((x) => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
Symbol.split
含义:一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由String.prototype.split()方法使用
js
class Splitter {
static [Symbol.split](target) {
return target.split('abc');
}
}
console.log('deabcfg'.split(Splitter)); // ['de', 'fg']
Symbol.toPrimitive
含义:一个方法,该方法将对象转换为相应的原始值。由ToPrimitive抽象操作使用。
js
var obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ''); // "[object Object]"
var obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == 'number') {
return 10;
}
if (hint == 'string') {
return 'hello';
}
return true;
}
};
console.log(+obj2); // 10 -- hint is "number"
console.log(`${obj2}`); // "hello" -- hint is "string"
console.log(obj2 + ''); // "true" -- hint is "default"
Symbol.toStringTag
含义:一个字符串,该字符串用于创建对象的默认字符串描述。有内置方法Object.prototype.toString()使用
js
let s = new Set();
console.log(s); // Set(0) {}
console.log(s.toString()); // [object Set]
console.log(s[Symbol.toStringTag]); // Set
class Demo {}
let demo = new Demo();
console.log(demo); // Demo {}
console.log(demo.toString()); // [object Object]
console.log(demo[Symbol.toStringTag]); // undefined
class Demo1 {
constructor() {
this[Symbol.toStringTag] = 'Demo1';
}
}
let demo1 = new Demo1();
console.log(demo1); // Demo1 {Symbol(Symbol.toStringTag): 'Demo1'}
console.log(demo1.toString()); // [object Demo1]
console.log(demo1[Symbol.toStringTag]); // Demo1
Symbol.unscopables
含义:一个对象,该对象所有的以及继承的属性,都会从关联的对象with环境绑定中排除
js
let o = {
name: '张三'
}
with(o) {
console.log(name); //张三
}
o[Symbol.unscopables] = {
name: true
}
with(o) {
console.log(name); // ReferenceError
}
一般不推荐使用with关键字,所以也不要使用Symbol.unscopables
总结一下
Symbol是js的八种基础数据类型之一;主要的作用在于表示一个独一无二的标识;它可以作为对象的属性键来避免属性键的冲突,并且由于它的不可变性,可以作为对象属性的值来保护对象属性的完整性。