深刻理解ES6

本文系《深刻理解ES6》讀書筆記

第一章 塊級做用域綁定

var 聲明初始化變量, 聲明能夠提高,但初始化不能夠提高。html

1、塊級聲明:

  1. let
  2. const
    • 默認使用,在某種程度上實現代碼不可變,減小錯誤發生的概率
    • 若是常量是對象,則對象中的值能夠修改
  3. 不能重複聲明,聲明不會提高

2、臨時死區:

JS引擎在掃描代碼發現變量聲明時,要麼將它們提高至做用域頂部(var聲明),要麼將聲明放到TDZ(臨時死區)中(letconst聲明)。訪問TDZ中的變量會觸發運行時錯誤。只有執行過變量聲明語句後,變量纔會從TDZ中移出,而後能夠正常訪問。node

第一種狀況:es6

if(condition) {
    console.log(typeof value); //引用錯誤!
    let value = "blue"; //TDZ
}
複製代碼

第二種狀況:web

console.log(typeof value); //'undefined'-->這裏不報錯,只有變量在TDZ中才會報錯
if(condition) {
    let value = "blue";
}
複製代碼

3、循壞中塊做用域綁定

  • 當即調用(IIFE)正則表達式

  • letconst之因此能夠在運用在for-infor-of循環中,是由於每次迭代會建立一個新的綁定(const在for循環中會報錯)。編程

4、全局塊做用域綁定

var 可能會在無心中覆蓋一個已有的全局屬性, letconst會在全局做用域下建立一個新的綁定,但該綁定不會添加爲全局對象的屬性。換句話說,使用letconst不能覆蓋全局變量,而只能遮蔽它。若是不是爲全局對象建立屬性,使用letconst要安全得多。json

注:若是但願在全局對象下定義變量,仍然可使用var。這種狀況常見於在瀏覽器中跨frame或跨window訪問代碼數組

第二章 字符串和正則表達式

1、UTF-8碼位

名詞解釋:promise

  • 碼位: 每個字符的「全球惟一的標識符,從0開始的數值」
  • 字符編碼:表示某個字符的數值或碼位即爲該字符的字符編碼。
  • 基本多文種平面(BMP,Basic Multilingual Plane)

    在UTF-16中,前2^16個碼位均以16位的編碼單元表示,這個範圍被稱做基本多文種平面瀏覽器

2、codePointAt()、 fromCodePoint() 和 normalize()

  • 兩個方法對應於charCodeAt()fromCharCode()
  • normalize(): 規範的統一,適用於比較排序,國際化。

3、正則表達式 u 和 y 修飾符、正則表達式的複製、flag屬性

  • u: 編碼單元 ---> 字符模式

這個方法儘管有效,可是當統計長字符串中的碼位數量時,運動效率很低。所以,你也可使用字符串迭代器解決效率低的問題,整體而言,只要有可能就嘗試着減少碼位計算的開銷。

檢測u修飾符支持:

function hasRegExpU() {
    try {
        var pattern = new RegExp('.', 'u');
        return true;
    } catch (ex) {
        return false;
    }
}
複製代碼
  • y: 第一次匹配不到就終止匹配

當執行操做時, y修飾符會把上次匹配後面一個字符的索引保存到lastIndexOf中;若是該操做匹配的結果爲空,則lastIndexOf會被重置爲0。g修飾符的行爲相似。

1. 只有調用exec()和test()這些正則表達式對象的方法時纔會涉及lastIndex屬性;
2. 調用字符串的方法,例如match(),則不會觸發粘滯行爲。
複製代碼
  • 正則表達式的複製
var re1 = /ab/i;
re2 = new RegExp(re1); //沒有修飾符複製
re3 = new RegExp(re1, "g"); //有修飾符(ES6)
複製代碼
  • flag屬性 --- 獲取正則表達式的修飾符

es5方法獲取正則表達式的修飾符:

function getFlags(re) {
    var text = re.toString();
    return text.substring(text.lastIndexOf('/' + 1, text.length);
}
複製代碼

模板字面量

多行字符串

基本的字符串格式化(字符串佔位符)

HTML轉義

  • 標籤模板
function passthru(literals, ...substitutions) {
    //返回一個字符串
    let result = "";
    //根據substitutions的數量來肯定循環的執行次數
    for(let i=0; i<substitutions.length; i++){
        result += literals;
        result += substitutions[i]
        console.log(literals[i]) 
        console.log(substitutions[i])
    }
    
    //合併最後一個literal
    result += literals[literals.length - 1];
    return result;
}

let count = 10;
price = 0.25;
message = passthru`${count} items cost $${(count * price).toFixed(2)}`;

console.log(message)
複製代碼
  • String.raw
String.raw`assda\\naadasd`

//代碼模擬(略)
複製代碼

第三章 函數

1、默認參數值

ES5默認參數值

下面函數存在什麼問題 ???

function makeRequest(url, timeout, callback) {
    
    timeout = timeout || 2000;
    callback = callback || function() {};
    
}
複製代碼

假如timeout傳入值0,這個值是合法的,可是也會被視爲一個假值,並最終將timeout賦值爲2000。在這種狀況下,更安全的選擇是經過typeof檢查參數類型,以下:

function makeRequest(url, timeout, callback) {
    
    timeout = (typeof timeout !== 'undefined') ? timeout :2000;
    callback = (typeof callback !== 'undefined') ? callback : function() {};
    
}
複製代碼

ES5默認參數值

function makeRequest(url, timeout = 2000, callback) {
    
    //函數的其他部分
    
}

//特別注意:此時 null 是一個合法值,因此不會使用 timeout 默認值,即 timeout = null
makeRequest('/foo', null, function(body){
    doSomething(body);
})
複製代碼

2、默認參數值對arguments的影響**

  • ES5:

非嚴格模式:參數變化,arguments對象隨之改變;

嚴格模式:不管參數如何變化,arguments對象再也不隨之改變;

  • ES6

非嚴格模式/嚴格模式:不管參數如何變化,arguments對象再也不隨之改變;

注: 在引用參數默認值的時候,只容許引用前面參數的值,即先定義的參數不能訪問後定義的參數。這能夠用默認參數的臨時死區來解釋。以下:

function add(first = second, second) {
    return first + second;
}

console.log(add(1, 1)); //2
console.log(add(undefined, 1)) //拋出錯誤

//解釋原理:
//add(1, 1)
let first = 1;
let second = 1;
//add(undefined, 1)
let first = second;
let second = 1; //處於臨時死區
複製代碼

3、不定參數的使用限制

  1. 每一個函數最多隻能聲明一個不定參數,並且必定要放在全部參數的末尾。
  2. 不定參數不能用於對象字面量setter之中(由於對象字面量setter的參數有且只有一個,而在不定參數的定義中,參數的數量能夠無限多

不管是否使用不定參數,arguments對象老是包含全部傳入函數的參數。

4、展開運算符

let value = [25, 50, 75, 100];
//es5
console.log(Math.max.apply(Math, values); //100
//es6
console.log(Math.max(...values)); //100
複製代碼

5、name 屬性

兩個有關函數名稱的特例:

  1. 經過bind()函數建立的函數,其名稱將帶有「bound」前綴;
  2. 經過Function構造函數建立的函數,其名稱將是「anonymous」.
var doSomething = function() {
    //空函數
}

console.log(doSomething.bind().name); //'bound doSomething'
console.log((new Function()).name); //'anonymous(匿名)'
複製代碼

切記: 函數name屬性的值不必定引用同名變量,它只是協助調試用的額外信息,因此不能使用name屬性的值來獲取對於函數的引用。

6、明確函數的多重用途

JS函數有兩個不一樣的內部方法:[[call]][[Construct]]

  • 當經過new關鍵字調用函數是,執行的是 [[Construct]] 函數,它負責建立一個一般被稱爲實例的新對象,而後再執行函數體,將this綁定到實例上(具備 [[Construct]] 方法的函數被統稱爲構造函數,箭頭函數沒有 [[Construct]] 方法 );
  • 若是不經過 new 關鍵字調用函數,則執行 [[call]] 函數,從而直接執行代碼中的函數體;

7、元屬性(Metaproperty)new.target

爲了解決判斷函數是否經過new關鍵字調用的問題,new.target橫空出世 (instance of ---> new.target)

在函數外使用new.target是一個語法錯誤。

8、塊級函數

  • ES5嚴格模式下,代碼塊中聲明函數會報錯;
  • ES6嚴格模式下, 能夠在定義該函數的代碼塊中訪問和調用它 (塊級函數提高,let變量不提高);
  • ES6非嚴格模式下,函數再也不提高至代碼塊的頂部,而是提高至外圍函數或全局做用域的頂部。

9、箭頭函數

箭頭函數與傳統的JS函數不一樣之處主要有如下幾個方面:

  1. 沒有thissuperargumentsnew.target綁定;
  2. 不能經過new關鍵字調用;
  3. 沒有原型;
  4. 不能夠改變this的綁定;
  5. 不支持arguments對象
  6. 不支持重複的命名參數

建立一個空函數

let doNothing = () => {};
複製代碼

返回一個對象字面量

let getTempItem = id => ({ id: id, name: "Temp"});
複製代碼

建立當即執行的函數

let person = ((name) => {
    
    return {
        getName: function() {
            return name;
        }
    }
    
})("xszi")

console.log(person.getName()); //xszi
複製代碼

箭頭函數沒有this綁定

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", function(event){
            this.doSomething(event.type); //拋出錯誤
        }, false)
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
複製代碼

使用bind()方法將函數的this綁定到PageHandler,修正報錯:

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", (function(event){
            this.doSomething(event.type); //不報錯
        }).bind(this), false)
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
複製代碼

