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

寫在前面

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

第1章 ES?現在與未來

  • 與ES5不同,ES6並不僅僅是爲這個語言新增一組API。它包括一組新的語法形式,其中的一部分可能是要花些時間才能理解和熟悉的。它還包括各種各樣的新的組織形式和操作各種數據類型的新的輔助API

1.2.transpiling

  • transpiling(transformation+compiling,轉換+編譯)的技術。簡單的說,其思路是利用專門的工具把你的ES6代碼轉換爲等價(或近似)的可以在ES5環境下工作的代碼
  • 並非所有的ES6新特性都需要使用transpiler,還有polyfill(也稱爲shim)這種模式。在可能的情況下,polyfill會爲新環境中的行爲定義在舊環境中的等價行爲。語法不能polyfill,而API通常可以

第2章 語法

2.1.塊作用域聲明

  • JavaScript中變量作用域的基本單元一直是function。如果需要創建一個塊作用域,最普遍的方法除了普通的函數聲明之外,就是立即調用函數表達式(IIFE)
  • let 聲明:可以創建綁定到任意塊的聲明。只需要一對{..}就可以創建一個作用域;過早訪問let 聲明的引用導致的這個ReferenceError嚴格說叫做臨時死亡區錯誤(TDZ)--你在訪問一個已經聲明但沒有初始化的變量;聲明時沒有賦值的變量會自動賦值爲undefined,所以let b;就等價於let b = undefined;;未聲明的變量,所以typeof是檢查它是否存在的唯一安全的方法;爲什麼我堅持認爲應該把所有的let聲明放在其所在作用域的最前面了,這樣就完全避免了不小心過早訪問的問題;
  • const聲明:const ,用於創建常量;它是一個設定了初始值之後就只讀的變量;const 聲明必須要有顯式的初始化;如果這個值是複雜值,比如對象或者數組,其內容仍然是可以修改的;是否使用const,理論上說,引擎更容易瞭解這個變量的值/類型永遠不會改變,那麼它就可以取消某些可能的追蹤;
  • 塊作用域函數:從ES6開始,塊內聲明的函數,其作用域在這個塊內

2.2.spread/rest

  • 當...用在數組之前時(實際上是任何iterable),它會把這個變量“展開”爲各個獨立的值
  • ...的另外一種常見用法基本上可以被看作反向的行爲;...把一系列值收集到一起成爲一個數組

2.3.默認參數值

  • 可能JavaScript最常見的一個技巧就是關於設定函數參數默認值的
  • 只能通過傳入比“期望”更少的參數來省略最後的若干參數(例如,後側的),而無法省略位於參數列表中間或者起始處的參數
  • 一個很重要的需要記住的JavaScript設計原則:undefined意味着缺失。也就是說,undefined和缺失是無法區別的,至少對於函數參數來說是如此
  • 默認值表達式:默認值表達式是惰性求值的,這意味着它們只在需要的時候運行--也就是說,是在參數的值省略或者爲undefined的時候;Function.prototype本身就是一個沒有操作的空函數;

2.4.解構

  • 可以把將數組或者對象屬性中帶索引的值手動賦值看作結構化賦值。專門語法,專用於數組解構和對象解構
  • 不只是聲明:除非需要把所有的賦值表達式都當作聲明,否則不應該在賦值中混入聲明。不然會出現語法錯誤
  • 重複賦值:對象解構形式允許多次列出同一個源屬性(持有值類型任意);記住:解構的目的不只是爲了打字更少,而是爲了可讀性更強;

2.5.太多,太少,剛剛好

  • 如果爲比解構/分解出來的值更多的值賦值,那麼就像期望的一樣,多餘的值會被賦爲undefined
  • 默認值賦值:使用與前面默認函數參數值類似的=語法,解構的兩種形式都可以提供一個用來賦值的默認值
  • 嵌套解構:可以把嵌套解構當作一種展開對象名字空間的簡單方法
  • 解構參數:解構賦值/默認值會被放在參數列表中,而重組的過程會被放在函數體的return語句中

2.6.對象字面量擴展

  • 簡潔屬性
  • 簡潔方法:嚴格說來,ES5定義了getter/setter字面量形式,但是沒怎麼被使用,主要是因爲缺少transpiler來處理這個新語法(實際上也是ES5新增的唯一主要新語法)
  • 計算屬性名:對象字面定義屬性名位置的[..]中可以放置任意合法表達式;計算屬性名最常見的用法可能就是和Symbols共同使用;
  • 設定[[Prototype]]
  • super對象

2.7.模板字面量

  • 這個特性的名稱非常具有誤導性,其根據你對單詞模板(template)的理解而定
  • 插入字符串字面量中的換行(新行)會在字符串值中被保留
  • 插入表達式:在插入字符串字面量的${..}內可以出現任何合法的表達式,包括函數調用、在線函數表達式調用,甚至其他插入字符串字面量;插入字符串字面量在它出現的詞法作用域內,沒有任何形式的動態作用域;
  • 標籤模板字面量:重新命名這個特性來明確表達其功能:標籤字符串字面量;哪些實際應用(用來把數字格式化爲美元表示法;其他應用包括全球化、本地化等的特殊處理)

