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

寫在前面

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

第一章 作用域是什麼

1.1.編譯原理

  • 編譯三個步驟:1、分詞/詞法分析;2、解析/語法分析;3、代碼生成
  • 分詞/詞法分析(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行爲指出變量在循環過程中不止被聲明一次,每次迭代都會聲明
  • 模塊模式需具備的兩個必要條件:1、必須有外部的封閉函數,該函數必須至少被調用一次;2、封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有作用域中形成閉包,並且可以訪問或修改私有的狀態

5.3.現在我懂了

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

5.4.循環和閉包

  • 我們使用 IIFE 在每次迭代時都創建一個新的作用域。換句話說,每次迭代我們都需要一個塊作用域

5.5.模塊

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

附錄

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

第二部分 this和對象原型

第一章 關於this

1.1.爲什麼要用this

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

1.2.誤解

  • this誤解:1、指向自身,誤理解成指向函數自身;2、指向函數作用域,誤理解成指向函數的作用域
  • 爲什麼需要從函數內部引用函數自身:常見的原因是遞歸(從函數內部調用這個函數)或者可以寫一個在第一次被調用後自己解除綁定的事件處理器
  • 一種傳統的但是現在已經被棄用和批判的用法,是使用 arguments.callee 來引用當前正在運行的函數對象。這是唯一一種可以從匿名函數對象內部引用自身的方法

1.3.this到底是什麼

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

第二章 this全面解析

2.1.調用位置

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

2.2.綁定規則

  • 默認綁定、隱式綁定、顯式綁定、new綁定
  • 默認綁定:常見的獨立函數調用,無法應用其他規則時默認規則。但使用嚴格模式(strict mode)時,不能將全局對象用於默認綁定,因此this會綁定到undefined
  • 隱式綁定:需要考慮調用位置是否有上下文對象,或者說是否被某個對象擁有或包含。當函數引用有上下文對象時,該規則會把函數調用中的this綁定到這個上下文對象
  • 一個最常見的 this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用默認綁定,從而把 this 綁定到全局對象或者 undefined 上,取決於是否是嚴格模式
  • 隱式丟失:1、var bar = obj.foo; bar();//會應用默認綁定;2、doFoo(obj.foo);//函數作爲參數傳遞,隱式賦值;3、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來調用函數時,通常會自動執行下面的操作:1、創建(或者說構造)一個全新的對象;2、這個新對象會被執行[[prototype]]連接;3、新對象會綁定到函數調用的this;4、如果函數沒有返回其他對象,那麼new表達式中的函數調用會自動返回這個新對象

2.3.優先級

  • 四條規則優先級:1、顯式綁定比隱式綁定更高;2、new綁定比隱式綁定更高;3、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 還會禁止刪除這個屬性
  • 不變性:1、對象常量 writable: false和configurable: false;2、禁止擴展:Object.preventExtensions(obj);3、密封:Object.seal在現有對象上調用Object.preventExtensions,並把現有屬性標記configuable: false;4、凍結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標識符,會導致:1、自我引用(遞歸,事件綁定等)更難;2、調用棧更難追蹤;3、代碼(稍微)更難理解
  • 鴨子類型:辨別特性,很脆弱的設計

6.5.內省

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

寫在後面

  • pdf書籍、筆記思維導圖、隨書代碼打包下載地址:後面補上
  • 紙質書京東購買地址:https://u.jd.com/FwSmuH(推薦購買紙質書來學習)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章