一、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); // false let 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); // true console.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 iterable for(const x of obj) { console.log(x); } // 2. 实现 [Symbol.iterator] let obj = { [Symbol.iterator]: function* () { let i = 0 while (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); // true console.log(arr1 instanceof Array); // true let arr2 = new Arr2(); arr2 = arr2.concat('arr222'); console.log(arr2 instanceof Arr2); // false console.log(arr2 instanceof Array); // true 复制代码
3. 与数组有关(Symbol.isConcatSpreadable)
- Symbol.isConcatSpreadable 表示一个布尔值,如果是 true,则意味着对象应 该用 Array.prototype.concat() 打平其(数组 || 伪数组)的元素.
// 1. 正常使用 Array.prototype.concat let arr1 = [1,2]; let arr2 = [3,4]; // 直接访问得到 undefinded ,但合并的结果是被拍平的,因此,可以认为默认情况下需要被拍平 console.log(arr2[Symbol.isConcatSpreadable]); // undefinded let 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]); // undefined arr2[Symbol.isConcatSpreadable] = false; console.log(arr2[Symbol.isConcatSpreadable]); // false let 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 是 foobar let 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 } 复制代码