2.8.箭頭函數

  • 箭頭函數定義包括一個參數列表(零個或多個參數,如果參數個數不是一個的話要用(..)包圍起來),然後是標識=>,函數體放在最後
  • 箭頭函數總是函數表達式;並不存在箭頭函數聲明
  • 箭頭函數是匿名函數表達式--他們沒有用於遞歸或者事件綁定/解綁定的命名引用
  • 我認爲=>箭頭函數轉變帶來的可讀性與被轉化函數的長度負相關。這個函數越長,=>帶來的好處就越小;函數越短,=>帶來的好處就越大
  • 不只是更短的語法,而是this:對=>的關注多數都在於從代碼中去掉function、return和{..}節省了那些寶貴的鍵盤輸入;對=>的關注多數都在於從代碼中去掉function、return和{..}節省了那些寶貴的鍵盤輸入;在箭頭函數內部,this綁定不是動態的,而是詞法的;底線:=>是關於this、arguments和super的詞法綁定。這個ES6的特性設計用來修正一些常見的問題,而不是bug、巧合或者錯誤

2.9.for...of循環

  • for..of循環的值必須是一個iterable,或者說它必須可以轉換/封箱到一個iterable對象的值。iterable就是一個能夠產生迭代器供循環使用的對象
  • JavaScript中默認爲(或提供)iterable的標準內建值包括:Arrays、Strings、Generators、Collections/TypedArrays
  • for..of循環也可以通過break、continue、return(如果在函數中的話)提前終止,並拋出異常

2.10.正則表達式

  • 一個事實:JavaScript中的正則表達式很長時間以來基本上沒有什麼變化
  • Unicode標識:在ES6之前,正則表達式只能基於PMB字符匹配,這意味着那些擴展字符會被當作兩個獨立的字符來匹配;在ES6中,u標識符表示正則表達式用Unicode(UTF-16)字符來解釋處理字符串,把這樣的擴展字符串當作單個實體來匹配;影響的是匹配部分的長度;還有一點需要注意,u標識符使得+和*這樣的量詞把整個Unicode碼點作爲單個字符而應用,而不僅僅是應用於字符的低位(也就是符號的最右部分);
  • 定點標識:ES6正則表達式中另外一個新增的標籤模式是y,通常稱爲“定點(sticky)模式”;定點主要是指在正則表達式的起點有一個虛擬的錨點,只從正則表達式的lastIndex屬性指定的位置開始匹配;
  • 正則表達式flags:ES6之前,想要通過檢查一個正則表達式對象來判斷它應用了哪些標識,需要把它從source屬性的內容中解析出來;在ES6中,現在可以用新的flags屬性直接得到這些值;ES6的另一個調整是如果把標識傳給已有的正則表達式,RegExp(..)構造器現在支持flags;

2.11.數字字面量擴展

  • 雖然可以用不同進制形式指定數字,但是數字的數字值還是保存的值,並且默認的輸出解釋形式總是十進制

2.12.Unicode

  • Unicode字符範圍從0x0000到0xFFFF,包含可能看到和接觸到的所有(各種語言的)標準打印字符。這組字符稱爲基本多語言平面(BMP)。BMP甚至包含了像雪人這樣的有趣的符號
  • 現在有了可以用於作Unicode轉義(在字符串和正則表達式中)的新形式,稱爲Unicode碼點轉義
  • 支持Unicode的字符:默認情況下,JavaScript字符串運算和方法不能感知字符串中俄astral符號;如何精確計算這樣的字符串長度。[...gclef].length
  • 字符定位:原生的ES6對此的答案是charAt,但不支持astral字符的原子性,也不會考慮組合符號的因素;ES6提供了codePointAt;組合String.fromCodePoint和codePointAt來獲得支持Unicode的charAt的更簡單且更優的方法;
  • Unicode標識符名

2.13.符號

  • ES6爲JavaScript引入了一個新的原生類型:Symbol。它和其他原生類型不一樣,symbol沒有字面量形式
  • 以下幾點需要注意:不能也不應該對Symbol()使用new。它並不是一個構造器,也不會創建一個對象;傳給Symbol()的參數是可選的。如果傳入了的話,應該是一個爲這個symbol的用途給出用戶友好描述的字符串;typeof的輸出是一個新的值("symbol"),這是識別symbol的首選方法;
  • 符號本身的內部值-稱爲它的名稱(name)-是不在代碼中出現且無法獲得的。可以把這個符號值想象爲一個自動生成的、(在應用內部)唯一的字符串值
  • 符號的主要意義是創建一個類似字符串的不會與其他任何值衝突的值
  • 如果需要把符號值作爲一個真正的字符串使用,那麼它就需要用String()或者toString()進行顯式類型轉換,因爲不允許隱式地把符號轉換爲字符串
  • 符號註冊:Symbol.for()在全局符號註冊表中搜索,來查看是否有描述文字相同的符號已經存在,如果有的話就返回它;換句話說,全局註冊表把符號值本身根據其描述文字作爲單例處理;具有諷刺意義的是,基本上符號的目的是爲了取代應用中的magic字符串(賦予特殊意義的任意字符串);可以使用Symbol.keyFor()提取註冊符號的描述文本(鍵值);
  • 作爲對象屬性的符號:如果把符號用作對象的屬性/鍵值,那麼它會以一種特殊的方式存儲,使得這個屬性不出現在對這個對象的一般屬性枚舉中;要取得對象的符號屬性,使用Object.getOwnPropertySymbols;

