js之Symbol类型
文章目錄
- ECMA2019資料
- 引入Symbol類型的背景
- Symbol類型簡介
- Symbol特性(所有數據類型都需要探討的問題)
- 創建
- Symbol值的描述
- Symbol值的類型轉換與運算
- 類型轉換
- 類型轉換舉例
- 涉及Symbol類型的運算: +-*/%
- 全局Symbol表
- Symbol.for() 與 Symbol.keyFor()
- 全局Symbol表模型
- 注意事項
- 全局的測試
- Symbol類型的辨析和理解
- Symbol值作為屬性名
- 含Symbol名屬性的對象的遍歷
- ES6內置Symbol值
- 說明:
- 簡介
- instanceof相關--Symbol.hasInstance
- 類數組連接展開--Symbol.isConcatSpreadable
- 指定生成衍生對象的構造函數--訪問器屬性Symbol.species
- String.prototype.match匹配相關--Symbol.match
- ToPrimitive抽象操作--Symbol.toPrimitive
- 類型字符串--Symbol.toStringTag
ECMA2019資料
官方文檔地址
ECMA2019離線版(html版+pdf版+ES6入門)
引入Symbol類型的背景
-
ES5 的對象屬性名都是字符串,這容易造成屬性名沖突的問題
舉例: 使用別人的模塊/對象, 又想為之添加新的屬性,這就容易使得新屬性名與原有屬性名沖突
Symbol類型簡介
-
Symbol是一種原始數據類型
- 其余原始類型: undefined 、 null 、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)
- Symbol表示獨一無二的值
- Symbol類型的"真實值"無法獲取,也就是說Symbol類型沒有對應的字面量
- Symbol類型的意義在于區分彼此和不重復,不在于真實值
Symbol特性(所有數據類型都需要探討的問題)
創建
- Symbol值只能通過Symbol()函數生成
- Symbol()函數前不能使用new命令(Symbol類型是原始值,不是對象)
- Symbol類型不能添加屬性(不是對象)
- Symbol類型是一種類似字符串的類型(可用作屬性名
ECMA2019標準相關:
?
? 解讀: (NewTarget是使用new命令調用函數時會創建的原生對象)
? (1) 如果NewTarget不為undefined(也就是使用了new命令), 拋出錯誤==>Symbol函數前不能使用new
? (2) 描述字符串description保存在symbol值內部的[[Description]]中
Symbol值的描述
-
Symbol 函數可以接受一個字符串作為參數
-
Symbol 函數的參數只是表示對當前 Symbol 值的描述
-
Symbol值的描述: 幫助開發者區分Symbol值
- 在控制臺打印兩Symbol值時,能區分開來
- 轉為字符串時,能區分開來
Symbol值的類型轉換與運算
類型轉換
根據ECMA2019,:
- 類型轉換是用一些抽象操作來描述的
- 隱式轉換是直接調用某一個抽象操作
- 顯示轉換是抽象操作的包裝(加入判斷和控制等)
- 類型的構造函數 當做 函數使用(例如: String())
- 一些可能調用抽象操作的方法(例如: toString()或valueOf())
主要的類型轉換抽象操作:
1.對象轉原始類型:
2.轉Boolean:
3.轉Number:
4.轉字符串:
5.原始類型轉對象:
如上圖, 直接對symbol值應用抽象操作(隱式轉換):
- ToBoolean(Symbol)==>true
- ToString(Symbol)==>報錯
- ToNumber==>報錯
類型轉換舉例
let sym = Symbol('My symbol');"your symbol is" + sym; //TypeError: can't convert symbol to string `your symbol is ${sym}`; //TypeError: can't convertString(sym); //'Symbol(My symbol)' sym.toString(); //'Symbol(My symbol)'為什么上述代碼中String()和sym.toString()可以成功將symbol轉換為字符串?
ECMA2019:
(1)Symbol作為原始類型, 有對應的包裝對象類型, 所以我們可以用sym.toString() 調用方法而不出錯.
更進一步,我們測試一下Symbol類型的實例是否可改變和添加屬性:
let s = Symbol('s');//實例對象的對象保護檢測 console.log(`s實例是否可擴展: ${Object.isExtensible(s)}`); console.log(`s實例是否被凍結: ${Object.isFrozen(s)}`); console.log(`s實例是否被封閉: ${Object.isSealed(s)}`); //輸出: // s實例是否可擴展: false // s實例是否被凍結: true // s實例是否被seal: true//實例對象是否存在toString實例方法與[[Symbol.toStringTag]] console.log(`s實例是否有自定義toString方法: ${s.hasOwnProperty('toString')}`); console.log(`s實例是否有[[Symbol.toStringTag]]: ${s.hasOwnProperty(Symbol.toStringTag)}`); //輸出: // s實例是否有自定義toString方法: false // s實例是否有[[Symbol.toStringTag]]: false//----------------------------------------------------------------------------------------------------------//Symbol.prototype的對象保護檢測 console.log(`Symbol.prototype是否可擴展: ${Object.isExtensible(Symbol.prototype)}`); console.log(`Symbol.prototype是否被凍結: ${Object.isFrozen(Symbol.prototype)}`); console.log(`Symbol.prototype是否被封閉: ${Object.isSealed(Symbol.prototype)}`); //輸出: // Symbol.prototype是否可擴展: true // Symbol.prototype是否被凍結: false // Symbol.prototype是否被封閉: false總結:
- Symbol類的實例是不可擴展的,不能添加,修改和刪除屬性
- 因此, sym.toString()所調用的,必定是Symbol.prototype.toString()原型方法
- 而Symbol.prototype.toString()調用Symbol.DescriptiveString(sym),返回一個組合字符串: "Symbol(" + 描述字符串 + ")"
(2)String(sym)可行的原因:
可見, 在進行ToString抽象操作之前,對參數進行了判斷, 并對為Symbol類型的情況另行處理(調用Symbol.DescriptiveString(value)),故而沒有發生我們預期中的報錯.
涉及Symbol類型的運算: ±*/%
結論: 一律報錯
原因:(ECMA2019)
加法:
減法:
乘除余:
可見, 進行運算,必然逃不過要進行抽象操作 ToNumber,而對Symbol值進行ToNumber抽象操作就會報錯.因此,Symbol值無法參與運算.
全局Symbol表
Symbol.for() 與 Symbol.keyFor()
-
應用背景:
- Symbol值解決了對象屬性不被覆蓋問題
- 但Symbol值只能作用在局部
- node環境: 不同時導出Symbol值, 導出對象的對應屬性無法被其他模塊主動訪問
- 瀏覽器屬性: 不同時傳遞Symbol值, 對象的對應屬性不能被其他iframe主動訪問
- 為此,出現了全局Symbol表及管理它的兩個函數Symbol.for()與Symbol.keyFor()
-
Symbol.for()函數
ECMA2019:
-
Symbol.keyFor()函數
ECMA2019:
全局Symbol表模型
注意事項
? Symbol.for()與Symbol.keyFor()都是針對全局Symbol表進行查詢和新建的, 其余位置的Symbol值不會被訪問或影響.
全局的測試
- node: 全局環境–js程序的運行環境(在各個模塊之上)
- 瀏覽器端: 全局環境–在各個frame之上
Symbol類型的辨析和理解
-
symbol數據類型的真實值
-
與symbol值關聯的字符串其實是 它的描述,方便控制臺打印時區分各個symbol值,而不是symbol值的真實值
-
symbol類型的真實值是無法獲取和訪問的,也并不重要,因為不會被開發者用到
-
symbol類型的意義在于它的唯一不可重復性,而不在于其真實值
-
-
如何理解symbol類型的值
-
symbol類型值的特點
- 新的原始數據類型(不同于number,string,boolean等)
- 可以充當屬性名和變量名(類似string)
- 除非是同一句symbol()生成的值,否則不相等(類似object)
根據以上特點,這個symbol類型的值特別像我們日常生活中的一種東西–二維碼. 二維碼的特點如下:
-
屬于圖片(不是數值,也不是字符串)(新的類型)
-
可以完成字符串的一些功能
-
掃碼得文本,掃碼獲得網址(文本和網址等都屬于字符串)
舉例: 二維碼生成網站
-
-
我們可以把symbol類型理解成 包含特定信息的二維碼
它包含的特定信息有三個:
- 生成它的symbol()代碼調用的行號
- 生成它的symbol()代碼調用的起始列號
- 該symbol()代碼調用所在文件的完整文件路徑(包含文件名)
通過這三個特定信息,我們不難看出每一個symbol()調用生成的symbol值(二維碼)都一定是唯一且不可重復的: (舉例說明)
完整文件路徑
行號與起始列號
行號起始列號 t1 1 10 t2 1 29 t3 2 10 由此可見: 要想三個信息都符合, 那必須是同一個文件中的同一句Symbol()生成的同一個symbol值.因此是唯一不可重復的.
接著,我們將這三個信息生成二維碼:
接著,我們用t1代表的symbol值作為屬性名給obj對象添加屬性:
var t1 = Symbol(); var t2 = Symbol(); var t3 = Symbol();console.log(`t1==t2: ${t1==t2} t1==t3: ${t1==t3} t2==t3: ${t2==t3}`); //全是falsevar obj = {[t1]: function(){console.log('hello_world');} };obj[t1]();
我們把symbol類型值比作二維碼的話, obj[t1](); 就相當于:
-
Symbol值作為屬性名
-
概述:
- 每個Symbol值均不相等,獨一無二
- Symbol值作為標識符
- 完全避免屬性同名問題
- 防止方法/屬性被不小心覆蓋
-
定義Symbol屬性名的三種方法:
-
當做普通字符串使用(不必加引號)
let mySymbol = Symbol();let a ={}; a[mySymbol] = 'hello'; -
字面量對象內的方括號定義
let mySymbol = Symbol();let a = {[mySymbol]:'hello' }; -
用Object.defineProperty定義(實質上還是直接當做普通字符串)
let mySymbol = Symbol();let a = {}; Object.defineProperty(a, mySymbol, {value: 'hello'});
-
-
注意事項
-
不能用點運算符訪問Symbol名的屬性
-
字面量方括號定義法中,方括號是必須的,否則仍是一個普通字符串名屬性
-
因為每一個symbol值都是獨一無二的
要使用以symbol為名的屬性,只能在定義symbol值的模塊內使用
要想將該對象的該屬性開放給其模塊訪問,必須同時導出: 對象 + symbol值(該特性可以用于分配和限制屬性的訪問權限?)
- 有權限的,導出symbol值
- 無權限的,不導出symbol值
-
含Symbol名屬性的對象的遍歷
- Symbol名屬性的鍵值無法被常規遍歷方法發現,包括:
- for…in
- for…of
- Object.keys()
- Object.getOwnPropertyNames()
- JSON.stringify()
- 獲取方法:
- Object.getOwnPropertySymbols()
- Reflect.ownKeys()–常規鍵名+Symbol鍵名
ES6內置Symbol值
說明:
-
這些值保存在Symbol的Constructor函數對象的屬性之中
-
這些值通常指向對象的內部方法
-
通過這些Symbol值修改對象內部方法不一定有效:
-
對象可能被凍結或封閉(例如: Symbol類型值)
-
Symbol名屬性的配置項可能被設為不可配置與不可修改
(即屬性描述符為:{configurable:false,writable:false})
-
簡介
instanceof相關–Symbol.hasInstance
foo instanceof Foo; ==> Foo[Symbol.hasInstance](foo)
下例展示了一個對instanceof的欺騙:
class Myclass{[Symbol.hasInstance](foo){return foo instanceof Array;} }[1,2,3] instanceof new Myclass; //true類數組連接展開–Symbol.isConcatSpreadable
? 該屬性規定了該對象用于Array.prototype.concat() 時,是否可以展開
let arr1 = ['c','d']; ['a','b'].concat(arr1,'e'); //['a','b','c','d','e'] arr1[Symbol.isConcatSpreadable]; //undefinedlet arr2 = ['c','d']; arr1[Symbol.isConcatSpreadable] = false; ['a','b'].concat(arr2,'e'); //['a','b',['c','d'],'e']ECMA2019相關資料:
(1)Array.prototype.concat
(2)IsConcatSpreadable(O)
解讀:
-
只在調用Array.prorotype.concat()函數中生效,
- 其他concat函數中無效(沒有針對該symbol名屬性的操作)
-
根據上述資料:
-
不僅可用于數組,也可以用于類數組對象(有length屬性,有數字鍵名)
-
不設置該symbol名屬性時(undefined值),數組與類數組表現相反:
這是因為IsConcatSpreadble(o)資料中的最后一句:return ?IsArray(o)
也就是說: 無定義時,數組默認展開,類數組默認不展開
-
數組/類數組之中的空位(數字鍵不連續)會保留到合并后的新數組中
-
指定生成衍生對象的構造函數–訪問器屬性Symbol.species
-
Symbol.species
-
是一個構造函數(類)的訪問器屬性,需要用get設置
-
存在于一些可進行衍生的類中
ECMA2019資料:
21.2.4.2get RegExp [ @@species ]
22.1.2.5get Array [ @@species ]
22.2.2.4get %TypedArray% [ @@species ]
23.1.2.2get Map [ @@species ]
23.2.2.2get Set [ @@species ]
24.1.3.3get ArrayBuffer [ @@species ]
24.2.3.2get SharedArrayBuffer [ @@species ]
25.6.4.6get Promise [ @@species ] -
該Symbol值代表的屬性(訪問器的返回值)是一個構造函數,被用于構造衍生對象
-
默認值: this.constructor,也就是用于創建衍生對象的原對象實例的構造函數
-
-
舉例說明: (以數組的map方法為例)
代碼:
class MyArray extends Array {static get [Symbol.species](){return Array;}introduce(){console.log('I am a MyArray instance');} }const a = new MyArray(); const b = a.map(x=>x); //利用Array.prototype.map()創建衍生對象bconsole.log(a.constructor); //[Function: MyArray] console.log(b instanceof MyArray); //false console.log(b.introduce); //undefined資料及知識補充:
Array.prototype.map
ArraySpeciesCreate
由上圖,可見, 確實是在創建衍生對象時,獲取了原對象的構造函數的Symbol.species屬性作為衍生對象的構造函數.結合示例代碼:
(1)原對象: 對象a–MyArray類的實例
(2)原對象的構造函數: MyArray類的構造函數
(3)原對象的構造函數的Symbol.species: 返回值為數組的構造函數–Array
String.prototype.match匹配相關–Symbol.match
因此: str.match(obj)等同于obj[Symbol.match](str)
class MyMatcher {[Symbol.match](str){return 'hello world'.indexOf(str);} }'e'.match(new MyMatcher()); //1同系列的其他Symbol:
- Symbol.search
- Symbol.replace
- Symbol.split
ToPrimitive抽象操作–Symbol.toPrimitive
? 其值是一個將對象類型轉換為原始類型值的方法, 被抽象操作ToPrimitive調用.
如上圖, 可見該函數接收一個字符串參數hint(三種取值對應三種模式):
- Number:需要轉成數值
- String:需要轉成字符串
- Default: 數值,字符串皆可(number)
示例代碼:
let obj = {[Symbol.toPrimitive](hint){switch(hint){case 'number': return 123;case 'default': return 'default';case 'string': return 'str';default: throw new Error();}} };console.log(2 * obj); //246 console.log(3 + obj); //3default, 此時hint為default,詳見ECMA2019 加號(addtion operator) console.log(String(obj)); //str類型字符串–Symbol.toStringTag
對象的 Symbol.toStringTag 屬性的值是一個字符串
在該對象上調用 Object.prototype.toString 方法,若該屬性存在,其值會出現在 toString 方法返回的字符串之中,表示對象的類型。也就是說,這個屬性可以用來定制[object Object] 或 [object Array] 中 object 后面的那個字符串.
ECMA2019資料:
總結
以上是生活随笔為你收集整理的js之Symbol类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 050_Scrapy 爬虫框架 案例四
- 下一篇: 无法访问Windows7默认共享的解决方