《你不知道的 javascript》是一個前端學習必讀的系列,讓不求甚解的JavaScript開發者迎難而上,深刻語言內部,弄清楚JavaScript每個零部件的用途。本書介紹了該系列的兩個主題:「做用域和閉包」以及「this和對象原型」。這兩塊也是值得咱們反覆去學習琢磨的兩塊只是內容,今天咱們用思惟導圖的方式來精讀一遍。(思惟導圖圖片可能有點小,記得點開看,你會有所收穫)javascript
做用域是一套規則,用於肯定在何處以及如何查找變量(標識符)。若是查找的目的是對 變量進行賦值,那麼就會使用 LHS 查詢;若是目的是獲取變量的值,就會使用 RHS 查詢。賦值操做符會致使 LHS 查詢。 的賦值操做。 =操做符或調用函數時傳入參數的操做都會致使關聯做用域的賦值操做。
JavaScript 引擎首先會在代碼執行前對其進行編譯,在這個過程當中,像 var a = 2 這樣的聲 明會被分解成兩個獨立的步驟:前端
LHS 和 RHS 查詢都會在當前執行做用域中開始,若是有須要(也就是說它們沒有找到所 需的標識符),就會向上級做用域繼續查找目標標識符,這樣每次上升一級做用域(一層 樓),最後抵達全局做用域(頂層),不管找到或沒找到都將中止。java
不成功的RHS引用會致使拋出 ReferenceError 異常。不成功的 LHS 引用會致使自動隱式地建立一個全局變量(非嚴格模式下),該變量使用 LHS 引用的目標做爲標識符,或者拋 出 ReferenceError 異常(嚴格模式下)。git
詞法做用域意味着做用域是由書寫代碼時函數聲明的位置來決定的。編譯的詞法分析階段 基本可以知道所有標識符在哪裏以及是如何聲明的,從而可以預測在執行過程當中如何對它 們進行查找。github
JavaScript 中有兩個機制能夠「欺騙」詞法做用域: eval(..) 和 with 。 前者能夠對一段包 含一個或多個聲明的「代碼」字符串進行演算,並藉此來修改已經存在的詞法做用域(在 運行時)。後者本質上是經過將一個對象的引用 看成 做用域來處理,將對象的屬性看成做 用域中的標識符來處理,從而建立了一個新的詞法做用域(一樣是在運行時)。設計模式
這兩個機制的反作用是引擎沒法在編譯時對做用域查找進行優化,由於引擎只能謹慎地認 爲這樣的優化是無效的。使用這其中任何一個機制都 將 致使代碼運行變慢。 不要使用它們。數組
函數是 JavaScript 中最多見的做用域單元。本質上,聲明在一個函數內部的變量或函數會 在所處的做用域中「隱藏」起來,這是有意爲之的良好軟件的設計原則。安全
但函數不是惟一的做用域單元。塊做用域指的是變量和函數不只能夠屬於所處的做用域, 也能夠屬於某個代碼塊(一般指 { .. } 內部)。微信
從 ES3 開始, try/catch 結構在 catch 分句中具備塊做用域。在 ES6 中引入了 let 關鍵字( var 關鍵字的表親), 用來在任意代碼塊中聲明變量。 if(..) { let a = 2; } 會聲明一個劫持了 if 的 { .. } 塊的變量,而且將變量添加到這個塊 中。數據結構
有些人認爲塊做用域不該該徹底做爲函數做用域的替代方案。兩種功能應該同時存在,開 發者能夠而且也應該根據須要選擇使用何種做用域,創造可讀、可維護的優良代碼。
咱們習慣將 var a = 2; 看做一個聲明,而實際上 JavaScript 引擎並不這麼認爲。它將 var a 和 a = 2 看成兩個單獨的聲明,第一個是編譯階段的任務,而第二個則是執行階段的任務。
這意味着不管做用域中的聲明出如今什麼地方,都將在代碼自己被執行前 首先 進行處理。 能夠將這個過程形象地想象成全部的聲明(變量和函數)都會被「移動」到各自做用域的最頂端,這個過程被稱爲提高。
聲明自己會被提高,而包括函數表達式的賦值在內的賦值操做並不會提高。
要注意避免重複聲明,特別是當普通的 var 聲明和函數聲明混合在一塊兒的時候,不然會引 起不少危險的問題!
閉包就好像從 JavaScript 中分離出來的一個充滿神祕色彩的未開化世界,只有最勇敢的人 纔可以到達那裏。但實際上它只是一個標準,顯然就是關於如何在函數做爲值按需傳遞的 詞法環境中書寫代碼的。
當函數能夠記住並訪問所在的詞法做用域,即便函數是在當前詞法做用域以外執行,這時 就產生了閉包。
若是沒能認出閉包,也不瞭解它的工做原理,在使用它的過程當中就很容易犯錯,好比在循 環中。但同時閉包也是一個很是強大的工具,能夠用多種形式來實現 模塊 等模式。模塊有兩個主要特徵:
(1)爲建立內部做用域而調用了一個包裝函數;
(2)包裝函數的返回 值必須至少包括一個對內部函數的引用,這樣就會建立涵蓋整個包裝函數內部做用域的閉 包。
如今咱們會發現代碼中處處都有閉包存在,而且咱們可以識別閉包而後用它來作一些有用 的事!
若是要判斷一個運行中函數的 this 綁定,就須要找到這個函數的直接調用位置。找到以後 就能夠順序應用下面這四條規則來判斷 this 的綁定對象。
必定要注意,有些調用可能在無心中使用默認綁定規則。若是想「更安全」地忽略 this 綁 定,你可使用一個 DMZ 對象,好比 ø = Object.create(null) ,以保護全局對象。ES6中的箭頭函數並不會使用四條標準的綁定規則, 而是根據當前的詞法做用域來決定 this ,具體來講,箭頭函數會繼承外層函數調用的 this 綁定(不管 this 綁定到什麼)。這 其實和 ES6 以前代碼中的 self = this 機制同樣。
JavaScript 中的對象有字面形式(好比 var a = { .. } )和構造形式(好比 var a = new Array(..) )。字面形式更經常使用,不過有時候構造形式能夠提供更多選項。
許多人都覺得「JavaScript 中萬物都是對象」,這是錯誤的。對象是 6 個(或者是 7 個,取 決於你的觀點)基礎類型之一。對象有包括 function 在內的子類型,不一樣子類型具備不一樣 的行爲,好比內部標籤 [object Array] 表示這是對象的子類型數組。
對象就是鍵 / 值對的集合。能夠經過 .propName 或者 ["propName"] 語法來獲取屬性值。訪 問屬性時, 引擎實際上會調用內部的默認 [[Get]] 操做(在設置屬性值時是 [[Put]] ), [[Get]] 操做會檢查對象自己是否包含這個屬性,若是沒找到的話還會查找 [[Prototype]] 鏈(參見第 5 章)。
屬性的特性能夠經過屬性描述符來控制,好比 writable 和 configurable 。此外,可使用 Object.preventExtensions(..) 、 Object.seal(..) 和 Object.freeze(..) 來設置對象(及其 屬性)的不可變性級別。
屬性不必定包含值——它們多是具有 getter/setter 的「訪問描述符」。此外,屬性能夠是 可枚舉或者不可枚舉的,這決定了它們是否會出如今 for..in 循環中。
你可使用 ES6 的 for..of 語法來遍歷數據結構(數組、對象, 等等)中的值, for..of 會尋找內置或者自定義的 @@iterator 對象並調用它的 next() 方法來遍歷數據值。
類是一種設計模式。 許多語言提供了對於面向類軟件設計的原生語法。 JavaScript 也有類 似的語法,可是和其餘語言中的類徹底不一樣。
類意味着複製。
傳統的類被實例化時,它的行爲會被複制到實例中。類被繼承時,行爲也會被複制到子類 中。
多態(在繼承鏈的不一樣層次名稱相同可是功能不一樣的函數)看起來彷佛是從子類引用父 類,可是本質上引用的實際上是複製的結果。
JavaScript 並不會(像類那樣)自動建立對象的副本。
混入模式(不管顯式仍是隱式)能夠用來模擬類的複製行爲,可是一般會產生醜陋而且脆 弱的語法,好比顯式僞多態( OtherObj.methodName.call(this, ...) ),這會讓代碼更加難 懂而且難以維護。
此外, 顯式混入實際上沒法徹底模擬類的複製行爲, 由於對象(和函數!別忘了函數也 是對象)只能複製引用, 沒法複製被引用的對象或者函數自己。 忽視這一點會致使許多 問題。
總地來講,在 JavaScript 中模擬類是得不償失的,雖然能解決當前的問題,可是可能會埋下更多的隱患。
若是要訪問對象中並不存在的一個屬性, [[Get]] 操做(參見第 3 章)就會查找對象內部 [[Prototype]] 關聯的對象。這個關聯關係實際上定義了一條「原型鏈」(有點像嵌套的做用域鏈),在查找屬性時會對它進行遍歷。
全部普通對象都有內置的 Object.prototype ,指向原型鏈的頂端(好比說全局做用域),如 果在原型鏈中找不到指定的屬性就會中止。 toString() 、 valueOf() 和其餘一些通用的功能 都存在於 Object.prototype 對象上,所以語言中全部的對象均可以使用它們。
關聯兩個對象最經常使用的方法是使用 new 關鍵詞進行函數調用, 在調用的 章)中會建立一個關聯其餘對象的新對象。4個步驟(第2章)中會建立一個關聯其餘對象的新對象。
使用 new 調用函數時會把新對象的 .prototype 屬性關聯到「其餘對象」。帶 new 的函數調用 一般被稱爲「構造函數調用」,儘管它們實際上和傳統面向類語言中的 類構造函數 不同。
JavaScript 是 中的機制有一個核心區別, 那就是不會進行復制, 對象之間是經過內部的
雖然這些 機制和傳統面向類語言中的「類初始化」和「類繼承」很類似, 可是 javascript 機制和傳統面向對象類語言中的「類初始化」和「類繼承」很類似可是 javascript 中的機制有一個核心區別,就是不會進行復制,對象之間是經過內部的 [[Prototype]] 鏈關聯的。
出於各類緣由,以「繼承」結尾的術語(包括「原型繼承」)和其餘面向對象的術語都無 法幫助你理解 JavaScript 的 真實 機制(不只僅是限制咱們的思惟模式)。
相比之下,「委託」是一個更合適的術語,由於對象之間的關係不是 複製 而是委託。
在軟件架構中你能夠 選擇是否 使用類和繼承設計模式。大多數開發者理所固然地認爲類是 惟一(合適)的代碼組織方式,可是本章中咱們看到了另外一種更少見可是更強大的設計模式: 行爲委託 。
行爲委託認爲對象之間是兄弟關係, 互相委託, 而不是父類和子類的關係。 JavaScript 的 [[Prototype]] 機制本質上就是行爲委託機制。也就是說,咱們能夠選擇在 JavaScript 中努 力實現類機制(參見第 4 和第 5 章),也能夠擁抱更天然的 [[Prototype]] 委託機制。
當你只用對象來設計代碼時,不只可讓語法更加簡潔,並且可讓代碼結構更加清晰。
對象關聯(對象以前互相關聯)是一種編碼風格,它倡導的是直接建立和關聯對象,不把 它們抽象成類。對象關聯能夠用基於 [[Prototype]] 的行爲委託很是天然地實現。
思惟導圖能比較清晰的還原整本書的知識結構體系,若是你還沒用看過這本書,能夠按照這個思惟導圖的思路快速預習一遍,提升學習效率。學習新事物總容易遺忘,我比較喜歡在看書的時候用思惟導圖作些記錄,便於本身後期複習,若是你已經看過了這本書,也建議你收藏複習。若是你有神馬建議或則想法,歡迎留言或加我微信交流:646321933