2.14.小結

  • 這些新語法形式中大多數的設計目的都是消除常見編程技巧中的痛點,比如爲函數設定默認值以及把參數的“其餘”部分收集到數組中
  • 而像=>箭頭函數這樣的特性看起來是爲了使代碼更簡潔的語法,實際上它有非常特別的行爲特性,應該只在適當的時候使用

第3章 代碼組織

3.1.迭代器

  • 迭代器(iterator)是一個結構化的模式,用於從源以一次一個的方式提取數據
  • 迭代器是一種有序的、連續的、基於拉取的用於消耗數據的組織方式
  • 接口:IteratorResult接口指定了從任何迭代器操作返回的值必須是下面這種形式的對象:{value: .., done: true/false}
  • next()迭代:迭代器的next(..)方法可以接受一個或多個可選參數。絕大多數內置迭代器沒有利用這個功能
  • 可選的return和throw:多數內置迭代器都沒有實現可選的迭代器接口-return (..)和throw(..);return(..)被定義爲向迭代器發送一個信號,表明消費者代碼已經完畢,不會再從其中提取任何值;throw(..)用於向迭代器報告一個異常/錯誤,迭代器針對這個信號的反應可能不同於針對return(..)意味着的完成信號;
  • 自定義迭代器:這種特定的用法強調了迭代器可以作爲一個模式來組織功能,而不僅僅是數據

3.2.生成器

  • ES6引入了一個全新的某種程度上說是奇異的函數形式,稱爲生成器
  • 生成器可以在執行當中暫停自身,可以立即恢復執行也可以過一段時間之後恢復執行。所以顯然它並不像普通函數那樣保證運行到完畢
  • 在執行當中的每次暫停/恢復循環都提供了一個雙向信息傳遞的機會,生成器可以返回一個值,恢復它的控制代碼也可以發回一個值
  • 語法:執行生成器,比如foo(5, 10),並不實際在生成器中運行代碼。相反,它會產生一個迭代器控制這個生成器執行其代碼;生成器還有一個可以在其中使用的新關鍵字,用來標示暫停點:yield;一個永不結束的循環就意味着一個永不結束的生成器,這是完全有效的,有時候完全就是你所需要的;yield嚴格上說不是一個運算符,儘管像yield 1這樣使用它的時候確實看起來像是運算符;yield..表達式和賦值表達式行爲上的類似性有一定概念上的合理性;因爲yield關鍵字的優先級很低,幾乎yield..之後的任何表達式都會首先計算,然後再通過yield發送;和=賦值一樣,yield也是“右結合”的,也就是說多個yield表達式連續出現等價於用(..)從右向左分組;yield * 可以調用另外一個生成器(通過委託到其迭代器),所以它也是可以通過調用自身執行某種生成器遞歸;
  • 迭代器控制:ES6之後 的一個早期提案會通過生成器內部一個獨立的元屬性,支持訪問傳入最初next(..)調用的值;可以把生成器看作是值的產生器,其中每次迭代就是產生一個值來消費;從更通用的意義上來說,可能更合理的角度是把生成器看作一個受控的、可傳遞的代碼執行;
  • 提前完成:生成器上附着的迭代器支持可選的return(..)和throw(..)方法。這兩種方法都有立即終止一個暫停的生成器的效果;return(..)除了可以手動調用,還可以在每次迭代的末尾被任何消耗迭代器的ES6構件自動調用,比如for...of循環和spread運算符;這個功能的目的是通知生成器如果控制代碼不再在它上面迭代,那麼它可能就會執行清理任務(釋放資源,重置狀態等);不要把yield語句放在finally子句內部,雖然這是有效且合法的,但確實是一個可怕的思路。它會延後你的return(..)調用的完成;和return(..)不同,迭代器的throw(..)方法從來不會被自動調用;
  • 錯誤處理:生成器的錯誤處理可以表達爲try...catch,它可以在由內向外和由外向內兩個方向工作
  • Transpile生成器:一個練習鞏固了生成器實際上就是狀態機邏輯的簡化語法這個概念
  • 生成器使用:兩個主要模式(產生一系列值;順序執行的任務隊列;)

