[書籍精讀] 《你不知道的JavaScript(上卷)》精讀筆記分享

寫在前面

  • 書籍介紹:JavaScript這門語言簡單易用,很容易上手,但其語言機制複雜微妙,即便是經驗豐富的JavaScript開發人員,若是沒有認真學習的話也沒法真正理解。本套書直面當前JavaScript開發人員不求甚解的大趨勢,深刻理解語言內部的機制,全面介紹了JavaScript中常被人誤解和忽視的重要知識點。
  • 個人簡評:《你不知道的JavaScript》系列分上中下三卷,這裏是上卷,主要講解做用域、原型等核心概念相關的。該系列書籍本人以爲就上卷寫的不錯,中卷有些冗餘,下卷講ES6比較粗糙。這裏推薦你們對上捲進行細讀。
  • !!文末有pdf書籍、筆記思惟導圖、隨書代碼打包下載地址,須要請自取!閱讀[書籍精讀系列]全部文章,請移步:推薦收藏-JavaScript書籍精讀筆記系列導航

第一章 做用域是什麼

1.1.編譯原理

  • 編譯三個步驟:一、分詞/詞法分析;二、解析/語法分析;三、代碼生成
  • 分詞/詞法分析(Tokenizing/Lexing):將由字符組成的字符串分解成(對編程語言來講)有意義的代碼塊,這些代碼塊被稱爲詞法單元(token)
  • 解析/語法分析(Parsing):將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的表明了程序語法結構的樹
  • 代碼生成:將 AST 轉換爲可執行代碼的過程
  • JavaScript大部分狀況下編譯發生在代碼執行前的幾微秒
  • JavaScript用盡各類辦法去保證性能最佳,好比JIT能夠延遲編譯設置重編譯

1.2.理解做用域

  • 引擎:負責整個JavaScript程序的編譯及執行過程
  • 編譯器負責語法分析及代碼生成等
  • 做用域:負責收集並維護由全部聲明的標識符(變量)組成的一系列查詢,並實施一套很是嚴格的規則,肯定當前執行的代碼對這些標識符的訪問權限
  • var a = 2; 引擎認爲這裏有兩個徹底不一樣的聲明, 一個由編譯器在編譯時處理, 另外一個則由引擎在運行時處理
  • 僞代碼進行歸納:「爲一個變量分配內存, 將其命名爲 a, 而後將值 2 保存進這個變量
  • 當變量出如今賦值操做的左側時進行 LHS 查詢, 出如今右側時進行 RHS 查詢
  • RHS 查詢與簡單地查找某個變量的值別無二致,而 LHS 查詢則是試圖找到變量的容器自己, 從而能夠對其賦值
  • LHS查詢,例如a=2,對變量賦值
  • RHS查詢,例如console.log(a),獲取變量的值
  • 增強理解,LHS賦值操做的目標是誰,RHS誰是賦值操做的源頭
  • 在嚴格模式中LHS查詢失敗時,並不會建立並返回一個全局變量,引擎會拋出同RHS查詢失敗時相似的ReferenceError異常

1.3.做用域嵌套

  • 在當前做用域中沒法找到某個變量時,引擎就會在外層嵌套的做用域中繼續查找,直到找到該變量,或抵達最外層的做用域(也就是全局做用域)爲止

1.4.異常

  • 不成功的 RHS 引用會致使拋出 ReferenceError 異常。 不成功的 LHS 引用會致使自動隱式地建立一個全局變量(非嚴格模式下), 該變量使用 LHS 引用的目標做爲標識符, 或者拋出 ReferenceError 異常(嚴格模式下)

第二章 詞法做用域

