1. 程式人生 > 其它 >前端開發系列035-基礎篇之Symbol符號型別

前端開發系列035-基礎篇之Symbol符號型別

title: '前端開發系列035-基礎篇之Symbol符號型別'
tags:
  - javaScript系列
categories: []
date: 2017-09-18 20:20:13
本文介紹ES6新增加的資料型別 `Symbol` , 包括基本使用、注意事項以及常用內建 Symbol等。 Symbol 型別介紹

Symbol 是 ES6提供的新特性,表示一種新的原始資料型別,我們可以稱之為符號型別,它是 JavaScript 語言中的第七種資料型別,在使用上類似於字串。JavaScript 語言的資料型別有:null undefined boolean number string object Symbol

console.log(typeof 123);      /* number */
console.log(typeof "abc");    /* string */
console.log(typeof true);     /* boolean */
console.log(typeof null);     /* object */
console.log(typeof undefined);/* undefined */
console.log(typeof {});       /* object */
console.log(typeof []);       /* object */
console.log(typeof Symbol()); /* symbol */

Symbol 型別的意義在於符號型別的資料總是一個獨一無二的值,這可以解決物件混入(Mixin)時候因為成員名衝突而導致的覆蓋問題。此外,需要注意 Symbol 是基本資料型別值,因此不支援通過 new 以建構函式的方式來呼叫。

/* Symbol符號型別資料的建立方式 */
/* 1.第一種建立方式 */
let s1 = Symbol();
let s2 = Symbol();
console.log(s1 == s2, s1, s2);
/* 輸出:false Symbol() Symbol() */

/* 2.第二種建立方式 */
/* 註解:在建立資料的時候傳入一個字串類似於名字 */
let s3 = Symbol("a");
let s4 = Symbol("b");
let s5 = Symbol("b");

console.log(s3, s4, s5, s3 == s4, s4 == s5);
/* 輸出:Symbol(a) Symbol(b) Symbol(b) false false */

/* Symbol解決了什麼問題? */
/* 演示程式碼-01 */
let o1 = {name:"zs",age:18}
let o2 = {name:"lw",address:"BeiJing"};
Object.assign(o1,o2);
console.log(o1);
/* 輸出:{ name: 'lw', age: 18, address: 'BeiJing' } */
/* 註解:我們在把 o2 成員混入到 o1 物件中的時候 name鍵值對因為已經存在所以發生了覆蓋。 */

/* 演示程式碼-02 */
let obj1 = { [Symbol("name")]: "zs", age: 18 }
let obj2 = { [Symbol("name")]: "lw", address: "BeiJing" };
Object.assign(obj1, obj2);
console.log(obj1);

/* 輸出: */
/* 
{ age: 18,
  address: 'BeiJing',
  [Symbol(name)]: 'zs',
  [Symbol(name)]: 'lw' 
}
*/

我們知道,在以前物件的鍵名只能是字串,當 ES6 引入 Symbol 之後,這也意味著普通物件的 key [屬性名] 支援stringSymbol兩種型別。不過,需要注意符號型別資料作為物件屬性來儲存資料的時候有一些小的注意點,下面通過程式碼來展示這些不同。

/* 符號型別資料作為物件的 key */
/* 方式(1) */
let o1 = { name: "Yong" };
o1[Symbol()] = "Hi@";
console.log(o1) /* { name: 'Yong', [Symbol()]: 'Hi@' } */

/* 注意:無法通過下面這種方式來讀取,因為是兩個不同的資料 */
console.log(o1[Symbol()]); /* undefined */

/* 方式(2) */
let mySymbol = Symbol();
let o2 = { name: "Yong" };
o2[mySymbol] = "Hello@";
console.log(o2) /* { name: 'Yong', [Symbol()]: 'Hello@' } */

/* 方式(3) */
let symbol_pro = Symbol();
let symbol_fnc = Symbol();

let o3 = {
    name: "Yong",
    [symbol_pro]: "Nice to meet u",
    [symbol_fnc]() {
        console.log("呼叫了函式——symbol_fnc")
    }
};
console.log(o3); /*{name: 'Yong', [Symbol()]:'Nice to meet u', [Symbol()]: [Function]}}*/
console.log(o3[symbol_pro]); /* Nice to meet u */
o3[symbol_fnc]();            /* 呼叫了函式——symbol_fnc */

/* 註解:不能直接以點語法的方式來訪問符號型別的屬性值 */
console.log(o3.symbol_pro); /* undefined */

