JavaScript:ES6:Symbol 作者:马育民 • 2024-12-23 18:20 • 阅读:10012 # 介绍 symbol是一个 **ES6标准** 新增的 **基本数据类型** 在JavaScript中,共有七种基本数据类型:string、number、bigint、boolean、null、undefined、**symbol**。 # 创建 创建 symbol 的值需要使用 `Symbol()` 函数,由于是 **基本数据类型**,所以 **不能** 使用 `new` ``` let s1 = Symbol('sym'); ``` 由于生成的 symbol 是一个值而不是对象,所以 **不能** 为其 **添加属性**。 ### 唯一 `Symbol()` 函数可以接受一个字符串作为参数,表示对该值的描述,因此即使定义 symbol 使用相同的参数互相之间也不是相同的: ``` let s1 = Symbol('sym'); let s2 = Symbol('sym'); s1 === s2 ; // false ``` # 特点 Symbol 是一种独特的且 **不可变** 的 **基本值类型**,它可以用来创建 **独一无二的标识符** ### 可作为属性名 可以作为对象的 **属性名** 使用,所以现在对象属性名可以为两种类型: - 原本的字符串类型 - 新增的 symbol 类型 凡是使用 symbol 命名的属性都是独一无二的,保证不与其他属性名产生冲突。 ### 不会转换 不同于字符串或数字,Symbol **不会** 被 **强制转换成其他类型**,也 **不能 被转成字符串或数字**。 ``` let s1 = Symbol('sym'); alert(s1); ``` 执行报错,如下: ``` TypeError: Cannot convert a Symbol value to a string ``` symbol 也不能与其他类型的值进行运算: ``` console.log('symbol is' + s1); ``` 执行报错,如下: ``` TypeError: Cannot convert a Symbol value to a string ``` 可以手动将 symbol 转换成字符串 ``` alert(s1.toString()); ``` 或者获得定义 symbol 时的描述: ``` alert(s1.description); ``` # 应用场景 - 使用 symbol 作为对象的属性名,为了避免 **第三方框架** 的 **同名属性 被覆盖** - 使用 symbol 定义常量。使用 symbol 定义的常量能保证常量的值都是不相等的 ``` const gender = { //这样就说明man就是一个独一无二的值,不用再man:'man' man: Symbol(), woman: Symbol(), } function isMan(gender) { switch (gender) { case gender.man: console.log('男性'); break; case gender.woman: console.log('女性'); break } } isMan(gender.man) //男性 ``` # 类型转换 ``` Boolean(s1); // true Number(s1); // TypeError: Cannot convert a Symbol value to a number parseInt(s1); // NaN ``` # 重复值 ### Symbol.for() 因为symbol类型的值都是独一无二的,但有时,我们要 **重复** 使用一个 symbol 时,可以用到 `Symbol.for()` 方法。 `Symbol.for()` 方法接受一个字符串参数,会在全局中搜索有没有以该参数命名的 symbol 的值,如果查找到就返回这个值。如果没有查到则重新生成一个值,并将该值以参数名称注册到全局。 ``` let s1 = Symbol.for('sym'); // 创建 let s2 = Symbol.for('sym'); // 查找 s1 === s2; // true ``` Symbol.for() 和 Symbol() 方法都会生成新的 symbol 类型的值,不同的是 Symbol.for() 方法会查找命名参数是否在全局中注册过,如果注册过的就不会创建新的值,而是会直接返回,所以我们可以使用到相同的 symbol 值。但使用 Symbol() 方法每次都会创建一个新的值,且不会注册到全局。 ### Symbol.keyFor() `Symbol.keyFor()` 方法表示获取一个 symbol 的值在全局中注册的命名参数 key,只有使用 `Symbol.for()` 创建的值才会有注册的命名参数,使用 `Symbol()` 生成的值则没有: ``` let s4 = Symbol('sym'); let s5 = Symbol.for('sym'); Symbol.keyFor(s4); // undefined Symbol.keyFor(s5); // sym ``` ### 全局命名 使用 `Symbol.for()` 注册的全局命名参数是真正意义上的全局,而不管是否运行在全局环境。 ``` let iframe = document.createElement('iframe'); iframe.src = 'http://www.baidu.com'; document.body.append(iframe); iframe.contentWindow.Symbol.for('sym') === Symbol.for('sym'); // true ``` # 内置的 symbol 值 除了自定义 symbol 的值,ES6 还提供了 11 个内置的 symbol 值,指向语言内部使用的方法。 ### Symbol.iterator 对象的 `Symbol.iterator` 属性,指向该对象的默认遍历器方法。 ``` const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); console.log(m.entries === m[Symbol.iterator]); // true for (let pair of m.entries()) { console.log(pair); } // ['key1', 'val1'] // ['key2', 'val2'] // ['key3', 'val3'] for (let pair of m[Symbol.iterator]()) { console.log(pair); } // ['key1', 'val1'] // ['key2', 'val2'] // ['key3', 'val3'] console.log([...m]); // [ [ 'key1', 'val1' ], [ 'key2', 'val2' ], [ 'key3', 'val3' ] ] ``` ### Symbol.hasInstance `Symbol.hasInstance` 指向一个内部方法,当使用 instanceof 运算符时,判断一个实例是否为某种类型时,会调用这个内部方法。 例如在调用:foo instanceof Foo 时,实际是调用 FooSymbol.hasInstance; ``` class Foo { [Symbol.hasInstance](foo){ return foo instanceof Array; } } [1,2,3] instanceof new Foo(); // true ``` ### Symbol.isConcatSpreadable 该属性的值是一个布尔型,表示当调用 Array.prototype.concat() 方法时,是否可以展开。 Symbol.isConcatSpreadable的默认值为undefined 例: ``` let arr = [1,2,3]; arr.concat([4,5]); // [1, 2, 3, 4, 5] ``` 数组默认可以展开,当设置了 Symbol.isConcatSpreadable = false ``` let arr = [1,2,3]; arr[Symbol.isConcatSpreadable] = false; arr.concat([4,5]); // [Array(3), 4, 5] ``` ### Symbol.species 对象的 Symbol.species 属性指向一个构造函数,在创建衍生对象可以更改构造函数指向。 例: ``` class MyArr extends Array{} let a = new MyArr(1,2,3); let b = a.filter(e => e >= 2); a instanceof MyArr; // true b instanceof MyArr; // true ``` 其中变量 b 是变量 a 的衍生对象,变量 b 也是 MyArr 的实例,而通过设置 Symbol.species 可以更改这个衍生对象的构造函数: ``` class MyArr extends Array{ static get [Symbol.species](){ return Array; } } let a = new MyArr(1,2,3); let b = a.filter(e => e >= 2); a instanceof MyArr; // true b instanceof MyArr; // false ``` 实际上,默认的 Symbol.species 属性等同: ``` static get [Symbol.species]() { return this; } ``` 总之该属性实在实例运行过程中,需要再次调用自身构造函数时,可以使用指定的构造函数。 ### Symbol.match 该属性指向一个函数,当执行 str.match(object) 时,如果该属性存在,会调用该属性指向的方法: ``` let str = 'e'; class StrObj { [Symbol.match](string){ return 'hello'.indexOf(string); } } str.match(new StrObj()); // 1 // 等同于 new StrObj()[Symbol.match](str); // 1 ``` ### Symbol.replace 该属性指向一个方法,当对象被 String.prototype.replace 方法调用时,会执行该方法: ``` String.prototype.replace(searchValue, replaceValue) // 等同于 searchValue[Symbol.replace](this, replaceValue) ``` ### Symbol.search 对象的 Symbol.search 属性,指向一个方法,当该对象被String.prototype.search 方法调用时,会返回该方法的返回值。 ``` String.prototype.search(regexp) // 等同于 regexp[Symbol.search](this) ``` ### Symbol.split 对象的 Symbol.split 属性,指向一个方法,当该对象被 String.prototype.split 方法调用时,会返回该方法的返回值。 ``` String.prototype.split(separator, limit) // 等同于 separator[Symbol.split](this, limit) ``` ### Symbol.toPrimitive 对象的 Symbol.toPrimitive 属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 Symbol.toPrimitive 被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式: Number:该场合需要转成数值 String:该场合需要转成字符串 Default:该场合可以转成数值,也可以转成字符串 ``` let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } } }; 2 * obj // 246 3 + obj // '3default' obj == 'default' // true String(obj) // 'str' ``` ### Symbol.toStringTag 对象的 Symbol.toStringTag 属性,指向一个方法。在该对象上面调用Object.prototype.toString 方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制 [object Object] 或 [object Array] 中 object 后面的那个字符串。 ``` // 例一 ({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]" // 例二 class Collection { get [Symbol.toStringTag]() { return 'xxx'; } } let x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]" ``` ### Symbol.unscopables 对象的 Symbol.unscopables 属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被 with 环境排除。 即使用 Symbol.unscopables 指定的对象,在使用 with 语法块时,不在当前作用域中寻找特定属性,而是从外层作用域开始查找。 ``` // 没有 unscopables 时 class MyClass { foo() { return 1; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 1 } // 有 unscopables 时 class MyClass { foo() { return 1; } get [Symbol.unscopables]() { return { foo: true }; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 2 } ``` 参考: https://blog.csdn.net/xcg132566/article/details/108109837 https://blog.csdn.net/darabiuz/article/details/121962153 原文出处:http://malaoshi.top/show_1GWH1tPFU7W.html