通俗易懂理解ES6 - ES6的變數型別及Iterator

code_boy發表於2019-05-06

引言

萬丈高樓平地起,欲練此功,必先打好基本功: )

在瞭解 ES6 新增的變數型別前,我們必須先知道 JavaScript 在ES6之前,有如下六種基本資料型別:Null、Undefined、Number、String、Boolean和Object。而 ES6 中,新增了第七種資料型別:Symbol。 上述七種資料型別作如下型別劃分:

基本型別: Undefined、Null、Boolean、String、Number,這五種型別的變數都是直接把實際值儲存在棧記憶體當中,操作或訪問的時候都是直接對實際值進行的.

引用型別: Object。Object 型別的變數是把指向堆記憶體的地址值儲存在棧記憶體當中的一類資料。(關於堆疊的知識將會在後面的文章中作介紹。)

有關基本型別和引用型別的說明,網上已經有很多文章有說明介紹,為免篇幅過長,這裡就不再重複敘述了。

Symbol型別:

這裡我們著重說一下 Symbol 型別:
Symbol 是一個函式,呼叫該函式,返回的唯一值就是 Symbol 型別值;

不允許在 Symbol 前使用 new,symbol 型別的值可通過直接呼叫 Symbol 函式建立

let symbol = Symbol();
symbol;         //Symbol()
typeof symbol;  //symbol
let symbol1 = new Symbol();     //Uncaught TypeError: Symbol is not a constructor
複製程式碼

Symbol 函式呼叫時,可接受一個引數,該引數會通過 toString 方法變為字串,作為 symbol 值的說明,傳入的引數不可以為 symbol 型別的值

let testStr = 'this is a string',
    testObj = {obj: 'this is a object'},
    testArr = ['this','is','a','array'],
    testFn = () => {
        console.log('this is a function');
    },
    testSym = Symbol('this is a symbol'),
    symbolStr = Symbol(testStr),            //Symbol(this is a string)
    symbolObj = Symbol(testObj),            //Symbol([object Object])
    symnolArr = Symbol(testArr),            //Symbol([object Object])
    symbolFn = Symbol(testFn),              //Symbol(() => {console.log('this is a function');})
    symbolSym = Symbol(testSym);            //Uncaught TypeError: Cannot convert a Symbol value to a string
複製程式碼

Symbol 函式呼叫後生成的值是唯一的

let symbol1 = Symbol('test'),
    symbol2 = Symbol('test');
symbol1 == symbol2;         //false
symbol1 === symbol2;        //false
複製程式碼

Symbol 值不能與其他型別值進行運算、隱式轉換,否則會報錯;但能通過 toString 方法顯式轉為字串。

let symbol = Symbol('this is symbol'),
    str = 'this is string',
    num = 2,
    symStr = symbol.toString();
    
let newStr = symbol + str;          //Uncaught TypeError: Cannot convert a Symbol value to a string
let newNum = Symbol + num;          //Uncaught TypeError: Cannot convert a Symbol value to a number
symStr;                             // Symbol(this is symbol)
複製程式碼

Symbol 值的唯一性,用於 Object 的屬性中,可以確保不會出現同名屬性

let symbol = Symbol('this is symbol'),
    symbol1 = Symbol('this is symbol');
let obj = {
    [symbol]: 'this is a',
    [symbol1]: 'this is b'
};
obj;            //{Symbol(this is symbol): "this is a", Symbol(this is symbol): "this is b"}

let str = 'test',
    str1 = 'test',
    obj = {};
obj[str] = '測試非symbol型別命名的屬性';
obj;            //{test: "測試非symbol型別命名的屬性"}
obj[str1] = '再次測試非symbol型別命名的屬性';
obj;            //{test: "再次測試非symbol型別命名的屬性"}
複製程式碼

Symbol 命名的屬性可通過 Object.getOwnPropertySymbols 獲取

let symbol = Symbol('this is symbol'),
    symbol1 = Symbol('this is symbol');
let symbolObj = {
    [symbol]: 'this is a',
    [symbol1]: 'this is b',
}
Object.getOwnPropertySymbols(symbolObj);    //[Symbol(this is symbol), Symbol('this is symbol')]
複製程式碼

Symbol 值命名的屬性不會出現在 Object.keys、for...in...中,通過Object.getOwnPropertyNames()、JSON.stringify() 也無法得到返回值。但該屬性是公開屬性,不是私有屬性。

let symbol = Symbol('this is symbol'),
    symbol1 = Symbol('this is symbol');
let obj = {
    [symbol]: 'this is a',
    [symbol1]: 'this is b',
    d: 'test'
};
for(key in obj){
    console.log(key);               // d
}
Object.keys(obj);                   // ['d']
Object.getOwnPropertyNames(obj);    // ['d']
JSON.stringify(obj);                //{d:'test'}

//仍可訪問到Symbol值定義的屬性鍵和屬性值
let symKeys = Object.getOwnPropertySymbols(obj);  
symKeys;                    //[Symbol(this is symbol), Symbol(this is symbol)]
symKeys[0];                 //Symbol(this is symbol)
obj[symKeys[0]];            //this is a
複製程式碼

可通過 Symbol.for('xxx') 獲得以'xxx'作為入參而生成的 Symbol 值,若不存在以'xxx'作為入參生成的 Symbol 值,則會自動建立一個以'xxx'為入參的 Symbol 值。