2.1.詞法階段

  • 詞法做用域就是定義在詞法階段的做用域。 換句話說,詞法做用域是由你在寫代碼時將變量和塊做用域寫在哪裏來決定的,所以當詞法分析器處理代碼時會保持做用域不變(大部分狀況下是這樣的)
  • 做用域查找會在找到第一個匹配的標識符時中止
  • 詞法做用域:定義在詞法階段的做用域,動態做用域:做用域氣泡,嚴格包含的,沒有任何函數能夠部分地出如今2個父級函數中
  • 不管函數在哪裏被調用,也不管它如何被調用,它的詞法做用域都只由函數被聲明時所處的位置決定
  • 詞法做用域是在寫代碼或者說定義時肯定的,而動態做用域是在運行時肯定的
  • 詞法做用域關注函數在何處聲明,而動態做用域關注函數從何處調用

2.2.欺騙詞法

  • eval和with
  • JavaScript 中的 eval(..) 函數能夠接受一個字符串爲參數, 並將其中的內容視爲好像在書寫時就存在於程序中這個位置的代碼
  • 在嚴格模式的程序中, eval(..) 在運行時有其本身的詞法做用域, 意味着其中的聲明沒法修改所在的做用域
  • eval執行字符串,使其能夠在運行期修改書寫期的詞法做用域,相似的還有setTimeout和SetInterval第一個參數傳入字符串的狀況
  • setTimeout(..) 和setInterval(..) 的第一個參數能夠是字符串, 字符串的內容能夠被解釋爲一段動態生成的函數代碼
  • 在嚴格模式中,eval()在運行時有其本身的詞法做用域,沒法修改所在的做用域
  • new Function(..) 函數的行爲也很相似, 最後一個參數能夠接受代碼字符串,並將其轉化爲動態生成的函數
  • with 能夠將一個沒有或有多個屬性的對象處理爲一個徹底隔離的詞法做用域, 所以這個對象的屬性也會被處理爲定義在這個做用域中的詞法標識符
  • 儘管 with 塊能夠將一個對象處理爲詞法做用域, 可是這個塊內部正常的 var 聲明並不會被限制在這個塊的做用域中, 而是被添加到 with 所處的函數做用域中
  • with聲明其實是根據你傳遞給它的對象憑空建立了一個全新的詞法做用域,這兩個機制(eval和with)的反作用是引擎沒法在編譯中對做用域查找進行優化
  • JavaScript 引擎會在編譯階段進行數項的性能優化。 其中有些優化依賴於可以根據代碼的詞法進行靜態分析, 並預先肯定全部變量和函數的定義位置, 才能在執行過程當中快速找到標識符
  • 最悲觀的狀況是若是出現了 eval(..) 或 with, 全部的優化可能都是無心義的, 所以最簡單的作法就是徹底不作任何優化

第三章 函數做用域和塊做用域

3.1.函數中的做用域

  • 函數做用域的含義是指屬於這個函數的所有變量均可以在整個函數的範圍內使用及複用(包括嵌套的做用域)

3.2.隱藏內部實現

  • 最小受權或最小暴露原則:是指在軟件設計中, 應該最小限度地暴露必要內容,而將其餘內容都「隱藏」 起來,好比某個模塊或對象的 API 設計
  • 變量衝突的一個典型例子存在於全局做用域中。當程序中加載了多個第三方庫時,若是它們沒有妥善地將內部私有的函數或變量隱藏起來,就會很容易引起衝突

3.3.函數做用域

  • 區分函數聲明和函數表達式最簡單方法看function關鍵字出如今聲明中的位置(不只僅是一行代碼,而是整個聲明中的位置) 5
  • 函數聲明和函數表達式之間最重要的區別是它們的名稱標識符將會綁定在何處
  • (function foo(){ .. }) 做爲函數表達式意味着 foo 只能在 .. 所表明的位置中被訪問,外部做用域則不行
  • 匿名函數表達式幾個缺點須要考慮:1.匿名函數在棧追蹤中不會顯示出有意義的函數名,使得調試很困難;2.若是沒有函數名,當函數須要引用自身時只能使用已通過期的 arguments.callee 引用,好比在遞歸中;3.匿名函數省略了對於代碼可讀性/可理解性很重要的函數名。一個描述性的名稱可讓代碼不言自明

