国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

使用ES6寫更好的JavaScript

Dionysus_go / 493人閱讀

摘要:但在可以用和的地方使用它們很有好處的。它會盡可能的約束變量的作用域,有助于減少令人迷惑的命名沖突。在回調函數外面,也就是中,它指向了對象。這就意味著當引擎查找的值時,可以找到值,但卻和回調函數之外的不是同一個值。

使用 ES6 寫更好的 JavaScript part I:廣受歡迎新特性 介紹

在ES2015規范敲定并且Node.js增添了大量的函數式子集的背景下,我們終于可以拍著胸脯說:未來就在眼前。

… 我早就想這樣說了

但這是真的。V8引擎將很快實現規范,而且Node已經添加了大量可用于生產環境的ES2015特性。下面要列出的是一些我認為很有必要的特性,而且這些特性是不使用需要像Babel或者Traceur這樣的翻譯器就可以直接使用的。

這篇文章將會講到三個相當流行的ES2015特性,并且已經在Node中支持了了:

用let和const聲明塊級作用域;

箭頭函數;

簡寫屬性和方法。

讓我們馬上開始。

let和const聲明塊級作用域

作用域是你程序中變量可見的區域。換句話說就是一系列的規則,它們決定了你聲明的變量在哪里是可以使用的。

大家應該都聽過 ,在JavaScript中只有在函數內部才會創造新的作用域。然而你創建的98%的作用域事實上都是函數作用域,其實在JavaScript中有三種創建新作用域的方法。你可以這樣:

創建一個函數。你應該已經知道這種方式。

創建一個catch塊。 我絕對沒喲開玩笑.

創建一個代碼塊。如果你用的是ES2015,在一段代碼塊中用let或者const聲明的變量會限制它們只在這個塊中可見。這叫做塊級作用域。

一個代碼塊就是你用花括號包起來的部分。 { 像這樣 }。在if/else聲明和try/catch/finally塊中經常出現。如果你想利用塊作用域的優勢,你可以用花括號包裹任意的代碼來創建一個代碼塊

考慮下面的代碼片段。

// 在 Node 中你需要使用 strict 模式嘗試這個
"use strict";

