JS中Symbol类型的介绍与基本用法
作者:想做后端的前端
1、 引入Symbol类型的背景
ES5 的对象属性名都是字符串,这容易造成属性名冲突的问题
举例: 使用别人的模块/对象, 又想为之添加新的属性,这就容易使得新属性名与原有属性名冲突
2、Symbol类型简介
symbol是一种原始数据类型
- 其余原始类型: 未定义(undefined) 、 空值(null)、布尔值(boolean)、字符串(string)、数值(number)、对象(object)
- symbol表示独一无二的值
- symbol类型的"真实值"无法获取,也就是说Symbol类型没有对应的字面量
- symbol类型的意义在于区分彼此和不重复,不在于真实值
Symbol()是一种原生函数
- 常见的原生函数有String()、Number()、Boolean()、Array()、Object()、Function()、RegExp()、Date()、Error()、Symbol()
3、基本用法
符号需要使用Symbol()函数初始化。
let sym = Symbol(); // 因为符号本身是原始类型,所以typeof操作符对符号返回symbol console.log(typeof sym); // symbol
调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关:
// Symbol的值是唯一的,不会出现相同值的常量 let genericSymbol = Symbol(); let otherGenericSymbol = Symbol(); console.log(genericSymbol == otherGenericSymbol); // false // 可以传入一个字符串参数作为对符号的描述 let fooSymbol = Symbol('foo'); let otherFooSymbol = Symbol('foo'); console.log(fooSymbol == otherFooSymbol); // false
符号没有字面量语法。 按照规范,只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。
let genericSymbol = Symbol(); console.log(genericSymbol); // Symbol() let fooSymbol = Symbol('foo'); console.log(fooSymbol); // Symbol(foo)
Symbol()函数不能与new关键字一起作为构造函数使用。
这样做是为了避免创建符号包装对象,像使用Boolean、String或Number那样,它们都支持构造函数且可用于初始化包含原始值的包装对象。
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 console.log(mySymbol);
如果想使用符号包装对象,可以借用Object()函数:
let mySymbol = Symbol(); let myWarppedSymbol = Object(mySymbol); console.log(typeof myWarppedSymbol); // "object"
4、 使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。
Symbol.for()方法:
let fooGlobalSymbol = Symbol.for('foo'); console.log(typeof fooGlobalSymbol); // symbol
Symbol.for()对每个字符串键都执行幂等操作。
第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。
后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。
// 创建新符号 let fooGlobalSymbol = Symbol.for('foo'); // 重用已有符号 let otherFooGlobalSymbol = Symbol.for('foo'); console.log(fooGlobalSymbol === otherFooGlobalSymbol); // true
采用相同符号,在全局注册表中定义的符号跟使用Symbol()定义的符号也不等同:
// 使用Symbol()定义 let localSymbol = Symbol('foo'); // 使用全局注册表定义 let globalSymbol = Symbol.for('foo'); console.log(localSymbol === globalSymbol); // false
全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换为字符串。
注册表中使用的键同时也会被用作符号描述。
let emptyGlobalSymbol = Symbol.for(); console.log(emptyGlobalSymbol); // Symbol(undefined)
使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。
// 创建全局符号 let s = Symbol.for('foo'); console.log(Symbol.keyFor(s)); // foo // 创建普通符号 let s2 = Symbol('bar'); console.log(Symbol.keyFor(s2)); // undefined
如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError。
Symbol.keyFor(123); // TypeError: 123 is not a symbol
5、 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
包括对象字面量属性和 Object.defineProperty(obj, prop, descriptor) / Object.defineProperties() 定义的属性。
对象字面量只能在计算属性语法中使用符号作为属性。
let s1 = Symbol('foo'), s2 = Symbol('bar'), s3 = Symbol('baz'), s4 = Symbol('qux'); let o = { // [属性],会对属性进行读取,并且转换成字符串。[s1]是读取了Symbol的字符串键'foo' [s1]: 'foo val' }; // 或 o[s1] = 'foo val'; console.log(o); // { [Symbol(foo)]: 'foo val' } Object.defineProperty(o, s2, { value: 'bar val' }); console.log(o); // {Symbol(foo): foo val, Symbol(bar): bar val} Object.defineProperties(o, { [s3]: { value: 'baz val' }, [s4]: { value: 'qux val' } }); console.log(o); // {Symbol(foo): foo val, Symbol(bar): baz val, // Symbol(foo): foo val, Symbol(bar): qux val}
let s1 = Symbol('foo'), s2 = Symbol('bar'); let o = { [s1]: 'foo val', [s2]: 'bar val', baz: 'baz val', qux: 'qux val' }; // Object.getOwnPropertySymbols()返回对象实例的符号属性数组 console.log(Object.getOwnPropertySymbols(o)); // [ Symbol(foo), Symbol(bar) ] // Object.getOwnPropertyNames()返回对象实例的常规属性数组 console.log(Object.getOwnPropertyNames(o)); // [ 'baz', 'qux' ] // Object.getOwnPropertyDescriptors()会返回同时包含常规和符号属性描述符的对象 console.log(Object.getOwnPropertyDescriptors(o)); // { // baz: { // value: 'baz val', // writable: true, // enumerable: true, // configurable: true // }, // qux: { // value: 'qux val', // writable: true, // enumerable: true, // configurable: true // }, // [Symbol(foo)]: { // value: 'foo val', // writable: true, // enumerable: true, // configurable: true // }, // [Symbol(bar)]: { // value: 'bar val', // writable: true, // enumerable: true, // configurable: true // } // } // Reflect.ownKeys()会返回两种类型的键 console.log(Reflect.ownKeys(o)); // [ 'baz', 'qux', Symbol(foo), Symbol(bar) ]
注意:Object.getOwnPropertyNames()和Object.getOwnProperty-Symbols()两个方法的返回值彼此互斥。
因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。
但是,如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键。
let o = { [Symbol('foo')]: 'foo val', [Symbol('bar')]: 'bar val' }; console.log(o); // { [Symbol(foo)]: 'foo val', [Symbol(bar)]: 'bar val' } let barSymbol = Object.getOwnPropertySymbols(o).find((Symbol) => Symbol.toString().match(/bar/)); console.log(barSymbol); // Symbol(bar)
6、 所有属性
属性 | 含义 |
---|---|
Symbol.asyncIterator | 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await…of循环。 |
Symbol.prototype.description | description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。 |
Symbol.hasInstance | 用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。 |
Symbol.isConcatSpreadable | 用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。 |
Symbol.iterator | 为每一个对象定义了默认的迭代器。该迭代器可以被 for…of 循环使用。 |
Symbol.match | 指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数 |
Symbol.matchAll | 内置通用(well-known)符号指定方法返回一个迭代器,该迭代器根据字符串生成正则表达式的匹配项。此函数可以被 String.prototype.matchAll() 方法调用。 |
Symbol.replace | 这个属性指定了当一个字符串替换所匹配字符串时所调用的方法。 |
Symbol.search | 指定了一个搜索方法,这个方法接受用户输入的正则表达式,返回该正则表达式在字符串中匹配到的下标,这个方法由以下的方法来调用 String.prototype.search()。 |
Symbol.species | 知名的 Symbol.species 是个函数值属性,其被构造函数用以创建派生对象。 |
Symbol.split | 指向 一个正则表达式的索引处分割字符串的方法。这个方法通过 String.prototype.split() 调用。 |
Symbol.toPrimitive | 是内置的 symbol 属性,其指定了一种接受首选类型并返回对象原始值的表示的方法。它被所有的强类型转换制算法优先调用。 |
Symbol.toStringTag | 内置通用(well-known)symbol 是一个字符串值属性,用于创建对象的默认字符串描述。它由 Object.prototype.toString() 方法内部访问。 |
Symbol.unscopables | 指用于指定对象值,其对象自身和继承的从关联对象的 with 环境绑定中排除的属性名称。 |
附:Symbol的使用场景
1、作为对象属性 当一个复杂对象中含有多个属性的时候,很容易将某个属性名覆盖掉,利用 Symbol 值作为属性名可以很好的避免这一现象
const name = Symbol('name'); const obj = { [name]: 'ClickPaas', }
2、ES6 中的类是没有 private 关键字来声明类的私有方法和私有变量的,但是我们可以利用 Symbol 的唯一性来模拟
const speak = Symbol(); class Person { [speak]() { console.log(123) } } let person = new Person() console.log(person[speak]())
因为使用者无法在外部创建出一个相同的 speak,所以就无法调用该方法
总结
到此这篇关于JS中Symbol类型的介绍与基本用法的文章就介绍到这了,更多相关JS Symbol类型用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!