3.4.塊做用域

  • 變量的聲明應該距離使用的地方越近越好,並最大限度地本地化
  • 塊做用域是一個用來對以前的最小受權原則進行擴展的工具,將代碼從在函數中隱藏信息擴展爲在塊中隱藏信息
  • 用with從對象中建立出的做用域僅在with聲明中而非外部做用域中有效
  • try/catch的catch分句會建立一個塊做用域,其中聲明的變量僅在catch內部有效
  • let 關鍵字能夠將變量綁定到所在的任意做用域中(一般是 { .. } 內部)
  • for 循環頭部的 let 不只將 i 綁定到了 for 循環的塊中,事實上它將其從新綁定到了循環的每個迭代中,確保使用上一個循環迭代結束時的值從新進行賦值
  • Tracer,Google維護的項目,正是用來將ES6代碼轉換成兼容ES6以前的環境
  • IIFE和try/catch均可以用來實現let塊做用域,但try/catch性能的確很糟糕
  • var a=2;JavaScript實際上會將其當作兩個聲明。var a;a=2;第一個定義聲明是在編譯階段進行的,第二個賦值會留在原地等待執行階段

第四章 提高

4.1.先有雞仍是先有蛋

  • 函數做用域和塊做用域的行爲是同樣的,能夠總結爲:任何聲明在某個做用域內的變量,都將附屬於這個做用域

4.2.編譯器再度來襲

  • 引擎會在解釋 JavaScript 代碼以前首先對其進行編譯。編譯階段中的一部分工做就是找到全部的聲明,並用合適的做用域將它們關聯起來
  • 正確的思考思路是,包括變量和函數在內的全部聲明都會在任何代碼被執行前首先被處理
  • 變量和函數聲明從它們在代碼中出現的位置被「移動」到了最上面。這個過程就叫做提高
  • 只有聲明自己會被提高,但函數會首先被提高,而後纔是變量
  • 函數聲明後面同名的var聲明會被忽略掉

4.3.函數優先

  • 一個值得注意的細節(這個細節能夠出如今有多個「重複」聲明的代碼中)是函數會首先被提高, 而後纔是變量
  • 一個普通塊內部的函數聲明一般會被提高到所在做用域的頂部

第五章 做用域閉包

5.1.啓示

  • 閉包是基於詞法做用域書寫代碼時所產生的天然結果,你甚至不須要爲了利用它們而有意識地建立閉包

5.2.實質問題

  • 函數在定義時的詞法做用域之外的地方被調用,閉包使得函數能夠繼續訪問定義時的詞法做用域
  • 若是將(訪問它們各自詞法做用域的)函數看成第一級的值類型並處處傳遞,你就會看到閉包在這些函數中的應用,在定時器,事件監聽器,Ajax請求,跨窗口通訊,Web workers或者任何其餘的異步(或者同步)任務中,只要有用了回調函數,實際上就是在使用閉包
  • 循環和閉包:延遲函數的回調會在循環結束時才執行
  • for/let行爲指出變量在循環過程當中不止被聲明一次,每次迭代都會聲明
  • 模塊模式需具有的兩個必要條件:一、必須有外部的封閉函數,該函數必須至少被調用一次;二、封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,而且能夠訪問或修改私有的狀態

5.3.如今我懂了

  • 在定時器、事件監聽器、Ajax 請求、 跨窗口通訊、 Web Workers 或者任何其餘的異步(或者同步)任務中,只要使用了回調函數, 實際上就是在使用閉包

5.4.循環和閉包

  • 咱們使用 IIFE 在每次迭代時都建立一個新的做用域。換句話說,每次迭代咱們都須要一個塊做用域

