一、Symbol 类型
Symbol 是 ES6 中新增的基本数据类型,它属于原始值,代表唯一的、不可变. 【目的】是保证对象属性的唯一性,也是为了解决属性冲突.
二、基本用法
- 使用 Symbol() 函数初始化,由于它是属于原始类型,所以 typeof 会直接返回 symbol
let sym = Symbol();
console.log(typeof sym);
- 初始化可传参,如 Symbol('hello') 那么 hello 就会作为这个 symbol 的描述,为了方便未来调试程序时有更直观的展示
let sym1 = Symbol();
console.log(sym1);
let sym2 = Symbol('this is sym2');
console.log(sym2);
- 通过 Symbol() 初始化同样参数的变量,也不会相等,可以理解为调用一次 Symbol() 就得到一个新的标识,不论参数是否相同
let sym1 = Symbol();
let sym2 = Symbol();
console.log(sym1 == sym2);
let sym3 = Symbol('foo');
let sym4 = Symbol('foo');
console.log(sym3 == sym4);
- Symbol()函数不能与 new 关键字一起作为构造函数使用,目的是为了避免和 Boolean、String 或 Number 的实例那样,存在包装对象
let myBoolean = new Boolean();
console.log(typeof myBoolean);
let myString = new String();
console.log(typeof myString);
let myNumber = new Number();
console.log(typeof myNumber);
let mySymbol = new Symbol();
let myBoolean = new Boolean();
let myString = new String();
let myNumber = new Number();
let mySymbol = Symbol();
myBoolean.add = ()=>{};
myString.add = ()=>{};
myNumber.add = ()=>{};
mySymbol.add = ()=>{};
console.log(myBoolean.add);
console.log(myString.add);
console.log(myNumber.add);
console.log(mySymbol.add);
let mySymbol = Object(Symbol());
mySymbol.add = ()=>{};
console.log(mySymbol.add);
let sym1 = Symbol.for('foo');
console.log(Symbol.keyFor(s1));
let sym2 = Symbol('bar');
console.log(Symbol.keyFor(sym2));
三、使用 Symbol 作为属性正常用于对象的【属性】
let s1 = Symbol('1'),
s2 = Symbol('2'),
s3 = Symbol('3'),
s4 = Symbol('4'),
s5 = Symbol('5');
let o = {
[s1]: 's1',
num: 1,
str: 'hello'
}
o[s2] = 's2'
Object.defineProperty(o, s3, { value: 's3' });
Object.defineProperties(o, {
[s4]: { value: 's4' },
[s5]: { value: 's5' },
});
console.log(o);
console.log(Object.getOwnPropertyNames(o));
console.log(Object.getOwnPropertySymbols(o));
console.log(Object.getOwnPropertyDescriptors(o));
console.log(Reflect.ownKeys(o));
四、常用内置符号
ECMAScript 6 引入了一批常用内置符号(well-known symbol)
- 目的是用于暴露语言内部的行为,开发者可以直接访问、重写或模拟这些行为.
- 内置符号都以 Symbol 工厂函数字符串属性的形式存在【例如:Symbol.iterator】.
- 所有内置符号属性都是不可写、不可枚举、不可配置的.
1、与遍历有关(Symbol.asyncIterator & Symbol.Iterator)
- Symbol.asyncIterator 表示实现异步迭代器 API 的函数,可 for-await-of 循环会利用这个函数执行异步迭代操作
- PS:ES2018 规范中定义的,只有在最新版的浏览器才支持
class Emitter {
constructor(max) {
this.max = max;
this.asyncIndex = 0;
}
async *[Symbol.asyncIterator]() {
while (this.asyncIndex < this.max) {
yield new Promise((resolve) => resolve(this.asyncIndex++));
}
}
}
let emitter = new Emitter(5);
for await (const x of emitter) {
console.log(x);
}
- Symbol.Iterator 表示实现迭代器 API 的函数,可用 for-of 循环这个函数执行迭代操作
- 能够使用 for-of 遍历的对象,要么自身实现了或者原型上存在键名为 [Symbol.iterator] 的函数,并且这个函数默认会返回一个对象,这个对象带有 next 函数的实现,比如 Generator 函数.
let obj = {};
for(const x of obj) {
console.log(x);
}
let obj = {
[Symbol.iterator]: function* () {
let i = 0
while (i < 5) {
yield i++;
}
}
}
for (const x of obj) {
console.log(x);
}
2. 与 instanceof 相关(Symblo.hasInstance | Symbol.species)
- instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型.
- Symblo.hasInstance 作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例,由 instanceof 操作符使用.
class Foo {}
let f = new Foo();
console.log(f instanceof Foo)
class Foo {}
let f = new Foo();
console.log(Foo[Symbol.hasInstance])
console.log(Foo[Symbol.hasInstance](f))
class Foo {
static [Symbol.hasInstance] = function () {
return false;
}
}
let f = new Foo();
console.log(f instanceof Foo);
- Symbol.species 作为一个属性表示一个函数值,该函数作为创建派生对象的构造函数.
class Arr1 extends Array {}
class Arr2 extends Array {
static get [Symbol.species]() {
return Array
}
}
let arr1 = new Arr1();
arr1 = arr1.concat('arr111');
console.log(arr1 instanceof Arr1);
console.log(arr1 instanceof Array);
let arr2 = new Arr2();
arr2 = arr2.concat('arr222');
console.log(arr2 instanceof Arr2);
console.log(arr2 instanceof Array);
3. 与数组有关(Symbol.isConcatSpreadable)
- Symbol.isConcatSpreadable 表示一个布尔值,如果是 true,则意味着对象应 该用 Array.prototype.concat() 打平其(数组 || 伪数组)的元素.
let arr1 = [1,2];
let arr2 = [3,4];
console.log(arr2[Symbol.isConcatSpreadable]);
let newArr = arr1.concat(arr2);
console.log(newArr);
let arr1 = [1,2];
let arr2 = [3,4];
console.log(arr2[Symbol.isConcatSpreadable]);
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr2[Symbol.isConcatSpreadable]);
let newArr = arr1.concat(arr2);
console.log(newArr);
let likeObj = { length: 2, 0: 'name', 1: 'age' };
let arr = [];
console.log(likeObj[Symbol.isConcatSpreadable]);
console.log(arr.concat(likeObj));
likeObj[Symbol.isConcatSpreadable] = true;
console.log(arr.concat(likeObj));
4. 与字符串相关 Symbol.[match | replace | search | split]
PS:以上这些方法,在正则表达式 RegExp.protype 上都有默认实现,因此字符串的大多数 api 都和 RegExp 对象一起使用,默认情况下,即便传递的不是 RegExp 类型,也会被强制转换为 RegExp 类型去使用.
- Symbol.match 作为一个属性表示一个正则表达式方法,该方法用正则表达式 去匹配字符串。由 String.prototype.match()方法使用
- string.match(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型,如:'11'.macth( {num:11} ) ==> '11'.macth(new RegExp( {num:11} ))
- string.match(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.match 方法
console.log('hello'.match(/llo/));
var obj = {};
var result = 'hello'.match(obj);
console.log(result);
var obj = {
[Symbol.match]: (target) => {
let index = target.indexOf('oo');
return {
value: target.slice(index,3),
index
};
}
};
console.log('foobar'.match(obj));
- Symbol.replace 作为一个属性表示一个正则表达式方法,该方法替换一个字符 串中匹配的子串。由 String.prototype.replace()方法使用
- string.replace(exp, str),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
- string.replace(exp, str) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.replace 方法
var target = 'hello';
var newStr = target.replace(/o/,'66');
console.log(newStr);
var target = 'hello';
var newStr = target.replace(['o'],'77');
console.log(newStr);
var target = 'hello';
var obj = {
[Symbol.replace]: function(target){
var index = target.indexOf('e');
return 'haha' + target.slice(index);
}
};
var newStr = target.replace(obj,'66');
console.log(newStr);
- Symbol.search 作为一个属性表示一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 String.prototype.search()方法使用
- string.search(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
- string.search(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.search 方法
var target = 'hello';
var index = target.search('ll');
console.log(index);
var target = 'hello';
var obj = {};
var index = target.search(obj);
console.log(index);
var target = 'hello';
var obj = {
[Symbol.search]: function(target){
var index = target.indexOf('ll');
return index;
}
};
var index = target.search(obj);
console.log(index);
- Symblo.split 作为一个属性表示一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 String.prototype.split()方法使用.
- string.split(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
- string.split(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.split 方法
var target = 'hello-world';
var arr = target.split('-');
console.log(arr);
var target = 'hello-world';
var arr = target.split(['-']);
console.log(arr);
var target = 'hello-world';
var obj = {
[Symbol.split]: function(target){
var length = target.length;
var arr = [];
for(var index = 0; index < length; index++){
arr[index] = target[index];
}
return arr;
}
};
var arr = target.split(obj);
console.log(arr);
5. 与复杂类型相关 Symbol.[toPrimitive | toStringTag | unscopables]
- Symbol.toPrimitive表示一个方法,该方法将对象转换为相应的原始值. 由 ToPrimitive 抽象操作使用
var obj = {};
console.log(obj + ' & hello');
console.log(obj - 1);
console.log(!!obj);
var obj = {
[Symbol.toPrimitive]: function (type) {
switch (type) {
case 'string':
return '666';
case 'number':
return 889;
case 'default':
default:
return 0;
}
}
};
console.log(String(obj));
console.log(obj - 1);
console.log(Boolean(obj));
- Symbol.toStringTag 表示一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 Object.prototype.toString()使用
var arr = [1],
obj = { name: 'zs' },
func = function getName() {
console.log(obj.name);
};
console.log(obj.toString());
console.log(arr.toString());
console.log(func.toString());
obj[Symbol.toStringTag] = 'Array';
console.log(obj.toString());
console.log(typeof obj);
- Symbol.unscopables 表示一个对象,该对象所有的以及继承的属性,都会从关联对象的 with 环境绑定中排除
- 不推荐使用 with,因此也不推荐使用 Symbol.unscopables
let o = { foo: 'bar' };
with (o) {
console.log(foo);
}
o[Symbol.unscopables] = {
foo: true
};
with (o) {
console.log(foo);
}
EDN