3.3.模塊

  • 在所有JavaScript代碼中,唯一最重要的代碼組織模式是模塊
  • 舊方法:傳統的模塊模式基於一個帶有內部變量和函數的外層函數,以及一個被返回的“public API”;其中常見的是異步模塊定義(AMD),還有一種是通用模塊定義(UMD);
  • 前進:對於ES6來說,我們不再需要依賴於封裝函數和閉包提供模塊支持;ES6模塊和過去我們處理模塊的方式之間的顯著概念區別;ES6模塊將會爲代碼組織提供完整支持,包括封裝、控制公開API以及引用依賴導入;長遠來看,ES6模塊從本質上說必然會取代之前所有的模塊格式和標準,即使是CommonJS,因爲ES6模塊是建立在語言的語法支持基礎上的;模塊transpiler/轉換工具是必不可少的。不管你是在編寫普通模塊、AMD、UMD、CommonJS還是ES6,這些工具都不得不解析轉化爲對代碼運行的所有環境都適用的形式;
  • 新方法:支撐ES6模塊的兩個主要新關鍵字是import和export;import和export都必須出現在使用它們的最頂層作用域。它們必須出現在所有代碼塊和函數的外面;沒有用export標示的一切都在模塊作用域內部保持私有。在模塊內沒有全局作用域;模塊還能訪問window和所有的“全局”變量,只是不作爲詞法上的頂層作用域;模塊導出不是像你熟悉的賦值運算符=那樣只是值或者引用的普通賦值。實際上,導出的是對這些東西(變量等)的綁定(類似於指針);默認導出把一個特定導出綁定設置爲導入模塊時的默認導出。綁定的名稱就是default;每個模塊只能有一個default;JavaScript引擎無法靜態分析平凡對象的內容,這意味着它無法對靜態import進行性能優化。讓每個成員獨立且顯式地導出的優點是引擎可以對其進行靜態分析和優化;
  • 模塊依賴環:首先必須聲明,儘量避免故意設計帶有環形依賴的系統;本質上說,相互導入,加上檢驗兩個import語句的有效性的靜態驗證,虛擬組合了兩個獨立的模塊空間(通過綁定),這樣foo(..)可以調用bar(..),反過來也是一樣;import語句的靜態加載語義意味着可以確保通過import相互依賴的“foo”和“bar”在其中任何一個運行之前,二者都會被加載、解析和編譯;
  • 模塊加載:import語句使用外部環境(瀏覽器、Node.js等)提供的獨立機制,來實際把模塊標識符字符串解析成可用的指令,用於尋找和加載所需的模塊。這個機制就是系統模塊加載器;如果在瀏覽器中,環境提供的默認模塊加載器會把模塊標識符解析爲URL。如果在像Node.js這樣的服務器上就解析爲本地文件系統路徑;模塊加載器本身不是由ES6指定的。他是獨立的、平行的標準,目前由WHATWG瀏覽器標準工作組管理;

3.4.類

  • class:新的ES6類機制的核心是關鍵字class,表示一個塊,其內容定義了一個函數原型的成員;ES6 class 本身並不是一個真正的實體,而是一個包裹着其他像函數和屬性這樣的具體實體並把他們組合到一起的元概念;
  • extends和super:ES6還通過面向類的常用術語extends提供了一個語法糖,用來在兩個函數原型之間建立[[Prototype]]委託鏈接-通常被誤稱爲“繼承”或者令人迷惑的標識爲“原型繼承”;在構造器中,super自動指向“父構造器”,在方法中,super會指向“父對象”,這樣就可以訪問其屬性/方法了;
  • new.target:新概念,稱爲元屬性;new target是一個新的在所有函數中都可用的“魔法”值,儘管在一般函數中它通常是undefined;在任何構造器中,new.target總是指向new實際上直接調用的構造器,即使構造器是在父類中且通過子類構造器用super(..)委託調用;除了訪問靜態/方法之外,類構造器中的new.target元屬性沒有什麼其他用處;
  • static:直接添加到這個類的函數對象上,而不是在這個函數對象的prototype對象上

3.5.小結

  • 迭代器提供了對數組或運算的順序訪問。可以通過像for..of...這些新語言特性來消耗迭代器
  • 生成器是支持本地暫停/恢復的函數。通過迭代器來控制。它們可以用於編程(也是交互地,通過yield/next消息傳遞)生成供迭代消耗的值
  • 模塊支持對實現細節的私有封裝,並提供公開導出API。模塊定義是基於文件的單例實例,在編譯時靜態決議
  • 類對基於原型的編碼提供了更清晰的語法。新增的super也解決了[[prototype]]鏈中相對引用的棘手問題

第4章 異步流控制

  • 管理異步的主要機制一直以來都是函數回調
  • ES6增加了一個新的特性來幫助解決只用回調實現異步的嚴重缺陷:Promise