使用箭頭函數修正:

let PageHandler = {
    
    id: '123456',
    init: function() {
        document.addEventListener("click", 
            event => this.doSomething(event.type), false);
    },
    
    doSomething: function(type) {
        console.log("handling " + type + "for" + this.id)
    }
    
}
複製代碼
  • 箭頭函數沒有 prototype屬性,它的設計初衷是 即用即棄, 不能用來定義新的類型。
  • 箭頭函數的中this取決於該函數外部非箭頭函數的this值,不能經過call(), apply()bind()方法來改變this的值。

箭頭函數沒有arguments綁定

始終訪問外圍函數的arguments對象

10、尾調用優化

  • ES5中,循環調用狀況下,每個未完成的棧幀都會保存在內存中,當調用棧變的過大時會形成程序問題。
  • ES6中尾調用優化,須要知足如下三個條件:
    • 尾調用不訪問當前棧幀的變量(也就是說函數不是一個閉包);
    • 在函數內部,尾調用是最後一條語句;
    • 尾調用的結果做爲函數值返回;

如何利用尾調用優化

function factorial(n) {
    if ( n<=1 ) {
        return 1;
    }
}else{
    
    //引擎沒法自動優化,必須在返回後執行乘法操做 
    return n * factorial(n-1);
    //隨調用棧尺寸的增大,存在棧溢出的風險
}
複製代碼
function factorial(n, p = 1) {
    if ( n<=1 ) {
        return 1 * p;
    }
}else{
    let result = n * p;
    //引擎可自動優化
    return  factorial(n-1, result);
    //不建立新的棧幀,而是消除並重用當前棧幀
}
複製代碼

第四章 擴展對象的功能性

1、對象的類別

  • 普通對象
  • 特異對象
  • 標準對象(ES6中規範中定義的對象,如Array,Date)
  • 內建對象:

腳本開始執行時存在於JS執行環境中的對象,全部標準對象都是內建對象。

var person = {
    name: 'xszi',
    sayName: function() {
        console.log(this.name);
    }
}

var person = {
    name: 'xszi',
    sayName() {
        console.log(this.name);
    }
}

//二者惟一的區別是,簡寫方式可使用super關鍵字
複製代碼

2、可計算屬性名(Computed Property Name)

在對象字面量中使用方括號表示的該屬性名稱是可計算的,它的內容將被求值並被最終轉化爲一個字符串。

以下:

var suffix = ' name';

var person = {
    ['first' + suffix]: 'xszi',
    ['last' + suffix]: 'wang'
};

console.log(person['first name']); //xszi
console.log(person['last name']) // wang
複製代碼

3、新增方法

ECMAScript 其中一個設計目標是:再也不建立新的全局函數,也不在Object.prototype上建立新的方法。

  • Object.is()

大多數狀況下,Object.is()與'==='運行結果相同,惟一區別在於 +0-0 識別爲不相等,而且NaNNaN等價。

  • Object.assign()

mixin()方法使用賦值操做符(assignment operator)= 來複制相關屬性,卻不能複製 訪問器屬性 到接受對象中,所以最終添加的方法棄用mixin而改用assign做爲方法名。

Object.assign() 方法能夠接受任意數量的源對象,並按指定的的順序將屬性賦值到接收對象中。因此若是多個源對象具備同名屬性,則排位靠後的源對象會覆蓋排位靠前的。

訪問器屬性Object.assign() 方法不能將提供者的訪問器屬性賦值到接收對象中。因爲 Object.assign()方法執行了賦值操做,所以提供者的訪問器屬性最終會轉變爲接受對象中的一個數據屬性。

eg:

var receiver = {};
     supplier = {
         get name() {
             return 'file.js'
         }
     };
     
Object.assign(receiver, supplier);

var descriptor = Object.getOwnPropertyDescriptor(receiver, 'name');

console.log(descriptor.value); // 'file.js'
console.log(descriptor.get); // undefined
複製代碼

4、自有屬性枚舉順序

自有屬性枚舉順序的基本規則是:

  1. 全部數字鍵按升序排序;
  2. 全部字符串鍵按照它們被加入對象的順序排序;
  3. 全部symbol按照他們被加入的順序排序。

5、加強對象的原型

  • 改變對象的原型
Object.setPrototypeOf(targetObject, protoObject);
複製代碼
  • 簡化原型訪問的Super引用

Super 引用至關於指向對象原型的指針,實際上也就是Object.getPrototypeOf(this), **必需要在使用簡寫方法的對象中使用 Super**引用。

Super引用不是動態變化的,它老是指向正確的對象。

6、正式的方法定義

ES6正式將方法定義爲一個函數,它會有一個內部的 [[HomeObject]] 屬性來容納這個方法從屬的對象。

Super的因此引用都經過 [[HomeObject]] 屬性來肯定後續的運行過程:

  1. [[HomeObject]] 屬性上調用Object.getPrototypeOf()方法來檢索原型的引用;
  2. 搜索原型找到同名函數;
  3. 設置this綁定而且調用相應的方法。

第五章 解構: 使數據訪問更便捷

1、對象解構

let node = {
    type: "Indetifier",
    name: "foo"
}

let {type, name} = node;

console.log(type); // Indetifier
console.log(name); // foo

複製代碼

不要忘記初始化程序(也就是符號右邊的值)

var {type, name}; //報錯,使用let和const一樣報錯
// 除使用解構外,使用var, let不強制要求提供初始化程序, 可是const必定須要;
複製代碼

2、解構賦值

let node = {
    type: "Indetifier",
    name: "foo"
}

type = 'Literal', name = 5;

//使用解構語法爲多個變量賦值
({type, name} = node);  //須要使用()包裹解構複製語句,{}是一個代碼塊,不能放在左邊

console.log(type); // Indetifier
console.log(name); // foo
複製代碼
  • 默認值與上章的 默認參數 相似
  • 爲非同名佈局變量賦值
let node = {
   type: "Indetifier",
   name: "foo"
}

let { type: localType, name: localName } = node;

console.log(localType); // Indetifier
console.log(localName); // foo
複製代碼