5.5.模塊

  • 模塊模式須要具有兩個必要條件:必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的模塊實例);封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,而且能夠訪問或者修改私有的狀態;
  • 基於函數的模塊並非一個能被穩定識別的模式(編譯器沒法識別),它們的 API 語義只有在運行時纔會被考慮進來。所以能夠在運行時修改一個模塊的API
  • ES6 模塊 API 更加穩定(API 不會在運行時改變)

附錄

  • 動態做用域並不關心函數和做用域是如何聲明以及在何處聲明的,只關心它們從何處調用
  • 換句話說,做用域鏈是基於調用棧的,而不是代碼中的做用域嵌套
  • 主要區別:詞法做用域是在寫代碼或者說定義時肯定的,而動態做用域是在運行時肯定的

第二部分 this和對象原型

第一章 關於this

1.1.爲何要用this

  • 箭頭函數:一、容易讓人混淆了this綁定規則和詞法做用域規則,二、另外箭頭函數是匿名而非具名的

1.2.誤解

  • this誤解:一、指向自身,誤理解成指向函數自身;二、指向函數做用域,誤理解成指向函數的做用域
  • 爲何須要從函數內部引用函數自身:常見的緣由是遞歸(從函數內部調用這個函數)或者能夠寫一個在第一次被調用後本身解除綁定的事件處理器
  • 一種傳統的可是如今已經被棄用和批判的用法,是使用 arguments.callee 來引用當前正在運行的函數對象。這是惟一一種能夠從匿名函數對象內部引用自身的方法

1.3.this究竟是什麼

  • this是在運行時進行綁定的,並非在編寫時綁定。它的上下文取決於函數調用時的各類條件
  • this的綁定和函數聲明的位置沒有關係,只取決於函數的調用方式

第二章 this全面解析

2.1.調用位置

  • 調用位置:尋找函數在代碼中被調用的地方(而不是聲明的位置)
  • 調用棧:爲了到達當前執行位置所調用的全部函數,在JavaScript調試器中能夠很方便查看
  • 另外一個查看調用棧的方法是使用瀏覽器的調試工具。 絕大多數現代桌面瀏覽器都內置了開發者工具,其中包含 JavaScript 調試器

2.2.綁定規則

  • 默認綁定、隱式綁定、顯式綁定、new綁定
  • 默認綁定:常見的獨立函數調用,沒法應用其餘規則時默認規則。但使用嚴格模式(strict mode)時,不能將全局對象用於默認綁定,所以this會綁定到undefined
  • 隱式綁定:須要考慮調用位置是否有上下文對象,或者說是否被某個對象擁有或包含。當函數引用有上下文對象時,該規則會把函數調用中的this綁定到這個上下文對象
  • 一個最多見的 this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用默認綁定,從而把 this 綁定到全局對象或者 undefined 上,取決因而否是嚴格模式
  • 隱式丟失:一、var bar = obj.foo; bar();//會應用默認綁定;二、doFoo(obj.foo);//函數做爲參數傳遞,隱式賦值;三、setTimeout(obj.foo, 100);//函數傳入語言內置函數,一樣
  • 顯式綁定:想在某個對象上強調調用函數,可使用函數call和apply方法。foo.call(obj)調用foo時強制把this綁定到obj上,若是傳入原始值,會轉換成它的對象形式,如new String(...),new Boolean(...)一般稱裝箱
  • 也會出現綁定丟失問題,硬綁定:不可能再修改它的this。bind(...)會返回一個硬編碼的新函數
  • 因爲硬綁定是一種很是經常使用的模式 因此在 ES5 中提供了內置的方法Function.prototype.bind
  • new 綁定:JavaScript中new的機制實際上和麪向類的語言徹底不一樣,使用new來調用函數時,一般會自動執行下面的操做:一、建立(或者說構造)一個全新的對象;二、這個新對象會被執行[[prototype]]鏈接;三、新對象會綁定到函數調用的this;四、若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象