var foo = "foo";
function baz() {
    if (foo) {
        var bar = "bar";
        let foobar = foo + bar;
    }
    // foo 和 bar 這里都可見 
    console.log("This situation is " + foo + bar + ". I"m going home.");

    try {
        console.log("This log statement is " + foobar + "! It threw a ReferenceError at me!");
    } catch (err) {
        console.log("You got a " + err + "; no dice.");
    }

    try {
        console.log("Just to prove to you that " + err + " doesn"t exit outside of the above `catch` block.");
    } catch (err) {
        console.log("Told you so.");
    }
}

baz();

try {
    console.log(invisible);
} catch (err) {
    console.log("invisible hasn"t been declared, yet, so we get a " + err);
}
let invisible = "You can"t see me, yet"; // let 聲明的變量在聲明前是不可訪問的

還有些要強調的

注意foobar在if塊之外是不可見的,因為我們沒有用let聲明;

我們可以在任何地方使用foo ,因為我們用var定義它為全局作用域可見;

我們可以在baz內部任何地方使用bar, 因為var-聲明的變量是在定義的整個作用域內都可見。

用let or const聲明的變量不能在定義前調用。換句話說,它不會像var變量一樣被編譯器提升到作用域的開始處。

const 與 let 類似,但有兩點不同。

必須給聲明為const的變量在聲明時賦值。不可以先聲明后賦值。

不能改變const變量的值,只有在創建它時可以給它賦值。如果你試圖改變它的值,會得到一個TyepError。

let & const: Who Cares?

我們已經用var將就了二十多年了,你可能在想我們真的需要新的類型聲明關鍵字嗎?(這里作者應該是想表達這個意思)

問的好,簡單的回答就是–不, 并不真正需要。但在可以用let和const的地方使用它們很有好處的。

let和const聲明變量時都不會被提升到作用域開始的地方,這樣可以使代碼可讀性更強,制造盡可能少的迷惑。

它會盡可能的約束變量的作用域,有助于減少令人迷惑的命名沖突。

這樣可以讓程序只有在必須重新分配變量的情況下重新分配變量。 const 可以加強常量的引用。

另一個例子就是 let 在 for 循環中的使用:

"use strict";

var languages = ["Danish", "Norwegian", "Swedish"];

//會污染全局變量!
for (var i = 0; i < languages.length; i += 1) {
    console.log(`${languages[i]} is a Scandinavian language.`);
}

console.log(i); // 4

for (let j = 0; j < languages.length; j += 1) {
    console.log(`${languages[j]} is a Scandinavian language.`);
}

try {
    console.log(j); // Reference error
} catch (err) {
    console.log(`You got a ${err}; no dice.`);
}

在for循環中使用var聲明的計數器并不會真正把計數器的值限制在本次循環中。 而let可以。

let在每次迭代時重新綁定循環變量有很大的優勢,這樣每個循環中拷貝自身 , 而不是共享全局范圍內的變量。

"use strict";

// 簡潔明了
for (let i = 1; i < 6; i += 1) {
    setTimeout(function() {
        console.log("I"ve waited " + i + " seconds!");
    }, 1000 * i);
}

// 功能完全混亂
for (var j = 0; j < 6; j += 1) {
        setTimeout(function() {
        console.log("I"ve waited " + j + " seconds for this!");
    }, 1000 * j);
}

第一層循環會和你想象的一樣工作。而下面的會每秒輸出 “I’ve waited 6 seconds!”。

好吧,我選擇狗帶。

動態this關鍵字的怪異

JavaScript的this關鍵字因為總是不按套路出牌而臭名昭著。

事實上,它的規則相當簡單。不管怎么說,this在有些情形下會導致奇怪的用法

"use strict";

const polyglot = {
    name : "Michel Thomas",
    languages : ["Spanish", "French", "Italian", "German", "Polish"],
    introduce : function () {
        // this.name is "Michel Thomas"
        const self = this;
        this.languages.forEach(function(language) {
            // this.name is undefined, so we have to use our saved "self" variable 
            console.log("My name is " + self.name + ", and I speak " + language + ".");
        });
    }
}

polyglot.introduce();

在introduce里, this.name是undefined。在回調函數外面,也就是forEach中, 它指向了polyglot對象。在這種情形下我們總是希望在函數內部this和函數外部的this指向同一個對象。

問題是在JavaScript中函數會根據確定性四原則在調用時定義自己的this變量。這就是著名的動態this 機制。

這些規則中沒有一個是關于查找this所描述的“附近作用域”的;也就是說并沒有一個確切的方法可以讓JavaScript引擎能夠基于包裹作用域來定義this的含義。

這就意味著當引擎查找this的值時,可以找到值,但卻和回調函數之外的不是同一個值。有兩種傳統的方案可以解決這個問題。

在函數外面把this保存到一個變量中,通常取名self,并在內部函數中使用;

或者在內部函數中調用bind阻止對this的賦值。

以上兩種辦法均可生效,但會產生副作用。

另一方面,如果內部函數沒有設置它自己的this值,JavaScript會像查找其它變量那樣查找this的值:通過遍歷父作用域直到找到同名的變量。這樣會讓我們使用附近作用域代碼中的this值,這就是著名的詞法this。

如果有樣的特性,我們的代碼將會更加的清晰,不是嗎?

箭頭函數中的詞法this

在 ES2015 中,我們有了這一特性。箭頭函數不會綁定this值,允許我們利用詞法綁定this關鍵字。這樣我們就可以像這樣重構上面的代碼了:

"use strict";

let polyglot = {
    name : "Michel Thomas",
    languages : ["Spanish", "French", "Italian", "German", "Polish"],
    introduce : function () {
        this.languages.forEach((language) => {
            console.log("My name is " + this.name + ", and I speak " + language + ".");
        });
    }
}

… 這樣就會按照我們想的那樣工作了。

箭頭函數有一些新的語法。

"use strict";

let languages = ["Spanish", "French", "Italian", "German", "Polish"];

// 多行箭頭函數必須使用花括號, 
// 必須明確包含返回值語句
    let languages_lower = languages.map((language) => {
    return language.toLowerCase()
});

// 單行箭頭函數,花括號是可省的,
// 函數默認返回最后一個表達式的值
// 你可以指明返回語句,這是可選的。
let languages_lower = languages.map((language) => language.toLowerCase());

// 如果你的箭頭函數只有一個參數,可以省略括號
let languages_lower = languages.map(language => language.toLowerCase());

// 如果箭頭函數有多個參數,必須用圓括號包裹
let languages_lower = languages.map((language, unused_param) => language.toLowerCase());

console.log(languages_lower); // ["spanish", "french", "italian", "german", "polish"]

// 最后,如果你的函數沒有參數,你必須在箭頭前加上空的括號。
(() => alert("Hello!"))();

MDN關于箭頭函數的文檔解釋的很好。

簡寫屬性和方法

ES2015提供了在對象上定義屬性和方法的一些新方式。

簡寫方法

在 JavaScript 中, method 是對象的一個有函數值的屬性:

"use strict";

const myObject = {
    const foo = function () {
        console.log("bar");
    },
}

在ES2015中,我們可以這樣簡寫:

"use strict";

const myObject = {
    foo () {
        console.log("bar");
    },
    * range (from, to) {
        while (from < to) {
            if (from === to)
                return ++from;
            else
                yield from ++;
        }
    }
}

注意你也可以使用生成器去定義方法。只需要在函數名前面加一個星號(*)。

這些叫做 方法定義 。和傳統的函數作為屬性很像,但有一些不同:

只能在方法定義處調用super;

不允許用new調用方法定義。

我會在隨后的幾篇文章中講到super關鍵字。如果你等不及了, Exploring ES6中有關于它的干貨。

簡寫和推導屬性

ES6還引入了簡寫和推導屬性 。

如果對象的鍵值和變量名是一致的,那么你可以僅用變量名來初始化你的對象,而不是定義冗余的鍵值對。

"use strict";

const foo = "foo";
const bar = "bar";

// 舊語法
const myObject = {
    foo : foo,
    bar : bar
};

// 新語法
const myObject = { foo, bar }

兩中語法都以foo和bar鍵值指向foo and bar變量。后面的方式語義上更加一致;這只是個語法糖。

當用揭示模塊模式來定義一些簡潔的公共 API 的定義,我常常利用簡寫屬性的優勢。

"use strict";

function Module () {
    function foo () {
        return "foo";
    }
    
    function bar () {
        return "bar";
    }
    
    // 這樣寫:
    const publicAPI = { foo, bar }
    
    /* 不要這樣寫:
    const publicAPI =  {
       foo : foo,
       bar : bar
    } */ 
    
    return publicAPI;
};

這里我們創建并返回了一個publicAPI對象,鍵值foo指向foo方法,鍵值bar指向bar方法。

推導屬性名

這是不常見的例子,但ES6允許你用表達式做屬性名。

"use strict";

const myObj = {
  // 設置屬性名為 foo 函數的返回值
    [foo ()] () {
      return "foo";
    }
};

function foo () {
    return "foo";
}

console.log(myObj.foo() ); // "foo"

根據Dr. Raushmayer在Exploring ES6中講的,這種特性最主要的用途是設置屬性名與Symbol值一樣。

Getter 和 Setter 方法

最后,我想提一下get和set方法,它們在ES5中就已經支持了。

"use strict";

// 例子采用的是 MDN"s 上關于 getter 的內容
//   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get
const speakingObj = {
    // 記錄 “speak” 方法調用過多少次
    words : [],
    
    speak (word) {
        this.words.push(word);
        console.log("speakingObj says " + word + "!");
    },
    
    get called () {
        // 返回最新的單詞
        const words = this.words;
        if (!words.length)
            return "speakingObj hasn"t spoken, yet.";
        else
            return words[words.length - 1];
    }
};

console.log(speakingObj.called); // "speakingObj hasn"t spoken, yet."

speakingObj.speak("blargh"); // "speakingObj says blargh!"

console.log(speakingObj.called); // "blargh"

使用getters時要記得下面這些:

Getters不接受參數;

屬性名不可以和getter函數重名;

可以用Object.defineProperty(OBJECT, "property name", { get : function () { . . . } }) 動態創建 getter

作為最后這點的例子,我們可以這樣定義上面的 getter 方法:

"use strict";

const speakingObj = {
    // 記錄 “speak” 方法調用過多少次
    words : [],
    
    speak (word) {
        this.words.push(word);
        console.log("speakingObj says " + word + "!");
    }
};

// 這只是為了證明觀點。我是絕對不會這樣寫的
function called () {
    // 返回新的單詞
    const words = this.words;
    if (!words.length)
        return "speakingObj hasn"t spoken, yet.";
    else
        return words[words.length - 1];
};

Object.defineProperty(speakingObj, "called", get : getCalled ) 
除了 getters,還有 setters。像平常一樣,它們通過自定義的邏輯給對象設置屬性。

"use strict";

// 創建一個新的 globetrotter(環球者)!
const globetrotter = {
    // globetrotter 現在所處國家所說的語言 
    const current_lang = undefined,
    
    // globetrotter 已近環游過的國家
    let countries = 0,
    
    // 查看環游過哪些國家了
    get countryCount () {
        return this.countries;
    }, 
    
    // 不論 globe trotter 飛到哪里,都重新設置他的語言
    set languages (language) {
        // 增加環游過的城市數
        countries += 1;
    
        // 重置當前語言
        this.current_lang = language; 
    };
};

globetrotter.language = "Japanese";
globetrotter.countryCount(); // 1

globetrotter.language = "Spanish";
globetrotter.countryCount(); // 2

上面講的關于getters的也同樣適用于setters,但有一點不同:

getter不接受參數,setters必須接受正好一個參數。

破壞這些規則中的任意一個都會拋出一個錯誤。

既然 Angular 2 正在引入TypeCript并且把class帶到了臺前,我希望get and set能夠流行起來… 但還有點希望它們不要流行起來。

結論

未來的JavaScript正在變成現實,是時候把它提供的東西都用起來了。這篇文章里,我們瀏覽了 ES2015的三個很流行的特性:

let和const帶來的塊級作用域;

箭頭函數帶來的this的詞法作用域;

簡寫屬性和方法,以及getter和setter函數的回顧。

使用 ES6 編寫更好的 JavaScript Part II:深入探究 [類] 辭舊迎新

在本文的開始,我們要說明一件事:

從本質上說,ES6的classes主要是給創建老式構造函數提供了一種更加方便的語法,并不是什么新魔法 —— Axel Rauschmayer,Exploring ES6作者

從功能上來講,class聲明就是一個語法糖,它只是比我們之前一直使用的基于原型的行為委托功能更強大一點。本文將從新語法與原型的關系入手,仔細研究ES2015的class關鍵字。文中將提及以下內容:

定義與實例化類;

使用extends創建子類;

子類中super語句的調用;

以及重要的標記方法(symbol method)的例子。

在此過程中,我們將特別注意 class 聲明語法從本質上是如何映射到基于原型代碼的。

讓我們從頭開始說起。

退一步說:Classes不是什么

JavaScript的『類』與Java、Python或者其他你可能用過的面向對象語言中的類不同。其實后者可能稱作面向『類』的語言更為準確一些。

在傳統的面向類的語言中,我們創建的類是對象的模板。需要一個新對象時,我們實例化這個類,這一步操作告訴語言引擎將這個類的方法和屬性復制到一個新實體上,這個實體稱作實例。實例是我們自己的對象,且在實例化之后與父類毫無內在聯系。

而JavaScript沒有這樣的復制機制。在JavaScript中『實例化』一個類創建了一個新對象,但這個新對象卻不獨立于它的父類。

正相反,它創建了一個與原型相連接的對象。即使是在實例化之后,對于原型的修改也會傳遞到實例化的新對象去。

原型本身就是一個無比強大的設計模式。有許多使用了原型的技術模仿了傳統類的機制,class便為這些技術提供了簡潔的語法。

總而言之:

JavaScript不存在Java和其他面向對象語言中的類概念;

JavaScript 的class很大程度上只是原型繼承的語法糖,與傳統的類繼承有很大的不同。

搞清楚這些之后,讓我們先看一下class。

類基礎:聲明與表達式

我們使用class 關鍵字創建類,關鍵字之后是變量標識符,最后是一個稱作類主體的代碼塊。這種寫法稱作類的聲明。沒有使用extends關鍵字的類聲明被稱作基類:

"use strict";

// Food 是一個基類
class Food {

    constructor (name, protein, carbs, fat) {
        this.name = name;
        this.protein = protein;
        this.carbs = carbs;
        this.fat = fat;
    }

    toString () {
        return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`
    }

    print () {
        console.log( this.toString() );
    }
}

const chicken_breast = new Food("Chicken Breast", 26, 0, 3.5);

chicken_breast.print(); // "Chicken Breast | 26g P :: 0g C :: 3.5g F"
console.log(chicken_breast.protein); // 26 (LINE A)

需要注意到以下事情:

類只能包含方法定義,不能有數據屬性;

定義方法時,可以使用簡寫方法定義;

與創建對象不同,我們不能在類主體中使用逗號分隔方法定義;

我們可以在實例化對象上直接引用類的屬性(如 LINE A)。

類有一個獨有的特性,就是 contructor 構造方法。在構造方法中我們可以初始化對象的屬性。

構造方法的定義并不是必須的。如果不寫構造方法,引擎會為我們插入一個空的構造方法:

"use strict";

class NoConstructor {
    /* JavaScript 會插入這樣的代碼:
     constructor () { }
    */
}

const nemo = new NoConstructor(); // 能工作,但沒啥意思

將一個類賦值給一個變量的形式叫類表達式,這種寫法可以替代上面的語法形式:

"use strict";

// 這是一個匿名類表達式,在類主體中我們不能通過名稱引用它
const Food = class {
    // 和上面一樣的類定義……
}

// 這是一個命名類表達式,在類主體中我們可以通過名稱引用它
const Food = class FoodClass {
    // 和上面一樣的類定義……

    //  添加一個新方法,證明我們可以通過內部名稱引用 FoodClass……        
    printMacronutrients () {
        console.log(`${FoodClass.name} | ${FoodClass.protein} g P :: ${FoodClass.carbs} g C :: ${FoodClass.fat} g F`)
    }
}

const chicken_breast = new Food("Chicken Breast", 26, 0, 3.5);
chicken_breast.printMacronutrients(); // "Chicken Breast | 26g P :: 0g C :: 3.5g F"

// 但是不能在外部引用
try {
    console.log(FoodClass.protein); // 引用錯誤
} catch (err) {
    // pass
}

這一行為與匿名函數與命名函數表達式很類似。

使用extends創建子類以及使用super調用

使用extends創建的類被稱作子類,或派生類。這一用法簡單明了,我們直接在上面的例子中構建:

"use strict";

// FatFreeFood 是一個派生類
class FatFreeFood extends Food {

    constructor (name, protein, carbs) {
        super(name, protein, carbs, 0);
    }

    print () {
        super.print();
        console.log(`Would you look at that -- ${this.name} has no fat!`);
    }

}

const fat_free_yogurt = new FatFreeFood("Greek Yogurt", 16, 12);
fat_free_yogurt.print(); // "Greek Yogurt | 26g P :: 16g C :: 0g F  /  Would you look at that -- Greek Yogurt has no fat!"

派生類擁有我們上文討論的一切有關基類的特性,另外還有如下幾點新特點:

子類使用class關鍵字聲明,之后緊跟一個標識符,然后使用extend關鍵字,最后寫一個任意表達式。這個表達式通常來講就是個標識符,但理論上也可以是函數。

如果你的派生類需要引用它的父類,可以使用super關鍵字。

一個派生類不能有一個空的構造函數。即使這個構造函數就是調用了一下super(),你也得把它顯式的寫出來。但派生類卻可以沒有構造函數。

在派生類的構造函數中,必須先調用super,才能使用this關鍵字(譯者注:僅在構造函數中是這樣,在其他方法中可以直接使用this)。

在JavaScript中僅有兩個super關鍵字的使用場景:

在子類構造函數中調用。如果初始化派生類是需要使用父類的構造函數,我們可以在子類的構造函數中調用super(parentConstructorParams),傳遞任意需要的參數。

引用父類的方法。在常規方法定義中,派生類可以使用點運算符來引用父類的方法:super.methodName。

我們的 FatFreeFood 演示了這兩種情況:

在構造函數中,我們簡單的調用了super,并將脂肪的量傳入為0。

在我們的print方法中,我們先調用了super.print,之后才添加了其他的邏輯。

不管你信不信,我反正是信了以上說的已涵蓋了有關class的基礎語法,這就是你開始實驗需要掌握的全部內容。

深入學習原型

現在我們開始關注class是怎么映射到JavaScript內部的原型機制的。我們會關注以下幾點:

使用構造調用創建對象;

原型連接的本質;

屬性和方法委托;

使用原型模擬類。

使用構造調用創建對象

構造函數不是什么新鮮玩意兒。使用new關鍵字調用任意函數會使其返回一個對象 —— 這一步稱作創建了一個構造調用,這種函數通常被稱作構造器:

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

// 使用 "new" 關鍵字調用 Food 方法,就是構造調用,該操作會返回一個對象
const chicken_breast = new Food("Chicken Breast", 26, 0, 3.5);
console.log(chicken_breast.protein) // 26

// 不用 "new" 調用 Food 方法,會返回 "undefined"
const fish = Food("Halibut", 26, 0, 2);
console.log(fish); // "undefined"

當我們使用new關鍵字調用函數時,JS內部執行了下面四個步驟:

創建一個新對象(這里稱它為O);

給O賦予一個連接到其他對象的鏈接,稱為原型;

將函數的this引用指向O;

函數隱式返回O。

在第三步和第四步之間,引擎會執行你函數中的具體邏輯。

知道了這一點,我們就可以重寫Food方法,使之不用new關鍵字也能工作:

"use strict";

// 演示示例:消除對 "new" 關鍵字的依賴
function Food (name, protein, carbs, fat) {
    // 第一步:創建新對象
    const obj = { };

    // 第二步:鏈接原型——我們在下文會更加具體地探究原型的概念
    Object.setPrototypeOf(obj, Food.prototype);

    // 第三步:設置 "this" 指向我們的新對象
    // 盡然我們不能再運行的執行上下文中重置 `this`
    // 我們在使用 "obj" 取代 "this" 來模擬第三步
    obj.name    = name;
    obj.protein = protein;
    obj.carbs   = carbs;
    obj.fat     = fat;

    // 第四步:返回新創建的對象
    return obj;
}

const fish = Food("Halibut", 26, 0, 2);
console.log(fish.protein); // 26

四步中的三步都是簡單明了的。創建一個對象、賦值屬性、然后寫一個return聲明,這些操作對大多數開發者來說沒有理解上的問題——然而這就是難倒眾人的黑魔法原型。

直觀理解原型鏈

在通常情況下,JavaScript中的包括函數在內的所有對象都會鏈接到另一個對象上,這就是原型。

如果我們訪問一個對象本身沒有的屬性,JavaScript就會在對象的原型上檢查該屬性。換句話說,如果你對一個對象請求它沒有的屬性,它會對你說:『這個我不知道,問我的原型吧』。

在另一個對象上查找不存在屬性的過程稱作委托。

"use strict";

// joe 沒有 toString 方法……
const joe    = { name : "Joe" },
    sara   = { name : "Sara" };

Object.hasOwnProperty(joe, toString); // false
Object.hasOwnProperty(sara, toString); // false

// ……但我們還是可以調用它!
joe.toString(); // "[object Object]",而不是引用錯誤!
sara.toString(); // "[object Object]",而不是引用錯誤!

盡管我們的 toString 的輸出完全沒啥用,但請注意:這段代碼沒有引起任何的ReferenceError!這是因為盡管joe和sara沒有toString的屬性,但他們的原型有啊。

當我們尋找sara.toString()方法時,sara說:『我沒有toString屬性,找我的原型吧』。正如上文所說,JavaScript會親切的詢問Object.prototype 是否含有toString屬性。由于原型上有這一屬性,JS 就會把Object.prototype上的toString返回給我們程序并執行。

sara本身沒有屬性沒關系——我們會把查找操作委托到原型上。

換言之,我們就可以訪問到對象上并不存在的屬性,只要其的原型上有這些屬性。我們可以利用這一點將屬性和方法賦值到對象的原型上,然后我們就可以調用這些屬性,好像它們真的存在在那個對象上一樣。

更給力的是,如果幾個對象共享相同的原型——正如上面的joe和sara的例子一樣——當我們給原型賦值屬性之后,它們就都可以訪問了,無需將這些屬性多帶帶拷貝到每一個對象上。

這就是為何大家把它稱作原型繼承——如果我的對象沒有,但對象的原型有,那我的對象也能繼承這個屬性。

事實上,這里并沒有發生什么『繼承』。在面向類的語言里,繼承指從父類復制屬性到子類的行為。在JavaScript里,沒發生這種復制的操作,事實上這就是原型繼承與類繼承相比的一個主要優勢。

在我們探究原型究竟是怎么來的之前,我們先做一個簡要回顧:

joe和sara沒有『繼承』一個toString的屬性;

joe和sara實際上根本沒有從Object.prototype上『繼承』;

joe和sara是鏈接到了Object.prototype上;

joe和sara鏈接到了同一個Object.prototype上。

如果想找到一個對象的(我們稱它作O)原型,我們可以使用 Object.getPrototypeof(O)。

然后我們再強調一遍:對象沒有『繼承自』他們的原型。他們只是委托到原型上。

以上。

接下來讓我們深入一下。

設置對象的原型

我們已了解到基本上每個對象(下文以O指代)都有原型(下文以P指代),然后當我們查找O上沒有的屬性,JavaScript引擎就會在P上尋找這個屬性。

至此我們有兩個問題:

以上情況函數怎么玩?

這些原型是從哪里來的?

名為Object的函數

在JavaScript引擎執行程序之前,它會創建一個環境讓程序在內部執行,在執行環境中會創建一個函數,叫做Object, 以及一個關聯對象,叫做Object.prototype。

換句話說,Object和Object.prototype在任意執行中的JavaScript程序中永遠存在。

這個Object乍一看好像和其他函數沒什么區別,但特別之處在于它是一個構造器——在調用它時返回一個新對象:

"use strict";

typeof new Object(); // "object"
typeof Object();     // 這個 Object 函數的特點是不需要使用 new 關鍵字調用

這個Object.prototype對象是個……對象。正如其他對象一樣,它有屬性。

關于Object和Object.prototype你需要知道以下幾點:

Object函數有一個叫做.prototype的屬性,指向一個對象(Object.prototype);

Object.prototype對象有一個叫做.constructor的屬性,指向一個函數(Object)。

實際上,這個總體方案對于JavaScript中的所有函數都是適用的。當我們創建一個函數——下文稱作 someFunction——這個函數就會有一個屬性.prototype,指向一個叫做someFunction.prototype 的對象。

與之相反,someFunction.prototype對象會有一個叫做.contructor的屬性,它的引用指回函數someFunction。

"use strict";

function foo () {  console.log("Foo!");  }

console.log(foo.prototype); // 指向一個叫 "foo" 的對象
console.log(foo.prototype.constructor); // 指向 "foo" 函數

foo.prototype.constructor(); // 輸出 "Foo!" —— 僅為證明確實有 "foo.prototype.constructor" 這么個方法且指向原函數

需要記住以下幾個要點:

所有的函數都有一個屬性,叫做 .prototype,它指向這個函數的關聯對象。

所有函數的原型都有一個屬性,叫做 .constructor,它指向這個函數本身。

一個函數原型的 .constructor 并非必須指向創建這個函數原型的函數……有點繞,我們等下會深入探討一下。

設置函數的原型有一些規則,在開始之前,我們先概括設置對象原型的三個規則:

『默認』規則;

使用new隱式設置原型;

使用Object.create顯式設置原型。

默認規則

考慮下這段代碼:

"use strict";

const foo = { status : "foobar" };

十分簡單,我們做的事兒就是創建一個叫foo的對象,然后給他一個叫status的屬性。

然后JavaScript在幕后多做了點工作。當我們在字面上創建一個對象時,JavaScript將對象的原型指向Object.prototype并設置其原型的.constructor指向Object:

"use strict";

const foo = { status : "foobar" };

Object.getPrototypeOf(foo) === Object.prototype; // true
foo.constructor === Object; // true
使用new隱式設置原型

讓我們再看下之前調整過的 Food 例子。

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

現在我們知道函數Food將會與一個叫做Food.prototype的對象關聯。

當我們使用new關鍵字創建一個對象,JavaScript將會:

設置這個對象的原型指向我們使用new調用的函數的.prototype屬性;

設置這個對象的.constructor指向我們使用new調用到的構造函數。

const tootsie_roll = new Food("Tootsie Roll", 0, 26, 0);

Object.getPrototypeOf(tootsie_roll) === Food.prototype; // true
tootsie_roll.constructor === Food; // true

這就可以讓我們搞出下面這樣的黑魔法:

"use strict";

Food.prototype.cook = function cook () {
    console.log(`${this.name} is cooking!`);
};

const dinner = new Food("Lamb Chops", 52, 8, 32);
dinner.cook(); // "Lamb Chops are cooking!"
使用Object.create顯式設置原型

最后我們可以使用Object.create方法手工設置對象的原型引用。

"use strict";

const foo = {
    speak () {
        console.log("Foo!");
    }
};

const bar = Object.create(foo);

bar.speak(); // "Foo!"
Object.getPrototypeOf(bar) === foo; // true

還記得使用new調用函數的時候,JavaScript在幕后干了哪四件事兒嗎?Object.create就干了這三件事兒:

創建一個新對象;

設置它的原型引用;

返回這個新對象。

你可以自己去看下MDN上寫的那個polyfill。
(譯者注:polyfill就是給老代碼實現現有新功能的補丁代碼,這里就是指老版本JS沒有Object.create函數,MDN上有手工擼的一個替代方案)

模擬 class 行為

直接使用原型來模擬面向類的行為需要一些技巧。

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

Food.prototype.toString = function () {
    return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
};

function FatFreeFood (name, protein, carbs) {
    Food.call(this, name, protein, carbs, 0);
}

// 設置 "subclass" 關系
// =====================
// LINE A :: 使用 Object.create 手動設置 FatFreeFood"s 『父類』.
FatFreeFood.prototype = Object.create(Food.prototype);

// LINE B :: 手工重置 constructor 的引用
Object.defineProperty(FatFreeFood.constructor, "constructor", {
    enumerable : false,
    writeable  : true,
    value      : FatFreeFood
});

在Line A,我們需要設置FatFreeFood.prototype使之等于一個新對象,這個新對象的原型引用是Food.prototype。如果沒這么搞,我們的子類就不能訪問『超類』的方法。

不幸的是,這個導致了相當詭異的結果:FatFreeFood.constructor是Function,而不是FatFreeFood。為了保證一切正常,我們需要在Line B手工設置FatFreeFood.constructor。

讓開發者從使用原型對類行為笨拙的模仿中脫離苦海是class關鍵字的產生動機之一。它確實也提供了避免原型語法常見陷阱的解決方案。

現在我們已經探究了太多關于JavaScript的原型機制,你應該更容易理解class關鍵字讓一切變得多么簡單了吧!

深入探究下方法

現在我們已了解到JavaScript原型系統的必要性,我們將深入探究一下類支持的三種方法,以及一種特殊情況,以結束本文的討論。

構造器;

靜態方法;

原型方法;

一種原型方法的特殊情況:『標記方法』。

并非我提出的這三組方法,這要歸功于Rauschmayer博士在探索ES6一書中的定義。

類構造器

一個類的constructor方法用于關注我們的初始化邏輯,constructor方法有以下幾個特殊點:

只有在構造方法里,我們才可以調用父類的構造器;

它在背后處理了所有設置原型鏈的工作;

它被用作類的定義。

第二點就是在JavaScript中使用class的一個主要好處,我們來引用一下《探索 ES6》書里的15.2.3.1 的標題:

子類的原型就是超類

正如我們所見,手工設置非常繁瑣且容易出錯。如果我們使用class關鍵字,JavaScript在內部會負責搞定這些設置,這一點也是使用class的優勢。

第三點有點意思。在JavaScript中類僅僅是個函數——它等同于與類中的constructor方法。

"use strict";

class Food {
    // 和之前一樣的類定義……
}

typeof Food; // "function"

與一般把函數作為構造器的方式不同,我們不能不用new關鍵字而直接調用類構造器:

const burrito = Food("Heaven", 100, 100, 25); // 類型錯誤

這就引發了另一個問題:當我們不用new調用函數構造器的時候發生了什么?

簡短的回答是:對于任何沒有顯式返回的函數來說都是返回undefined。我們只需要相信用我們構造函數的用戶都會使用構造調用。這就是社區為何約定構造方法的首字母大寫:提醒使用者要用new來調用。

"use strict";

function Food (name, protein, carbs, fat) {
    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

const fish = Food("Halibut", 26, 0, 2); // D"oh . . .
console.log(fish); // "undefined"

長一點的回答是:返回undefined,除非你手工檢測是否使用被new調用,然后進行自己的處理。

ES2015引入了一個屬性使得這種檢測變得簡單: new.target.

new.target是一個定義在所有使用new調用的函數上的屬性,包括類構造器。 當我們使用new關鍵字調用函數時,函數體內的new.target的值就是這個函數本身。如果函數沒有被new調用,這個值就是undefined。

"use strict";

// 強行構造調用
function Food (name, protein, carbs, fat) {
    // 如果用戶忘了手工調用一下
    if (!new.target)
        return new Food(name, protein, carbs, fat);

    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

const fish = Food("Halibut", 26, 0, 2); // 糟了,不過沒關系!
fish; // "Food {name: "Halibut", protein: 20, carbs: 5, fat: 0}"

在ES5里用起來也還行:

"use strict";

function Food (name, protein, carbs, fat) {

    if (!(this instanceof Food))
        return new Food(name, protein, carbs, fat);

    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

MDN文檔講述了new.target的更多細節,而且給有興趣者配上了ES2015規范作為參考。規范里有關 [[Construct]] 的描述很有啟發性。

靜態方法

靜態方法是構造方法自己的方法,不能被類的實例化對象調用。我們使用static關鍵字定義靜態方法。

"use strict";

class Food {
     // 和之前一樣……

     // 添加靜態方法
     static describe () {
         console.log(""Food" 是一種存儲了營養信息的數據類型");
     }
}

Food.describe(); // ""Food" 是一種存儲了營養信息的數據類型"

靜態方法與老式構造函數中直接屬性賦值相似:

"use strict";

function Food (name, protein, carbs, fat) {
    Food.count += 1;

    this.name    = name;
    this.protein = protein;
    this.carbs   = carbs;
    this.fat     = fat;
}

Food.count = 0;
Food.describe = function count () {
    console.log(`你創建了 ${Food.count} 個 food`);
};

const dummy = new Food();
Food.describe(); // "你創建了 1 個 food"
原型方法

任何不是構造方法和靜態方法的方法都是原型方法。之所以叫原型方法,是因為我們之前通過給構造函數的原型上附加方法的方式來實現這一功能。

"use strict";

// 使用 ES6:
class Food {

    constructor (name, protein, carbs, fat) {
        this.name = name;
        this.protein = protein;
        this.carbs = carbs;
        this.fat = fat;
    }

    toString () {  
        return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
    }

    print () {  
        console.log( this.toString() );  
    }
}

// 在 ES5 里:
function Food  (name, protein, carbs, fat) {
    this.name = name;
    this.protein = protein;
    this.carbs = carbs;
    this.fat = fat;
}

// 『原型方法』的命名大概來自我們之前通過給構造函數的原型上附加方法的方式來實現這一功能。
Food.prototype.toString = function toString () {
    return `${this.name} | ${this.protein}g P :: ${this.carbs}g C :: ${this.fat}g F`;
};

Food.prototype.print = function print () {
    console.log( this.toString() );
};

應該說明,在方法定義時完全可以使用生成器。

"use strict";

class Range {

    constructor(from, to) {
        this.from = from;
        this.to   = to;
    }

    * generate () {
        let counter = this.from,
            to      = this.to;

        while (counter < to) {
            if (counter == to)
                return counter++;
            else
                yield counter++;
        }
    }
}

const range = new Range(0, 3);
const gen = range.generate();
for (let val of range.generate()) {
    console.log(`Generator 的值是 ${ val }. `);
    //  Prints:
    //    Generator 的值是 0.
    //    Generator 的值是 1.
    //    Generator 的值是 2.
}
標志方法

最后我們說說標志方法。這是一些名為Symbol值的方法,當我們在自定義對象中使用內置構造器時,JavaScript引擎可以識別并使用這些方法。

MDN文檔提供了一個Symbol是什么的簡要概覽:

Symbol是一個唯一且不變的數據類型,可以作為一個對象的屬性標示符。

創建一個新的symbol,會給我們提供一個被認為是程序里的唯一標識的值。這一點對于命名對象的屬性十分有用:我們可以確保不會不小心覆蓋任何屬性。使用Symbol做鍵值也不是無數的,所以他們很大程度上對外界是不可見的(也不完全是,可以通過Reflect.ownKeys獲得)

"use strict";

const secureObject = {
    // 這個鍵可以看作是唯一的
    [new Symbol("name")] : "Dr. Secure A. F."
};

console.log( Object.getKeys(superSecureObject) ); // [] -- 標志屬性不太好獲取    
console.log( Reflect.ownKeys(secureObject) ); // [Symbol("name")] -- 但也不是完全隱藏的

對我們來講更有意思的是,這給我們提供了一種方式來告訴 JavaScript 引擎使用特定方法來達到特定的目的。

所謂的『眾所周知的Symbol』是一些特定對象的鍵,當你在定義對象中使用時他們時,JavaScript引擎會觸發一些特定方法。

這對于JavaScript來說有點怪異,我們還是看個例子吧:

"use strict";

// 繼承 Array 可以讓我們直觀的使用 "length"
// 同時可以讓我們訪問到內置方法,如
// map、filter、reduce、push、pop 等
class FoodSet extends Array {

    // foods 把傳遞的任意參數收集為一個數組
    // 參見:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
    constructor(...foods) {
        super();
        this.foods = [];
        foods.forEach((food) => this.foods.push(food))
    }

     // 自定義迭代器行為,請注意,這不是多么好用的迭代器,但是個不錯的例子
     // 鍵名前必須寫星號
     * [Symbol.iterator] () {
        let position = 0;
        while (position < this.foods.length) {
          if (position === this.foods.length) {
              return "Done!"
          } else {
              yield `${this.foods[ position++ ]} is the food item at position ${position}`;
          }
         }
     }

     // 當我們的用戶使用內置的數組方法,返回一個數組類型對象
     // 而不是 FoodSet 類型的。這使得我們的 FoodSet 可以被一些
     // 期望操作數組的代碼操作
     static get [Symbol.species] () {
         return Array;
     }
}

const foodset = new FoodSet(new Food("Fish", 26, 0, 16), new Food("Hamburger", 26, 48, 24));

// 當我們使用 for ... of 操作 FoodSet 時,JavaScript 將會使用
// 我們之前用 [Symbol.iterator] 做鍵值的方法
for (let food of foodset) {
    // 打印全部 food
    console.log( food );
}

// 當我們執行數組的 `filter` 方法時,JavaScript 創建并返回一個新對象
// 我們在什么對象上執行 `filter` 方法,新對象就使用這個對象作為默認構造器來創建
// 然而大部分代碼都希望 filter 返回一個數組,于是我們通過重寫 [Symbol.species]
// 的方式告訴 JavaScript 使用數組的構造器
const healthy_foods = foodset.filter((food) => food.name !== "Hamburger");

console.log( healthy_foods instanceof FoodSet ); //
console.log( healthy_foods instanceof Array );

當你使用for...of遍歷一個對象時,JavaScript將會嘗試執行對象的迭代器方法,這一方法就是該對象 Symbol.iterator屬性上關聯的方法。如果我們提供了自己的方法定義,JavaScript就會使用我們自定義的。如果沒有自己制定的話,如果有默認的實現就用默認的,沒有的話就不執行。

Symbo.species更奇異了。在自定義的類中,默認的Symbol.species函數就是類的構造函數。當我們的子類有內置的集合(例如Array和Set)時,我們通常希望在使用父類的實例時也能使用子類。

通過方法返回父類的實例而不是派生類的實例,使我們更能確保我們子類在大多數代碼里的可用性。而Symbol.species可以實現這一功能。

如果不怎么需要這個功能就別費力去搞了。Symbol的這種用法——或者說有關Symbol的全部用法——都還比較罕見。這些例子只是為了演示:

我們可以在自定義類中使用JavaScript內置的特定構造器;

用兩個普通的例子展示了怎么實現這一點。

結論

ES2015的class關鍵字沒有帶給我們 Java 里或是SmallTalk里那種『真正的類』。寧可說它只是提供了一種更加方便的語法來創建通過原型關聯的對象,本質上沒有什么新東西。

使用ES6寫更好的JavaScript part III:好用的集合和反引號 簡介

ES2015發生了一些重大變革,像promises和generators. 但并非新標準的一切都高不可攀。 – 相當一部分新特性可以快速上手。

在這篇文章里,我們來看下新特性帶來的好處:

新的集合: map,weakmap,set, weakset

大部分的new String methods

模板字符串。

我們開始這個系列的最后一章吧。

模板字符串

模板字符串 解決了三個痛點,允許你做如下操作:

定義在字符串內部的表達式,稱為 字符串插值。

寫多行字符串無須用換行符 (n) 拼接。

使用“raw”字符串 – 在反斜杠內的字符串不會被轉義,視為常量。

“use strict”;

/* 三個模板字符串的例子:

字符串插值,多行字符串,raw 字符串。
================================= */
// ================================== 
// 1. 字符串插值 :: 解析任何一個字符串中的表達式。 
console.log(1 + 1 = ${1 + 1});

// ================================== 
// 2. 多行字符串 :: 這樣寫: 
let childe_roland = 
I saw them and I knew them all. And yet 
Dauntless the slug-horn to my lips I set,
And blew “Childe Roland to the Dark Tower came.” // … 代替下面的寫法: child_roland = ‘I saw them and I knew them all. And yet ’ + ‘Dauntless the slug-horn to my lips I set, ’ + ‘And blew “Childe Roland to the Dark Tower came.”’; // ================================== // 3. raw 字符串 :: 在字符串前加 raw 前綴,javascript 會忽略轉義字符。 // 依然會解析包在 ${} 的表達式 const unescaped = String.rawThis ${string()} doesn"t contain a newline! function string () { return “string”; } console.log(unescaped); // ‘This string doesn’t contain a newline! ’ – 注意 會被原樣輸出 // 你可以像 React 使用 JSX 一樣,用模板字符串創建 HTML 模板 const template = ` Example I’m a pure JS & HTML template! ` function getClass () { // Check application state, calculate a class based on that state return “some-stateful-class”; } console.log(template); // 這樣使用略顯笨,自己試試吧! // 另一個常用的例子是打印變量名: const user = { name : ‘Joe’ }; console.log(“User’s name is ” + user.name + “.”); // 有點冗長 console.log(User"s name is ${user.name}.); // 這樣稍好一些

使用字符串插值,用反引號代替引號包裹字符串,并把我們想要的表達式嵌入在${}中。

對于多行字符串,只需要把你要寫的字符串包裹在反引號里,在要換行的地方直接換行。 JavaScript 會在換行處插入新行。

使用原生字符串,在模板字符串前加前綴String.raw,仍然使用反引號包裹字符串。

模板字符串或許只不過是一種語法糖 … 但它比語法糖略勝一籌。

新的字符串方法

ES2015也給String新增了一些方法。他們主要歸為兩類:

通用的便捷方法

擴充 Unicode 支持的方法。

在本文里我們只講第一類,同時unicode特定方法也有相當好的用例 。如果你感興趣的話,這是地址在MDN的文檔里,有一個關于字符串新方法的完整列表。

startsWith & endsWith

對新手而言,我們有String.prototype.startsWith。 它對任何字符串都有效,它需要兩個參數:

一個是 search string 還有

整形的位置參數 n。這是可選的。

String.prototype.startsWith方法會檢查以nth位起的字符串是否以search string開始。如果沒有位置參數,則默認從頭開始。

如果字符串以要搜索的字符串開頭返回 true,否則返回 false。

"use strict";

const contrived_example = "This is one impressively contrived example!";

// 這個字符串是以 "This is one" 開頭嗎?
console.log(contrived_example.startsWith("This is one")); // true

// 這個字符串的第四個字符以 "is" 開頭?
console.log(contrived_example.startsWith("is", 4)); // false

// 這個字符串的第五個字符以 "is" 開始?
console.log(contrived_example.startsWith("is", 5)); // true
endsWith

String.prototype.endsWith和startswith相似: 它也需要兩個參數:一個是要搜索的字符串,一個是位置。

然而String.prototype.endsWith位置參數會告訴函數要搜索的字符串在原始字符串中被當做結尾處理。

換句話說,它會切掉nth后的所有字符串,并檢查是否以要搜索的字符結尾。

"use strict";

const contrived_example = "This is one impressively contrived example!";

console.log(contrived_example.endsWith("contrived example!")); // true

console.log(contrived_example.slice(0, 11)); // "This is one"
console.log(contrived_example.endsWith("one", 11)); // true

// 通常情況下,傳一個位置參數向下面這樣:
function substringEndsWith (string, search_string, position) {
    // Chop off the end of the string
    const substring = string.slice(0, position);

    // 檢查被截取的字符串是否已 search_string 結尾
    return substring.endsWith(search_string);
}
includes

ES2015也添加了String.prototype.includes。 你需要用字符串調用它,并且要傳遞一個搜索項。如果字符串包含搜索項會返回true,反之返回false。

"use strict";

const contrived_example = "This is one impressively contrived example!";

// 這個字符串是否包含單詞 impressively ?
contrived_example.includes("impressively"); // true

ES2015之前,我們只能這樣:

"use strict";
contrived_example.indexOf("impressively") !== -1 // true

不算太壞。但是,String.prototype.includes是 一個改善,它屏蔽了任意整數返回值為true的漏洞。

repeat

還有String.prototype.repeat。可以對任意字符串使用,像includes一樣,它會或多或少地完成函數名指示的工作。

它只需要一個參數: 一個整型的count。使用案例說明一切,上代碼:

const na = "na";

console.log(na.repeat(5) + ", Batman!"); // "nanananana, Batman!"
raw

最后,我們有String.raw,我們在上面簡單介紹過。

一個模板字符串以 String.raw 為前綴,它將不會在字符串中轉義:

/* 單右斜線要轉義,我們需要雙右斜線才能打印一個右斜線,
 在普通字符串里會被解析為換行
  *   */
console.log("This string  has fewer  backslashes  and 
 breaks the line.");

// 不想這樣寫的話用 raw 字符串
String.raw`This string  has too many  backslashes  and 
 doesn"t break the line.`
Unicode方法

雖然我們不涉及剩余的 string 方法,但是如果我不告訴你去這個主題的必讀部分就會顯得我疏忽。

Dr Rauschmayer對于Unicode in JavaScript的介紹

他關于ES2015’s Unicode Support in Exploring ES6和The Absolute Minimum Every Software Developer Needs to Know About Unicode 的討論。

無論如何我不得不跳過它的最后一部分。雖然有些老但是還是有優點的。

這里是文檔中缺失的字符串方法,這樣你會知道缺哪些東西了。

String.fromCodePoint & String.prototype.codePointAt;

String.prototype.normalize;

Unicode point escapes.

集合

ES2015新增了一些集合類型:

Map和WeakMap

Set和WeakSet。

合適的Map和Set類型十分方便使用,還有弱變量是一個令人興奮的改動,雖然它對Javascript來說像舶來品一樣。

Map

map就是簡單的鍵值對。最簡單的理解方式就是和object類似,一個鍵對應一個值。

"use strict";

// 我們可以把 foo 當鍵,bar 當值
const obj = { foo : "bar" };

// 對象鍵為 foo 的值為 bar
obj.foo === "bar"; // true

新的Map類型在概念上是相似的,但是可以使用任意的數據類型作為鍵 – 不止strings和symbols–還有除了pitfalls associated with trying to use an objects a map的一些東西。

下面的片段例舉了 Map 的 API.

"use strict";

// 構造器
let scotch_inventory = new Map();

// BASIC API METHODS
// Map.prototype.set (K, V) :: 創建一個鍵 K,并設置它的值為 V。
scotch_inventory.set("Lagavulin 18", 2);
scotch_inventory.set("The Dalmore", 1);

// 你可以創建一個 map 里面包含一個有兩個元素的數組
scotch_inventory = new Map([["Lagavulin 18", 2], ["The Dalmore", 1]]);

// 所有的 map 都有 size 屬性,這個屬性會告訴你 map 里有多少個鍵值對。
// 用 Map 或 Set 的時候,一定要使用 size ,不能使用 length
console.log(scotch_inventory.size); // 2

// Map.prototype.get(K) :: 返回鍵相關的值。如果鍵不存在返回 undefined
console.log(scotch_inventory.get("The Dalmore")); // 1
console.log(scotch_inventory.get("Glenfiddich 18")); // undefined

// Map.prototype.has(K) :: 如果 map 里包含鍵 K 返回true,否則返回 false
console.log(scotch_inventory.has("The Dalmore")); // true
console.log(scotch_inventory.has("Glenfiddich 18")); // false

// Map.prototype.delete(K) :: 從 map 里刪除鍵 K。成功返回true,不存在返回 false
console.log(scotch_inventory.delete("The Dalmore")); // true -- breaks my heart

// Map.prototype.clear() :: 清楚 map 中的所有鍵值對
scotch_inventory.clear();
console.log( scotch_inventory ); // Map {} -- long night

// 遍歷方法
// Map 提供了多種方法遍歷鍵值。 
//  重置值,繼續探索
scotch_inventory.set("Lagavulin 18", 1);
scotch_inventory.set("Glenfiddich 18", 1);

/* Map.prototype.forEach(callback[, thisArg]) :: 對 map 里的每個鍵值對執行一個回調函數 
  *   你可以在回調函數內部設置 "this" 的值,通過傳遞一個 thisArg 參數,那是可選的而且沒有太大必要那樣做
  *   最后,注意回調函數已經被傳了鍵和值 */
scotch_inventory.forEach(function (quantity, scotch) {
    console.log(`Excuse me while I sip this ${scotch}.`);
});

// Map.prototype.keys() :: 返回一個 map 中的所有鍵
const scotch_names = scotch_inventory.keys();
for (let name of scotch_names) {
    console.log(`We"ve got ${name} in the cellar.`);
}

// Map.prototype.values() :: 返回 map 中的所有值
const quantities = scotch_inventory.values();
for (let quantity of quantities) {
    console.log(`I just drank ${quantity} of . . . Uh . . . I forget`);
}

// Map.prototype.entries() :: 返回 map 的所有鍵值對,提供一個包含兩個元素的數組 
//   以后會經常看到 map 里的鍵值對和 "entries" 關聯 
const entries = scotch_inventory.entries();
for (let entry of entries) {
    console.log(`I remember! I drank ${entry[1]} bottle of ${entry[0]}!`);
}

但是Object在保存鍵值對的時候仍然有用。 如果符合下面的全部條件,你可能還是想用Object:

當你寫代碼的時候,你知道你的鍵值對。

你知道你可能不會去增加或刪除你的鍵值對。

你使用的鍵全都是 string 或 symbol。

另一方面,如果符合以下任意條件,你可能會想使用一個 map。

你需要遍歷整個map – 然而這對 object 來說是難以置信的.

當你寫代碼的時候不需要知道鍵的名字或數量。

你需要復雜的鍵,像 Object 或 別的 Map (!).

像遍歷一個map一樣遍歷一個object是可行的,但奇妙的是–還會有一些坑潛伏在暗處。 Map更容易使用,并且增加了一些可集成的優勢。然而object是以隨機順序遍歷的,map是以插入的順序遍歷的。

添加隨意動態鍵名的鍵值對給一個object是可行的。但奇妙的是: 比如說如果你曾經遍歷過一個偽 map,你需要記住手動更新條目數。

最后一條,如果你要設置的鍵名不是string或symbol,你除了選擇Map別無選擇。

上面的這些只是一些指導性的意見,并不是最好的規則。

WeakMap

你可能聽說過一個特別棒的特性垃圾回收器,它會定期地檢查不再使用的對象并清除。

To quote Dr Rauschmayer:

WeakMap 不會阻止它的鍵值被垃圾回收。那意味著你可以把數據和對象關聯起來不用擔心內存泄漏。

換句換說,就是你的程序丟掉了WeakMap鍵的所有外部引用,他能自動垃圾回收他們的值。

盡管大大簡化了用例,考慮到SPA(單頁面應用) 就是用來展示用戶希望展示的東西,像一些物品描述和一張圖片,我們可以理解為API返回的JSON。

理論上來說我們可以通過緩存響應結果來減少請求服務器的次數。我們可以這樣用Map :

"use strict";

const cache = new Map();

function put (element, result) {
    cache.set(element, result);
}

function retrieve (element) {
    return cache.get(element);
}

… 這是行得通的,但是有內存泄漏的危險。

因為這是一個SPA,用戶或許想離開這個視圖,這樣的話我們的 “視圖”object就會失效,會被垃圾回收。

不幸的是,如果你使用的是正常的Map ,當這些object不使用時,你必須自行清除。

使用WeakMap替代就可以解決上面的問題:

"use strict";

const cache = new WeakMap(); // 不會再有內存泄露了

// 剩下的都一樣

這樣當應用失去不需要的元素的引用時,垃圾回收系統可以自動重用那些元素。

WeakMap的API和Map相似,但有如下幾點不同:

在WeakMap里你可以使用object作為鍵。 這意味著不能以String和Symbol做鍵。

WeakMap只有set,get,has,和delete方法 – 那意味著你不能遍歷weak map.

WeakMaps沒有size屬性。

不能遍歷或檢查WeakMap的長度的原因是,在遍歷過程中可能會遇到垃圾回收系統的運行: 這一瞬間是滿的,下一秒就沒了。

這種不可預測的行為需要謹慎對待,TC39(ECMA第39屆技術委員會)曾試圖避免禁止WeakMap的遍歷和長度檢測。

其他的案例,可以在這里找到Use Cases for WeakMap,來自Exploring ES6.

Set

Set就是只包含一個值的集合。換句換說,每個set的元素只會出現一次。

這是一個有用的數據類型,如果你要追蹤唯一并且固定的object ,比如說聊天室的當前用戶。

Set和Map有完全相同的API。主要的不同是Set沒有set方法,因為它不能存儲鍵值對。剩下的幾乎相同。

"use strict";

// 構造器
let scotch_collection = new Set();

// 基本的 API 方法
// Set.prototype.add (O) :: 和 set 一樣,添加一個對象
scotch_collection.add("Lagavulin 18");
scotch_collection.add("The Dalmore");

// 你也可以用數組構造一個 set
scotch_collection = new Set(["Lagavulin 18", "The Dalmore"]);

// 所有的 set 都有一個 length 屬性。這個屬性會告訴你 set 里有多少對象
//   用 set 或 map 的時候,一定記住用 size,不用 length
console.log(scotch_collection.size); // 2

// Set.prototype.has(O) :: 包含對象 O 返回 true 否則返回 false
console.log(scotch_collection.has("The Dalmore")); // true
console.log(scotch_collection.has("Glenfiddich 18")); // false

// Set.prototype.delete(O) :: 刪除 set 中的 O 對象,成功返回 true,不存在返回 false
scotch_collection.delete("The Dalmore"); // true -- break my heart

// Set.prototype.clear() :: 刪除 set 中的所有對象
scotch_collection.clear();
console.log( scotch_collection ); // Set {} -- long night.

/* 迭代方法
 * Set 提供了多種方法遍歷
 *  重新設置值,繼續探索 */
scotch_collection.add("Lagavulin 18");
scotch_collection.add("Glenfiddich 18");

/* Set.prototype.forEach(callback[, thisArg]) :: 執行一個函數,回調函數
 *  set 里在每個的鍵值對。 You can set the value of "this" inside 
 *  the callback by passing a thisArg, but that"s optional and seldom necessary. */
scotch_collection.forEach(function (scotch) {
    console.log(`Excuse me while I sip this ${scotch}.`);
});

// Set.prototype.values() :: 返回 set 中的所有值
let scotch_names = scotch_collection.values();
for (let name of scotch_names) {
    console.log(`I just drank ${name} . . . I think.`);
}

// Set.prototype.keys() ::  對 set 來說,和 Set.prototype.values() 方法一致
scotch_names = scotch_collection.keys();
for (let name of scotch_names) {
    console.log(`I just drank ${name} . . . I think.`);
}

/* Set.prototype.entries() :: 返回 map 的所有鍵值對,提供一個包含兩個元素的數組 
 *   這有點多余,但是這種方法可以保留 map API 的可操作性
 *    */
const entries = scotch_collection.entries();
for (let entry of entries) {
    console.log(`I got some ${entry[0]} in my cup and more ${entry[1]} in my flask!`);
}
WeakSet

WeakSet相對于Set就像WeakMap相對于 Map :

在WeakSet里object的引用是弱類型的。

WeakSet沒有property屬性。

不能遍歷WeakSet。

Weak set的用例并不多,但是這兒有一些Domenic Denicola稱呼它們為“perfect for branding” – 意思就是標記一個對象以滿足其他需求。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79610.html

相關文章

  • [譯] 在你學習 React 之前必備 JavaScript 基礎

    摘要:前言在理想的狀態下,你可以在深入了解之前了解和開發的所有知識。繼承另一個類的類,通常稱為類或類,而正在擴展的類稱為類或類。這種類型的組件稱為無狀態功能組件。在你有足夠的信心構建用戶界面之后,最好學習。 原文地址:JavaScript Basics Before You Learn React 原文作者: Nathan Sebhastian 寫在前面 為了不浪費大家的寶貴時間,在開...

    Chaz 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...

    tuniutech 評論0 收藏0
  • Javascript 打包工具

    摘要:所以,打包工具就出現了,它可以幫助做這些繁瑣的工作。打包工具介紹僅介紹款主流的打包工具,,,,以發布時間為順序。它定位是模塊打包器,而屬于構建工具。而且在其他的打包工具在處理非網頁文件比如等基本還是需要借助它來實現。 本文當時寫在本地,發現換電腦很不是方便,在這里記錄下。 前端的打包工具 打包工具可以更好的管理html,css,javascript,使用可以錦上添花,不使用也沒關系...

    Sleepy 評論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評論0 收藏0
  • 通過分析這段代碼進化歷程,或許能夠加深您對JavaScript作用域理解

    摘要:前言這里我們不討論作用域鏈的問題,這些問題您可以看下我之前寫的東西,通過這一段代碼,讓我們重新認識。這回我們主要來分享一下,中作用域的創建方式。立即執行函數是個不錯的選擇,但具名的立即執行函數可以讓代碼本身更具有可讀性,是個最佳實踐。 前言 這里我們不討論作用域鏈的問題,這些問題您可以看下我之前寫的東西,通過這一段代碼,讓我們重新認識JavaScript。這回我們主要來分享一下,Jav...

    goji 評論0 收藏0

發表評論

0條評論

Dionysus_go

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<