type: localType語法的含義是讀取名爲type的屬性並將其只存儲在變量localType

  • 嵌套對象解構

3、數組解構

  • 解構賦值

數組解構也可用於賦值上下文,但不須要用小括號包裹表達式,這一點與對象解構的的約定不一樣。

let colors = ['red', 'green', 'blue'], firstColor = 'black', secondColor = 'purple';

[firstColor, secondColor] = colors;

console.log(firstColor); // 'red'
console.log(secondColor); // 'green'
複製代碼

交換值

let a = 1, b = 2;

[a, b] = [b, a];

console.log(a); //2
console.log(b); //1

複製代碼
  • 嵌套數組解構(地址的解構賦值)
let colors = ['red', ['green', 'lightgreen'], 'blue'];

let [firstColor, [secondColor]] = colors;

console.log(firstColor); //red
console.log(secondColor); //green

複製代碼
  • 不定元素(在被解構的數組中,不定元素必須爲最後一個條目,在後面繼續添加逗號會致使程序拋出語法錯誤)
  • 混合解構(混合對象和數組解構,使得咱們從JSON配置中提取信息時,再也不須要遍歷整個結構了。)
  • 解構參數
function setCookie(name, value, options) {

    options = options || {};
    
    let secure = options.secure,
        path = options.path,
        domian= options.domain,
        expires = options.expires;
        
    //設置cookie代碼
}

// 第三個參數映射到options中

setCookie('type', 'js', {
    secure: true,
    expires: 60000
})
複製代碼

上面函數存在一個問題:僅查看函數的聲明部分,沒法辨識函數的預期參數,必須經過閱讀函數體才能夠肯定全部參數的狀況。可使用 解構參數 來優化:

function setCookie(name, value, {secure, path, domain, expires}}) {
    //設置cookie代碼
}

setCookie('type', 'js',{
    secure: true,
    expires: 60000
})
複製代碼
  1. 必須傳值的解構參數;
  2. 解構參數的默認值。

第六章 SymbolSymbol屬性

Symbol出現以前,人們一直經過屬性名來訪問全部屬性,不管屬性名由什麼元素構成,所有經過一個字符串類型的名稱來訪問;私有名稱原來是爲了讓開發者們建立非字符串名稱而設計的,可是通常的技術沒法檢測這些屬性的私有名稱。

經過Symbol能夠爲屬性添加非字符串名稱,可是其隱私性就被打破了。

1、建立、使用SymbolSymbol共享體系

  • 建立、使用
let firstName = Symbol();
let person = {};

person[firstName] = 'xszi';
console.log(person[firstName]); //xszi
複製代碼

因爲Symbol是原始值,所以調用new Symbol()會致使程序拋出錯誤。

Symbol函數接受一個可選參數,其可讓你添加一段文本描述即將建立的Symbol,這段描述 不可用於屬性訪問。該描述被存儲在內部的 [[Description]] 屬性中,只有當調用SymboltoString()方法時才能夠讀取這個屬性。

  • Symbol共享體系

有時咱們可能但願在不一樣代碼中共享同一個Symbol(在很大的代碼庫中或跨文件追蹤Symbol很是困難),ES6提供了一個能夠全局訪問的全局Symbol註冊表,即便用Symbol.for()方法。

let uid = Symbol.for('uid');
let object = {};

object[uid] = '12345';

console.log(object[uid]); //'12345'
console.log(uid); // 'Symbol(uid)'
複製代碼

實現原理Symbol.for()方法首先在全局Symbol註冊表中搜索鍵爲‘uid’的Symbol是否存在,若是存在,直接返回已有的Symbol;不然,建立一個新的Symbol,並使用這個鍵在Symbol全局註冊表中註冊,隨即返回新建立的Symbol

可使用Symbol.keyFor()方法在Symbol全局註冊表中檢索與Symbol有關的鍵。

Symbol全局註冊表是一個相似全局做用域的共享環境,也就是說你不能假設目前環境中存在哪些鍵。當使用第三方組件時,儘可能使用Symbol鍵的命名空間減小命名衝突。如 jQuery.

2、Symbol與類型強制轉換,屬性檢索

  • console.log()會調用SymbolString()方法
desc = String(uid);

desc = uid + ''; //報錯,不能轉爲字符串類型

desc = uid / 2; //報錯,不能轉爲數字類型
複製代碼
  • 屬性檢索
    • Object.keys() 返回可枚舉屬性
    • Object.getOwnPropertyNames() 不考慮可枚舉性,一概返回
    • Object.getOwnProperty-Symbols() ES6用來檢索對象中的Symbol屬性

全部對象一開始沒有本身獨有的屬性,可是對象能夠從原型鏈中繼承Symbol屬性。

3、經過well-know Symbol暴露內部操做

仍是不怎麼理解,找到一個使用Symbol的實際場景才能更好理解!

第七章 Set集合與Map集合

Set 和 Map 主要的應用場景在於 數據重組數據儲存

Set 是一種叫作集合的數據結構,Map 是一種叫作字典的數據結構

1、用對象屬性模擬Set和Map集合

//set
var set = Object.create(null);

set.foo = true;

//檢查屬性是否存在
if(set.foo){
    //要執行的代碼
}
複製代碼
//map
var map = Object.create(null);

map.foo = "bar";

//獲取已存值
var value = map.foo;

console.log(value);
複製代碼

通常來講,Set集合常被用於檢查對象中是否存在某個鍵名,而Map集合常被用來獲取已存的信息。

全部對象的屬性名必須是字符串類型,必須確保每一個鍵名都是字符串類型且在對象中是惟一的

2、Set集合

- 有序
- 不重複

+0和-0在Set中被認爲是相等的。

Set構造函數能夠接受全部可迭代對象做爲參數
複製代碼
  • Set中的方法: add、has、delete、clear,forEach,size(屬性)

forEach遍歷Set,回調函數中value和key的值相等,個人理解: Set集合中的元素都是不重複的,因此能夠把值做爲鍵,即「以值爲鍵」。以下:

let set = new Set([1, 2]);

set.forEach(function(value, key, ownerSet)){
    console.log(key + " " + value);
    console.log(ownerSet === set);
});
複製代碼

在回調函數中使用this引用

let set = new Set([1, 2]);

let processor = {
    output(value) {
        console.log(value);
    },
    process(dataSet) {
        dataSet.forEach(function(value){
            this.output(value);
        }, this);
    }
};

processor.process(set);
複製代碼

箭頭函數this

let set = new Set([1, 2]);

let processor = {
    output(value) {
        console.log(value);
    },
    process(dataSet) {
        dataSet.forEach(value => this.output(value));
    }
};

processor.process(set);
複製代碼
  • 將Set集合轉換爲數組
let set = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
    array = [...set];
    
console.log(array); //[1, 2, 3, 4, 5]
複製代碼
function eliminbateDuplicates(items){
    return [...new Set(items)]
}

let numbers = [1, 2, 3, 3, 3, 4, 5];
    noDuplicates = eliminateDuplicates(numbers);

console.log(noDuolicates); //[1, 2, 3, 4, 5]
複製代碼

3、 Weak Set 集合

解決Set集合的強引用致使的內存泄漏問題

Weak Set集合只存儲對象的弱引用,而且不能夠存儲原始值;集合中的弱引用若是是對象惟一的引用,則會被回收並釋放相應內存。

Weak Set集合的方法:add, has,delete

  • SetWeak Set 的區別:
差別 Set Weak Set
最大區別 保存對象值的強引用 保存對象值的弱引用
方法傳入非對象參數 正常 報錯
可迭代性 可迭代 不可迭代
支持forEach方法 支持 不支持
支持size屬性 支持 不支持

4、 Map集合

- 有序
- 鍵值對
複製代碼

在對象中,沒法用對象做爲對象屬性的鍵名;可是Map集合中,卻能夠這樣作。

