符号Symbol介绍及应用
前言
隨著前端不斷的發展,ES5 原本的 6 種數據類型 number、string、boolean、undefined、null 、object 已經不夠使用,所有 ES6 新增符號數據類型 symbol 基礎類型,相信大家會發現在工作中很少會使用到 symbol 從而導致認為符號沒有什么作用,而實際上符號的出現減少了 js 中一些魔法使用,下面我們就對符號 symbol 深入學習
符號是ES6新增的一個數據類型,它通過使用函數 Symbol(符號描述)來創建符
符號設計的初衷,是為了給對象設置私有屬性
共享符號可以根據某個符號名稱(符號描述)能夠得到同一個符號,通過使用函數 Symbol.for("符號名/符號描述")來創建
Symbol 特點
-
沒有字面量(每個Symbol 都是通過Symbol函數創建)
-
每次調用 Symbol 函數得到的符號永遠不相等,無論符號名是否相同
-
符號可以作為對象的屬性名存在,這種屬性稱之為符號屬性(es6 之前對象屬性只能是字符串或者數字)
-
符號無法被隱式轉換,不能被用于數學運算、字符串拼接或其他隱式轉換,但符號可以通過 String 構造函數顯式的轉換為字符串
-
不能被 new執行(不能進行構造函數) 但是有原型
Symbol.iterator
該符號可以判斷數據是否擁有迭代功能,它可以影響數據迭代器遍歷功能
// 對象默認不具備 Symbol.iterator 屬于不可以被遍歷的數據結構 // 具有可迭代數據有: argument / NodeList / HTMLcollection / String / Set / Map / generator // 改寫對象使對象也具有 for of 遍歷 Object.prototype[Symbol.iterator] = function () {let self = this;let keys = Reflect.ownKeys(this); // 獲取所有 key 屬性只包含 Symbol 符號屬性let index = 0;return {next() {let key = keys[index++];// 遍歷結束if (index > keys.length) {return {done: true,value: undefined}}return {done: false,value: {key,value: self[key]}}}} } // 測試原本對象不具有 for of 現在已經具備 var ob = {a: 1,b: 2,c: 3,d: 5 } for (var key of ob) {console.log(key); } // 由上面結果可知我們可以使用 Symbol.iterator 進行一些不具備迭代數據進行改造以此滿足需求Symbol.hasInstance
該符號用于定義構造函數的靜態成員,它將影響 instanceof 的判定
// instanceof 實現原理是 obj instanceof fn ==> fn[Symbol.hasInstance](obj) 先查看原型中 fn[Symbol.hasInstance](obj) 返回值 // 寫法一(構造函數) function A() { } Object.defineProperty(A, Symbol.hasInstance, {value: function (obj) {return false; // 所有都返回 false} }) const obj = new A(); console.log(A[Symbol.hasInstance](obj)); // false console.log(obj instanceof A); // 實際等同上面面寫法// 寫法二(類) class Fn {constructor() { this.x = Symbol.for("xxx"); // 設置知名符號} static [Symbol.hasInstance](obj) {return obj.x && obj.x === Symbol.for("xxx");} } // 方法二只要不是 Fn 創建的構造函數使用 instanceof 返回都是 falseSymbol.isConcatSpreadable
該知名符號會影響數組的 concat 方法
// 數組中使用 concat 方法合并根據連接的數據是否為數組或者類數組進行分割合并 const arr = [3]; const arr2 = [5, 6, 7, 8]; arr2[Symbol.isConcatSpreadable] = false; const result = arr.concat(56, arr2) console.log(result) // [3, 56, [5,6,7,8]] // 類數組 var obj = {0: 8,1: 9,2: 10,length: 3,[Symbol.isConcatSpreadable]: true, // 使用時會進行類數組拆分為數組每項進行拼接,如果不設置則直接連接類數組對象 } const result2 = arr.concat(4, obj); console.log(result2); // [ 3, 4, 8, 9, 10 ] // 由上面可值類數組設置 Symbol.isConcatSpreadable 時可以進行拆分每項進行添加而不是添加整體Symbol.toPrimitive
該知名符號會影響類型轉換的結果
// Symbol.toPrimitive 知名符號可以改變引用類型轉化為基礎類型結果 class Temperature {constructor(degree) {this.degree = degree;}// 注意不是靜態方法哦[Symbol.toPrimitive](type) {// type: "default" "string" "number"if (type === "default") {return this.degree + "攝氏度";}else if (type === "number") {return this.degree;}else if (type === "string") {return this.degree + "℃";}} } const t = new Temperature(30); console.log(t + ""); // 30攝氏度 console.log(t / 2); // 15 console.log(String(t)); // 30C// 請問下面函數參數為何時候能打印 function isTrue(o) {// 注意不要全等不然真沒有結果if (o == 1 && o == 2 && o == 3) {console.log("ojbk");} } // 方法一 var o = {a: 1,[Symbol.toPrimitive](hint) {return this.a ++;} } // 方法二 var o = {a: 1,valueOf() {return this.a++;} } // 方法三 var o = {a: 1,toString() {return this.a++;} } // 此題就是利用引用類型隱式轉化為基本類型首先看 [Symbol.toPrimitive]() 然后看 valueOf() 最后看 toString()Symbol.toStringTag
該知名符號會影響 Object.prototype.toString 的返回值
// Symbol.toStringTag 可以實現使用 toString 判斷類型時更加精確的知道自身的構造函數 // 寫法一 class Person {constructor() {// 通過 Symbol.toStringTag 改寫 Object.prototype.toString.call 更準確this[Symbol.toStringTag] = "Person"} } // 寫法二 Person .prototype[Symbol.toStringTag] = "FN" // 等價上面寫法 const p = new Person(); const arr = []; console.log(Object.prototype.toString.apply(p)); //[object Person] console.log(Object.prototype.toString.apply(arr)) //[object Array] // 由上面可知我們可以改造 toString() 返回更加精確的值Symbol 應用
-
對象設置私有屬性,(類中設置外部不能訪問的屬性方法)
-
給對象設置唯一屬性值,(實現 Function.prototype.call 方法)
-
框架中redux/vuex公共管理狀態管理時候,派發的行為標識可以基于Symbol類型進行宏管理
實現類中私有屬性
// 實現類中私有屬性使其外面不能訪問到只能內部使用 const Hero = (() => {const getRandom = Symbol();return class {constructor(a) {this.a= a;}attack() {const dmg = this.a* this[getRandom](0.8, 1.1);console.log(dmg);}// 私有屬性隨機函數[getRandom](min, max) { return Math.random() * (max - min) + min;}} })(); const h = new Hero(3); h.attack(); // 不報錯 h[Symbol]() // 報錯 // 但是我們也可以使用一些不尋常的手段獲取使用 const sybs = Object.getOwnPropertySymbols(Hero.prototype); console.log(h[sybs[0]](3)); // 不報錯 // 但是強烈不建議上面使用類中的私有屬性手寫實現call方法
// 手寫實現自己 mycall 方法 Function.prototype.myCall = function (content, ...arg) {content = (content === undefined || content === null) ? (typeof window === "undefined" ? {} : window) : Object(content); // 這是確定 this 指向判斷是否node環境,如果傳入基礎類型進行變為包裝類型let key = Symbol(); // 唯一值content[key] = this; // 將 方法綁定到對象唯一屬性上let result = content[key](...arg); // 函數可能有返回值delete content[key]; // 用完要刪除該唯一屬性return result } // 同理我們可以基于 Symbol 符號封裝 apply 方法實現更準確 instanceof
// 重寫 instanceof 方法確保不備被別人重寫 class fn {constructor() {this.x = Symbol.for("xxx"); }// 設置這個是讓 instanceof 不能通過原型鏈改變, 因為原有的 instanceof 是判斷實列是否為構造函數容易改寫原型static [Symbol.hasInstance](obj) {return obj.x && obj.x === Symbol.for("xxx");} } let f = new fn(); console.log(f instanceof fn) // true // instanceof 實現原理是 obj instanceof fn ==> fn[Symbol.hasInstance](obj) 先查看原型中 fn[Symbol.hasInstance](obj) 返回值 let arr = [1, 2, 3, 4]; Object.setPrototypeOf(arr, fn.prototype); // 設置原型 console.log(arr instanceof fn); // false // 由上面可知雖然上面設置原型但是現在判斷依然為 false實現更準確 toString 返回值
// 實現下面代理 // console.log(Object.prototype.toString.call(f)); ==> "[object,FN]" // 寫法一 class FN {constructor() {// 通過 Symbol.toStringTag 改寫 Object.prototype.toString.call 更準確this[Symbol.toStringTag] = "FN"} } // 寫法二 FN.prototype[Symbol.toStringTag] = "FN" // 等價方法一 let AA = new FN(); console.log(Object.prototype.toString.call(AA)) // "[object,FN]" // console.log(AA.constructor == FN) // 實例的 constructor 指向構造函數 // console.log(AA.__proto__ == FN.prototype) // 實例的 __proto__ 指向構造函數的原型實現對象具有遍歷功能
// 實現所有對象都具備 for of 遍歷功能 Object.prototype[Symbol.iterator] = function () {let self = this;let keys = Reflect.ownKeys(self); // 獲取所有的對象屬性包括符號屬性let index = 0;return {next() {if (index > keys.length - 1) {return {done: true,value: undefined}}let key = keys[index++]; // 保存到外部因為避免兩次 ++ return {done: false,value: { key, value: self[key] }}}} } // 以上使用到 Reflet.ownKeys() 為反射功能,能獲取所有對象屬性總結
通過上面的知名符號的學習是不是相當于發現了新大陸,原來符號 symbol 還能這么玩,因為有了符號的加入,所以我們就能更好的控制魔法的語法,也從而讓我們知道一些語法底層是怎么實現的。
-
Symbol.iterator 可以實現改造迭代器使其具備 for of 功能
-
Symbol.hasInstance 可以改寫更加精確的 instanceof 返回值使其設置原型無效
-
Symbol.toStringTag 可以設置重寫的 toString 方法值返回更加精確指向自身構造函數
-
Symbol.toPrimitive 控制引用類型轉化為基礎類型值方法
總結
以上是生活随笔為你收集整理的符号Symbol介绍及应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库原理知识点总结一
- 下一篇: codeforces 894A QAQ