《你不知道的 javascript》是一個前端學習必讀的系列,讓不求甚解的JavaScript開發者迎難而上,深刻語言內部,弄清楚JavaScript每個零部件的用途。本書《你不知道的javascript》中卷介紹了該系列的兩個主題:「類型和語法」以及「異步與性能」。這兩塊也是值得咱們反覆去學習琢磨的兩塊只是內容,今天咱們用思惟導圖的方式來精讀一遍。(思惟導圖圖片可能有點小,記得點開看,你會有所收穫)javascript
JavaScript 有 七 種 內 置 類 型: null 、 undefined 、 boolean 、 number 、 string 、 object 和 symbol ,可使用 typeof 運算符來查看。前端
變量沒有類型,但它們持有的值有類型。類型定義了值的行爲特徵。java
很 多 開 發 人 員 將 undefined 和 undeclared 混 爲 一 談, 但 在 JavaScript 中 它 們 是 兩 碼 事。 undefined 是值的一種。 undeclared 則表示變量尚未被聲明過。node
遺憾的是, JavaScript 卻將它們混爲一談, 在咱們試圖訪問 "undeclared" 變量時這樣報 錯:ReferenceError: a is not defined, 並 且 typeof 對 undefined 和 undeclared 變 量 都 返 回 "undefined" 。git
然而,經過 typeof 的安全防範機制(阻止報錯)來檢查 undeclared 變量,有時是個不錯的辦法。github
JavaScript 中的數組是經過數字索引的一組任意類型的值。字符串和數組相似,可是它們的 行爲特徵不一樣, 在將字符做爲數組來處理時須要特別當心。 JavaScript 中的數字包括「整 數」和「浮點型」。算法
基本類型中定義了幾個特殊的值。編程
null 類型只有一個值 null , undefined 類型也只有一個值 undefined 。 全部變量在賦值之 前默認值都是 undefined 。 void 運算符返回 undefined 。數組
數 字 類 型 有 幾 個 特 殊 值, 包 括 NaN ( 意 指「not a number」 , 更 確 切 地 說 是「invalid number」 )、 +Infinity 、 -Infinity 和 -0 。安全
簡單標量基本類型值(字符串和數字等)經過值複製來賦值 / 傳遞, 而複合值(對象等) 經過引用複製來賦值 / 傳遞。 JavaScript 中的引用和其餘語言中的引用 / 指針不一樣,它們不 能指向別的變量 / 引用,只能指向值。
JavaScript
爲基本數據類型值提供了封裝對象,稱爲原生函數(如 String 、 Number 、 Boolean 等)。它們爲基本數據類型值提供了該子類型所特有的方法和屬性(如: String#trim() 和 Array#concat(..) )。
對於簡單標量基本類型值,好比 "abc" ,若是要訪問它的 length 屬性或 String.prototype 方法, JavaScript 引擎會自動對該值進行封裝(即用相應類型的封裝對象來包裝它)來實現對這些屬性和方法的訪問。
本章介紹了 JavaScript 的數據類型之間的轉換,即強制類型轉換:包括顯式和隱式。
強制類型轉換經常爲人詬病, 但實際上不少時候它們是很是有用的。 做爲有使命感的 JavaScript 開發人員,咱們有必要深刻了解強制類型轉換,這樣就能取其精華,去其糟粕。
顯式強制類型轉換明確告訴咱們哪裏發生了類型轉換, 有助於提升代碼可讀性和可維 護性。
隱式強制類型轉換則沒有那麼明顯,是其餘操做的反作用。感受上好像是顯式強制類型轉 換的反面,實際上隱式強制類型轉換也有助於提升代碼的可讀性。
在處理強制類型轉換的時候要十分當心,尤爲是隱式強制類型轉換。在編碼的時候,要知 其然,還要知其因此然,並努力讓代碼清晰易讀。
JavaScript 語法規則中的許多細節須要咱們多花點時間和精力來了解。 從長遠來看, 這有 助於更深刻地掌握這門語言。
語句和表達式在英語中都能找到類比——語句就像英語中的句子,而表達式就像短語。表 達式能夠是簡單獨立的,不然可能會產生反作用。
JavaScript 語法規則之上是語義規則(也稱做上下文)。例如, { } 在不一樣狀況下的意思不 盡相同,能夠是語句塊、對象常量、解構賦值(ES6)或者命名函數參數(ES6)。
JavaScript 詳細定義了運算符的優先級(運算符執行的前後順序)和關聯(多個運算符的 組合方式)。只要熟練掌握了這些規則,就能對如何合理地運用它們做出本身的判斷。
ASI(自動分號插入)是 JavaScript 引擎的代碼解析糾錯機制, 它會在須要的地方自動插 入分號來糾正解析錯誤。問題在於這是否意味着大多數的分號都不是必要的(能夠省略), 或者因爲分號缺失致使的錯誤是否均可以交給 JavaScript 引擎來處理。
JavaScript 中有不少錯誤類型, 分爲兩大類:早期錯誤(編譯時錯誤, 沒法被捕獲)和運 行時錯誤(能夠經過 try..catch 來捕獲)。全部語法錯誤都是早期錯誤,程序有語法錯誤 則沒法運行。
函數參數和命名參數之間的關係很是微妙。尤爲是 arguments 數組,它的抽象泄漏給咱們 挖了很多坑。所以,儘可能不要使用 arguments ,若是非用不可,也切勿同時使用 arguments 和其對應的命名參數。
finally 中代碼的處理順序須要特別注意。它們有時能派上很大用場,但也容易引發困惑, 特別是在和帶標籤的代碼塊混用時。總之,使用 finally 旨在讓代碼更加簡潔易讀,切忌 弄巧成拙。
switch 相對於 if..else if.. 來講更爲簡潔。須要注意的一點是,若是對其理解得不夠透 徹,稍不注意就很容易出錯。
實際上, JavaScript 程序老是至少分爲兩個塊:第一塊 如今 運行;下一塊 未來 運行, 以響 應某個事件。儘管程序是一塊一塊執行的,可是全部這些塊共享對程序做用域和狀態的訪 問,因此對狀態的修改都是在以前累積的修改之上進行的。
一旦有事件須要運行, 事件循環就會運行, 直到隊列清空。 事件循環的每一輪稱爲一個 tick。 用戶交互、IO 和定時器會向事件隊列中加入事件。
任意時刻,一次只能從隊列中處理一個事件。執行事件的時候,可能直接或間接地引起一 個或多個後續事件。
併發是指兩個或多個事件鏈隨時間發展交替執行,以致於從更高的層次來看,就像是同時 在運行(儘管在任意時刻只處理一個事件)。
一般須要對這些併發執行的「進程」(有別於操做系統中的進程概念)進行某種形式的交 互協調,好比須要確保執行順序或者須要防止競態出現。這些「進程」也能夠經過把自身 分割爲更小的塊,以便其餘「進程」插入進來。
回調函數是 JavaScript 異步的基本單元。可是隨着 JavaScript 愈來愈成熟,對於異步編程領域的發展,回調已經不夠用了。
第一,大腦對於事情的計劃方式是線性的、阻塞的、單線程的語義,可是回調錶達異步流 程的方式是非線性的、非順序的,這使得正確推導這樣的代碼難度很大。難於理解的代碼 是壞代碼,會致使壞 bug。
咱們須要一種更同步、更順序、更阻塞的的方式來表達異步,就像咱們的大腦同樣。
第二,也是更重要的一點,回調會受到控制反轉的影響,由於回調暗中把控制權交給第三 方(一般是不受你控制的第三方工具!)來調用你代碼中的 continuation。 這種控制轉移導 致一系列麻煩的信任問題,好比回調被調用的次數是否會超出預期。
能夠發明一些特定邏輯來解決這些信任問題,可是其難度高於應有的水平,可能會產生更 笨重、更難維護的代碼,而且缺乏足夠的保護,其中的損害要直到你受到 bug 的影響纔會 被發現。
咱們須要一個通用的方案來解決這些信任問題。無論咱們建立多少回調,這一方案都應可 以複用,且沒有重複代碼的開銷。
咱們須要比回調更好的機制。到目前爲止,回調提供了很好的服務,可是將來的 JavaScript 須要更高級、功能更強大的異步模式。本書接下來的幾章會深刻探討這些新型技術。
Promise 很是好,請使用。它們解決了咱們因只用回調的代碼而備受困擾的控制反轉問題。
它們並無擯棄回調,只是把回調的安排轉交給了一個位於咱們和其餘工具之間的可信任 的中介機制。
Promise 鏈也開始提供(儘管並不完美)以順序的方式表達異步流的一個更好的方法,這 有助於咱們的大腦更好地計劃和維護異步 JavaScript 代碼。咱們將在第 4 章看到針對這個 問題的一種更好的解決方案!
生成器是 ES6 的一個新的函數類型, 它並不像普通函數那樣老是運行到結束。 取而代之 的是, 生成器能夠在運行當中(徹底保持其狀態)暫停, 而且未來再從暫停的地方恢復 運行。
這種交替的暫停和恢復是合做性的而不是搶佔式的,這意味着生成器具備獨一無二的能力 來暫停自身,這是經過關鍵字 yield 實現的。不過,只有控制生成器的迭代器具備恢復生 成器的能力(經過 next(..) )。
yield / next(..) 這一對不僅是一種控制機制,實際上也是一種雙向消息傳遞機制。 yield .. 表 達式本質上是暫停下來等待某個值,接下來的 next(..) 調用會向被暫停的 yield 表達式傳回 一個值(或者是隱式的 undefined )。
在異步控制流程方面,生成器的關鍵優勢是:生成器內部的代碼是以天然的同步 / 順序方 式表達任務的一系列步驟。其技巧在於,咱們把可能的異步隱藏在了關鍵字 yield 的後面, 把異步移動到控制生成器的迭代器的代碼部分。
換句話說,生成器爲異步代碼保持了順序、同步、阻塞的代碼模式,這使得大腦能夠更自 然地追蹤代碼,解決了基於回調的異步的兩個關鍵缺陷之一。
本部分的前四章都是基於這樣一個前提:異步編碼模式使咱們可以編寫更高效的代碼,通 常可以帶來很是大的改進。可是,異步特性只能讓你走這麼遠,由於它本質上仍是綁定在 一個單事件循環線程上。
所以,在這一章裏,咱們介紹了幾種可以進一步提升性能的程序級別的機制。
Web Worker 讓你能夠在獨立的線程運行一個 JavaScript 文件(即程序),使用異步事件在 線程之間傳遞消息。 它們很是適用於把長時間的或資源密集型的任務卸載到不一樣的線程 中,以提升主 UI 線程的響應性。
SIMD 打算把 CPU 級的並行數學運算映射到 JavaScript API, 以得到高性能的數據並行運算,好比在大數據集上的數字處理。
最後, asm.js 描述了 JavaScript 的一個很小的子集, 它避免了 JavaScript 難以優化的部分 (好比垃圾收集和強制類型轉換),而且讓 JavaScript 引擎識別並經過激進的優化運行這樣 的代碼。能夠手工編寫 asm.js, 可是會極端費力且容易出錯,相似於手寫彙編語言(這也 是其名字的由來)。實際上, asm.js 也是高度優化的程序語言交叉編譯的一個很好的目標, 好比 Emscripten 把 C/C++ 轉換成 JavaScript(https://github.com/kripken/em... 。
JavaScript 還有一些更加激進的思路已經進入很是早期的討論, 儘管本章並無明確包含 這些內容,好比近似的直接多線程功能(而不是藏在數據結構 API 後面)。無論這些最終 會不會實現,仍是咱們將只能看到更多的並行特性偷偷加入 JavaScript, 但確實能夠預見, 將來 JavaScript 在程序級別將得到更加優化的性能。
對一段代碼進行有效的性能測試,特別是與一樣代碼的另一個選擇對比來看看哪一種方案 更快,須要認真注意細節。
與其打造你本身的統計有效的性能測試邏輯,不如直接使用 Benchmark.js 庫,它已經爲你 實現了這些。可是,編寫測試要當心,由於咱們很容易就會構造一個看似有效實際卻有缺 陷的測試,即便是微小的差別也可能扭曲結果,使其徹底不可靠。
從儘量多的環境中獲得儘量多的測試結果以消除硬件/ 設備的誤差, 這一點很重要。 jsPerf.com 是很好的網站,用於衆包性能測試運行。
遺憾的是,不少經常使用的性能測試執迷於可有可無的微觀性能細節,好比 x++ 對比 ++x 。編 寫好的測試意味着理解如何關注大局, 好比關鍵路徑上的優化以及避免落入相似不一樣的 JavaScript 實現細節這樣的陷阱中。
尾調用優化是 ES6 要求的一種優化方法。它使 JavaScript 中本來不可能的一些遞歸模式變 得實際。 TCO 容許一個函數在結尾處調用另一個函數來執行,不須要任何額外資源。這意味着,對遞歸算法來講,引擎再也不須要限制棧深度。
思惟導圖能比較清晰的還原整本書的知識結構體系,若是你還沒用看過這本書,能夠按照這個思惟導圖的思路快速預習一遍,提升學習效率。學習新事物總容易遺忘,我比較喜歡在看書的時候用思惟導圖作些記錄,便於本身後期複習,若是你已經看過了這本書,也建議你收藏複習。若是你有神馬建議或則想法,歡迎留言或加我微信交流:646321933,備註技術交流