1. 程式人生 > >JavaScript 中的Symbols, Iterators, Generators, Async/Await, and Async Iterators

JavaScript 中的Symbols, Iterators, Generators, Async/Await, and Async Iterators

本文中,我將介紹symbols,global symbols,iterators,iterables,generators ,async/await 和async iterators。我將首先解釋“ 為什麼 ”他們在那裡,並展示他們如何使用一些有用的例子。

符號 在ES2015中,建立了一個新的(第6個)資料型別symbol。

為什麼建立這個資料型別呢?

原因#1 - 新增具有向後相容性的新核心功能 JavaScript開發人員和ECMAScript委員會(TC39)需要一種方法來新增新的物件屬性,而不會破壞像for in迴圈或JavaScript方法這樣的現有方法Object.keys。

例如,如果我有一個物件,var myObject = {firstName:‘raja’, lastName:‘rao’} 如果我執行Object.keys(myObject)它將返回[firstName, lastName] 。

現在,如果我們新增另一個屬性,對於myObject來說 newProperty,如果你執行Object.keys(myObject)它應該 仍然返回舊值(即,以某種方式使之忽略新加入的newproperty),並且只顯示[firstName, lastName] -而不是[firstName, lastName, newProperty] 。那要怎麼才能實現呢?

我們之前無法真正做到這一點,因此一個名為Symbols的新資料型別被建立了。

如果你使用Symbols新增newProperty,那麼Object.keys(myObject)會忽略它(因為它不知道它),仍然返回[firstName, lastName] !

原因#2 - 避免名稱衝突 他們還希望保持這些屬性的獨特性。通過這種方式,他們可以繼續向全域性新增新屬性(並且可以新增物件屬性),而無需擔心名稱衝突。

例如,假設您有一個物件,您要將自定義toUpperCase新增到全域性Array.prototype中。

現在,假設您載入了另一個庫(或ES2019出來)並且它的Array.prototype.toUpperCase有不同的版本.然後您的函式可能會因名稱衝突而中斷

 Array.prototype.toUpperCase = function () {
        var i;
        for (i = 0; i < this.length; i++) {
            this[i] = this[i].toUpperCase()
        }
        return this
    }
    var myArray = ['abc', 'bcd']
    myArray.toUpperCase()

那你怎麼解決這個未知情況下的名稱衝突?這就是它的Symbols用武之地。它們在內部建立了獨特的值,允許您建立新增屬性而不必擔心名稱衝突。

理由#3 - 通過全域性變數Symbols的鉤子能啟用核心方法 假設您需要一些核心功能,比如說String.prototype.search呼叫自定義功能。 也就是:‘somestring’.search(myObject); 應該呼叫myObject’s search 方法並將 ‘somestring’ 作為引數傳遞!我們怎麼做?

在ES2015提出了一稱為“well-known” symbols的全域性 symbols。只要您的物件將其中一個symbols 作為屬性,您就可以重定向核心函式來呼叫您的函式!

我們現在不能談論這個問題,本文將在後面詳細介紹所有細節。現在,讓我們瞭解符號實際上是如何工作的。

建立符號 您可以通過呼叫名為的全域性函式/物件來建立符號Symbol 。該函式返回資料型別的值symbol。

var mySymbols= symbols() 

注意:Symbols看起來可能像物件,因為它們有方法,但它們不是 - 它們是原始的。您可以將它們視為與常規物件具有某些相似性的“特殊”物件,但它們的行為與常規物件不同。

Symbols具有與物件類似的方法,但與物件不同,它們是不可變的且唯一的。

不能通過“new”關鍵字建立Symbols 因為Symbols不是物件而new關鍵字會返回物件

var mySymbol = new Symbol(); //丟擲錯誤

Symbols 可以有描述為 - 它只是用於記錄目的。

// mySymbol變數現在包含一個“符號”唯一值
//它的描述是“some text” 
const mySymbol = Symbol('some text');

Symbol 是獨一無二的

const mySymbol1 = Symbol('some text');
const mySymbol2 = Symbol('some text');
mySymbol1 == mySymbol2 // false

如果我們使用“Symbol.for”方法,Symbol的行為就像一個單例 您可以通過Symbol.for()代替Symbol()建立Symbol。 只需要輸入一個“key”(字串)來建立一個符號。如果一個符號key已經存在,它只返回舊符號!因此,如果我們使用該Symbol.for方法,它就像一個singleton 。

var mySymbol1 = Symbol .for('some key'); //建立一個新符號
var mySymbol2 = Symbol .for('some key'); // ** 返回相同的符號
 mySymbol1 == mySymbol2 // true

真正原因是使用 .for是在一個地方建立一個Symbol ,並從其他地方接收了相同的 Symbol 。

