nodejs死亡筆記之溯本歸源--node4.0+與ECMAScript6新特性(two)

前言:本文接上一篇文章繼續講解nodev4的新特性


Generator

基本概念

Generator函數有多種理解角度。它是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即暫停執行)。Generator函數是一個狀態機,封裝了多個內部狀態。

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

形式上,Generator函數是一個普通函數,但是有兩個特徵。一是function命令與函數名之間有一個星號;二是函數體內部使用yield語句,定義不同的內部狀態(yield在英語裏的意思就是“產出”)。

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    var hw = helloWorldGenerator();

上面代碼定義了一個Generator函數helloWorldGenerator,它內部有兩個yield語句“hello”和“world”,即該函數有三個狀態:hello,world和return語句(結束執行)。

然後,Generator函數的調用方法與普通函數一樣,也是在函數名後面加上一對圓括號。不同的是,調用Generator函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,遍歷器對象(Iterator Object)。

next方法

  調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield語句(或return語句)爲止。換言之,Generator函數是分段執行的,yield語句是暫停執行的標記,而next方法可以恢復執行。

    hw.next()
    // { value: 'hello', done: false }
    hw.next()
    // { value: 'world', done: false }
    hw.next()
    // { value: 'ending', done: true }
    hw.next()
    // { value: undefined, done: true }

上面代碼一共調用了四次next方法。

  第一次調用,Generator函數開始執行,直到遇到第一個yield語句爲止。next方法返回一個對象,它的value屬性就是當前yield語句的值hello,done屬性的值false,表示遍歷還沒有結束。

  第二次調用,Generator函數從上次yield語句停下的地方,一直執行到下一個yield語句。next方法返回的對象的value屬性就是當前yield語句的值world,done屬性的值false,表示遍歷還沒有結束。

  第三次調用,Generator函數從上次yield語句停下的地方,一直執行到return語句(如果沒有return語句,就執行到函數結束)。next方法返回的對象的value屬性,就是緊跟在return語句後面的表達式的值(如果沒有return語句,則value屬性的值爲undefined),done屬性的值true,表示遍歷已經結束。

  第四次調用,此時Generator函數已經運行完畢,next方法返回對象的value屬性爲undefined,done屬性爲true。以後再調用next方法,返回的都是這個值。

總結一下,調用Generator函數,返回一個遍歷器對象,代表Generator函數的內部指針。以後,每次調用遍歷器對象的next方法,就會返回一個有着value和done兩個屬性的對象。value屬性表示當前的內部狀態的值,是yield語句後面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束。

yield語句*

用來在一個Generator函數裏面執行另一個Generator函數,我們需要用yield*語句。

如果yield命令後面跟的是一個遍歷器對象,需要在yield命令後面加上星號,表明它返回的是一個遍歷器對象。這被稱爲yield*語句。

    function* anotherGenerator(i) {
      yield i + 1;
      yield i + 2;
      yield i + 3;
    }
    function* generator(i){
      yield i;
      yield* anotherGenerator(i);
      yield i + 10;
    }
    var gen = generator(10);
    console.log(gen.next().value); // 10
    console.log(gen.next().value); // 11
    console.log(gen.next().value); // 12
    console.log(gen.next().value); // 13
    console.log(gen.next().value); // 20

運行結果就是使用一個遍歷器,遍歷了多個Generator函數,有遞歸的效果。

另外,本文對 Generator 函數的介紹很簡單,如果有需要,會單獨寫一篇博客介紹。


Promise

Promise簡介

所謂Promise,就是一個對象,用來傳遞異步操作的消息。

Promise對象有以下兩個特點。

(1)對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled)和Rejected(已失敗)。只有異步操作的結果可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從Pending變爲Resolved和從Pending變爲Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

基本用法

ES6規定,Promise對象是一個構造函數,用來生成Promise實例。