2.3.優先級

  • 四條規則優先級:一、顯式綁定比隱式綁定更高;二、new綁定比隱式綁定更高;三、new修改了硬綁定調用中的this
  • new 和 call/apply 沒法一塊兒使用, 所以沒法經過 new foo.call(obj1) 來直接進行測試
  • MDN 提供的一種bind(..) 實現
  • 之因此要在 new 中使用硬綁定函數,主要目的是預先設置函數的一些參數,這樣在使用new 進行初始化時就能夠只傳入其他的參數。 bind(..) 的功能之一就是能夠把除了第一個參數(第一個參數用於綁定 this)以外的其餘參數都傳給下層的函數(這種技術稱爲「部分應用」, 是「柯里化」 的一種)
  • 能夠按照下面的順序來進行判斷this:1. 函數是否在 new 中調用(new 綁定)?若是是的話 this 綁定的是新建立的對象;2. 函數是否經過 call、apply(顯式綁定)或者硬綁定調用?若是是的話,this 綁定的是指定的對象;3. 函數是否在某個上下文對象中調用(隱式綁定)?若是是的話,this 綁定的是那個上下文對象;4. 若是都不是的話,使用默認綁定。若是在嚴格模式下, 就綁定到 undefined,不然綁定到全局對象;

2.4.綁定例外

  • 若是把null或者undefined做爲this的綁定對象傳入call,apply或者bind會被忽略,實際應用的是默認綁定規則
  • Object.create(null)和{}很像,但不會建立Object.prototype,因此比{}更空
  • 注意:對於默認綁定來講,決定 this 綁定對象的並非調用位置是否處於嚴格模式,而是函數體是否處於嚴格模式

2.5.this詞法

  • ES6 中介紹了一種沒法使用這些規則的特殊函數類型:箭頭函數
  • 箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決定 this

第三章 對象

3.1.語法

  • 對象能夠兩種形式定義:構造形式和聲明形式,即new Object()和{}
  • 惟一的區別是,在文字聲明中你能夠添加多個鍵/值對,可是在構造形式中你必須逐個添加屬性

3.2.類型

  • JavaScript共六種主要類型(語言類型),string、number、boolean、null、undefined、object,前五種是基本類型
  • JavaScript中萬物皆是對象,顯然是錯誤的
  • 有些內置對象的名字看起來和簡單基礎類型同樣,不過實際上它們的關係更復雜
  • 原始值 "I am a string" 並非一個對象, 它只是一個字面量,而且是一個不可變的值。若是要在這個字面量上執行一些操做,好比獲取長度、訪問其中某個字符等,那須要將其轉換爲 String 對象
  • 在JavaScript中二進制前三位都爲0的會被判斷爲object類型,null的二進制所有是0,故被斷定爲Object類型
  • null和undefined沒有對應的構造形式,只有文字形式,而Date只有構造形式
  • 對於 Object、 Array、 Function 和 RegExp(正則表達式) 來講, 不管使用文字形式仍是構造形式,它們都是對象,不是字面量
  • Error 對象不多在代碼中顯式建立,通常是在拋出異常時被自動建立。也可使用 new Error(..) 這種構造形式來建立,不過通常來講用不着
  • 數組和普通的對象都根據其對應的行爲和用途進行了優化,因此最好只用對象來存儲鍵/值對,只用數組來存儲數值下標/值對 14

