您好,登錄后才能下訂單哦!
Symbols 是 ES6 引入了一個新的數據類型 ,它為 JS 帶來了一些好處,尤其是對象屬性時。 但是,它們能為我們做些字符串不能做的事情呢?
在深入探討 Symbol 之前,讓我們先看看一些 JavaScript 特性,許多開發人員可能不知道這些特性。
背景
js 中的數據類型總體來說分為兩種,他們分別是:值類型 和 引用類型
值類型(基本類型):數值型(Number),字符類型(String),布爾值型(Boolean),null 和 underfined
引用類型(類):函數,對象,數組等
值類型理解:變量之間的互相賦值,是指開辟一塊新的內存空間,將變量值賦給新變量保存到新開辟的內存里面;之后兩個變量的值變動互不影響,例如:
var a = 10; //開辟一塊內存空間保存變量a的值“10”; var b = a; //給變量 b 開辟一塊新的內存空間,將 a 的值 “10” 賦值一份保存到新的內存里; //a 和 b 的值以后無論如何變化,都不會影響到對方的值;
一些語言,比如 C,有引用傳遞和值傳遞的概念。JavaScript 也有類似的概念,它是根據傳遞的數據類型推斷的。如果將值傳遞給函數,則重新分配該值不會修改調用位置中的值。但是,如果你修改的是引用類型,那么修改后的值也將在調用它的地方被修改。
引用類型理解:變量之間的互相賦值,只是指針的交換,而并非將對象(普通對象,函數對象,數組對象)復制一份給新的變量,對象依然還是只有一個,只是多了一個指引~~;例如:
var a = { x: 1, y: 2 }; //需要開辟內存空間保存對象,變量 a 的值是一個地址,這個地址指向保存對象的空間; var b = a; // 將a 的指引地址賦值給 b,而并非復制一給對象且新開一塊內存空間來保存; // 這個時候通過 a 來修改對象的屬性,則通過 b 來查看屬性時對象屬性已經發生改變;
值類型(神秘的 NaN 值除外)將始終與具有相同值的另一個值類型的完全相等,如下:
const first = "abc" + "def"; const second = "ab" + "cd" + "ef"; console.log(first === second); // true
但是完全相同結構的引用類型是不相等的:
const obj1 = { name: "Intrinsic" }; const obj2 = { name: "Intrinsic" }; console.log(obj1 === obj2); // false // 但是,它們的 .name 屬性是基本類型: console.log(obj1.name === obj2.name); // true
對象在 JavaScript 語言中扮演重要角色,它們的使用無處不在。對象通常用作鍵/值對的集合,然而,以這種方式使用它們有一個很大的限制: 在 symbol 出現之前,對象鍵只能是字符串,如果試圖使用非字符串值作為對象的鍵,那么該值將被強制轉換為字符串,如下:
const obj = {}; obj.foo = 'foo'; obj['bar'] = 'bar'; obj[2] = 2; obj[{}] = 'someobj'; console.log(obj); // { '2': 2, foo: 'foo', bar: 'bar', '[object Object]': 'someobj' }
Symbol 是什么
Symbol() 函數會返回 symbol 類型的值,該類型具有靜態屬性和靜態方法。它的靜態屬性會暴露幾個內建的成員對象;它的靜態方法會暴露全局的 symbol 注冊,且類似于內建對象類,但作為構造函數來說它并不完整,因為它不支持語法:“new Symbol()”。所以使用 Symbol 生成的值是不相等:
const s1 = Symbol(); const s2 = Symbol(); console.log(s1 === s2); // false
實例化 symbol 時,有一個可選的第一個參數,你可以選擇為其提供字符串。 此值旨在用于調試代碼,否則它不會真正影響symbol 本身。
const s1 = Symbol("debug"); const str = "debug"; const s2 = Symbol("xxyy"); console.log(s1 === str); // false console.log(s1 === s2); // false console.log(s1); // Symbol(debug)
symbol 作為對象屬性
symbol 還有另一個重要的用途,它們可以用作對象中的鍵,如下:
const obj = {}; const sym = Symbol(); obj[sym] = "foo"; obj.bar = "bar"; console.log(obj); // { bar: 'bar' } console.log(sym in obj); // true console.log(obj[sym]); // foo console.log(Object.keys(obj)); // ['bar']
乍一看,這看起來就像可以使用 symbol 在對象上創建私有屬性,許多其他編程語言在其類中有自己的私有屬性,私有屬性遺漏一直被視為 JavaScript 的缺點。
不幸的是,與該對象交互的代碼仍然可以訪問其鍵為 symbol 的屬性。 在調用代碼尚不能訪問 symbol 本身的情況下,這甚至是可能的。 例如,Reflect.ownKeys() 方法能夠獲取對象上所有鍵的列表,包括字符串和 symbol :
function tryToAddPrivate(o) { o[Symbol("Pseudo Private")] = 42; } const obj = { prop: "hello" }; tryToAddPrivate(obj); console.log(Reflect.ownKeys(obj)); // [ 'prop', Symbol(Pseudo Private) ] console.log(obj[Reflect.ownKeys(obj)[1]]); // 42
注意:目前正在做一些工作來處理在 JavaScript 中向類添加私有屬性的問題。這個特性的名稱被稱為私有字段,雖然這不會使所有對象受益,但會使類實例的對象受益。私有字段從 Chrome 74 開始可用。
代碼部署后可能存在的 BUG 沒法實時知道,事后為了解決這些 BUG,花了大量的時間進行 log 調試,這邊順便給大家推薦一個好用的 BUG 監控工具 Fundebug。
防止屬性名稱沖突
符號可能不會直接受益于 JavaScript 為對象提供私有屬性。然而,他們是有益的另一個原因。當不同的庫希望向對象添加屬性而不存在名稱沖突的風險時,它們非常有用。
Symbol 為 JavaScrit 對象提供私有屬性還有點困難,但 Symbol 還有別外一個好處,就是避免當不同的庫向對象添加屬性存在命名沖突的風險。
考慮這樣一種情況:兩個不同的庫想要向一個對象添加基本數據,可能它們都想在對象上設置某種標識符。通過簡單地使用 id 作為鍵,這樣存在一個巨大的風險,就是多個庫將使用相同的鍵。
function lib1tag(obj) { obj.id = 42; } function lib2tag(obj) { obj.id = 369; }
通過使用 Symbol,每個庫可以在實例化時生成所需的 Symbol。然后用生成 Symbol 的值做為對象的屬性:
const library1property = Symbol("lib1"); function lib1tag(obj) { obj[library1property] = 42; } const library2property = Symbol("lib2"); function lib2tag(obj) { obj[library2property] = 369; }
出于這個原因,Symbol 似乎確實有利于 JavaScript。
但是,你可能會問,為什么每個庫在實例化時不能簡單地生成隨機字符串或使用命名空間?
const library1property = uuid(); // random approach function lib1tag(obj) { obj[library1property] = 42; } const library2property = "LIB2-NAMESPACE-id"; // namespaced approach function lib2tag(obj) { obj[library2property] = 369; }
這種方法是沒錯的,這種方法實際上與 Symbol 的方法非常相似,除非兩個庫選擇使用相同的屬性名,否則不會有沖突的風險。
在這一點上,聰明的讀者會指出,這兩種方法并不完全相同。我們使用唯一名稱的屬性名仍然有一個缺點:它們的鍵非常容易找到,特別是當運行代碼來迭代鍵或序列化對象時。考慮下面的例子:
const library2property = "LIB2-NAMESPACE-id"; // namespaced function lib2tag(obj) { obj[library2property] = 369; } const user = { name: "Thomas Hunter II", age: 32 }; lib2tag(user); JSON.stringify(user); // '{"name":"Thomas Hunter II","age":32,"LIB2-NAMESPACE-id":369}'
如果我們為對象的屬性名使用了 Symbol,那么 JSON 輸出將不包含它的值。這是為什么呢? 雖然 JavaScript 獲得了對 Symbol 的支持,但這并不意味著 JSON 規范已經改變! JSON 只允許字符串作為鍵,JavaScript 不會嘗試在最終 JSON 有效負載中表示 Symbol 屬性。
const library2property = "f468c902-26ed-4b2e-81d6-5775ae7eec5d"; // namespaced approach function lib2tag(obj) { Object.defineProperty(obj, library2property, { enumerable: false, value: 369 }); } const user = { name: "Thomas Hunter II", age: 32 }; lib2tag(user); console.log(user); // {name: "Thomas Hunter II", age: 32, f468c902-26ed-4b2e-81d6-5775ae7eec5d: 369} console.log(JSON.stringify(user)); // {"name":"Thomas Hunter II","age":32} console.log(user[library2property]); // 369
通過將 enumerable 屬性設置為 false 而“隱藏”的字符串鍵的行為非常類似于 Symbol 鍵。它們通過 Object.keys() 遍歷也看不到,但可以通過 Reflect.ownKeys() 顯示,如下的示例所示:
const obj = {}; obj[Symbol()] = 1; Object.defineProperty(obj, "foo", { enumberable: false, value: 2 }); console.log(Object.keys(obj)); // [] console.log(Reflect.ownKeys(obj)); // [ 'foo', Symbol() ] console.log(JSON.stringify(obj)); // {}
在這點上,我們幾乎重新創建了 Symbol。隱藏的字符串屬性和 Symbol 都對序列化器隱藏。這兩個屬性都可以使用Reflect.ownKeys()方法讀取,因此它們實際上不是私有的。假設我們為屬性名的字符串版本使用某種名稱空間/隨機值,那么我們就消除了多個庫意外發生名稱沖突的風險。
但是,仍然有一個微小的區別。由于字符串是不可變的,而且 Symbol 總是保證惟一的,所以仍然有可能生成字符串組合會產生沖突。從數學上講,這意味著 Symbol 確實提供了我們無法從字符串中得到的好處。
在 Node.js 中,檢查對象時(例如使用 console.log() ),如果遇到名為 inspect 的對象上的方法,將調用該函數,并將打印內容。可以想象,這種行為并不是每個人都期望的,通常命名為 inspect 的方法經常與用戶創建的對象發生沖突。
現在 Symbol 可用來實現這個功能,并且可以在 equire(‘util').inspect.custom 中使用。inspect 方法在 Node.js v10 中被廢棄,在 v1 1 中完全被忽略, 現在沒有人會偶然改變檢查的行為。
模擬私有屬性
這里有一個有趣的方法,我們可以用來模擬對象上的私有屬性。這種方法將利用另一個 JavaScript 特性: proxy(代理)。代理本質上封裝了一個對象,并允許我們對與該對象的各種操作進行干預。
代理提供了許多方法來攔截在對象上執行的操作。我們可以使用代理來說明我們的對象上可用的屬性,在這種情況下,我們將制作一個隱藏我們兩個已知隱藏屬性的代理,一個是字符串 _favColor,另一個是分配給 favBook 的 S ymbol :
let proxy; { const favBook = Symbol("fav book"); const obj = { name: "Thomas Hunter II", age: 32, _favColor: "blue", [favBook]: "Metro 2033", [Symbol("visible")]: "foo" }; const handler = { ownKeys: target => { const reportedKeys = []; const actualKeys = Reflect.ownKeys(target); for (const key of actualKeys) { if (key === favBook || key === "_favColor") { continue; } reportedKeys.push(key); } return reportedKeys; } }; proxy = new Proxy(obj, handler); } console.log(Object.keys(proxy)); // [ 'name', 'age' ] console.log(Reflect.ownKeys(proxy)); // [ 'name', 'age', Symbol(visible) ] console.log(Object.getOwnPropertyNames(proxy)); // [ 'name', 'age' ] console.log(Object.getOwnPropertySymbols(proxy)); // [Symbol(visible)] console.log(proxy._favColor); // 'blue'
使用 _favColor 字符串很簡單:只需閱讀庫的源代碼即可。 另外,通過蠻力找到動態鍵(例如前面的 uuid 示例)。但是,如果沒有對 Symbol 的直接引用,任何人都不能 從proxy 對象訪問'Metro 2033'值。
Node.js 警告:Node.js 中有一個功能會破壞代理的隱私。 JavaScript 語 言本身不存在此功能,并且不適用于其他情況,例 如 Web 瀏覽器。 它允許在給定代理時獲得對底層對象的訪問權。 以下是使用此功能打破上述私有屬性示例的示例:
const [originalObject] = process.binding("util").getProxyDetails(proxy); const allKeys = Reflect.ownKeys(originalObject); console.log(allKeys[3]); // Symbol(fav book)
現在,我們需要修改全局 Reflect 對象,或者修改 util 流程綁定,以防止它們在特定的 Node.js 實例中使用。
以上所述是小編給大家介紹的JavaScript 為什么要有 Symbol 類型詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。