這篇文章帶給大家的內容是關於JavaScript中Symbol 類型有什麼用?有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
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 註冊,且類似於內建物件類,但作為建構子來說它並不完整,因為它不支援語法: "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 還有另一個重要的用途,它們可以用作物件中的鍵,如下:
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開始可用。
符號可能不會直接受益於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 实例中使用。但这是一个可怕的兔子洞。
本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的JavaScript教程视频栏目!
以上是JavaScript中Symbol 類型有什麼用?的詳細內容。更多資訊請關注PHP中文網其他相關文章!