3.3.內容

  • .a 語法一般被稱爲「屬性訪問」, ["a"] 語法一般被稱爲「鍵訪問」
  • 這兩種語法的主要區別在於 . 操做符要求屬性名知足標識符的命名規範,而 [".."] 語法 能夠接受任意 UTF-8/Unicode 字符串做爲屬性名
  • 在對象中,屬性名永遠都是字符串。若是你使用 string(字面量)之外的其餘值做爲屬性名,那它首先會被轉換爲一個字符串
  • 數組也是對象,因此雖然每一個下標都是整數,你仍然能夠給數組添加屬性。雖然添加了命名屬性(不管是經過 . 語法仍是 [] 語法), 數組的 length 值並未發生變化
  • 咱們還不肯定「複製」 一個函數意味着什麼。有些人會經過 toString() 來序列化一個函數的源代碼(可是結果取決於 JavaScript 的具體實現, 並且不一樣的引擎對於不一樣類型的函數處理方式並不徹底相同)
  • 對象屬性描述符:value(值)、writable(是否可修改)、enumrable(是否可枚舉)、configurable(是否可配置)、get、set
  • 無論是否是處於嚴格模式, 嘗試修改一個不可配置的屬性描述符都會出錯。注意:如你所見,把 configurable 修改爲false 是單向操做, 沒法撤銷!
  • 除了沒法修改, configurable:false 還會禁止刪除這個屬性
  • 不變性:一、對象常量 writable: false和configurable: false;二、禁止擴展:Object.preventExtensions(obj);三、密封:Object.seal在現有對象上調用Object.preventExtensions,並把現有屬性標記configuable: false;四、凍結Object.freeze在現有對象上調用Object.seal並把數據訪問屬性標記writable: false
  • 存在性:in操做符會檢查屬性是否在對象及其[[prototype]]原型鏈中,hasOwnProperty只會檢查屬性是否在對象中,不會檢查[[prototype]]鏈,object.keys和object.getOwnPropertyNames都只會查找對象直接包含的屬性

3.4.遍歷

  • 遍歷:forEach遍歷並忽略回調函數返回值,沒法break跳出;every一直運行到回調函數返回false,some一直運行到回調函數返回true;for...of遍歷值而不是屬性
  • every(..) 和 some(..) 中特殊的返回值和普通 for 循環中的 break 語句相似,它們會提早終止遍歷

第四章 混合對象「類」

4.1.類理論

  • 面向對象編程強調的是數據和操做數據的行爲本質上是互相關聯的(固然,不一樣的數據有不一樣的行爲),所以好的設計就是把數據以及和它相關的行爲打包(或者說封裝)起來
  • 類的另外一個核心概念是多態,這個概念是說父類的通用行爲能夠被子類用更特殊的行爲重寫
  • 面向類的設計模式:實例化(instantiation) 繼承(inheritance)(相對)多態(polymophism)

4.2.類的機制

  • 類的機制:許多面向類的語言中,「標準庫」會提供stack類,是一種"棧"數據結構
  • Stack類內部會有一些變量來存儲數據,同時提供一些公有的可訪問行爲(方法),從而讓你的代碼能夠和(隱藏的)數據進行交互(好比添加、刪除數據)
  • 藍圖---建築,類比於 類---實例

4.3.類的繼承

  • 對於真正的類來講,構造函數是屬於類的,而JavaScript是相反的,實際上類是屬於構造函數的,類的繼承其實就是複製
  • 多態是一個很是普遍的話題,咱們如今所說的「相對」 只是多態的一個方面:任何方法均可以引用繼承層次中高層的方法(不管高層的方法名和當前方法名是否相同)
  • 多態的另外一個方面是,在繼承鏈的不一樣層次中一個方法名能夠被屢次定義,當調用方法時會自動選擇合適的定義
  • 須要注意,子類獲得的僅僅是繼承自父類行爲的一份副本。子類對繼承到的一個方法進行「重寫」,不會影響父類中的方法,這兩個方法互不影響,所以才能使用相對多態引用訪問父類中的方法
  • 多重繼承意味着全部父類的定義都會被複制到子類中

4.4.混入

  • 在繼承或者實例化時, JavaScript 的對象機制並不會自動執行復制行爲。簡單來講, JavaScript 中只有對象,並不存在能夠被實例化的「類」
  • 顯式混入mixin,沒法(用標準、可靠的方法)真正的複製,只能複製對共享函數對象的引用
  • 隱式混入call,經過this綁定實現
  • 混入模式(不管顯式仍是隱式) 能夠用來模擬類的複製行爲,可是一般會產生醜陋而且脆弱的語法,好比顯式僞多態(OtherObj.methodName.call(this, ...)), 這會讓代碼更加難懂而且難以維護