用“關鍵”字Key描述 Symbol,只是為了讓她更清楚,如果你不使用Symbol.for ,那麼符號是獨一無二的。但是,如果您使用它,那麼如果您key 不是唯一的,則返回的符號也不是唯一的。

注意: Symbol.for如果鍵是相同的,你將最終覆蓋值,這將使符號非唯一!所以儘可能避免這種情況!

var mySymbol1 = Symbol('some key'); //建立一個新符號
var mySymbol2 = Symbol('some key'); // ** 返回相同的符號
var mySymbol3 = Symbol .for('some key'); //建立一個新符號
var mySymbol4 = Symbol .for('some key'); // ** 返回相同的符號

 mySymbol3== mySymbol4 // true
 mySymbol1 == mySymbol2 // false

Symbol可以是物件屬性鍵,這也是使用Symbols的主要方式之一 作為物件屬性!

const mySymbol = Symbol('some descrition'); //建立一個新符號
const myObject = {name:‘bmw’}
myObject [mySymbol] = 'this is a car'

console.log(myObject [mySymbol] )//this is a car

注意, Symbol 作為屬性名,該屬性不會出現在for…in、for…of迴圈中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,可以獲取指定物件的所有 Symbol 屬性名。

Object.getOwnPropertySymbols方法返回一個數組,成員是當前物件的所有用作屬性名的 Symbol 值。Symbol 值作為物件屬性名時,不能用點運算子,Symbol 值必須放在方括號之中。 使用 Symbol 值定義屬性時不適用用點運算子,因為點運算子僅適用於字串屬性,因此您應使用括號[]運算子。

使用Symbol 的3個主要原因

原因#1 - 符號對於迴圈和其他方法是不可見的 下面示例中的for-in迴圈遍歷一個物件,obj但它不知道(或忽略)prop3,prop4因為它們是符號。 const obj = {};

let foo = Symbol("foo");
Object.defineProperty(obj, foo, {
  value: "foobar",
});
for (let i in obj) {
  console.log(i); // 無輸出
}
Object.getOwnPropertyNames(obj)
// []
Object.getOwnPropertySymbols(obj)
// [Symbol(foo)]

上面程式碼中,使用Object.getOwnPropertyNames方法得不到Symbol屬性名,需要使用Object.getOwnPropertySymbols方法。

另一個新的 API,Reflect.ownKeys方法可以返回所有型別的鍵名,包括常規鍵名和 Symbol 鍵名。

let obj = {
  [Symbol('my_key')]: 1,
  enum: 2,
  nonEnum: 3
};
Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]

原因#2 - Symbol是唯一的 假設您想要一個Array.prototype.includes在全域性Array物件上呼叫的功能。它將與JavaScript(ES2018)開放對外的預設方法衝突。如何在不碰撞的情況下新增它?

首先,建立一個具有適當名稱的變數includes併為其指定一個Symbol。然後將此變數(現在是Symbol)新增到全域性Array使用[ ]表示法。分配您想要的任何功能。

最後:使用括號表示法呼叫該函式,您必須在括號內傳遞實際Symbol,如:arrincludes而不是字串。

var includes = Symbol('will store custom includes method')
    Array.prototype[includes] = ()=> {
        console.log('inside includes fun');
    }
    var arr = [1,2,3]
    console.log(arr.includes(1));
    console.log(arr['includes'](1));
    console.log(arr[includes]());
    

理由#3。Well-known Symbols (即, “global” symbols) 預設情況下,JavaScript會自動建立一堆符號變數並將它們分配給全域性Symbol物件(是的,Symbol()我們用來建立符號)。

在ECMAScript2015中,Symbols提供了 11 個內建的 Symbol 值,指向語言內部使用的方法。

這些符號的一些例子是:Symbol.match,Symbol.replace,Symbol.search,Symbol.iterator和Symbol.split。

由於這些全域性符號是全域性的並且是公開的,我們可以使核心方法呼叫我們的自定義函式而不是內部函式。

一個例子: Symbol.search 物件的Symbol.search屬性,指向一個方法,當該物件被String.prototype.search方法呼叫時,會返回該方法的返回值。

String.prototype.search(regexp)
// 等同於
regexp[Symbol.search](this)

class MySearch {
  constructor(value) {
    this.value = value;
  }
  [Symbol.search](string) {
    return string.indexOf(this.value);
  }
}
'foobar'.search(new MySearch('foo')) // 0

'ragrao'.search(/rao/)4
'ragrao'.search('rao')4