let symbol = Symbol('this is symbol'),
    symbol1 = Symbol.for('this is symbol'),
    symbol2 = Symbol.for('this is symbol');
symbol === symbol1      //false
symbol === symbol2      //false
symbol1 === symbol2     //true
複製程式碼

12. 通過 Symbol.keyFor 方法可返回一個已“登記”的 Symbol 型別值的修飾詞。

通過 Symbol 建立的值有如下兩種情況: 被登記的與未被登記的。
什麼是被登記的?在

let s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"

var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined
複製程式碼

這裡還有一些 MDN 關於 Symbol 的屬性介紹,因為感覺在日常開發中使用的概率比較低,因此也不贅述一些自己的理解了,有興趣的朋友可以去看看developer.mozilla.org/zh-CN/docs/…


關於 Symbol 的總結: 對於 Symbol 的使用,實用性最高的我覺得是 symbol 值用於屬性命名的情況,在一些開發情況下,對一個 Object 物件進行遍歷的時候,希望某一些屬性不被 for in 或 Object.keys 遍歷出來,避免做 if 或 switch 情況處理,這時候使用 Symbol 定義屬性名是個不錯的選擇。
在原有 ES5 的屬性命名和賦值過程中,多人協作開發可能會導致一些屬性名重新命名而導致值覆蓋,對“半私有屬性”屬性使用 Symbol 命名屬性名會是個很好的選擇。


Iterator - 迭代器

ES6 在原有的資料結構型別( Array、Object )上新增了兩種型別( Map、Set ),我們在使用的時候還可以通過自由組合的形式使用這些結構型別達到自己想要的資料結構,這就需要一種統一的介面機制供我們呼叫處理不同的資料結構 —— Iterator。

ES6中,只要被遍歷“物件”存在 可迭代協議 , 即System.iterator 屬性,該物件都是被認為是“可遍歷的”。

在 ES6 中,有三類資料結構原生具備 Iterator 介面:陣列、某些類似陣列的物件(如 字串、類似陣列形式的物件)、Set 和 Map 結構資料

迭代器協議 定義了一種標準的方式來產生一個有限或無限序列的值,每次遍歷都會首先呼叫被遍歷資料集合物件中的 [Symbol.iterator]() 方法,該方法返回一個 Symbol物件的iterator屬性 ,該屬性擁有執行迭代物件的 next 方法,並返回一個物件,如下是一段模擬 next 方法的程式碼

function Iterator(arr){
    let nextIndex = 0;
    return {
        next: function(){
            return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: undefined, done: true};
        },
        [Symbol.iterator]: function() { return nextIndex }
    }
}
let example = Iterator(['a', 'b']);

example.next()      // {value: "a", done: false}
example.next()      // {value: "b", done: false}
example.next()      // {value: undefined, done: true}       //遍歷結束
複製程式碼

返回的物件中必須包含如下兩個屬性

{
    done,       //迭代是否已經執行完畢  迭代完畢時返回true,否則返回false,返回false時會繼續執行迭代
    value       //當前成員的值     迭代完畢時返回undefined,否則返回當前成員的值
}
複製程式碼

在某些情景下,JS會預設呼叫 Symbol.iterator (Iterator介面)

  1. 擴充套件運算子

    擴充套件運算子(…)會預設呼叫 iterator 介面。

  2. 解構賦值

    對陣列和Set結構進行解構賦值時,會預設呼叫 Symbol.iterator 方法。

  3. yield*

    yield*後面跟的是一個可遍歷的結構,它會呼叫該結構的 iterator 介面。

  4. for...of

    執行 for...of迴圈時,會呼叫 iterator 介面對資料進行處理。

  5. Array.from()

    Array.form() 時,會遍歷資料,呼叫 iterator 介面返回相應資料

  6. 其它情況還有 Map()Set()WeakMap()WeakSet() (比如new Map([['a',1],['b',2]]))Promise.all()Promise.race()


順帶一提:

在ES6中,具有 System.iterator 屬性的物件均可通過 for...of 進行遍歷

let arr = ['1','2','3'];
arr.pro = 'test';
for (let i in arr) {
  console.log(arr[i]);          //1  2  3  test
}
for(let i of arr) {
    console.log(arr[i]);        //1  2  3
}
複製程式碼

for...of 的相比於 forEachfor...in ,其好處在於: forEach 迴圈無法通過 breakcontinuereturn 跳出迴圈,而 for...of 可以; for...in 迴圈設計的目的是用於遍歷包含鍵值對的物件,對陣列並不是那麼友好,而 for...of 遍歷輸出的值會在輸出資料後預設遍歷結束。


關於 Iterator 的總結: Iterator作為一種統一的介面機制供我們呼叫處理不同的資料結構,讓可以用相同的方式來遍歷集合,而不用去考慮集合的內部實現,若資料的形式發生改變,只要資料內部還存在 System.iterator 屬性,那遍歷的程式碼就可以不用做修改直接使用。 同時,其服務於 for...of 迴圈, for...of 又有效地避免了以往對資料集僅能通過 forEachfor..in 遍歷時遇到的一部分問題。

以上。

文章觀點內容如有錯誤歡迎指出交流,相互進步

相關文章