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 />
```javascript
const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
创建局部的 symbol
// 使用 Symbol函数创建
// Symbol([description])
var mysymbol = Symbol("my symbol");
var mysymbol1 = Symbol('my symbol');
// symbol 可以具有字符串类型的描述,但是即使描述相同,symbol也不相等。
// 每次都会创建一个新的 symbol类型:
mysymbol == mysymbol1 // false
var 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] // 1
obj[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] // undefined
let 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 // true
c 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 // false
b 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 Object
o[Symbol.iterator] = function() {
var v = 0
return {
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)
// es2019
console.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'); // true
Symbol("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']