天天看點

為何使用 JavaScript Symbol 【譯】

Symbol是javascript最新的基本資料類型(primitive),并帶來了一些好處,特别是作為object的辨別符(properties)的時候。可是,這個和用string作為辨別符有什麼特别的呢?

在我們研究Symbol之前,我們先看看許多開發者沒有注意到的javascript的一些特性。

背景

Javascript主要可以分為兩大類。第一類是基本資料類型(primitives),第二類是對象(Object)(包含函數)。基本資料類型包括數字(Number)(所有的整數,小數,

Infinity

NaN

),布爾值(Boolean),字元串(String),

undefined

null

(注意⚠️: 雖然

typeof null === 'object'

,但

null

仍是基本資料類型)。

基本資料類型是不可修改的。當然一個被基本資料指派的變量(variable)可以重新被指派。例如,

let x = 1; x++;

,這裡是重新對變量

x

指派,并沒有修改基本類型數字

1

像一些語言(如C語言)一樣,javascript也有引用傳遞(pass-by-reference)和值傳遞(pass-by-value)的概念。當你給一個函數傳值的時候,在函數内重新指派(reassign)時不會修改原來那個值,然而修改(modify)一個非基本資料類型(non-primitive)時,原來那個數值也會被修改。可以看下面例子:

function primitiveMutator(val) {
  val = val + 1;
}
let x = 1;
primitiveMutator(x);
console.log(x); // 1
function objectMutator(val) {
  val.prop = val.prop + 1;
}
let obj = { prop: 1 };
objectMutator(obj);
console.log(obj.prop); // 2
           

複制

當基本資料類型的值相等時,這個變量總是完全相等的(除了

NaN

)。

const first = "abc" + "def";
const second = "ab" + "cd" + "ef";
console.log(first === second); // true
console.log(NaN === NaN); // false
           

複制

然而,非基本資料類型(non-primitive)卻不是這樣。

const obj1 = { name: "Intrinsic" };
const obj2 = { name: "Intrinsic" };
console.log(obj1 === obj2); // false
// 他們 name 屬性是 基本資料類型
console.log(obj1.name === obj2.name); // true
           

複制

在javascript中對象(Object)是一個重要的角色,在任何地方都能見到它的身影。Object通常包含了許多鍵值(key/value)。當symbols還沒出現的時候,Object的鍵(key)隻能是字元串(String),這個給Object帶來了一些限制。當使用一個非字元串(non-string)作為Object的key時,這個值會被轉成字元串。例如:

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' }
           

複制

注意⚠️:

Map

可以允許 key 不是字元串。

Symbol 是什麼

現在我們已經知道基本資料類型(primitive)是什麼了。一個Symbol是不能重複建立的基本資料類型。也就是說,一個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('debug');
console.log(s1 === str); // false
console.log(s1 === s2); // false
console.log(s1); // Symbol(debug)
           

複制

注意⚠️:

Symbol.for

是全局建立,它會首先檢查給定的 key 是否已經在系統資料庫中了。假如是,則會直接傳回上次存儲的那個。

Symbols 作為對象屬性的辨別符

Symbols有個很重要的用法,他們可以作為對象的keys。例如:

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']
           

複制

注意到

Object.keys()

不會傳回Symbol。第一眼看上去,symbols可以作為Object的私有變量。許多語言有私有變量,而javascript沒有。可惜的是,還是有辦法通路到symbol作為Object的key的值。例如,

Reflect.ownKeys()

方法可以列出Object的包括string和symbol的所有key。

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
           

複制

防止辨別符沖突

Symbols可能不能直接給Object添加私有屬性。但是,在給Object加屬性辨別符可以防止名字沖突,這起到非常重要的作用。例如,有兩個庫想要給一個對象加一個自己的唯一辨別符号,這兩個庫都用字元串

id

作為key,這裡會産生很大的危險許多庫用着同樣的key.

function lib1tag(obj) {
  obj.id = 42;
}
function lib2tag(obj) {
  obj.id = 369;
}
           

複制

使用symbol作為key就不會有這個沖突了。

const library1property = Symbol('lib1');
function lib1tag(obj) {
  obj[library1property] = 42;
}
const library2property = Symbol('lib2');
function lib2tag(obj) {
  obj[library2property] = 369;
}
           

複制

如果我們用symbol作為一個對象的key,

JSON.stringify

不會包含這個值。這是因為javascript支援symbol并不意味着JSON也支援。JSON隻允許用字元串作為key的對象。但定義

enumerable

為false時,當字元串的key也會被隐藏,就像symbol一樣。他們都會在

Object.keys

JSON.stringify

中隐藏,在

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值都是唯一的。一個symbol值能作為對象屬性的辨別符,這個值在

Object.keys

JSON.stringify

是隐藏的,在

Reflect.ownKeys()

會列出。