let map = new Map(),
    key1 = {},
    key2 = {};
    
map.set(key1, 5);
map.set(key2, 42);

console.log(map.get(key1)); //5
console.log(map.get(key2)); //42
複製代碼

以上代碼分別用對象 key1key2 做爲兩個鍵名在Map集合裏存儲了不一樣的值。這些鍵名不會強制轉換成其餘形式,因此這兩個對象在集合中是獨立存在的,也就是說,不須要修改對象自己就能夠爲其添加一些附加信息

Map集合的方法:setgethas(key)delete(key)clearforEachsize(屬性)

Map集合初始化過程當中,能夠接受任意數據類型的鍵名,爲了確保它們在被存儲到Map集合中以前不會被強制轉換爲其餘數據類型,於是只能將它們放在數組中,由於這是惟一一種能夠準確地呈現鍵名類型的方式。

5、 Weak Map集合

無序 鍵值對

- 弱引用Map集合,集合中鍵名必須是一個對象,若是使用非對象鍵名會報錯;
- 鍵名對於的值若是是一個對象,則保存的是對象的強引用,不會觸發垃圾回收機制。
複製代碼
  • Weak Map 最大的用途是保存Web頁面中的DOM元素。
let map = new WeakMap(),
    element = document.querySelector('.element');
    
map.set(element, "Original");

let value = map.get(element);
console.log(value); //"Original"

//移除element元素
element.parentNode.removeChild(element);
element = null;

//此時 Weak Map集合爲空,數據被同步清除

複製代碼
  • Weak Map集合的方法

set, get, has, delete

私有對象數據

存儲對象實例的私有數據是Weak Map的另一個應用:

var Person = (function(){
    var privateData = {},
    privateData = 0;
    
    function Person(name){
        Object.defineProperty(this, "_id", { value: privateId++ });
        
        privateData[this._id] = {
            name: name
        };
    }
    
    Person.prototype.getName = function() {
        return privateData[this._id].name;
    }
    
    return Person;
}());

複製代碼

上面這種方法沒法獲知對象實例什麼時候被銷燬,不主動管理的話,privateData中的數據就永遠不會消失,須要使用Weak Map來解決這個問題。

let Person = (function(){
    let privateData = new WeakMap(),
    privateData = 0;
    
    function Person(name){
        privateData.set(this, {name: name});
    }
    
    Person.prototype.getName = function() {
        return privateData.get(this).name;
    }
    
    return Person;
}());
複製代碼

當你要在Weak Map集合與普通的Map集合之間作出選擇時,須要考慮的主要問題是,是否只用對象做爲集合的鍵名。

第八章 迭代器(Iterator)和生成器(Generator)

迭代器的出現旨在消除循環複雜性並減小循環中的錯誤。

1、什麼是迭代器?

迭代器是一種特殊對象,他具備一些專門爲迭代過程設計的專有端口,全部的迭代器對象都有一個next()方法,每次調用都返回一個結果對象。

ES5語法 實現一個迭代器

function createIterator(items) {
    
    var i = 0;
    
    return {
        next: function() {
        
            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;
            
            return {
                done: done,
                value: value
            };
        }
    }
}

var iterator = createIterator([1, 2, 3]);

console.log(iterator.next()); //"{ value: 1, done: false}"
console.log(iterator.next()); //"{ value: 2, done: false}"
console.log(iterator.next()); //"{ value: 3, done: false}"
console.log(iterator.next()); //"{ value: undefined, done: true}"
複製代碼

2、什麼是生成器?

生成器是一種返回迭代器的函數,經過function關鍵字後的星號(*)來表示,函數中會用到新的關鍵字yield

function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

let iterator = createIterator();

console.log(iterator.next().value); //1
console.log(iterator.next().value); //2
console.log(iterator.next().value); //3
複製代碼

yield的使用限制

yield關鍵字只可在生成器內部使用,在其餘地方使用會致使程序拋出語法錯誤,即便在生成器內部的函數裏使用也會報錯,與return關鍵字同樣,不能穿透函數的邊界。

不能用箭頭函數來建立生成器

3、可迭代對象和for-of循環

可迭代對象具備Symbol.iterator屬性,是一種與迭代器密切相關的對象。Symbol.iterator經過指定的函數能夠返回一個做用於附屬對象的迭代器。

  • 檢測對象是否爲可迭代對象
function isIterable(object) {
    return typeof object[Symbol.iterator] === 'function';
}

console.log(isIterable([1, 2, 3])); //true
複製代碼
  • 建立可迭代對象

默認狀況下,開發者定義的對象都是不可迭代對象,但若是給Symbol.iterator添加一個生成器,則能夠將其變爲可迭代對象。

let collection = {
    items: [],
    *[Symbol.iterator]() {
        for (let item of this.items) {
            yield item;
        }
    }
}

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for (let x of collection) {
    console.log(x);
}

1
2
3
複製代碼

4、內建迭代器

  • 集合對象迭代器

    • entries()
    • values()
    • keys()
  • 字符串迭代器

  • NodeList迭代器

5、高級迭代器功能

  • 給迭代器傳遞參數
function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; //4 + 2
    yield second + 3; //5 + 3
}

let iterator = createIterator();

console.log(iterator.next()); // '{ value: 1, done: false }'
console.log(iterator.next(4)); // '{ value: 6, done: false }'
console.log(iterator.next(5)); // '{ value: 8, done: false }'
console.log(iterator.next()); // '{ value: undefined, done: true }'
複製代碼
  • 在迭代器中拋出錯誤

調用next()方法命令迭代器繼續執行(可能提供一個值),調用throw()方法也會命令迭代器繼續執行,但同時也拋出一個錯誤,在此以後的執行過程取決於生成器內部的代碼。

  • 生成器返回語句

展開運算符與for-of循環語句會直接忽略經過return語句指定的任何返回值,只要done一變爲true就當即中止讀取其餘的值。

  • 委託生成器

在生成器裏面再委託另外兩個生成器

  • 異步任務執行(******)

生成器使人興奮的特性與異步編程有關。

function run(taskDef) {
    
    //建立一個無使用限制的迭代器
    let task = taskDef();
    
    //開始執行任務
    let result = task.next();
    
    //循環調用next() 的函數
    function step() {
    
        //若是任務未完成,則繼續執行
        if(!result.done){
            result = task.next();
            //result = task.next(result.value) 向任務執行器傳遞數據
            step();
        }
    }
    
    //開始迭代執行
    step();
}
複製代碼

第九章 JavaScript 中的類

ES6中的類與其餘語言中的仍是不太同樣,其語法的設計實際上借鑑了Javascript的動態性。

ES5 中的近類結構,建立一個自定義類型:

  1. 首先,建立一個構造函數;
  2. 而後,定義另外一個方法並賦值給構造函數的原型。
function PersonType(name) {
    this.name = name;
}

PersonType.prototype.sayName = function() {
    console.log(this.name);
}

var person = new PersonType('waltz');
person.sayName(); //'waltz'

console.log(person instanceof PersonType); //true
console.log(person instance of Object); //true

複製代碼

1、 類的聲明

  • 基本的類聲明方法
class PersonClass {
    
    //等價於PersonType的構造函數
    constructor(name){
        this.name = name;
    }
    
    //等價於PersonType.protoType.sayName
    sayName() {
        console.log(this.name);
    }
}

let person = new PersonClass('waltz');
person.sayName(); //'waltz'

console.log(person instanceof PersonClass); //true
console.log(person instanceof Object); //true

console.log(typeof PersonClass); //'function'
console.log(typeof PersonClass.prototype.sayName) //'function'
複製代碼

自有屬性是實例中的屬性,不會出如今原型上,且只能在類的構造函數或方法中建立。建議你在構造函數中建立全部的自有屬性,從而只經過一處就能夠控制類中的全部自有屬性。

與函數不一樣的是,類屬性不可被賦予新值。