4.1.Promise

  • 介紹:一些錯誤觀念:Promise不是對回調的替代。Promise在回調代碼和將要執行這個任務的異步代碼之間提供了一種可靠的中間機制來管理回調;可以把Promise鏈接到一起,這就把一系列異步完成的步驟串聯了起來;Promise鏈提供了一個近似的異步控制流;還有一種定義Promise的方式,就是把它看作一個未來值,對一個值得獨立於時間的封裝容器;換句話說,Promise可以被看作是同步函數返回值的異步版本;Promise的決議結果只有兩種可能:完成或拒絕,附帶一個可選的單個值;Promise只能被決議(完成或者拒絕)一次;
  • 構造和使用Promise:提供給構造器Promise(..)的兩個參數都是函數,一般稱爲resolve(..)和reject(..);Promise有一個then(..)方法,接受一個或兩個回調函數作爲參數;默認的成功回調把完成值傳出,默認的出錯回調會傳遞拒絕原因值;如果永遠不通過then(..)或catch(..)調用來觀察的話,它就會一直保持未處理狀態;
  • Thenable:Promise(..)構造器的真正實例是Promise。但還有一些類promise對象,稱爲thenable,一般來說,它們也可以用Promise機制解釋;任何提供了then(..)函數的對象(或函數)都被認爲是thenable;一般來說,如果從某個其他系統接收到一個自稱promise或者thenable的東西,不應該盲目信任它;避免把可能被誤認爲thenable的值直接用於Promise機制;
  • Promise API:Promise API還提供了一些靜態方法與Promise一起工作;Promise.resolve(..)創建了一個決議到傳入值的promise;resolve(..)和Promise.resolve(..)可以接受promise並接受它的狀態/決議,而reject(..)Promise.reject(..)並不區分接收的值是什麼;Promise.all([..])等待所有都完成(或者第一個拒絕),而Promise.race([..])等待第一個完成或者拒絕;Promise.all([])將會立即完成(沒有完成值),Promise.race([])將會永遠掛起。因此建議,永遠不要用空數組使用這些方法;

4.2.生成器+Promise

  • 這個重要的模式需要理解一下:生成器可以yield一個promise,然後這個promise可以被綁定,用其完成值來恢復這個生成器的運行
  • Promise是一種把普通回調或者chunk控制反轉反轉回來的可靠系統
  • 把Promise的可信任性與生成器的同步代碼組合在一起有效解決了回調所有的重要缺陷
  • 本質上說,只要代碼中出現超過兩個異步步驟的流控制邏輯,都可以也應該使用由run工具驅動的promise-yield生成器以異步風格表達控制流

4.3.小結

  • ES6新增了Promise來彌補回調的主要缺陷之一:缺少對可預測行爲方式的保證。Promise代表了來自可能異步的任務的未來完成值,跨越同步和異步邊界對行爲進行規範化
  • Promise與生成器的結合完全實現了重新安排異步流控制代碼來消除醜陋的回調亂燉(或稱地獄)

第5章 集合

  • Map就像是一個對象(鍵/值對),但是鍵值並非只能爲字符串,而是可以使用任何值-甚至是另一個對象或map
  • Set與數組(值的序列)類似,但是其中的值是唯一的;如果新增的值是重複的,就會被忽略。
  • 還有相應的弱(與內存/垃圾回收相關)版本:WeakMap和WeakSet

5.1.TypedArray

  • 實際上帶類型的數組更多是爲了使用類數組語義(索引訪問等)結構化訪問二進制數據
  • 大小端:理解下面這點很重要:arr的映射是按照運行JavaScript的平臺的大小端設置(大端或小端)進行的;大小端的意思是多字節數字(比如前面代碼片段中創建的16位無符號整型)中低字節(8位)位於這個數字字節表示中德右側還是左側;目前Web上最常用的是小端表示方式,但是肯定存在不採用這種方式的瀏覽器;
  • 多視圖:單個buffer可以關聯多個視圖
  • 帶類數組構造器:帶類型數組構造器的實例幾乎和普通原生數組完全一樣。一些區別包括具有固定的長度以及值都屬於某種“類型”;不能對TypedArray使用不合理的Array.prototype方法,比如修改器(splice(..)push(..)等)和concat(..);要清楚TypedArray中德元素是限制在聲明的位數大小中的;要解決平方值溢出的侷限,可以使用TypedArray.from(..)函數;

5.2.Map

  • 瞭解對象是創建無序鍵/值對數據結構,也稱爲映射(map)的主要機制
  • 對象作爲映射的主要缺點是不能使用非字符串值作爲鍵
  • 唯一的缺點就是不能使用方括號[ ]語法設置和獲取值,但完全可以使用get(..)set(..)方法完美代替
  • Map值
  • Map鍵:map的本質是允許你把某些額外的信息(值)關聯到一個對象(鍵)上,而無需把這個信息放入對象本身;對於map來說,儘管可以使用任意類型的值作爲鍵,但通常我們會使用對象,因爲字符串或者其他基本類型已經可以作爲普通對象的鍵使用;