第五章 原型

5.1.[[Prototype]]

  • Object的原理:全部普通的[prototype]鏈最終都會指向內置的Object.prototype
  • constructor是一個很是不可靠而且不安全的引用,儘可能避免使用這些引用
  • 調用Object.create會憑空建立一個「新」對象並把新對象內部的[prototype]關聯到你指定的對象

5.2.「類」

  • a instanceof Foo,在a的整條[prototype]鏈中是否有Foo.prototype指向的對象
  • JavaScript和麪向類的語言不一樣,它並無類來做爲對象的抽象模式或者說藍圖,JavaScript中只有對象
  • 繼承意味着複製操做,JavaScript(默認)並不會複製對象屬性,相反,JavaScript會在兩個對象之間建立一個關聯,這樣一個對象就能夠經過委託訪問另外一個對象的屬性和函數

5.3.技術

  • 在JavaScript中,對於「構造函數」最準確的解釋是全部帶new的函數調用
  • 實際上,構造函數和你程序中其餘函數沒有任何區別,當在普通函數調用前加上new關鍵詞以後,就會把這個函數調用變成一個「構造函數調用」
  • new會劫持全部普通函數並用構造函數的形式來調用它
  • 奇怪的__prototype__(在ES6以前並非標準),屬性引用了內部(prototype)對象

5.4.對象關聯

  • 原型鏈:若是在對象上沒有找到須要的屬性或者方法引用,引擎就會繼續在[prototype]關聯的對象上進行查找,同理,若是在後者中也沒找到,就到須要的引用就會繼續查找它的prototype,以此類推
  • Object.create會建立一個新對象(bar)並把它關聯到咱們指定的對象(foo)
  • 能夠充分發揮[prototype]機制的威力(委託)而且避免沒必要要的麻煩(好比使用new的構造函數調用會生成.prototype和.constructor引用)
  • Object.create(null)會建立一個擁有空[prototype]連接的對象,這個對象沒法委託

第六章 行爲委託

6.1.面向委託的設計

  • 回顧:[Prototype]機制就是指對象中的一個內部連接引用另外一個對象
  • JavaScript中這個機制的本質就是對象之間的關聯關係
  • 把思路從類和繼承的設計模式轉換到委託行爲的設計模式

6.2.類與對象

  • 委託行爲意味着某些對象(XYZ)在找不到屬性或者方法引用時會把這個請求委託給另外一個對象(Task),這是一種極其強大的設計模式和父類、子類、繼承、多態徹底不一樣
  • 對象關聯風格的代碼相較於對象與類風格更加簡潔,由於只關注一件事,對象之間的關係

6.4.更好的語法

  • ES6中的class仍然是經過[prototype]機制實現的
  • 匿名函數沒有name標識符,會致使:一、自我引用(遞歸,事件綁定等)更難;二、調用棧更難追蹤;三、代碼(稍微)更難理解
  • 鴨子類型:辨別特性,很脆弱的設計

6.5.內省

  • 內省:檢查實例的類型,主要目的是經過建立方式來判斷對象的結構和功能
  • instanceof:由於Foo.prototype在a1的[prototype]鏈上,因此instanceof操做告訴咱們a1是Foo類的一個實例。從語法角度上說:instanceof彷佛是檢查a1和Foo的關係,但實際上它想說的是a1和Foo.prototype(引用的對象)是互相關聯的
  • 行爲委託認爲對象之間是兄弟關係,互相委託,而不是父類和子類的關係。咱們能夠選擇在JavaScript中努力實現類機制,也能夠擁抱更天然的[prototype]委託機制

寫在後面

  • pdf書籍、筆記思惟導圖、隨書代碼打包下載地址:後面補上
  • 紙質書京東購買地址:https://u.jd.com/FwSmuH(推薦購買紙質書來學習)
相關文章
相關標籤/搜索