2、 爲什麼使用類語法

  • 函數聲明能夠被提高,而類聲明與let聲明相似,不能被提高;真正執行聲明語句以前,它們會一直存在於臨時死區(TDZ)中。
  • 類聲明中的全部代碼將自動運行在嚴格模式下,並且沒法強行讓代碼脫離嚴格模式執行。
  • 在自定義類型中,須要經過Object.defineProperty() 方法手工指定某個方法爲不可枚舉;而在類中,全部的方法都是不可枚舉的。
  • 每一個類都有一個名爲[[Construct]]的內部方法,經過關鍵字new調用那些不含[[Construct]]的方法會致使程序拋出錯誤。
  • 使用除關鍵字new之外的方式調用類的構造函數會致使程序拋出錯誤。
  • 在類中修改類名會致使程序報錯。

3、類表達式

和函數的聲明形式和表達式相似。

在js引擎中,類表達式的實現與類聲明稍有不一樣。對於類聲明來講,經過let定義的外部綁定與經過const定義的內部綁定具備相同的名稱。而命名類表達式經過const定義名稱,從而只能在類的內部使用。

4、做爲一等公民的類

在程序中。一等公民是指一個能夠傳入函數,能夠從函數返回,而且能夠賦值給變量的值。(JS函數是一等公民)

function createIbject(classDef) {
    return new classDef();
}

let Obj = createObject(class {

    sayHi() {
        console.log('Hi!')
    }
});
obj.sayHi(); //'Hi!'
複製代碼

類表達式還有另外一種使用方式,經過當即調用類構造函數能夠建立單例。用new調用類表達式,緊接着經過一對小括號調用這個表達式:

let person = new class {

    constructor(name) {
        this.name = name;
    }
    
    sayName() {
        console.log(this.name);
    }
}('waltz');

person.sayName(); // 'waltz'

複製代碼

依照這種模式可使用類語法建立單例,而且不會再做用域中暴露類的引用。

5、訪問器屬性

class CustomHtmlElement() {

    constructor(element){
        this.element = element;
    }
    
    //建立getter
    get html() {
        return this.element.innerHTML;
    }
    
    //建立setter
    set html(value) {
        this.element.innnerHTML = value;
    }
    
}

var descriptor = Object.getOwnPropertyDescriptor(CustomHtmlElement.prototype, "html");
console.log("get" in descriptor); //true
console.log("set" in descriptor); //true
console.log(descriptor.enumerable); //false
複製代碼

6、可計算成員名稱

//類方法
let methodName = "sayName";

class PersonClass(name) {
    
    constructor(name) {
        this.name = name;
    }
    
    [methodName]() {
        console.log(this.name);
    }
};

let me = new PersonClass("waltz");
me.sayName(); // 'waltz'
複製代碼
//訪問器屬性
let propertyName = 'html';
class CustomHTMLElement)() {
    
    constructor(element) {
        this.element = element;
    }
    
    get [propertyName]() {
        return this.element.innerHTML;
    }
    
    set [propertyName](value) {
        this.element.innerHTML = value;
    }
}
複製代碼

7、生成器方法

class MyClass {

    *createIterator() {
        yield 1;
        yield 2;
        yield 3;
    }
    
}

let instance = new MyClass();
let iterator = instance.createIterator();

複製代碼

若是用對象來表示集合,又但願經過簡單的方法迭代集合中的值,那麼生成器方法就派上用場了。

儘管生成器方法很實用,但若是你的類是用來表示值的 集合 的,那麼爲它定義一個 默認迭代器 更有用。

8、靜態成員

直接將方法添加到構造函數中來模擬靜態成員是一種常見的模式。

function PersonType(name) {
    this.name = name;
}

//靜態方法
PersonType.create = function(name) {
    return new PersonType(name);
}

//實例方法
PersonType.protoType.sayName = function() {
    console.log(this.name);
};

var person = PersonType.create('waltz');
複製代碼

類等價:

class PersonClass {

    // 等價於PersonType構造函數
    constructor(name) {
        this.name = name;
    }
    
    //等價於PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
    
    //等價於PersonType.create
    static create(name) {
        return new PersonClass(name);
    }
}

let person = PersonClass.create('waltz');
複製代碼

類中的全部方法和訪問器屬性均可以用static關鍵字來定義,惟一的限制是不能將static用於定義構造函數方法。

不可在實例中訪問靜態成員,必需要直接在類中訪問靜態成員。

9、繼承與派生類

ES5實現

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function Square(length) {
    Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
    constuctor: {
        value: Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});

var square = new Square(3);

console.log(square.getArea()); // 9
console.log(square instanceof Square); //true
console.log(square instanceof Rectangle); true

複製代碼

ES6類實現

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }
    
    getArea() {
        return this.length * this.width;
    }
}

class Square extends Rectangle {
    //派生類指定了構造函數則必需要調用 super()
    constructor(length) {
        
        //等價於Retangle.call(this, length, length)
        super(length, length);
    }
    
    //若是不使用構造函數,則當建立新的類實例時會自動調用 super() 並傳入全部參數
}

var square = new Square(3);

console.log(square.getArea()); //9
console.log(square instanceof Square); //true
console.log(square instanceof Rectangle); //true
複製代碼

使用super()的小貼士:

  • 只可在派生類的構造函數中使用super(),若是嘗試在非派生類(不是用extends聲明的類)或函數中使用則會致使程序拋出錯誤。
  • 在構造函數中訪問this以前必定要調用super(),它負責初始化this,若是在調用super()以前嘗試訪問this會致使程序錯誤。
  • 若是不想調用super(),則惟一的方法是讓類的構造函數返回一個對象。

類方法遮蔽 --- 派生類中的方法總會覆蓋基類中的同名方法。

靜態成員繼承 --- 若是基類有靜態成員,那麼這些靜態成員在派生類中也可用。

派生自表達式的類 --- 只要表達式能夠解析爲一個函數而且具備[[Constructor]]屬性和原型,那麼就能夠用extends進行派生。 extends強大的功能使得類能夠繼承自任意類型的表達式,從而創造更多可能性。

因爲能夠動態肯定使用哪一個基類,於是能夠建立不一樣的繼承方法

let SerializationMixin = {
    serialize() {
        return JSON.stringify(this);
    }
};

let AreaMixin = {
    getArea() {
        return this.length * this.width;
    }
};

function mixin(...mixins) {
    var base = function() {};
    Object.assign(base.prototype, ...mixins);
    return base;
}

class Square extends mixin(AreaMixin, SerializableMixin) {
    constructor(length) {
        super();
        this.length = length;
        this.width = length;
    }
}

var x = new Square(3);
console.log(x.getArea());  //9
console.log(x.serialize()); // "{'length': 3, 'width': 3}"

//若是多個mixin對象具備相同屬性,那麼只有最後一個被添加的屬性被保留。
複製代碼

內建對象的繼承

class MyArray extends Array {
    //空
}

var colors = new MyArray();
colors[0] = "red";

console.log(colors.length); //1
colors.length = 0;
console.log(colors[0]); //undefined

複製代碼

Symbol.species屬性

內建對象繼承的一個實用之處,本來在內建對象中返回實例自身的方法將自動返回派生類的實例。

Symbol.species是諸多內部Symbol中的一個,它被用於定義返回函數的靜態訪問器屬性。被返回的函數是一個構造函數,每當要在實例的方法中(不是在構造函數中)建立類的實例時必須使用這個構造函數。

通常來講,只要想在類方法中調用this.constructor,就應該使用Symbol.species屬性,從而讓派生類重寫返回類型。並且若是你正從一個已定義Symbol.species屬性的類建立派生類,那麼確保使用哪一個值而不是使用構造函數。

class MyArray extends Array {
    static get [Symbol.species]() {
        return Array;
    }
}

let items = new MyArray(1, 2, 3, 4),
    subitems = items.slice(1, 3);
    
