symbol 是原始值,且符号实例是唯一、不可变的 。symbol 用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。 所以常用的场景:创建唯一记号 。
基本用法 初始化 Symbol函数前不能使用new命令 ,否则会报错。
无参: let s = Symbol();
typeof s // symbol
有参: Symbol函数可以接受一个字符串作为参数 ,表示对 Symbol 实例的描述 ,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
1 2 3 4 5 6 7 8 let s1 = Symbol ('foo' );let s2 = Symbol ('bar' );s1 s2 s1.toString () s2.toString ()
若参数的值是对象 ,会调用对象的toString
方法
1 2 3 4 5 6 7 const obj = { toString ( ) { return 'abc' ; } }; const sym = Symbol (obj);sym
使用 Symbol 直接传入一个函数 ,会调用 toString
函数,将函数内容转换为字符串
1 2 3 4 5 let a = function ( ){ console .log ('哈哈哈' ); } console .log (Symbol (a)); console .log (typeof Symbol (a));
参数只是对当前 Symbol 值的描述,相同参数 的Symbol函数的 返回值不相等 (唯一性) 。
1 2 3 4 5 6 7 8 9 10 11 let s1 = Symbol ();let s2 = Symbol ();s1 === s2 let s1 = Symbol ('foo' );let s2 = Symbol ('foo' );s1 === s2
Symbol 值不能与其他类型的值进行运算,会报错。
1 2 3 4 5 6 7 let sym = Symbol ('My symbol' );"your symbol is " + sym`your symbol is ${sym} `
Symbol可以显示转化为字符串和布尔类型,但是不可转化为Number类型 。
1 2 3 4 5 6 7 8 let sym = Symbol ('My symbol' );String (sym) sym.toString () Boolean (sym) Number (sym)
全局注册 使用同一个 Symbol 值,全局注册Symbol.for方法 可以做到这一点。
原理: Symbol.for()
不会每次调用就返回一个新的 Symbol
类型的值,而是会先检查给定的key
是否已经存在 ,如果不存在才会新建一个值。
1 2 3 4 5 6 7 8 9 10 11 let s1 = Symbol .for ('foo' );let s2 = Symbol .for ('foo' );s1 === s2 Symbol .for ("bar" ) === Symbol .for ("bar" )Symbol ("bar" ) === Symbol ("bar" )
检查某个属性是否已经登记,Symbol.keyFor()
方法返回已登记的 Symbol
类型值的参数值。
使用Symbol.keyFor()
来查询全局符号注册表。通过接收参数为 symbol
类型,返回该 symbol 对应的字符串键,如果查询不到则返回 undefined
Symbol.keyFor
仅对 Symbol.for()
方法创建的 symbol 实例才可以查找到,如果是 Symbol()
方法创建将返回 undefined。
1 2 3 4 5 let s1 = Symbol .for ("foo" );Symbol .keyFor (s1) let s2 = Symbol ("foo" );Symbol .keyFor (s2)
Symbol应用场景 作为对象属性名
需要使用 Symbol 对象属性的时候,用 obj[]
,必须使用 []
方式获取属性,不能用点运算符。 同理,在对象的内部,使用 Symbol
值定义属性时,Symbol
值必须放在方括号之中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let mySymbol = Symbol ();let a = {};a[mySymbol] = 'Hello!' ; let a = { [mySymbol]: 'Hello!' }; let a = {};Object .defineProperty (a, mySymbol, { value : 'Hello!' });a[mySymbol]
Symbol 类型用于定义一组常量,保证这组常量的值都是不相等的。(高级用法)
1 2 3 4 5 6 7 8 9 const log = {};log.levels = { DEBUG : Symbol ('debug' ), INFO : Symbol ('info' ), WARN : Symbol ('warn' ) }; console .log (log.levels .DEBUG , 'debug message' );console .log (log.levels .INFO , 'info message' );
获取Symbol类型属性 Symbol
创建的值是不可枚举 的。
可获取symbol
Object.assign
将属性从源对象复制到目标对象,会包含 Symbol
类型作为 key
的属性(不可枚举属性不会复制 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 const symbolKey = Symbol ('key' );const source = { [symbolKey]: 'Symbol Property' , regularProperty : 'Regular Property' }; Object .defineProperty (source,"w" ,{ value :456 , enumerable :true , configurable :true , writable :true }) Object .defineProperty (source,"r" ,{ value :123 , enumerable :false , configurable :false , writable :false }) const target = {};Object .assign (target, source);console .log (target);
Object.getOwnPropertySymbols
方法可以获取指定对象的所有 Symbol
属性名
Reflect.ownKeys
方法可以获取指定对象的所有 Symbol
属性名
1 2 3 4 5 6 7 8 9 let symbol = Symbol ('test' );let obj = {[symbol]:123 };for (const key in obj){ console .log (key); } console .log (obj[symbol]); console .log (Object .keys (obj)); console .log (Object .getOwnPropertySymbols (obj)); console .log (Reflect .ownKeys (obj));
不可获取symbol 因为Symbol 创建的值是不可枚举的,所以一般遍历对象的结果都不会包含 symbol 内容
for in
循环 :循环会遍历对象的可枚举属性 ,但会忽略不可枚举的属性
Object.keys()
:方法返回一个数组,其中包含对象的所有可枚举属性 的名称。
JSON.stringify()
:只会序列化对象的可枚举属性 ,而不会包含不可枚举属性
JSON.stringify
的时候,如果对象中 key
或者 value
都是 Symbol类型时候。转换过程会把它忽略掉
JSON.stringify
直接转换 symbol类型数据,转换后的结果为 undefined
JSON.stringify
转换的是一个对象,无论 key
还是 value
中有 symbol类型,都会忽略掉
Object.getOwnPropertyNames()
:返回一个数组,其中包含对象的所有属性( 包括不可枚举属性 )的名称,但是 不包括使用symbol
值作为名称的属性
使用Symbol来替代常量(消除魔术字符串)
魔术字符串 指的是,在代码中 多次出现、与代码形成强耦合 的某一个具体的字符串或者数值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getArea (shape, options ) { let area = 0 ; switch (shape) { case 'Triangle' : area = .5 * options.width * options.height ; break ; } return area; } getArea ('Triangle' , { width : 100 , height : 100 });
常用的消除魔术字符串的方法,就是把 Triangle
写成一个变量 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const shapeType = { triangle : 'Triangle' } function getArea (shape, options ) { let area = 0 ; switch (shape) { case shapeType.triangle : area = .5 * options.width * options.height ; break ; } return area; } getArea (shapeType.triangle , { width : 100 , height : 100 });
这样就消除了代码的强耦合性。分析发现shapeType.triangle
的值是什么无关紧要,只要保证不与shapeType其他属性值冲突就好 。
1 2 3 4 const shapeType = { triangle : Symbol () }
使用Symbol定义类的私有属性/方法
由于Symbol常量 PASSWORD
被定义在a.js
所在的模块中,外面的模块获取不到这个Symbol,也 不可能再创建一个一模一样的Symbol出来(因为Symbol是唯一的) ,因此这个PASSWORD的Symbol只能被限制在a.js
内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const PASSWORD = Symbol ()class Login { constructor (username, password ) { this .username = username this [PASSWORD ] = password } checkPassword (pwd ) { return this [PASSWORD ] === pwd } } export default Login import Login from './a' const login = new Login ('admin' , '123456' )login.checkPassword ('123456' ) login.PASSWORD login[PASSWORD ] login["PASSWORD" ]
Symbol内置属性 Symbol.iterator
Symbol.iterator
用来为对象定义默认的迭代器。它被用来在for-o
f循环中实现对对象的迭代,或用于扩展操作符。**Array
,Map
,Set
,String
都有内置的迭代器**。但是普通对象是不支持迭代器功能的,也就不能使用 for of 循环遍历。
接下来使用 Symbol.iterator
实现一个可迭代对象:
Symbol.iterator属性中使用next 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let symbolObjTest1 = { 0 :"a" , 1 :"b" , 2 :"c" , length :3 , [Symbol .iterator ]:function ( ){ let index = 0 ; return { next ( ){ return { value :symbolObjTest1[index++], done :index>symbolObjTest1.length } } } } } for (const iterator1 of symbolObjTest1){ console .log (iterator1); }
Symbol.iterator属性中使用Generator 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let symbolObjTest2 = { 0 :"d" , 1 :"e" , 2 :"f" , length :3 , [Symbol .iterator ]:function *(){ let index = 0 ; while (index<symbolObjTest2.length ){ yield symbolObjTets2[index++] } } } for (const iterator2 of symbolObjTest2){ console .log (iterator2); }
不影响原始对象遍历,遍历正常返回key value 1 2 3 4 5 6 7 8 9 const obj = {a :1 ,b :2 ,c :3 };obj[Symbol .iterator ] = function *(){ for (const key of Object .keys (this )){ yield [key,this [key]] } } for (const [key,value] of obj){ console .log (`${key} :${value} ` ); }
将一个class对象实现支持迭代器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Animal { constructor (name,sex,isMammal ){ this .name = name; this .sex = sex; this .isMammal = isMammal; } } class Zoo { constructor ( ){ this .animals = []; } addAnimals (animal ){ this .animals .push (animal); } [Symbol .iterator ](){ let index = 0 ; const animals = this .animals ; return { next ( ){ return { value :animals[index++], done :index>animals.length } } } } } const zoo = new Zoo ();zoo.addAnimals (new Animal ('dog' ,'victory' ,true )); zoo.addAnimals (new Animal ('pig' ,'defeat' ,false )); zoo.addAnimals (new Animal ('cat' ,'defeat' ,false )); for (const animal of zoo) { console .log (`${animal.name} ;${animal.sex} ;${animal.isMammal} ` ) }
Symbol.toStringTag
Symbol.toStringTag
官方描述是一个字符串值属性,用于创建对象的默认字符串描述。由 Object.property.toString()
方法 内部访问
最常见的场景是判断类型:
1 2 3 4 5 6 const toStringCallFun = Object .prototype .toString .call ;toStringCallFun (new Date ); toStringCallFun (new String ); toStringCallFun (Math ); toStringCallFun (undefined ); toStringCallFun (null );
默认情况下,toString()
方法被每个 Object 对象继承,如果此方法在自定义对象中未被覆盖, toString()
返回“ [object type]
”,其中 type
是对象的类型 。
1 2 3 4 5 6 7 8 9 10 11 12 13 function Student (score,age ){ this .score = score; this .age = age; } let student = new Student ('100' ,13 );console .log (student.toString ()); Student .prototype .toString = function ( ){ return `年龄${this .age} ;成绩${this .score} ` } console .log (student.toString ());
在ES6 之后大多数内置的对象提供了它们自己的 Symbol.toStringTag
标签,toString
时回默认返回 Symbol.toStringTag
键对应的值。比如:
1 2 3 4 Object .prototype .toString .call (new Map ()); Object .prototype .toString .call (function * () {}); Object .prototype .toString .call (Promise .resolve ());
但是在早期不是所有对象都有 toStringTag
属性,没有 toStringTag
属性的对象也会被toString()
方法识别并返回特定的类型标签。如下:
1 2 3 4 5 6 7 let toStringFunc = Object .prototype .toString toStringFunc.call ('foo' ) toStringFunc.call ([1 , 2 ]) toStringFunc.call (3 ) toStringFunc.call (true ) toStringFunc.call (undefined ) toStringFunc.call (null )
自己创建的类,toString()
找不到 toStringTag
属性!只会默认返回 Object
标签。
给类增加一个 toStringTag
属性,自定义的类也就拥有了自定义的类型标签
1 2 3 4 5 6 class TestClass { get [Symbol .toStringTag ](){ return "TestToStringTag" } } Object .prototype .toString .call (new TestClass ());
Symbol.toPrimitive
Symbol.toPrimitive
用来指定对象在隐式调用valueOf
和toString
方法时的行为。可以用它来为对象提供自定义的字符串和数字表示形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Life { valueOf ( ) { return 42 ; } [Symbol .toPrimitive ](hint) { switch (hint) { case "number" : return this .valueOf (); case "string" : return "Forty Two" ; case "default" : return true ; } } } const myLife = new Life ();console .log (+myLife); console .log (`${myLife} ` ); console .log (myLife + 0 ); console .log (myLife - 0 );
Symbol.asyncIterator
Symbol.asyncIterator
用来为对象定义一个异步的迭代器 。可以用它来为对象启用异步迭代 。它可以用于遍历异步数据流,比如异步生成器函数、异步可迭代对象等。这个特性在需要处理异步数据流时非常有用。
举一个实际的应用场景:假设正在开发一个异步数据源处理器,其中包含了大量的异步数据,比如网络请求、数据库查询等。这些数据需要被逐个获取并处理,同时由于数据量非常大,一次性获取全部数据会导致内存占用过大,因此需要使用异步迭代器来逐个获取数据并进行处理 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class AsyncDataSource { constructor (data ) { this ._data = data; } async *[Symbol .asyncIterator ]() { for (const item of this ._data ) { const result = await this ._processAsyncData (item); yield result; } } async _processAsyncData (item ) { return new Promise ((resolve ) => { setTimeout (() => { resolve (item.toUpperCase ()); }, 1000 ); }); } } async function processData ( ) { const dataSource = new AsyncDataSource (['a' , 'b' , 'c' , 'd' , 'e' ]); for await (const data of dataSource) { console .log (data); } } processData ();
Symbol.hasInstance
Symbol.hasInstance
用来确认一个对象是否是构造函数的实例 。它可以用来更改 instanceof
操作符的行为 。
1 2 3 4 5 6 7 8 class MyArray { static [Symbol .hasInstance ](instance) { return Array .isArray (instance); } } const arr = [1 , 2 , 3 ];console .log (arr instanceof MyArray );
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable
用来确定对象在与其他对象连接时是否应该被展开 。它可以用来更改 Array.prototype.concat
方法的行为 。
1 2 3 4 const arr1 = [1 , 2 , 3 ];const spreadable = { [Symbol .isConcatSpreadable ]: true , 0 : 4 , 1 : 5 , 2 : 6 , length : 3 };console .log ([].concat (arr1, spreadable));
改成false
后:
Symbol.species
Symbol.species
用来指定创建派生对象时要使用的构造函数 。它可以用来自定义创建新对象的内置方法的行为 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class MyArray extends Array { static get [Symbol .species ]( "Symbol.species" ) { return Array ; } } const myArray = new MyArray (1 , 2 , 3 );const mappedArray = myArray.map (x => x * 2 );console .log (mappedArray instanceof MyArray ); console .log (mappedArray instanceof Array ); class MyArray extends Array { static get [Symbol .species ]( "Symbol.species" ) { return MyArray ; } } const myArray = new MyArray (1 , 2 , 3 );const mappedArray = myArray.map (x => x * 2 );console .log (mappedArray instanceof MyArray ); console .log (mappedArray instanceof Array );
Symbol.match
Symbol.match
用来在使用 String.prototype.match
方法时确定要搜索的值。 它可以用来更改类似于RegExp对象的 match
方法的行为 。
1 2 3 4 5 6 7 8 9 const myRegex = /test/ ;'/test/' .startsWith (myRegex); const re = /foo/ ;re[Symbol .match ] = false ; "/foo/" .startsWith (re); "/bar/" .endsWith (re);
Symbol.matchall
Symbol.matchAll
内置通用(well-known)符号指定方法返回一个迭代器,该迭代器根据字符串生成正则表达式的匹配项。
此函数可以被String.prototype.matchAll()
方法调用。
1 2 3 4 5 6 const myRegex = /foo/g ;const str = 'How many foos in the the foo foo bar?' ;for (let result of myRegex[Symbol .matchAll ](str)) { console .log (result); }
Symbol.replace
Symbol.replace
用来在使用 String.prototype.replace
方法时确定替换值 。它可以用来更改类似于RegExp对象的 replace
方法的行为 。
1 2 3 4 5 6 7 let replaceHyphens = { [Symbol .replace ](string, replacer) { return string.replace (/-/g , replacer); } }; console .log ('123-45-678' .replace (replaceHyphens, ':' ));
Symbol.search
Symbol.search 属性定义了当 String.prototype.search()
方法被调用时,如何返回字符串中匹配项的索引 。
1 2 3 4 5 6 7 let searchObject = { [Symbol .search ](string) { return string.indexOf ('JavaScript' ); } }; console .log ('Hello JavaScript!' .search (searchObject));
Symbol.split
Symbol.split
用来在使用 String.prototype.split
方法时确定分隔值 。它可以用来更改类似于RegExp对象的 split
方法的行为 。
1 2 3 4 5 6 7 8 9 const customSplit = str => str.split (/\d+/ );const customRegExp = { [Symbol .split ]: customSplit }; const string = "foo123bar456baz" ;string.split (customRegExp);
Symbol.unscopables
Symbol.unscopables
用于确定应该从 with
语句的作用域中排除哪些对象属性 。它可以用来更改 with
语句的行为 。
1 2 3 4 5 6 7 8 9 10 11 12 13 const person = { age : 42 }; person[Symbol .unscopables ] = { age : true }; with (person) { console .log (age); }
Symbol.dispose
“显式资源管理”是指用户通过使用命令式方法(如Symbol.dispose
)或声明式方法(如使用块作用域声明)显式地管理“资源”的生命周期的系统。
Symbol.dispose
是 JavaScript 中的一个新的全局符号。任何带有 Symbol.dispose
功能的都被视为“资源”—— “具有特定生命周期的对象” ——并且可以与关键字 using
一起使用。
1 2 3 4 5 const resource = { [Symbol .dispose ]: () => { console .log ("Hooray!" ); }, };
还可以使用 Symbol.asyncDispose
and await using
来处理需要异步处理的资源:
1 2 3 4 5 6 7 8 const getResource = ( ) => ({ [Symbol .asyncDispose ]: async () => { await someAsyncFunc (); }, }); { await using resource = getResource (); }
这将在继续之前等待 Symbol.asyncDispose
函数。
这对于数据库连接等资源很有用,例如您希望在这些资源中确保连接在程序继续运行之前关闭。
下面是一些例子:
文件处理 没有 using
: 1 2 3 4 5 6 7 import { open } from "node:fs/promises" ;let filehandle;try { filehandle = await open ("thefile.txt" , "r" ); } finally { await filehandle?.close (); }
使用 using
: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { open } from "node:fs/promises" ;const getFileHandle = async (path: string ) => { const filehandle = await open (path, "r" ); return { filehandle, [Symbol .asyncDispose ]: async () => { await filehandle.close (); }, }; }; { await using file = getFileHandle ("thefile.txt" ); }
数据库连接 使用 using
管理数据库连接是 C#
中的一个常见用例。
没有 using
: 1 2 3 4 5 6 const connection = await getDb ();try { } finally { await connection.close (); }
使用 using
: 1 2 3 4 5 6 7 8 9 10 11 12 13 const getConnection = async ( ) => { const connection = await getDb (); return { connection, [Symbol .asyncDispose ]: async () => { await connection.close (); }, }; }; { await using { connection } = getConnection (); }