一、Symbol 类型
Symbol 是 ES6 中新增的基本数据类型,它属于原始值,代表唯一的、不可变. 【目的】是保证对象属性的唯一性,也是为了解决属性冲突.
二、基本用法
- 使用 Symbol() 函数初始化,由于它是属于原始类型,所以 typeof 会直接返回 symbol
let sym = Symbol();
console.log(typeof sym); // symbol
- 初始化可传参,如 Symbol(‘hello’) 那么 hello 就会作为这个 symbol 的描述,为了方便未来调试程序时有更直观的展示
let sym1 = Symbol();
console.log(sym1); // Symbol()let sym2 = Symbol('this is sym2');
console.log(sym2); // Symbol(this is sym2)
- 通过 Symbol() 初始化同样参数的变量,也不会相等,可以理解为调用一次 Symbol() 就得到一个新的标识,不论参数是否相同
let sym1 = Symbol();
let sym2 = Symbol();
console.log(sym1 == sym2); // falselet sym3 = Symbol('foo');
let sym4 = Symbol('foo');
console.log(sym3 == sym4); // false
- Symbol()函数不能与 new 关键字一起作为构造函数使用,目的是为了避免和 Boolean、String 或 Number 的实例那样,存在包装对象
// 1. 实例化例子
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: Symbol is not a constructor// 2. 包装对象例子
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);// undefind// 3. Symbol 转包装对象
let mySymbol = Object(Symbol());
mySymbol.add = ()=>{};
console.log(mySymbol.add);// ()=>{}
- 全局符号注册表
- Symbol() 每初始化一次,就会生成一个新 symbol
- Symbol.for() 初始化时可以传递字符串参数,并检查注册表是否存在对应的 symbol,如果没有则直接存储在注册表中,下一次在调用 Symbol.for() 并且传参一致时,直接返回注册表中存在的 symbol
let sym1 = Symbol.for('foo'); // 创建新符号 let sym2 = Symbol.for('foo'); // 重用已有符号 console.log(sym1 === sym2); // trueconsole.log(Symbol('foo') === Symbol('foo')); // false console.log(Symbol('foo') === Symbol.for('foo')); // false
- Symbol.keyFor() 查询注册表,参数为全局 symbol,即用 Symbol.for() 创建得到的 symbol
// 创建全局符号
let sym1 = Symbol.for('foo');
console.log(Symbol.keyFor(s1)); // foo// 创建普通符号
let sym2 = Symbol('bar');
console.log(Symbol.keyFor(sym2)); // undefined
三、使用 Symbol 作为属性正常用于对象的【属性】
// 1. symbol 定义let s1 = Symbol('1'),s2 = Symbol('2'),s3 = Symbol('3'),s4 = Symbol('4'),s5 = Symbol('5');// 2. 多种赋值方式,和普通对象属性一致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); // {num: 1, str: 'hello', Symbol(1): 's1', Symbol(2): 's2', Symbol(3): 's3', Symbol(4): 's4', Symbol(5): 's5',}// 3. 获取 key 的方式不一致,getOwnPropertyNames 和 getOwnPropertySymbols 方法是互斥的console.log(Object.getOwnPropertyNames(o)); // ['num', 'str']console.log(Object.getOwnPropertySymbols(o)); // [Symbol(1), Symbol(2), Symbol(3), Symbol(4), Symbol(5)]// 4. 获取所有的对象自身上的属性(普通属性 + symbol属性)console.log(Object.getOwnPropertyDescriptors(o)); // {num: {…}, str: {…}, Symbol(1): {…}, Symbol(2): {…}, Symbol(3): {…}, …}console.log(Reflect.ownKeys(o)); // ['num', 'str', Symbol(1), Symbol(2), Symbol(3), Symbol(4), Symbol(5)]
四、常用内置符号
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 函数.
// 1. 未实现 [Symbol.iterator]let obj = {};// 抛出错误: Uncaught TypeError: obj is not iterablefor(const x of obj) {console.log(x);}// 2. 实现 [Symbol.iterator]let obj = {[Symbol.iterator]: function* () {let i = 0while (i < 5) {yield i++;}}}for (const x of obj) {console.log(x); // 0 1 2 3 4}
2. 与 instanceof 相关(Symblo.hasInstance | Symbol.species)
- instanceof 操作符可以用来确定一个对象实例的原型链上是否有原型.
- Symblo.hasInstance 作为一个属性表示“一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例,由 instanceof 操作符使用.
// 1. 通过 instanceof 判断class Foo {}let f = new Foo();console.log(f instanceof Foo) // true// 2. 通过 [Symbol.hasInstance] 的判断class Foo {}let f = new Foo();console.log(Foo[Symbol.hasInstance]) // ƒ [Symbol.hasInstance]() { [native code] }console.log(Foo[Symbol.hasInstance](f)) // true// 3. 自定义 instanceof 的返回值class Foo {static [Symbol.hasInstance] = function () {return false;}}let f = new Foo();console.log(f instanceof Foo); // false
- 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); // trueconsole.log(arr1 instanceof Array); // truelet arr2 = new Arr2();arr2 = arr2.concat('arr222');console.log(arr2 instanceof Arr2); // falseconsole.log(arr2 instanceof Array); // true
3. 与数组有关(Symbol.isConcatSpreadable)
- Symbol.isConcatSpreadable 表示一个布尔值,如果是 true,则意味着对象应 该用 Array.prototype.concat() 打平其(数组 || 伪数组)的元素.
// 1. 正常使用 Array.prototype.concatlet arr1 = [1,2];let arr2 = [3,4];// 直接访问得到 undefinded ,但合并的结果是被拍平的,因此,可以认为默认情况下需要被拍平console.log(arr2[Symbol.isConcatSpreadable]); // undefindedlet newArr = arr1.concat(arr2); console.log(newArr);// [1, 2, 3, 4]// 2. 修改不需要被拍平的元素let arr1 = [1,2];let arr2 = [3,4];console.log(arr2[Symbol.isConcatSpreadable]); // undefinedarr2[Symbol.isConcatSpreadable] = false;console.log(arr2[Symbol.isConcatSpreadable]); // falselet newArr = arr1.concat(arr2);console.log(newArr); // [1, 2, Array(2)]// 3. 伪数组也具备这个属性let likeObj = { length: 2, 0: 'name', 1: 'age' }; let arr = [];console.log(likeObj[Symbol.isConcatSpreadable]); // undefined console.log(arr.concat(likeObj)); // [{ length: 1, 0: 'name', 1: 'age' }] likeObj[Symbol.isConcatSpreadable] = true; console.log(arr.concat(likeObj)); // ['name', 'age']
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 方法
// 1. string.match(RegExp)
console.log('hello'.match(/llo/)); // ["llo", index: 2, input: "hello", groups: undefined]// 2. string.match(非 RegExp 类型)
var obj = {};
var result = 'hello'.match(obj);// 这里相当于 'hello'.match( new RegExp(obj) )
console.log(result); // ["e", index: 1, input: "hello", groups: undefined]// 3. 自定义 string.match 的匹配规则
var obj = {[Symbol.match]: (target) => { // target 是 foobarlet index = target.indexOf('oo');return {value: target.slice(index,3),index};}
};
console.log('foobar'.match(obj)); // {value: "oo", index: 1}
- Symbol.replace 作为一个属性表示一个正则表达式方法,该方法替换一个字符 串中匹配的子串。由 String.prototype.replace()方法使用
- string.replace(exp, str),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
- string.replace(exp, str) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.replace 方法
// 1. Symbol.replace(exp, str)
var target = 'hello';
var newStr = target.replace(/o/,'66');
console.log(newStr); // hell66// 2. Symbol.replace(非 RegExp 类型, str)
var target = 'hello';
var newStr = target.replace(['o'],'77'); // 相当于 target.replace(new RegExp(['o']),'77')
console.log(newStr); // hell77// 3. 实现自定义 Symbol.replace 替换规则
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); // hahaello
- Symbol.search 作为一个属性表示一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由 String.prototype.search()方法使用
- string.search(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
- string.search(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.search 方法
// 1. string.search(exp)
var target = 'hello';
var index = target.search('ll');
console.log(index); // 2// 2. string.search(非 RegExp 类型)
var target = 'hello';
var obj = {};
var index = target.search(obj); // target.search(new RegExp(obj))
console.log(index); // 1// 3. 实现自定义 string.search 查找规则
var target = 'hello';
var obj = {[Symbol.search]: function(target){var index = target.indexOf('ll');return index;}
};
var index = target.search(obj);
console.log(index); // 2
- Symblo.split 作为一个属性表示一个正则表达式方法,该方法在匹配正则表达式的索引位置拆分字符串。由 String.prototype.split()方法使用.
- string.split(exp),如果传入的参数(exp) 是非 RegExp 类型,就会被转成 RegExp 类型
- string.split(exp) ,如果不想让参数(exp) 被强转成 RegExp 类型,可以给传入的 exp 实现 Symbol.split 方法
// 1. string.split(RegExp)
var target = 'hello-world';
var arr = target.split('-');
console.log(arr); // ["hello", "world"]// 2. string.split(非 RegExp 类型)
var target = 'hello-world';
var arr = target.split(['-']);// 相当于 target.split(new RegExp(['-']))
console.log(arr); // ["hello", "world"]// 3. 实现自定义 string.split 拆分规则
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); // ["h", "e", "l", "l", "o", "-", "w", "o", "r", "l", "d"]
5. 与复杂类型相关 Symbol.[toPrimitive | toStringTag | unscopables]
- Symbol.toPrimitive表示一个方法,该方法将对象转换为相应的原始值. 由 ToPrimitive 抽象操作使用
// 1. 复杂类型 转 基本类型
var obj = {};// 1.1 obj 转 string
console.log(obj + ' & hello'); // [object Object] & hello// 1.2 obj 转 number
console.log(obj - 1); // NaN// 1.3 obj 转 boolean
console.log(!!obj); // true// 2. 实现自定义的转换规则
var obj = {[Symbol.toPrimitive]: function (type) {switch (type) {case 'string':return '666';case 'number':return 889;case 'default':default:return 0;}}
};
// 2.1 obj 转 string
console.log(String(obj)); // 666 & hello
// 2.2 obj 转 number
console.log(obj - 1); // 888
// 2.3 obj 转 boolean
console.log(Boolean(obj)); // true
- Symbol.toStringTag 表示一个字符串,该字符串用于创建对象的默认字符串描述。由内置方法 Object.prototype.toString()使用
// 1. Object.prototype.toString() 用于返回数据类型
var arr = [1],obj = { name: 'zs' },func = function getName() { console.log(obj.name); };
// 1.1 调用 Object.prototype.toString()
console.log(obj.toString()); // [object Object]
// 1.2 这两个调用的都不是 Object.prototype.toString 方法// 1.2.1 Array.prototype.toString()
console.log(arr.toString()); // 1// 1.2.2 Function.prototype.toString()
console.log(func.toString()); // function getName() { console.log(obj.name); }// 3. 通过 Symbol.toStringTag 更改 Object 类型
obj[Symbol.toStringTag] = 'Array';
console.log(obj.toString()); // [object Array]// typeof 返回类型不会受影响
console.log(typeof obj); // object
- Symbol.unscopables 表示一个对象,该对象所有的以及继承的属性,都会从关联对象的 with 环境绑定中排除
- 不推荐使用 with,因此也不推荐使用 Symbol.unscopables
// 1. 正常可见
let o = { foo: 'bar' };
with (o) {console.log(foo); // bar
}// 2. 设置不可见
o[Symbol.unscopables] = {foo: true
};
with (o) {console.log(foo); // ReferenceError
}