5.3.WeakMap

  • WeakMap是map的變體,二者的多數外部行爲都是一樣的,區別在於內部內存分配(特別是其GC)的工作方式
  • WeakMap(只)接受對象作爲鍵。這些對象是被弱持有的,也就是說如果對象本身被垃圾回收的話,在WeakMap中的這個項目也會被移除
  • WeakMap沒有size屬性或clear()方法,也不會暴露任何鍵、值或項目上的迭代器
  • 需要注意的是,WeakMap只是弱持有它的鍵,而不是值

5.4.Set

  • set是一個值的集合,其中的值唯一(重複會被忽略)
  • 除了會把-0和0當作是一樣的而不加區別之外,has(..)中的比較算法和Object.is(..)幾乎一樣
  • Set迭代器:set固有的唯一性是它最有用的特性;set的唯一性不允許強制轉換,所以1和“1”被認爲是不同的值;

5.5.WeakSet

  • 就像WeakMap弱持有它的鍵(對其值是強持有的)一樣,WeakSet對其值也是弱持有的(這裏並沒有鍵)
  • WeakSet的值必須是對象,而並不像set一樣可以是原生類型值

5.6.小結

  • TypedArray提供了對二進制數據buffer的各種整型類型“視圖”,比如8位無符號整型和32位浮點型。對二進制數據的數組訪問使得運算更容易表達和維護,從而可以更容易操縱視頻、音頻、canvas數據等這樣的複雜數據
  • Map是鍵/值對,其中的鍵不只是字符串/原生類型,也可以是對象。Set是成員值(任意類型)唯一的列表
  • WeakMap也是map,其中的鍵(對象)是弱持有的,因此當它是對這個對象的最後一個引用的時候,GC(垃圾回收)可以回收這個項目。WeakSet也是set,其中的值是弱持有的,也就是如果其中的項目是對這個對象最後一個引用的時候,GC可以移除它

第6章 新增API

6.1.Array

  • 各種JavaScript用戶庫擴展最多的特性之一就是數組(Array)類型
  • 1.靜態函數Array.of(..):Array(..)構造器有一個衆所周知的陷阱,就是如果只傳入一個參數,並且這個參數是數字的話,那麼不會構造一個值爲這個數字的單個元素的數組,而是構造一個空數組,其length屬性爲這個數字
  • 2.靜態函數Array.from(..):JavaScript中的“類(似)數組對象”是指一個有length屬性,具體說是大於等於0的整數值的對象;普遍的需求就是把他們轉爲真正的數組,Array.prototype.slice.call(arrLike);新的ES6 Array.from(..)方法都是更好理解、更優雅、更簡潔的替代方法;
  • 3.創建數組和子類型:of(..)和from(..)都使用訪問它們的構造器來構造數組
  • 4.原型方法copyWidthin(..):Array.copyWithin(..)是一個新的修改器方法,所有數組都支持;它從一個數組中複製一部分到同一個數組的另一個位置,覆蓋這個位置所有原來的值;copyWithin(..)方法不會增加數組的長度。到達數組結尾複製就會停止;
  • 5.原型方法fill(..):用指定值完全(或部分)填充已存在的數組;可選接收參數start和end,它們指定了數組要填充的子集位置;
  • 6.原型方法find(..):在數組中搜索一個值得最常用方法一直是indexOf(..)方法,這個方法返回找到值的索引,如果沒有找到就返回-1;indexOf(..)需要嚴格匹配===;ES6的find(..)一旦回到返回true/真值,會返回實際的數組值;
  • 7.原型方法findIndex(..):如果需要嚴格匹配的索引值,那麼使用indexOf(..),如果需要自定義匹配的索引值,那麼使用findIndex(..)
  • 8.原型方法entries()、values()、keys():它提供了同樣的迭代器方法entries()、values()和keys(),從這個意義上說,他是一個集合

6.2.Object

  • 1.靜態函數Object.is(..):靜態函數Object.is(..)執行比===比較更嚴格的值比較;Object.is(..)調用底層的SameValue算法;如果需要嚴格識別NaN或者-0,那麼應該選擇Object.is(..);
  • 2.靜態函數Object.getOwnPropertySymbols(..):它直接從對象上取得所有的符號屬性
  • 3.靜態函數Object.setPrototypeOf(..):設置對象的[[Prototype]]用於行爲委託
  • 4.靜態函數Object.assign(..):很多JavaScript庫/框架提供了用於把一個對象的屬性複製/混合到另一個對象中德工具;ES6新增了Object.assign(..),對於每個源來說,它的可枚舉和自己擁有的(也就是不是“繼承來的”)鍵值,包括符號都會通過簡單=賦值被複制;不可枚舉的屬性和非自有的屬性都被排除在賦值過程之外;Object.create(..)是ES5工具,創建一個[[Prototype]]鏈接的空對象;

6.3.Math

  • 更優化地執行這些計算,或者執行比手動版本精度更高的計算

