JavaScript - Symbol类型
Posted 友人A ㅤ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript - Symbol类型相关的知识,希望对你有一定的参考价值。
Symbol(符号)是ECMAScript 6新增的数据类型。
符号是原始值,且符号实例是唯一、不可变的。
用途:确保对象属性使用唯一标识符,不会发生属性冲突的危险。用来创建唯一记号,进而用作非字符串形式的对象属性。
1. 基本用法
符号需要使用Symbol()函数初始化。
let sym = Symbol();
// 因为符号本身是原始类型,所以typeof操作符对符号返回symbol
console.log(typeof sym); // symbol
调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:
// Symbol的值是唯一的,不会出现相同值的常量
let genericSymbol = Symbol();
let otherGenericSymbol = Symbol();
console.log(genericSymbol == otherGenericSymbol); // false
// 可以传入一个字符串参数作为对符号的描述
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
console.log(fooSymbol == otherFooSymbol); // false
符号没有字面量语法。 按照规范,只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。
let genericSymbol = Symbol();
console.log(genericSymbol); // Symbol()
let fooSymbol = Symbol('foo');
console.log(fooSymbol); // Symbol(foo)
Symbol()函数不能与new关键字一起作为构造函数使用。
这样做是为了避免创建符号包装对象,像使用Boolean、String或Number那样,它们都支持构造函数且可用于初始化包含原始值的包装对象。
let myBoolean = new Boolean();
console.log(typeof myBoolean); // "object"
let myString = new String();
console.log(typeof myString); // "object"
let myNumber = new Number();
console.log(typeof myNumber); // "object"
let mySymbol = new Symbol(); // 报错,TypeError
console.log(mySymbol);
如果想使用符号包装对象,可以借用Object()函数:
let mySymbol = Symbol();
let myWarppedSymbol = Object(mySymbol);
console.log(typeof myWarppedSymbol); // "object"
2. 使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。
Symbol.for()
方法:
let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol); // symbol
Symbol.for()对每个字符串键都执行幂等操作。
- 第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
- 后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。
// 创建新符号
let fooGlobalSymbol = Symbol.for('foo');
// 重用已有符号
let otherFooGlobalSymbol = Symbol.for('foo');
console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
采用相同符号,在全局注册表中定义的符号跟使用Symbol()定义的符号也不等同:
// 使用Symbol()定义
let localSymbol = Symbol('foo');
// 使用全局注册表定义
let globalSymbol = Symbol.for('foo');
console.log(localSymbol === globalSymbol); // false
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串。
注册表中使用的键同时也会被用作符号描述。
let emptyGlobalSymbol = Symbol.for();
console.log(emptyGlobalSymbol); // Symbol(undefined)
使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。
// 创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); // foo
// 创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keyFor(s2)); // undefined
如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError。
Symbol.keyFor(123); // TypeError: 123 is not a symbol
3. 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
包括对象字面量属性和 Object.defineProperty(obj, prop, descriptor) / Object.defineProperties() 定义的属性。
对象字面量只能在计算属性语法中使用符号作为属性。
let s1 = Symbol('foo'),
s2 = Symbol('bar'),
s3 = Symbol('baz'),
s4 = Symbol('qux');
let o = {
// [属性],会对属性进行读取,并且转换成字符串。[s1]是读取了Symbol的字符串键'foo'
[s1]: 'foo val'
};
// 或 o[s1] = 'foo val';
console.log(o); // { [Symbol(foo)]: 'foo val' }
Object.defineProperty(o, s2, { value: 'bar val' });
console.log(o); // {Symbol(foo): foo val, Symbol(bar): bar val}
Object.defineProperties(o, {
[s3]: { value: 'baz val' },
[s4]: { value: 'qux val' }
});
console.log(o); // {Symbol(foo): foo val, Symbol(bar): baz val,
// Symbol(foo): foo val, Symbol(bar): qux val}
let s1 = Symbol('foo'),
s2 = Symbol('bar');
let o = {
[s1]: 'foo val',
[s2]: 'bar val',
baz: 'baz val',
qux: 'qux val'
};
// Object.getOwnPropertySymbols()返回对象实例的符号属性数组
console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(foo), Symbol(bar) ]
// Object.getOwnPropertyNames()返回对象实例的常规属性数组
console.log(Object.getOwnPropertyNames(o)); // [ 'baz', 'qux' ]
// Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象
console.log(Object.getOwnPropertyDescriptors(o));
// {
// baz: {
// value: 'baz val',
// writable: true,
// enumerable: true,
// configurable: true
// },
// qux: {
// value: 'qux val',
// writable: true,
// enumerable: true,
// configurable: true
// },
// [Symbol(foo)]: {
// value: 'foo val',
// writable: true,
// enumerable: true,
// configurable: true
// },
// [Symbol(bar)]: {
// value: 'bar val',
// writable: true,
// enumerable: true,
// configurable: true
// }
// }
// Reflect.ownKeys()会返回两种类型的键
console.log(Reflect.ownKeys(o)); // [ 'baz', 'qux', Symbol(foo), Symbol(bar) ]
注意:
Object.getOwnPropertyNames()
和Object.getOwnProperty-Symbols()
两个方法的返回值彼此互斥。
因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。
但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键。
let o = {
[Symbol('foo')]: 'foo val',
[Symbol('bar')]: 'bar val'
};
console.log(o); // { [Symbol(foo)]: 'foo val', [Symbol(bar)]: 'bar val' }
let barSymbol = Object.getOwnPropertySymbols(o).find((Symbol) =>
Symbol.toString().match(/bar/));
console.log(barSymbol); // Symbol(bar)
4. Symbol.asyncIterator
这个符号表示实现异步迭代器API的函数。
- for-await-of循环会利用这个函数执行异步迭代操作。
- 循环时,它们会调用以Symbol.asyncIterator为键的函数,并期望这个函数会返回一个实现迭代器API的对象。很多时候,返回的对象是实现该API的AsyncGenerator。
class Foo {
async * [Symbol.asyncIterator]() {}
}
let f = new Foo();
console.log(f[Symbol.asyncIterator]()); // Object [AsyncGenerator] {}
技术上,这个由Symbol.asyncIterator函数生成的对象应该通过其next()方法陆续返回Promise实例。可以通过显式地调用next()方法返回,也可以隐式地通过异步生成器函数返回。
class Emitter {
constructor(max) {
this.max = max;
this.asyncIdx = 0;
}
async * [Symbol.asyncIterator]() {
while (this.asyncIdx < thia.max) {
yield new Promise((resolve) =>
resolve(this.asyncIdx++));
}
}
}
async function asyncCount() {
let emitter = new Emitter(5);
for await (const x of emitter) {
console.log(x);
}
}
asyncCount();
// 0
// 1
// 2
// 3
// 4
5. Symbol.hasInstance
该方法决定一个构造器对象是否认可一个对象是它的实例。
instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型。
function Foo() {}
let f = new Foo();
console.log(f instanceof Foo); // true
class Bar {}
let b = new Bar();
console.log(b instanceof Bar); // true
在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。
以Symbol. hasInstance为键的函数会执行同样的操作,只是操作数对调了一下。
function Foo() {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance](f)); // true
class Bar {};
let b = new Bar();
console.log(Bar[Symbol.hasInstance](b)); // true
这个属性定义在Function的原型上,因此默认在所有函数和类上都可以调用。由于instanceof操作符会在原型链上寻找这个属性定义,就跟在原型链上寻找其他属性一样,因此可以在继承的类上通过静态方法重新定义这个函数。
class Bar {}
class Baz extends Bar {
static[Symbol.hasInstance]() {
return false;
}
}
let b = new Baz();
console.log(Bar[Symbol.hasInstance](b)); // true
console.log(b instanceof Bar); // true
console.log(Baz[Symbol.hasInstance](b)); // false
console.log(b instanceof Baz); // false
6. Symbol.isConcatSpreadable
这个符号作为属一个属性,表示一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()展开其数组元素。
ES6中的Array.prototype.concat()方法会根据接收到的对象类型选择如何将一个类数组对象拼接成数组实例。
let initial = ['foo'];
let array = ['bar'];
console.log(array[Symbol.isConcatSpreadable]); // undefined
console.log(initial[Symbol.isConcatSpreadable]); // undefined
// 默认情况下,展开其元素连接到结果中
console.log(initial.concat(array)); // [ 'foo', 'bar' ]
// 设置 Symbol.isConcatSpreadable 为 false,整个对象被追加到数组末尾
array[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(array)); // [ 'foo', [ 'bar', [Symbol(Symbol.isConcatSpreadable)]: false ] ]
// 类数组对象默认情况下会被追加到数组末尾
let arrayLikeObject = { length: 1, 0: 'baz' };
console.log(arrayLikeObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(arrayLikeObject)); // [ 'foo', { '0': 'baz', length: 1 } ]
// 设置 Symbol.isConcatSpreadable 为 true,展开其元素连接到结果中
arrayLikeObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLikeObject)); // [ 'foo', 'baz' ]
let otherObject = new Set().add('qux');
console.log(otherObject[Symbol.isConcatSpreadable]); // undefined
console.log(initial.concat(otherObject)); // [ 'foo', Set(1) { 'qux' } ]
otherObject[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(otherObject)); // [ 'foo' ]
7. Symbol.iterator
这个符号作为一个属性表示“一个方法,该方法返回对象默认的迭代器。由for-of语句使用”,这个符号表示实现迭代器API的函数。
class Foo { * [Symbol.iterator]() {}
}
let f = new Foo();
// 返回的对象是实现该API的Generator
console.log(f[Symbol.iterator]()); // Object [Generator] {}
技术上,这个由Symbol.iterator函数生成的对象应该通过其next()方法陆续返回值。可以通过显式地调用next()方法返回,也可以隐式地通过生成器函数返回。
class Emitter {
constructor(max) {
this.nax = max;
this.idx = 0;
} * [Symbol.iterator]() {
while (this.idx < this.max) {
yield this.idx++;
}
}
}
function count() {
let emitter = new Emitter(5);
for (const x of emitter) {
console.log(x);
}
}
count();
// 0
// 1
// 2
// 3
// 4
8. Symbol.match
这个符号作为一个属性表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用”。
console.log(RegExp.prototype[Symbol.match]); // [Function: [Symbol.match]]
console.log('foobar'.match(/bar/)); // [ 'bar', index: 3, input: 'foobar', groups: undefined ]
class FooMatcher {
static[Symbol.match](target) {
return target.includes('foo');
}
}
console.log('foobar'.match(FooMatcher)); // true
console.log('barbaz'.match(FooMatcher)); // false
class StringMatcher {
constructor(str) {
this.str = str;
};
[Symbol.match](target) {
return target.includes(this.str);
}
}
console.log('foobar'.match(new StringMatcher('foo'))); // true
console.log('barbaz'.match(new StringMatcher('qux'))); // false
9. Symbol.replace
这个符号作为一个属性表示“一个正则表达式方法,该方法替换一个字符串中匹配的子串。由String.prototype.replace()方法使用”。
console.log(RegExp.prototype[Symbol.replace]); // [Function: [Symbol.replace]]
console.log('foobarbaz'.replace(/bar/, 'qux')); // fooquxbaz
Symbol.replace函数接收两个参数,即调用replace()方法的字符串实例和替换字符串。
class FooReplacer {
static[Symbol.replace](target, replacement) {
return target.split('foo').join(replacement);
}
}
console.log('barfoobaz'.replace(FooReplacer, 'qux')); // barquxbaz
class StringReplacer {
constructor(str) {
this以上是关于JavaScript - Symbol类型的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript ES6 奇妙的Symbol类型(╹ڡ╹ )