console.log(items instanceof MyArray); //true
console.log(subitems instanceof Array); //true
console.log(subitems instanceof MyArray); //false
複製代碼

10、在類的構造函數中使用new.target

在簡單狀況下,new.target等於類的構造函數。

由於類必須經過new關鍵字才能調用,因此在列的構造函數中,new.target屬性永遠不會是undefined

第十章 改進的數組功能

1、建立數組

1.1 ES6以前建立數組的方法:

  • 調用Array構造函數
  • 用數組字面量語法

1.2 ES6:

  • Array.of();

    做用:幫助開發者們規避經過Array構造函數建立數組是的怪異行爲,由於,Array構造函數表現的與傳入的的參數類型及數量有些不符;

    function createArray(arrayCreator, value){
        return arrayCreator(value);
    }
    
    let items = createArray(Array.of, value)
    複製代碼
  • Array.from();

    ES5方法將類數組轉化爲真正的數組:

    function makeArray(arrayLike) {
        var result = [];
        for(var i=0, len = arrayLike.length; i<len; i++) {
            result.push(arrayLike[i]);
        }
        
        return result;
    }
    
    function doSomething() {
        var args = makeArray(arguments);
        
        //使用args
    }
    複製代碼

    改進方法:

    function makeArray(arrayLike) {
        return Array.prototype.slice.call(arrayLike);
    }
    
    function doSomething() {
        var args = makeArray(arguments);
        
        //使用args
    }
    複製代碼

    ES6-Array.from():

    function doSomething() {
        var args = Array.from(arguments);
        
        //使用args
    }
    複製代碼

1.3 映射轉換

若是想要進一步轉化數組,能夠提供一個映射函數做爲Array.from()的第二個參數,這個函數用來將類數組對象中的每個值轉換成其餘形式,最後將這些結果儲存在結果數組的相應索引中。

function translate() {
    return Array.from(arguments, (value) => value + 1);
}

let numbers = translate(1, 2, 3);
console.log(numbers); //2, 3, 4
複製代碼

也能夠傳入第三個參數來表示映射函數的this值

let helper = {
    diff: 1,
    
    add(value) {
        return value + this.diff;
    }
};

function translate() {
    return Array.from(arguments, helper.add, helper);
}

let numbers = translate(1, 2, 3);
console.log(numbers); //2, 3, 4
複製代碼

Array.from()轉換可迭代對象

let numbers = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
};

let numbers = Array.from(numbers, (value) => value + 1);

console.log(numbers2); //2, 3, 4
複製代碼

若是一個對象既是類數組又是可迭代的,那麼Array.from()方法會根據迭代器來決定轉換那個值。

2、爲全部數組添加的新方法

2.1 find()方法和findIndex()方法

一旦回調函數返回truefind()方法和findIndex()方法都會當即中止搜索數組剩餘的部分。

適用於根據某個條件查找匹配的元素,若是隻想查找與某個值匹配的元素,則indexOf()方法和lastIndexOf()方法是更好的選擇。

2.2 fill()方法

  • 傳入一個值,會用這個值重寫數組中的全部值;
  • 傳入第二個索引參數,表示從該索引位置開始替換;

若是開始索引或結束索引爲負值,那麼這些值會與數組的length屬性相加來做爲最終的位置。

2.3copyWith()方法

傳入兩個參數,一個是開始填充值的索引位置,另外一個是開始複製值的索引位置。(若是索引存在負值,也會與數組的length屬性相加做爲最終值

3、定型數組

3.1 定義

定型數組能夠爲JavaScript帶來快速的換位運算。ES6採用定型數組做爲語言的正式格式來確保更好的跨JavaScript引擎兼容性以及與JavaScript數組的互操做性。

所謂定型數組,就是將任何數字轉換爲一個包含數字比特的數組。

定型數組支持存儲和操做如下8種不一樣的數值類型:

  • 有符號的8位整數(int8)
  • 無符號的8位整數(uint8)
  • 有符號的16位整數(int16)
  • 無符號的16位整數(uint16)
  • 有符號的32位整數(int32)
  • 無符號的32位整數(uint32)
  • 32位浮點數(float32)
  • 64位浮點數(float64)

全部與定型數組有關的操做和對象都集中在這8個數據類型上,可是在使用它們以前,須要建立一個 數組緩衝區 存儲這些數據。

3.2 數組緩衝區

數組緩衝區是全部定型數組的根基,它是一段能夠包含特定數量字節的內存地址。(相似c語言malloc()分配內存)

let buffer = new ArrayBuffer(10); //分配10字節
console.log(buffer.byteLength); // 10
複製代碼

數組緩衝區包含的實際字節數量在建立時就已肯定,能夠修改緩衝區內的數據,可是不能改變緩衝區的尺寸大小。

DataView類型是一種通用的數組緩衝區視圖,其支持全部8種數值型數據類型。

let buffer = new ArrayBuffer(10),
    view = new DataView(buffer);
複製代碼

能夠基於同一個數組緩衝區建立多個view, 於是能夠爲應用申請一整塊獨立的內存地址,而不是當須要空間時再動態分配。

  • 獲取讀取試圖信息

  • 讀寫視圖信息

    • 視圖是獨立的,不管數據以前是經過何種方式存儲的,你均可在任意時刻讀取或寫入任意格式的數據。
let buffer = new ArrayBuffer(2),
    view = new DataView(buffer); 

view.setInt(0, 5);
view.setInt(1, -1);

console.log(view.getInt16(0)); // 1535
console.log(view.getInt8(0)); //5
console.log(view.getInt8(1)); //-1
複製代碼
  • 定型數組是視圖

    • ES6定型數組其實是用於數組緩衝區的特定類型的視圖,你能夠強制使用特定的數據類型。而不是使用通用的DataView對象來操做數組緩衝區。
    • 建立定型數組的三種方法。

3.3 定型數組與普通數組的類似之處

能夠修改length屬性來改變普通數組的大小,而定型數組的length屬性是一個不可寫屬性,因此不能修改定型數組的大小。

3.4 定型數組與普通數組的差異

定型數組和普通數組最重要的差異是:定型數組不是普通數組。

定型數組一樣會檢查數據類型的合法性,0被用於代替因此非法值。

  • 附加方法

set(): 將其它數組複製到已有的定型數組。

subarray(): 提取已有定型數組的一部分做爲一個新的定型數組。

第十一章、Promise與異步編程

1、異步編程的背景知識

JavaScript既能夠像事件和回調函數同樣指定稍後執行的代碼,也能夠明確指示代碼是否成功執行。

JavaScript引擎一次只能執行一個代碼塊,因此須要跟蹤即將運行的代碼,那些代碼被放在一個任務隊列中,每當一段代碼準備執行時,都會被添加到任務隊列。每當JavaScript引擎中的一段代碼結束執行,事件循環(event loop) 會執行隊列中的下一個任務,它是JavaScript引擎中的一段程序,負責監督代碼執行並管理任務隊列。

事件模型--->回調模式--->Promise

2、Promise的基礎知識

Promise至關於異步操做結果的佔位符,它不會去訂閱一個事件,也不會傳遞一個回調函數給目標函數,而是讓函數返回一個Promise對象。like:

// readFile承諾將在將來的某個時刻完成
let promise = readFile("example.txt");
複製代碼

操做完成後,Promise會進入兩個狀態:

  • Fulfilled Promise異步操做成功完成;
  • Rejected 因爲程序錯誤或一些其餘緣由,Promise異步操做未能成功完成。

內部屬性[[PromiseState]]被用來表示Promise的三種狀態:"pending"、"fulfilled"、"rejected"。這個屬性不暴露在Promise對象上,因此不能以編程的方式檢測Promise的狀態,只有當Promise的狀態改變時,經過then()方法採起特定的行動。

若是一個對象實現了上述的then()方法,那這個對象咱們稱之爲thenable對象。全部的Promise都是thenable對象,但並不是全部thenable對象都是Promise

then方法

catch方法(至關於只給其傳入拒絕處理程序的then()方法)

// 拒絕
promise.catch(function(err)) {
    console.error(err.message);
});
複製代碼