6.4.Number

  • 兩個新增內容就是指向已有的全局函數的引用:Number.parseInt(..)和Number.parseFloat(..)
  • 1.靜態屬性:新增了一些輔助數字常量:Number.EPSOLON、Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER
  • 2.靜態屬性Number.isNaN(..):標準全局工具isNaN(..)自出現以來就是有缺陷的,它對非數字的東西都返回true,而不是隻對真實的NaN值返回true,因爲它把參數強制轉換爲數字類型(可能會錯誤地導致NaN)。ES6增加了一個修正工具Number.isNaN,可以按照期望工作
  • 3.靜態屬性Number.isFinite(..):標準的全局isFinite(..)會對參數進行強制類型轉換,但是Number.isFinite(..)會略去這種強制行爲
  • 4.整型相關靜態函數:JavaScript的數字值永遠都是浮點數(IEE-754);檢查一個值得小數部分是否非0,x===Math.floor(x),ES6新增了Number.isInteger(..);JavaScript代碼本身不會因爲只使用整數而運行得更快,但是,只有在使用整數時,引擎纔可以採用優化技術(比如asm.js);

6.5.字符串

  • 1.Unicode函數:String.fromCodePoint(..)、String.codePointAt(..)和String.normalize(..)新增這些函數是爲了提高JavaScript字符串值uiUnicode的支持;字符串原型方法normalize(..)用於執行Unicode規範化,或者把字符用“合併符”連接起來或者把合併的字符解開;
  • 2.靜態函數String.raw(..):String.raw(..)工具作爲內置標籤函數提供,與模板字符串字面值一起使用,用於獲得不應用任何轉義序列的原始字符串
  • 3.原型函數repeat(..):重複字符串
  • 4.字符串檢查函數:又新增了3個用於搜索/檢查的新方法:startsWidth、endsWidth和includes

第7章 元編程

寫在前面

  • 元編程是指操作目標是程序本身的行爲特性的編程。換句話說,它是程序的編程的編程
  • 元編程關注以下一點或幾點:代碼查看自身,代碼修改自身,代碼修改默認語言特性,以便影響其他代碼
  • 元編程的目標是利用語言自身的內省能力是代碼的其餘部分更具描述性、表達性和靈活性

7.1.函數名稱

  • 默認情況下,name屬性不可寫,但可配置,也就是說如果需要的話,可使用Object.defineProperty(..)來手動修改

7.2.元屬性

  • 元屬性以屬性訪問的形式提供特殊的其他方法無法獲取的元信息
  • 對於所有的元編程技術都要小心,不要編寫過於機靈的代碼,讓未來的你或者其他代碼維護者難以理解

7.3.公開符號

  • 除了在自己的程序中定義符號之外,JavaScript預先定義了一些內置符號,稱爲公開符號
  • Symbol.iterator表示任意對象上的一個專門位置(屬性),語言機制自動在這個位置上尋找一個方法,這個方法構造一個迭代器來消耗這個對象的值
  • 最常見的一個元編程任務,就是在一個值上進行內省來找出它是什麼種類,這通常是爲了確定其上適合執行何種運算。最常用的內省技術是toString()和instanceof
  • 符號@@species,這個符號控制要生成新實例時,類的內置方法使用哪一個構造器
  • 抽象類型轉換運算ToPrimitive,它用在對象爲了某個操作必須被強制轉換爲一個原生類型值得時候。ES6之前,沒有辦法控制這個行爲
  • ES6中,在任意對象值上作爲屬性的符號@@toPrimitivesymbol都可以通過指定一個方法來定製這個ToPrimitive強制轉換
  • 對於正則表達式對象,有4個公開符號可以被覆蓋:@@match、@@search、@@split、@@replace
  • 符號@@isConcatSpreadable可以被定義爲任意對象的布爾型屬性(Symbol.isConcattSpreadable),用來指示如果把它傳給一個數組的concat(..)是否應該將其展開
  • 符號@@unscopables可以被定義爲任意對象的對象屬性(Symbol.unscopables),用來指示使用with語句時哪些屬性可以或部可以暴露爲詞法變量
  • strict模式下不允許with語句,因此應當被認爲是語言的過時特性。不要使用它

7.4.代理

  • ES6種新增的最明顯的元編程特性之一是Proxy(代理)特性
  • 代理是一種由你創建的特殊的對象,它“封裝”另一個普通對象--或者說擋在這個普通對象的前面
  • 代理侷限性:可以在對象上執行的很廣泛的一組基本操作都可以通過這些元編程處理函數trap;代理處理函數總會有一些不變形(invariant),亦即不能被覆蓋的行爲;這些不變性限制了自定義代理行爲的能力,但它們的目的只是爲了防止你創建詭異或罕見(或者不一致)的行爲;
  • 可取消代理:想要創建一個在你想要停止它作爲代理時便可以被停用的代理。解決方案是創建可取消代理;可取消代理用Proxy.revocable(..)創建,這是一個普通函數,而不像Proxy(..)一樣是構造器;一旦可取消代理被取消,任何對它的訪問(觸發它的任意trap)都會拋出TypeError;可取消代理的一個可能應用場景是,在你的應用中把代理分發到第三方,其中管理你的模型數據,而不是給出真實模型本身的引用;
  • 使用代理:代理成爲了代碼交互的主要對象,而實際目標對象保持隱藏/被保護的狀態

