symbol是es6中的新增型別嗎

2022-03-07 19:01:34

symbol是es6中的新增型別。symbol是ECMAScript6中引入的一種新的基本資料型別,表示獨一無二的值;Symbol型別的值需要使用Symbol()函數來生成。

本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。

Symbol 是 ECMAScript6 中引入的一種新的資料型別,表示獨一無二的值;它為 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 開始可用。

防止屬性名稱衝突

符號可能不會直接受益於 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 範例中使用。但這是一個可怕的兔子洞。

【相關推薦:、】

以上就是symbol是es6中的新增型別嗎的詳細內容,更多請關注TW511.COM其它相關文章!