與下面調用相同

promise.then(null, function(err)){
    // 拒絕
    console.error(error.message);
});
複製代碼

Promise比事件和回調函數更好用

  • 若是使用事件,在遇到錯誤時不會主動觸發;
  • 若是使用回調函數,則必需要記得每次都檢查錯誤參數;
  • 不給Promise添加拒絕處理程序,那全部失敗就自動被忽略了,因此一段要添加拒絕處理程序。

若是一個Promise處於已處理狀態,在這以後添加到任務隊列中的處理程序仍將進行。

3、建立未完成的Promise

Promise的執行器會當即執行,而後才執行後續流程中的代碼:

let promise = new Promise(function(resolve, reject){
    console.log("Promise");
    resolve();
})

console.log("Hi!");

//Promise
//Hi!
複製代碼

完成處理程序和拒絕處理程序老是在 執行器 完成後被添加到任務隊列的末尾。

4、建立已處理的Promise

  • 使用Promise.resolve()

  • 使用Promise.reject()

    若是向Promise.resolve()方法或Promise.reject()方法傳入一個Promise, 那麼這個Promise會被直接返回。

  • PromiseThenable對象

    Promise.resolve()方法和Promise.reject()方法均可以接受非PromiseThenable對象做爲參數。若是傳入一個非PromiseThenable對象,則這些方法會建立一個新的Promise,並在then()函數中被調用。

    PromiseThenable對象: 擁有then()方法而且接受resolvereject這兩個參數的普通對象。

    若是不肯定某個對象是否是Promise對象,那麼能夠根據預期的結果將其傳入Promise.resolve()方法中或Promise.object()方法中,若是它是Promise對象,則不會有任何變化。

5、執行器錯誤

每一個執行器都隱含一個try-catch塊,因此錯誤會被捕獲並傳入拒絕處理程序。

6、全局的Promise拒絕處理

有關Promise的其中一個 最具爭議 的問題是,若是在沒有拒絕處理程序的狀況下拒絕一個Promise,那麼不會提示失敗信息。

6.1 Node.js環境的拒絕處理

  • unhandledRejection

    在一個事件循環中,當Promise被拒絕,而且沒有提供拒絕處理程序時,觸發該事件。

    let rejected;
    
    process.on("unhandledRejection", function(reason, promise){
        console.log(reason.message); // "Explosion!"
        console.log(rejected === promise); // true
    });
    
    rejected = Promise.reject(new Error("Explosion!"));
    複製代碼
  • rejectionHandled

    在一個事件循環以後,當Promise被拒絕時,若拒絕處理程序被調用,觸發該事件。

    let rejected;
    
    process.on("rejectionHandled", function(promise){
        console.log(rejected === promise); // true
    });
    
    rejected = Promise.reject(new Error("Explosion!"));
    
    //等待添加拒絕處理程序
    setTimeout(function(){
        rejected.catch(function(value){
            console.log(value.message); // "Explosion!"
        });   
    }, 1000);
    複製代碼

6.2 瀏覽器環境 的拒絕處理

  • unhandledRejection(描述與Node.js相同)

  • rejectionHandled

瀏覽器中的實現與Node.js中的幾乎徹底相同,兩者都是用一樣的方法將Promise及其拒絕值存儲在Map集合中,而後再進行檢索。惟一的區別是,在事件處理程序中檢索信息的位置不一樣。

7、串聯Promise

每次調用then()方法或catch()方法時實際上建立並返回了另外一個Promise,只有當第一個Promise完成或被拒絕後,第二個纔會被解決。

務必在Promise鏈的末尾留有一個拒絕處理程序以確保可以正確處理全部可能發生的錯誤。

拒絕處理程序中返回的值仍可用在下一個Promise的完成處理程序中,在必要時,即便其中一個Promise失敗也能恢復整條鏈的執行。

8、在Promise中返回Promise

在完成或拒絕處理程序中返回Thenable對象不會改變Promise執行器的執行動機,先定義的Promise的執行器先執行,後定義的後執行。

9、響應多個Promise

  • Promise.All()方法
  • Promise.race()方法

10、自Promise繼承

Promise與其餘內建類型同樣,也能夠做爲基類派生其餘類,因此你能夠定義本身的Promise變量來擴展內建Promise的功能。

11、基於Promise的異步任務執行

let fs = require("fs");
function run(taskDef) {

    //建立迭代器
    let task = taskDef();
    
    //開始執行任務
    let result = task.next();
    
    //遞歸函數遍歷
    (function step() {
    
        //若是有更多任務要作
        if(!result.done) {
            
            //用一個Promise來解決會簡化問題
            let promise = Promise.resolve(result.value);
            promise.then(function(value) {
                result = task.next(value);
                step();
            }).catch(function(error){
                result = task.throw(error);
                step();
            })
        }
    }());
}

//定義一個可用於任務執行器的函數

function readFile(filename) {
    return new Promise(function(resolve, reject) {
       fs.readFile(filename, function(err, contents){
            if(err){
                reject(err);
            }else{
                resolve(contents);
            }
       }); 
    });
}

//執行一個任務

run(function*(){
    let contents = yield readFile("config.json");
    doSomethingWith(contents);
    console.log("done");
})
複製代碼

ES2017 await

第十二章、代理(Proxy)和反射(Reflection)API

代理(Proxy)是一種能夠攔截並改變底層JavaScript引擎操做的包裝器,在新語言中經過它暴露內部運做的對象。

1、代理和反射

調用 new Proxy()可建立代替其餘目標對象的代理,它虛擬化了目標,因此兩者看起來功能一致。

代理能夠攔截 JavaScript 引擎內部目標的底層對象操做,這些底層操做被攔截後會觸發響應特定操做的陷阱函數。

反射APIReflect對象的形式出現,對象中方法的默認特性與相同的底層操做一致,而代理能夠覆寫這些操做,每一個代理陷阱對應一個命名和參數都相同的Reflect方法。

2、使用set陷阱驗證屬性 / 用get陷阱驗證對象解構(Object Shape)

set代理陷阱能夠攔截寫入屬性的操做,get代理陷阱能夠攔截讀取屬性的操做。

let target = {
    name: "target"
}

let proxy = new Proxy(target, {
    set(trapTarget, key, value, receiver) {
        
        //忽略不但願受到影響的已有屬性
        if(!trapTarget.hasOwnProperty(key)) {
            if(isNaN(value)) {
                throw new TypeError("屬性必須是數字");
            }
        }
        
        //添加屬性
        return Reflect.set(trapTarget, key, value, receiver);
    }
});

//添加一個新屬性
proxy.count = 1;
console.log(proxy.count); //1
console.log(target.count); //1

//因爲目標已有name屬性於是能夠給它賦值
proxy.name = "proxy";
console.log(proxy.name); //"proxy"
console.log(target.name); //"proxy"
複製代碼
let proxy = new Proxy({},{
    get(trapTarget, key, receiver) {
        if (!(key in receiver)) {
            throw new TypeError("屬性" + key + "不存在");
        }
        
        return Reflect.get(trapTarget, key, receiver);
    }
});

//添加一個屬性,程序仍正常運行
proxy.name = "proxy";
console.log(proxy.name); // "proxy"

//若是屬性不存在,則拋出錯誤
console.log(proxy.nme); // 拋出錯誤
複製代碼

3、使用has陷阱隱藏已有屬性

4、使用deleteProperty陷阱防止刪除屬性

5、原型代理陷阱

  • 原型代理陷阱的運行機制