下面代碼創造了一個Promise實例。

    var promise = new Promise(function(resolve, reject){
      // ... some code

      if (/* 異步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });

Promise構造函數接受一個函數作爲參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由JavaScript引擎提供,不用自己部署。

resolve函數的作用是,將Promise對象的狀態從“未完成”變爲“成功”(即從Pending變爲Resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去;reject函數的作用是,將Promise對象的狀態從“未完成”變爲“失敗”(即從Pending變爲Rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去。

Promise實例生成以後,可以用then方法分別指定Resolved狀態和Reject狀態的回調函數。

    promise.then(function(value) {
      // success
    }, function(value) {
      // failure
    });

then方法可以接受兩個回調函數作爲參數。第一個回調函數是Promise對象的狀態變爲Resolved時調用,第二個回調函數是Promise對象的狀態變爲Reject時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作爲參數。

Promise.prototype.then()與catch()示意圖:

這裏寫圖片描述

因爲Promise.prototype.then和 Promise.prototype.catch方法返回 promises, 所以它們可以被鏈式調用—一種被稱爲 composition 的操作.

Promise.prototype.then()

then()方法返回一個Promise。它有兩個參數,分別爲Promise在 success 和 failure 情況下的回調函數。

    p.then(onFulfilled, onRejected);
    p.then(function(value) {
       // 滿足
      }, function(reason) {
      // 拒絕
    });

一個Function, 當 Promise 爲 fulfilled 時調用. 該函數有一個參數, 爲肯定結果 value;爲 rejected 時調用. 該函數有一個參數, 爲否定原因 reason。

因爲then方法返回一個Promise,你可以輕易地鏈式調用then。

    var p2 = new Promise(function(resolve, reject) {
      resolve(1);
    });

    p2.then(function(value) {
      console.log(value); // 1
      return value + 1;
    }).then(function(value) {
      console.log(value); // 2
    });

Promise.prototype.catch()

catch()方法只處理Promise被拒絕的情況,並返回一個Promise。該方法的行爲和調用Promise.prototype.then(undefined, onRejected)相同。
語法

    p.catch(onRejected);

    p.catch(function(reason) {
       // 拒絕
    });

onRejected當Promise被拒絕時調用的Function。該函數調用時會傳入一個參數:拒絕原因。
示例:使用catch方法

    var p1 = new Promise(function(resolve, reject) {
      resolve("成功");
    });

    p1.then(function(value) {
      console.log(value); // "成功!"
      throw "哦,不!";
    }).catch(function(e) {
      console.log(e); // "哦,不!"
    });

catch方法主要作用於處理promise組合。

Promise.all()

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

var p = Promise.all([p1,p2,p3]);

  上面代碼中,Promise.all方法接受一個數組作爲參數,p1、p2、p3都是Promise對象的實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲Promise實例,再進一步處理。(Promise.all方法的參數不一定是數組,但是必須具有iterator接口,且返回的每個成員都是Promise實例。)

p的狀態由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

下面是一個具體的例子。

    // 生成一個Promise對象的數組
    var promise = Promise.resolve(3);
    Promise.all([true, promise])
           .then(values => {
               console.log(values); // [true, 3]
            });

Promise.race()

race 函數返回一個Promise,這個Promise根據傳入的Promise中的第一個確定狀態–不管是接受還是拒絕–的狀態而確定狀態。

Promise.race方法同樣是將多個Promise實例,包裝成一個新的Promise實例。

    var p1 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 500, "one"); 
    });
    var p2 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 100, "two"); 
    });
    Promise.race([p1, p2]).then(function(value) {
      console.log(value); // "two"
      // Both resolve, but p2 is faster
    });

上面代碼中,只要p1、p2之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的Promise實例的返回值,就傳遞給p的回調函數。

Promise.reject()

Promise.reject(reason)方法也會返回一個新的Promise實例,該實例的狀態爲rejected。Promise.reject方法的參數reason,會被傳遞給實例的回調函數。

使用靜態的Promise.reject()方法

    Promise.reject("Testing static reject").then(function(reason) {
      // 未被調用
    }, function(reason) {
      console.log(reason); // "測試靜態拒絕"
    });

    Promise.reject(new Error("fail")).then(function(error) {
      // 未被調用
    }, function(error) {
      console.log(error); // 堆棧跟蹤
    });

Promise.resolve()

Promise.resolve(value)方法返回一個以給定值resolve掉的Promise對象。但如果這個值是thenable的(就是說帶有then方法),返回的promise會“追隨”這個thenable的對象,接收它的最終狀態(指resolved/rejected/pendding/settled);否則這個被返回的promise對象會以這個值被fulfilled。
語法

    Promise.resolve(value);
    Promise.resolve(promise);
    Promise.resolve(thenable);

value用來resolve待返回的promise對象的參數。既可以是一個Promise對象也可以是一個thenable。

靜態方法 Promise.resolve返回一個Promise對象,這個Promise對象是被resolve的。
示例

使用靜態方法Promise.resolve


    Promise.resolve("Success").then(function(value) {
      console.log(value); // "Success"
    }, function(value) {
      // not called
    });

以一個數組進行resolve

    var p = Promise.resolve([1,2,3]);
    p.then(function(v) {
      console.log(v[0]); // 1
    });

resolve另一個Promise對象

    var original = Promise.resolve(true);
    var cast = Promise.resolve(original);
    cast.then(function(v) {
      console.log(v); // true
    });

Symbol

Symbol簡介

  ES6引入了一種新的原始數據類型Symbol,表示獨一無二的值。它是JavaScript語言的第七種數據類型,前六種是:Undefined、Null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。

  Symbol值通過Symbol函數生成。這就是說,對象的屬性名現在可以有兩種類型,一種是原來就有的字符串,另一種就是新增的Symbol類型。凡是屬性名屬於Symbol類型,就都是獨一無二的,可以保證不會與其他屬性名產生衝突。

    let s = Symbol();
    typeof s
    // "symbol"

  注意,Symbol函數前不能使用new命令,否則會報錯。這是因爲生成的Symbol是一個原始類型的值,不是對象。也就是說,由於Symbol值不是對象,所以不能添加屬性。基本上,它是一種類似於字符串的數據類型。

Symbol函數可以接受一個字符串作爲參數,表示對Symbol實例的描述,主要是爲了在控制檯顯示,或者轉爲字符串時,比較容易區分。

Symbol函數的參數只是表示對當前Symbol值的描述,因此相同參數的Symbol函數的返回值是不相等的。

    var s1 = Symbol("foo");
    var s2 = Symbol("foo");
    console.log(s1 === s2); // false

Symbol值不能與其他類型的值進行運算,會報錯。

但是,Symbol值可以顯式轉爲字符串。

作爲屬性名的Symbol

  由於每一個Symbol值都是不相等的,這意味着Symbol值可以作爲標識符,用於對象的屬性名,就能保證不會出現同名的屬性。這對於一個對象由多個模塊構成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。

    var mySymbol = Symbol();
    // 第一種寫法
    var a = {};
    a[mySymbol] = 'Hello!';
    // 第二種寫法
    var a = {
      [mySymbol]: 'Hello!'
    };
    // 第三種寫法
    var a = {};
    Object.defineProperty(a, mySymbol, { value: 'Hello!' });
    // 以上寫法都得到同樣結果
    console.log(a[mySymbol]); // "Hello!"

上面代碼通過方括號結構和Object.defineProperty,將對象的屬性名指定爲一個Symbol值。

Symbol值作爲對象屬性名時,不能用點運算符。

在對象的內部,使用Symbol值定義屬性時,Symbol值必須放在方括號之中。

    let s = Symbol();
    let obj = {
      [s](arg) { ... }
    };

Symbol類型還可以用於定義一組常量,保證這組常量的值都是不相等的。

Symbol值作爲屬性名時,該屬性還是公開屬性,不是私有屬性。

屬性名遍歷

Symbol作爲屬性名,該屬性不會出現在for…in、for…of循環中,也不會被Object.keys()、Object.getOwnPropertyNames()返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,可以獲取指定對象的所有Symbol屬性名。

Object.getOwnPropertySymbols方法返回一個數組,成員是當前對象的所有用作屬性名的Symbol值。

    var obj = {};
    var a = Symbol('a');
    var b = Symbol('b');

    obj[a] = 'Hello';
    obj[b] = 'World';
    var objectSymbols = Object.getOwnPropertySymbols(obj);
    console.log(objectSymbols);
    // [Symbol(a), Symbol(b)]

Symbol.for()

使用給定的key搜索現有符號,如果找到則返回符號。否則將得到一個新的使用給定的key在全局符號註冊表中創建的符號。

  有時,我們希望重新使用同一個Symbol值,Symbol.for方法可以做到這一點。它接受一個字符串作爲參數,然後搜索有沒有以該參數作爲名稱的Symbol值。如果有,就返回這個Symbol值,否則就新建並返回一個以該字符串爲名稱的Symbol值。

    var s1 = Symbol.for('foo');
    var s2 = Symbol.for('foo');
    console.log(s1 === s2); // true

上面代碼中,s1和s2都是Symbol值,但是它們都是同樣參數的Symbol.for方法生成的,所以實際上是同一個值。

  Symbol.for()與Symbol()這兩種寫法,都會生成新的Symbol。它們的區別是,前者會被登記在全局環境中供搜索,後者不會。Symbol.for()不會每次調用就返回一個新的Symbol類型的值,而是會先檢查給定的key是否已經存在,如果不存在纔會新建一個值。比如,如果你調用Symbol.for(“cat”) 30次,每次都會返回同一個Symbol值,但是調用Symbol(“cat”) 30次,會返回30個不同的Symbol值。

    Symbol.for("bar") === Symbol.for("bar")
    // true
    Symbol("bar") === Symbol("bar")
    // false

上面代碼中,由於Symbol()寫法沒有登記機制,所以每次調用都會返回一個不同的值。

Symbol.keyFor()

爲給定符號從全局符號註冊表中檢索一個共享符號鍵。

Symbol.keyFor方法返回一個已登記的Symbol類型值的key。

    var s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"

    var s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined

上面代碼中,變量s2屬於未登記的Symbol值,所以返回undefined。


箭頭函數=>

基本用法

箭頭函數就是個是簡寫形式的函數表達式,並且它擁有詞法作用域的this值.箭頭函數總是匿名的.

ES6允許使用“箭頭”(=>)定義函數。

    var f = v => v;

    //等同於:

    var f = function(v) {
      return v;
    };

如果箭頭函數不需要參數或需要多個參數,就使用一個圓括號代表參數部分。

    var f = () => 5;
    // 等同於
    var f = function (){ return 5 };

    var sum = (num1, num2) => num1 + num2;
    // 等同於
    var sum = function(num1, num2) {
      return num1 + num2;
    };

如果箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回。

    var sum = (num1, num2) => { return num1 + num2; }

由於大括號被解釋爲代碼塊,所以如果箭頭函數直接返回一個對象,必須在對象外面加上括號。

    var getTempItem = id => ({ id: id, name: "Temp" });

簡化回調函數

箭頭函數的一個用處是簡化回調函數。

  // 正常函數寫法
    [1,2,3].map(function (x) {
      return x * x;
    });

    // 箭頭函數寫法
    [1,2,3].map(x => x * x);

另一個例子是

  // 正常函數寫法
    var result = values.sort(function(a, b) {
      return a - b;
    });

    // 箭頭函數寫法
    var result = values.sort((a, b) => a - b);

如果不用箭頭函數,可能就要佔用多行,而且還不如現在這樣寫醒目。

使用注意點

箭頭函數有幾個使用注意點。

(1)函數體內的this對象,綁定定義時所在的對象,而不是使用時所在的對象。

(2)不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。

(3)不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用Rest參數代替。

(4)不可以使用yield命令,因此箭頭函數不能用作Generator函數。

this對象的指向是可變的,但是在箭頭函數中,它是固定的。

    function Timer () {
      this.seconds = 0
      setInterval(() => this.seconds++, 1000)
    }
    var timer = new Timer()
    setTimeout(() => console.log(timer.seconds), 3100)
    // 3

上面代碼中,Timer函數內部的setInterval-調用了_this.seconds-屬性,通過箭頭函數將_this綁定在Timer的實例對象。否則,輸出結果是0,而不是3。


模板字符串

模板字符串簡介

模板字符串允許嵌入表達式,並且支持多行字符串和字符串插補特性。

  模板字符串使用反引號 ( ) 來代替普通字符串中的用雙引號和單引號。模板字符串可以包含特定語法(${expression})的佔位符。佔位符中的表達式和周圍的文本會一起傳遞給一個默認函數,該函數負責將所有的部分連接起來,如果一個模板字符串由表達式開頭,則該字符串被稱爲帶標籤的模板字符串,該表達式通常是一個函數,它會在模板字符串處理後被調用,在輸出最終結果前,你都可以在通過該函數對模板字符串來進行操作處理。
語法

    'string text'

    'string text line 1'
    'string text line 2'

    'string text ${expression} string text'

    tag 'string text ${expression} string text'

多行字符串

在新行中插入的任何字符都是模板字符串中的一部分,使用普通字符串,你可以通過以下的方式獲得多行字符串:

    console.log("string text line 1\n\
    string text line 2");
    // "string text line 1
    // string text line 2"

要獲得同樣效果的多行字符串,只需使用如下代碼:

    console.log(`string text line 1
    string text line 2`);
    // "string text line 1
    // string text line 2"

表達式插補

在普通字符串中嵌入表達式,必須使用如下語法:

    var a = 5;
    var b = 10;
    console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
    // "Fifteen is 15 and
    // not 20."

現在通過模板字符串,我們可以使用一種更優雅的方式來表示:

    var a = 5;
    var b = 10;
    console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
    // "Fifteen is 15 and
    // not 20."

帶標籤的模板字符串

  模板字符串的一種更高級的形式稱爲帶標籤的模板字符串。它允許您通過標籤函數修改模板字符串的輸出。標籤函數的第一個參數是一個包含了字符串字面值的數組(在本例中分別爲“Hello”和“world”);第二個參數,在第一個參數後的每一個參數,都是已經被處理好的替換表達式(在這裏分別爲“15”和“50”)。 最後,標籤函數返回處理好的字符串。在後面的示例中,標籤函數的名稱可以爲任意的合法標示符。

    var a = 5;
    var b = 10;

    function tag(strings) {
      console.log(arguments);    // { '0': [ 'Hello ', ' world ', '' ], '1': 15, '2': 50 }
      console.log(strings[0]);   // "Hello "
      console.log(strings[1]);   // " world "
      console.log(arguments[1]);  // 15
      console.log(arguments[2]);  // 50

      return "Hubwiz!";
    }

    console.log(tag`Hello ${ a + b } world ${ a * b}`);
    // "Hubwiz!"

我們打印出arguments可以看到 處理的參數爲

    { '0': [ 'Hello ', ' world ', '' ], '1': 15, '2': 50 }

我們已有的Hello和world參數放到一個數組中,後續處理的參數依次生成。

原始字符串

在標籤函數的第一個參數中,存在一個特殊的屬性raw ,我們可以通過它來訪問模板字符串的原始字符串。

    function tag(strings, values) {
      console.log(strings.raw[0]); 
      // "string text line 1 \\n string text line 2"
    }

    tag'string text line 1 \n string text line 2';

另外,使用String.raw() 方法創建原始字符串和使用默認模板函數和字符串連接創建是一樣的。

    String.raw`Hi\n${2+3}!`;
    // "Hi\\n5!"

安全性

由於模板字符串能夠訪問變量和函數,因此不能由不受信任的用戶來構造。

    "use strict"
    let a = 10;
    console.warn(`${a+=20}`); // "30"
    console.warn(a); // 30

新特性暫時就告一段落,有需要補充的歡迎大家聯繫我。

發佈了46 篇原創文章 · 獲贊 675 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章