/* 方式(4) */
let o4 = {name:"Yong"};
let sym = Symbol();
Object.defineProperty(o4, sym, { value: "Xia" });
console.log(o4[sym], o4);  /* Xia { name: 'Yong' } */

現在我們知道了 Symbol的特性以及基本使用,其中最重要的一點那就是 Symbol 型別的資料是獨一無二的,可是在某些時候我們可能想要對符號型別的資料進行復用,這種情況可以考慮使用Symbol.for方法來建立。 Symbol.for接受一個字串作為引數,然後搜尋有沒有以該引數作為名稱的 Symbol值,如果有那麼句直接返回這個已經存在的 Symbol值,如果沒有那麼就會新建並返回一個以該字串作為名稱的 Symbol值。

/* (1) Symbol.for()  */
/* 1.每次都會建立不同的符號型別資料 */
let s1 = Symbol("foo");
let s2 = Symbol("foo");
console.log(s1, s2, s1 == s2); /* Symbol(foo) Symbol(foo) false */

/* 2.註冊以複用(s3 和 s4實際是同一個值) */
let s3 = Symbol.for("test");
let s4 = Symbol.for("test");
console.log(s3, s4, s3 == s4); /* Symbol(test) Symbol(test) true */

/* (2) Symbol.keyFor() 
該方法返回一個已經登記的 Symbol型別值的 key,
如果指定的符號型別資料未登記(不是通過 Symbol.for() 來建立,那麼將返回 undefined) */

console.log(Symbol.keyFor(s3), Symbol.keyFor(s4)) /* test test */
console.log(Symbol.keyFor(s1), Symbol.keyFor(s2)) /* undefined undefined */
Symbol 型別的資料( 鍵值對 )並不適用於普通的遍歷。
let o = {
    id: "41",
    name: "Yong",
    [Symbol("age")]: 18,
    [Symbol("address")]: "QuJin"
}

/* 通過for...in 迴圈遍歷 */
for (const k in o) {
    console.log(k, o[k])
}

/* 列印輸出: for...in迴圈並不能遍歷符號型別資料
id 41
name Yong */

console.log(Object.keys(o));                  /* [ 'id', 'name' ] */
console.log(Object.getOwnPropertyNames(o));   /* [ 'id', 'name' ] */
console.log(Object.getOwnPropertySymbols(o)); /* [ Symbol(age), Symbol(address) ] */
console.log(Reflect.ownKeys(o));             
 /* [ 'id', 'name', Symbol(age), Symbol(address) ] */

內建的 Symbol

我們除了可以通過 Symbol() 來建立自定義的符號型別資料外,ES6還為我們提供了內建的 Symbol 值,這些值指向 JavaScript 語言內部使用的方法,下面先簡單列出這些內建的 Symbol 值。

Symbol.match              字串相關方法...
Symbol.replace
Symbol.search
Symbol.split
Symbol.toPrimitive
Symbol.toStringTag
Symbol.unscopables
Symbol.hasInstance        當使用 instanceof 運算子的時候會呼叫該方法以判斷物件是否為建構函式的例項。
Symbol.isConcatSpreadable 布林型別值,決定 Array.prototype.concat() 是否可以展開。
Symbol.species            當例項化的時候內部會呼叫該方法,用於提供 this 來建立例項物件。
Symbol.iterator           指向預設的遍歷器方法,物件在進行for...of迴圈的時候會呼叫該方法。

這裡簡單通過程式碼來演示和驗證Symbol.hasInstance | Symbol.isConcatSpreadable 的使用。

/* 1.Symbol.hasInstance */
function Person(name, age) {
    this.name = name;
    this.age = age;
}

/* [Function: [Symbol.hasInstance]] */
console.log(Person[Symbol.hasInstance])

let p = new Person("Yong", 18);
console.log(p instanceof Person)            /* true */
console.log(Person[Symbol.hasInstance](p)); /* true */
/* 註解:檢查 p 物件的原型物件是否在 Person 的原型鏈上面 */
/* 當執行 p instanceof Person 的時候,內部實際呼叫的是 Person[Symbol.hasInstance](p) */

/* 2.Symbol.isConcatSpreadable */
let arr1 = [3, 2, 1];
let arr2 = ["abc", "def"];
arr2[Symbol.isConcatSpreadable] = true;
let result = arr1.concat(4, arr2, 456);
console.log(result);

/* 當arr2[Symbol.isConcatSpreadable] = true; */
/* result == [ 3, 2, 1, 4, 'abc', 'def', 456 ] */

/* 當arr2[Symbol.isConcatSpreadable] = false; */
/* result == [ 3, 2, 1, 4, ['abc', 'def'], 456 ] */