原型代理陷阱有一些限制:

  1. getPrototypeOf陷阱必須返回對象或null,只要返回值必將致使運行時錯誤,返回值檢查能夠確保Object.getPropertyOf()返回的老是預期的值;
  2. setPropertyOf陷阱中,若是操做失敗則返回的必定是false,此時Object.setPrototypeOf()會拋出錯誤,若是setPrototypeOf返回了任何不是false的值,那麼Object.setPrototypeOf()便假設操做成功。
  • 爲何有兩組方法

Object.getPrototypeOf()Object.setPrototypeOf()是高級操做,建立伊始便給開發者使用的;而Reflect.getPrototypeOf()Reflect.setPrototypeOf()方法則是底層操做,其賦予開發者能夠訪問以前只在內部操做的[[GetPrototypeOf]][[SetPrototypeOf]]的權限。

Object.setPrototypeOf()Reflect.setPrototypeOf()之間在返回值上有微妙的差別,前者返回傳入的對象,後者返回布爾值。

6、對象可擴展性陷阱

  • preventExtensions(阻止擴展)
  • isExtensible(判斷是否可擴展)

相比高級功能方法而言,底層的具備更嚴格的錯誤檢查。

7、屬性描述符陷阱

  • defineProperty(定義屬性)
  • getOwnPropertyDescriptor(獲取屬性)

給Object.defineProperty()添加限制

若是讓陷阱返回true而且不調用Reflect.defineProperty()方法,則可讓Object.definePropperty()方法靜默失效,這既消除了錯誤又不會真正定義屬性。

描述符對象限制

defineProperty陷阱被調用時,descriptor對象有value屬性卻沒有name屬性,這是由於descriptor不是實際傳入Object.defineProperty()方法的第三個參數的引用,而是一個只包含那些被容許使用的屬性的新對象。Reflect.defineProperty()方法一樣也忽略了描述符上的全部非標準屬性。

8、ownKeys陷阱

ownKeys陷阱經過Reflect.ownKeys()方法實現默認的行爲,返回的數組中包含全部自有屬性的鍵名,字符串類型和Symbol類型的都包含在內。

  • Object.getOwnPropertyNames()方法和Object.keys()方法返回的結果將Symbol類型的屬性名排除在外。
  • Object.getOwnPropertySymbols()方法返回的結果將字符串類型的屬性名排除在外。
  • Object.assign()方法支持字符串和Symbol兩種類型。

9、函數代理中的applyconstruct陷阱

全部的代理陷阱中,只有applyconstruct的代理目標是一個函數。

  • 驗證函數參數
  • 不用new調用構造函數

能夠經過檢查new target的值來肯定函數是不是經過new來調用的。

假設Numbers()函數定義在你沒法修改的代碼中,你知道代碼依賴new target,但願函數避免檢查卻仍想調用函數。在這種狀況下,用new調用時的行爲已被設定,因此你只能使用apply陷阱。

  • 覆寫抽象基類構造函數
  • 可調用的類構造函數

10、可撤銷代理

11、解決數組問題

  • 檢測數組索引
  • 添加新元素時增長length的值
  • 減小length的值來刪除元素

12、實現MyArray類

想要建立使用代理的類,最簡單的方法是像往常同樣定義類,而後在構造函數中返回一個代理,那樣的話,當類實例化時返回的對象是代理而不是實例(構造函數中的this是該實例)。

將代理用做原型

雖然從類構造函數返回代理很容易,但這也意味着每建立一個實例都要建立一個新代理。然而有一種方法可讓全部的實例共享一個代理:將代理用做原型。

  • 在原型上使用get陷阱
  • 在原型上使用set陷阱
  • 在原型上使用has陷阱
  • 將代理用做類的原型

第十三章 用模塊封裝代碼

1、什麼是模塊?

模塊是自動運行在嚴格模式下而且沒有辦法退出運行的Javascript代碼。

注:在模塊的頂部,this的值是undefined;模塊不支持HTML風格的代碼註釋。

  • 導出的基本語法
  • 導入的基本語法
    • 導入單個綁定
    • 導入多個綁定導入

綁定的微妙怪異之處

export var name = "xszi";
export function setName(newName) {
    name = newName;
}

//導入以後
import { name, setName } from "./example.js";

console.log(name);  //xszi
setName("waltz");
console.log(name); //waltz

name = "hahha"; //拋出錯誤

複製代碼
  • 導入和導出重命名

  • 模塊的默認值

    • 導出默認值
    • 導入默認值

    只能爲每一個模塊設置一個默認的導出值,導出時屢次使用default關鍵字是一個語法錯誤。

    用逗號將默認的本地名稱與大括號包裹的非默認值分隔開,請記住,在import語句中,默認值必須排在非默認值以前。

  • 從新導出一個綁定

  • 無綁定導入

即便沒有任何導出或導入的操做,這也是一個有效的模塊。

無綁定導入最有可能被應用與建立PilyfillShim

Shim: 是一個庫,它將一個新的API引入到一箇舊的環境中,並且僅靠舊環境中已有的手段實現。

Polyfill: 一個用在瀏覽器API上的Shim,咱們一般的作法是先檢查當前瀏覽器是否支持某個API,若是不支持的話就加載對用的polyfill

把舊的瀏覽器想一想成一面有裂縫的牆,這些polyfill會幫助咱們把這面牆的裂縫填平。

2、加載模塊

  1. 在web瀏覽器中使用模塊

    //加載一個JavaScript模塊文件
    <script type="module" src="module.js"></script>
    複製代碼
    //內聯引入模塊
    <script type="module">
    import { sum } from "./example.js";
    let result = sum(1, 2)
    </script>
    複製代碼
    • web瀏覽器中的模塊加載順序

    模塊與腳本不一樣,它是獨一無二的,能夠經過import關鍵字來指明其所依賴的其餘文件,而且這些文件必須被加載進該模塊才能正確執行。爲了支持該功能,<script type="module">執行時自動應用defer屬性。

    每一個模塊均可以從一個或多個其餘的模塊導入,這會使問題複雜化。所以,首先解析模塊以識別全部導入語句;而後,每一個導入語句都觸發一次獲取過程(從網絡或從緩存),而且在全部導入資源都被加載和執行後纔會執行當前模塊。

    • web瀏覽器中的異步模塊加載
    //沒法保證這兩個哪一個先執行
    <script type="module" async src="module1.js"></script>
    <script type="module" async src="module2.js"></script>
    複製代碼

    將模塊做爲Worker加載

    Worker能夠在網頁上下文以外執行JavaScript代碼。

    //按照腳本的方式加載script.js
    let worker = new Worker("script.js");
    複製代碼
    //按照模塊的方式加載module.js
    let worker = new Worker("module.js", {type: "module"});
    複製代碼

A ECMAScript6中較小的改動

1、 安全整數

IEEE 754只能準確的表示-2^53 ~ 2^53之間的整數:

var inside = Number.MAX_SAFE_INTEGER,
    outside = inside + 1;

console.log(Number.isInteger(inside)); //true
console.log(Number.isSafeInteger(inside)); //true

console.log(Number.isInteger(outside)); //true
console.log(Number.isSafeInteger(outside)); //false
複製代碼

2、 新的Math方法

提升一般的數學計算的速度

3、 Unicode 標識符

4、正式化_ptoto_屬性

實際上,_proto_Object.getPrototypeOf()方法和Object.setPrototypeOf()方法的早期實現。

B 瞭解ECMAScript 7 (2016)

1、指數運算符

5 ** 2 == Math.pow(5, 2); //true
複製代碼

求冪運算符在JavaScript全部二進制運算符中具備最高的優先級(一元運算符的優先級高於**)

2、Array.prototype.includes()方法

奇怪之處:

includes()方法認爲+0和-0是相等的,Object.is()方法會將+0和-0識別爲不一樣的值。

3、函數做用域嚴格模式的一處改動

ECMAScript 2016 規定在參數被解構或有默認參數的函數中禁止使用"use strict"指令。只有參數爲不包含解構或默認值的簡單參數列表才能夠在函數體中使用"use strict"

相關文章
相關標籤/搜索