7.5.Reflect API

  • Reflect對象是一個平凡對象(就像Math),不像其他內置原生值一樣是函數/構造器
  • 它持有對應於各種可控的元編程任務的靜態函數
  • 有一個區別是如果第一個參數(目標對象)不是對象的話,Object.*相應工具會試圖把它類型轉換爲一個對象
  • Reflect的元編程能力提供了模擬各種語法特性的編程等價物,把之前隱藏的抽象操作暴露出來。可以利用這些能力擴展功能和API,以實現領域特定語言(DSL)
  • 屬性排序:在ES6之前,一個對象鍵/屬性的列出順序是依賴於具體實現,並未在規範中定義;對於ES6來說,擁有屬性的列出順序是是由[[OwnPropertyKeys]]算法定義的;其順序爲(首先,按照數字上升排序,枚舉所有整數索引擁有的屬性;然後,按照創建順序枚舉其餘的擁有的字符串屬性名;最後,按照創建順序枚舉擁有的符號屬性;)

7.6.特性測試

  • 特性測試,就是一種由你運行的用來判斷一個特性是否可用的測試
  • JavaScript中最常用的特性測試是檢查一個API是否存在,如果不存在的話,定義一個polyfill

7.7.尾遞歸調用

  • 通常,在一個函數內部調用另一個函數的時候,會分配第二個棧幀來獨立管理第二個函數調用的變量/函數
  • 當考慮遞歸編程的時候(一個函數重複調用自身)-或者兩個或多個函數彼此調用形成遞歸-調用棧的深度很容易達到成百上千,甚至更多
  • JavaScript引擎不得不設置一個武斷的限制來防止這種編程技術引起瀏覽器和設備內存消耗盡而崩潰。“RangeError:Maximum call stack size exceeded”
  • 調用棧深度限制不由規範控制。它是依賴於具體實現的,並且根據瀏覽器和設備不同而有所不同
  • 尾調用優化(Tail Call Optimization, TCO) - 於是額外的棧幀分配是不需要的。引擎不需要對下一個函數調用創建一個新的棧幀,只需複用已有的棧幀

第8章 ES6之後

  • 在新特性還沒有被需要支持的所有瀏覽器都實現的情況下,transpiler和polyfill是我們遷移到新特性的橋樑

8.1.異步函數

  • async function 本質上就是生成器+Promise+run(..)模式的語法糖;它們底層的運作方式是一樣的
  • 警告:async function有一個沒有解決的問題,因爲它只返回一個promise,所以沒有辦法從外部取消一個正在運行的async function實例;Promise是不可取消的(至少在編寫本部分的時候是如此);

8.2.Object.observe

  • 可能在後ES6,我們將會看到通過工具Object.observe(..)直接添加到語言中的支持。
  • 本質上說,這個思路就是你可以建立一個偵聽者(listener)來觀察對象的改變,然後在每次變化發生時調用一個回調
  • 可以觀察的改變有6種類型:add、update、delete、reconfigure、setPrototype、preventExtensions
  • 1.自定義改變事件:除了前面6類內置改變事件,也可以偵聽和發出自定義改變事件
  • 2.結束觀測

8.3.冪運算符

  • 有提案提出爲JavaScript新增一個運算符用於執行冪運算,就像Math.pow(..)一樣

8.4.對象屬性與...

  • ...運算符展開和收集數組的用法很直觀,那麼對於對象呢

8.5.Array#includes

  • 出現了一個獲得了大量支持的提案,提出增加一個真正返回布爾值的數組搜索方法includes(..)
  • Array.includes(..)使用的匹配邏輯能夠找到Nan值,但是無法區分-0和0

8.6.SIMD

  • SIMD API暴露了可以同時對多個數字值運算的各種底層(CPU)指令

8.7.WebAssembly(WASM)

  • 最近(以及不久的將來)JavaScript語言設計修改上的最大壓力之一就是需要成爲更適合從其他語言(比如C/C++、ClojureScript)變換/交叉編譯的目標語言
  • ASM.js是合法JavaScript的一個子集,這個子集最嚴格地限制了那些使得JavaScript引擎難以優化的行爲。結果就是兼容ASM.js的代碼運行在支持ASM的引擎上時效率有巨大的提升,幾乎與原生優化的等價C程序相當
  • WASM提出了一種代碼的高度壓縮AST(語法樹)二進制表示格式,然後可以直接向JavaScript引擎發出指令,而它的基礎結構,不需要通過JavaScript解析,甚至不需要符合JavaScript的規則

寫在後面

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章