symbol 特性
唯一,不会重名
Symbol('my') !== Symbol('my') // true my 只是描述符
不可修改
- 不可枚举
Symbols 在 for…in 迭代中不可枚举。另外,Object.getOwnPropertyNames() 不会返回 symbol 对象的属性 - 不能和其他类型的值进行运算
```javascript let sym = Symbol(‘My symbol’);
“your symbol is “ + sym
// TypeError: can’t convert symbol to string
your symbol is ${sym}
// TypeError: can’t convert symbol to string
- 不能用点运算符。<br />```javascriptconst mySymbol = Symbol();const a = {};a.mySymbol = 'Hello!';a[mySymbol] // undefineda['mySymbol'] // "Hello!"
创建局部的 symbol
// 使用 Symbol函数创建// Symbol([description])var mysymbol = Symbol("my symbol");var mysymbol1 = Symbol('my symbol');// symbol 可以具有字符串类型的描述,但是即使描述相同,symbol也不相等。// 每次都会创建一个新的 symbol类型:mysymbol == mysymbol1 // falsevar obj = {};obj[mysymbol] = function () {console.log("mysymbol")}obj[mysymbol1] = function () {console.log("mysymbol1")}obj // {Symbol(my symbol): ƒ, Symbol(my symbol): ƒ}// 使用 Symbol 定义的对象属性不会重名
创建 symbol 包装对象
不能使用new Symbol,因为生成的值不是对象,而是一种 symbol 的基本数据类型,因为 es6开始不支持创建显示的包装器对象,可以使用 Object()函数创建
var sym = Symbol("foo");typeof sym; // "symbol"var symObj = Object(sym);typeof symObj; // "object"var obj = {[sym]: 1};obj[sym] // 1obj[symObj]; // still 1
在对象中查找 Symbol 属性
Object.getOwnPropertySymbols(obj) // (2) [Symbol(my symbol), Symbol(my symbol)]
内置的symbol值
一些标准中提到的 symbol,可以在全局的 symbol 函数的属性中找到。
Symbol.hasInstance
对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance(foo)]。
class MyClass {[Symbol.hasInstance](foo) {return foo instanceof Array;}}[1, 2, 3] instanceof new MyClass() // true
Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
let arr1 = ['c', 'd'];['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']arr1[Symbol.isConcatSpreadable] // undefinedlet arr2 = ['c', 'd'];arr2[Symbol.isConcatSpreadable] = false;['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable默认等于undefined。该属性等于true时,也有展开的效果。
类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才可以展开。
let obj = {length: 2, 0: 'c', 1: 'd'};['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']obj[Symbol.isConcatSpreadable] = true;['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
Symbol.isConcatSpreadable属性也可以定义在类里面。
class A1 extends Array {constructor(args) {super(args);this[Symbol.isConcatSpreadable] = true;}}class A2 extends Array {constructor(args) {super(args);}get [Symbol.isConcatSpreadable] () {return false;}}let a1 = new A1();a1[0] = 3;a1[1] = 4;let a2 = new A2();a2[0] = 5;a2[1] = 6;[1, 2].concat(a1).concat(a2)// [1, 2, 3, 4, [5, 6]]
注意,Symbol.isConcatSpreadable的位置差异,A1是定义在实例上,A2是定义在类本身,效果相同。
Symbol.species
对象的Symbol.species属性,指向一个构造函数。创建衍生对象时,会使用该属性。
class MyArray extends Array {}const a = new MyArray(1, 2, 3);const b = a.map(x => x);const c = a.filter(x => x > 1);b instanceof MyArray // truec instanceof MyArray // true
上面代码中,子类MyArray继承了父类Array,a是MyArray的实例,b和c是a的衍生对象。你可能会认为,b和c都是调用数组方法生成的,所以应该是数组(Array的实例),但实际上它们也是MyArray的实例。Symbol.species属性就是为了解决这个问题而提供的。现在,我们可以为MyArray设置Symbol.species属性。
class MyArray extends Array {static get [Symbol.species]() { return Array; }}
上面代码中,由于定义了Symbol.species属性,创建衍生对象时就会使用这个属性返回的函数,作为构造函数。这个例子也说明,定义Symbol.species属性要采用get取值器。默认的Symbol.species属性等同于下面的写法。
static get [Symbol.species]() {return this;}
现在,再来看前面的例子。
class MyArray extends Array {static get [Symbol.species]() { return Array; }}const a = new MyArray();const b = a.map(x => x);b instanceof MyArray // falseb instanceof Array // true
Symbol.match
对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。
String.prototype.match(regexp)// 等同于regexp[Symbol.match](this)class MyMatcher {[Symbol.match](string) {return 'hello world'.indexOf(string);}}'e'.match(new MyMatcher()) // 1
Symbol.replace
对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
String.prototype.replace(searchValue, replaceValue)// 等同于searchValue[Symbol.replace](this, replaceValue)
下面是一个例子。
const x = {};x[Symbol.replace] = (...s) => console.log(s);'Hello'.replace(x, 'World') // ["Hello", "World"]
Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是Hello,第二个参数是替换后的值,上面例子是World。
Symbol.toPrimitive
Symbol.toStringTag
Symbol.iterator
我们可以使用 Symbol.iterator 来自定义 for…of 在对象上的行为:
var o = new Objecto[Symbol.iterator] = function() {var v = 0return {next: function() {return { value: v++, done: v > 10 }}}};for(var v of o)console.log(v); // 0 1 2 3 ... 9
代码中我们定义了iterator之后,用for(var v of o)就可以调用这个函数,然后我们可以根据函数的行为,产生一个for…of的行为。
这里我们给对象o添加了 Symbol.iterator 属性,并且按照迭代器的要求定义了一个0到10的迭代器,之后我们就可以在for of中愉快地使用这个o对象啦。
const s = symbol('foo');console.log(s.toString()); // symbol(foo)// es2019console.log(s.description); // foo
Symbol.asyncIterator
**Symbol.asyncIterator** 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于[for await...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of)循环。
你可以通过设置[Symbol.asyncIterator]属性来自定义异步可迭代对象。
const myAsyncIterable = new Object();myAsyncIterable[Symbol.asyncIterator] = async function*() {yield "hello";yield "async";yield "iteration!";};(async () => {for await (const x of myAsyncIterable) {console.log(x);// expected output:// "hello"// "async"// "iteration!"}})();
创建全局共享的 Symbol
Symbol.for()
它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
Symbol.for('foo'); === Symbol.for('foo'); // trueSymbol("bar") === Symbol("bar") // false
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key。
let s1 = Symbol.for("foo");Symbol.keyFor(s1) // "foo"let s2 = Symbol("foo");Symbol.keyFor(s2) // undefined
Symbol.for()的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。
iframe = document.createElement('iframe');iframe.src = String(window.location);document.body.appendChild(iframe);iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')// true
实例
消除魔术字符串
// 如果使用到一个字符串// 可以写成变量,定义枚举const type = {type1: 'type1',type2: 'type2'}// type.type1 等于什么值不重要,只要确保不会跟其他属性的值冲突即可const type = {type1: Symbol('type1'),type2: Symbol('type2')}
属性名遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {[Symbol('my_key')]: 1,enum: 2,nonEnum: 3};Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]
由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
var symSex = Symbol('sex')function Func (name,sex) {this[symSex] = sex;this.name = name;}var func = new Func("foo",'ladayboy')Object.getOwnPropertyNames(func) // ['name']