Symbol.search(DEFAULT BEHAVIOR)的內部工作原理 解析 ‘rajarao’.search(‘rao’); 將“rajarao”轉換為String物件 new String(“rajarao”) 將“rao”轉換為RegExp物件 new Regexp(“rao”) 呼叫search“rajarao”字串物件的方法。 search方法內部呼叫Symbol.search作用於“rao”物件上(將搜尋委託回“rao”物件)並傳遞“rajarao”。像這樣的東西:"rao"Symbol.search "rao"Symbol.search返回指數的結果4來search發揮作用,最後,search返回4到我們的程式碼。

迭代器和Iterables

在一些示例中,我們不能使用for-of迴圈或spread運算子來從Users類中提取資料。我們必須使用自定義get方法。

class Users{
        constructor(user){
            this.users = users
        }
        get(){
            return this.users
        }
    }
    const  allUsers = new Users([{name:'raja'},{name:'john'},{name:'matt'}])
    for (const user of Users){
        console.log(item)//TypeError Users is no iterable
    }
    [...allUsers]// TypeError Users is no iterable

遵循以下這6個規則,則主要物件被稱為“ 可迭代 ”。

  1. 主物件/類應該儲存一些資料。
  2. 主物件/類必須具有global “well-known” symbol,symbol.iterator作為其屬性,該symbol根據規則3至6實現特定方法。
  3. 此symbol.iterator方法必須返回另一個物件 - “迭代器”物件。
  4. 這個“迭代器”物件必須有一個稱為next方法的方法。
  5. 該next方法應該可以訪問儲存在規則1中的資料。
  6. 如果我們呼叫iteratorObj.next(),如果它想要返回更多值或者它不想再返回任何資料,它應該將規則#1中的一些儲存資料作為{value:,done:false},或者作為{done:true} 格式返回 在這裡插入圖片描述

Generator 函式

Generator 函式是 ES6 提供的一種非同步程式設計解決方案,語法行為與傳統函式完全不同。

語法上,首先可以把它理解成,Generator 函式是一個狀態機,封裝了多個內部狀態。

執行 上Generator 函式會返回一個遍歷器物件,也就是說,Generator 函式除了狀態機,還是一個遍歷器物件生成函式。返回的遍歷器物件,可以依次遍歷 Generator 函式內部的每一個狀態。

形式上,Generator 函式是一個普通函式,但是有兩個特徵。

  1. 一是,function關鍵字與函式名之間有一個星號;
  2. 二是,函式體內部使用yield表示式,定義不同的內部狀態(yield在英語裡的意思就是“產出”)。 Generator 功能主要有兩個:
  3. 為迭代提供更高級別的抽象
  4. 提供更新的控制流來幫助解決諸如“回撥地獄”之類的問題。

#1 - 迭代的包裝器 關於Generator 的一些要點:

Generator方法*在類中有一個新語法,而Generator函式有語法function * myGenerator(){}。 呼叫生成器myGenerator()返回一個generator也實現iterator協議(規則)的物件,因此我們可以將其用作iterator對外介面用的返回值。 生成器使用特殊yield語句來返回資料。 yield 語句跟蹤以前的呼叫,並從它停止的地方繼續。 如果你yield在迴圈中使用它,它只會在每次我們next()在迭代器上呼叫方法時執行一次。 例1: 下面的程式碼向您展示瞭如何使用生成器方法(*getIterator())而不是使用該Symbol.iterator方法並實現next遵循所有規則的方法。

    class Users{
        constructor(users){
            this.users = users
            this.len = users.length
        }
        *getInterator(){
            for (let i in this.users){
                yield this.users[i]
            }
        }
    }
    const  allUsers = new Users([{name:'raja'},{name:'john'},{name:'matt'}])
    const  allUsersIterator = allUsers.getIterator()
    console.log(allUsersIterator.next());
    console.log(allUsersIterator.next());
    console.log(allUsersIterator.next());
    console.log(allUsersIterator.next());
    
    for (const u of allUsersIterator){
        console.log(u.name)
    }
    console.log([...allUsersIterator]);

例2: 您可以進一步簡化它。使函式成為生成器(帶*語法),並使用一次yield返回一個值,如下所示。

    function*Users(users){
        for (let i in users){
            yield users[i++]
        }
    }
    const  allUsers = new Users([{name:'raja'},{name:'john'},{name:'matt'}])
    console.log(allUsers.next());
    console.log(allUsers.next());
    console.log(allUsers.next());
    console.log(allUsers.next());

    for (const u of allUsersIterator){
        console.log(u.name)
    }
    console.log([...allUsers]);

原因#2 - 提供更好和更新的控制流程 幫助提供新的控制流程,幫助我們以新的方式編寫程式並解決諸如“回撥地獄”之類的問題。

請注意,與普通函式不同,生成器函式可以yield(儲存函式state和return值)並準備好在其產生的點處獲取其他輸入值。 寫到這裡才發現篇是雞肋