-- 會員 / 註冊 --  
 帳號:
 密碼:
  | 註冊 | 忘記密碼
3/26 新書到! 3/19 新書到! 3/14 新書到! 12/12 新書到!
購書流程Q & A站務留言版客服信箱
3ds MaxMayaRhinoAfter EffectsSketchUpZBrushPainterUnity
PhotoShopAutoCadMasterCamSolidWorksCreoUGRevitNuke
C#CC++Java遊戲程式Linux嵌入式PLCFPGAMatlab
駭客資料庫搜索引擎影像處理FluentVR+ARANSYS深度學習
單晶片AVROpenGLArduinoRaspberry Pi電路設計CadenceProtel
HadoopPythonStm32CortexLabview手機程式AndroidiPhone
可查書名,作者,ISBN,3dwoo書號
詳細書籍分類

JavaScript語言精髓與編程實踐(第3版)

( 簡體 字)
作者:周愛民類別:1. -> 程式設計 -> 網路編程 -> Javascript
譯者:
出版社:電子工業出版社JavaScript語言精髓與編程實踐(第3版) 3dWoo書號: 52869
詢問書籍請說出此書號!

缺書
NT售價: 720

出版日:6/1/2020
頁數:776
光碟數:0
站長推薦:
印刷:黑白印刷語系: ( 簡體 版 )
加入購物車 加到我的最愛
(請先登入會員)
ISBN:9787121386695
作者序 | 譯者序 | 前言 | 內容簡介 | 目錄 | 
(簡體書上所述之下載連結耗時費功, 恕不適用在台灣, 若讀者需要請自行嘗試, 恕不保證)
作者序:

譯者序:

前言:

推薦序 1
一本不是所有人都需要的好書
這個有點繞口的標題,是從豆瓣上本書第 1 版的一個書評標題照錄而來的。豆瓣上排名前列的評論還有“這是一本硬書”“國內技術原創書中稀有的‘異數’”等。實際上,我覺得不僅是國內,算上在市面上能看到的所有 JavaScript 相關的書,本書都絕對堪稱“硬書”“異數”。
傳統上,許多大部頭的 JavaScript 相關的圖書,會有大量篇幅介紹 DOM 相關的 API 和如何結合語言與平臺 API 進行 Web 前端編程,這些年也可能換成是 Node.js 的 API 和服務器端編程。從入門或進階來說,這樣的編排都是合適的,因為結合特定平臺和領域的具體編程實踐可以更快速地建立學習的正向反饋。專注 JavaScript 語言本身的書也不是沒有,ES6 時代到來之后,頗有幾本書全面細致地介紹了 JavaScript 語言的新特性。甚至有很有名的書,會一直講到不為多數人所知的語言細節,受到中高級開發者的追捧。不過這些書還都是用來“學習”語言的書。
愛民的這本書,卻不是一本“學習”用的書,而是一本“闡釋”用的書。不要說 JavaScript 初學者,就算你有三五年甚至十年的 JavaScript 開發經驗,讀起這本書可能也不易。因為絕大部分開發者不習慣這樣思考問題。比方說,這本書大的章節是按照結構化、面向對象、函數式、動態化等編程范式來展開討論的,最新版中還加入了“并行計算”。
有些讀者或許也看過一些談編程范式的書,甚至專門談在 JavaScript 語言中使用某一種編程范式的書(比如近年來隨著某框架而在 JavaScript 圈逐漸火起來的函數式編程),但這些書還都是引領你“學習”一個范式,教你“應用”一個范式的書。愛民這本書的出發點與其他書不同,并不是為了學習、應用“范式”,而是為了分析“編程語言”,取之為線索。為此,需要系統性地逐一論述多種主要范式,然后將 JavaScript 語言的要素分解并歸納入不同范式下進行討論。需要注意的是,JavaScript 語言與每種范式代表性的經典編程語言都有很大的不同。所以在這個過程中,讀者也可以注意體悟多種范式是以怎樣一種方式不完美卻可用地并存于 JavaScript 這門語言之中的。
在每章的開始,先有十數頁的概述來論述范式和其背后的思想源流,故這一部分幾乎總是要以跳出 JavaScript 這單一語言的視角來論述的。這些概述也絕不是簡單地從其他書或資料中拿一些內容拼湊而成的,而是愛民以自己數十年編程和架構的心得理解精煉而成的。光這些概述,在本書第 1 版出版時的技術圖書市場上前所未見,到今日 JavaScript 的相關圖書汗牛充棟,恐怕也仍然獨此一家。
不過,這也帶來一個問題,就是對于絕大多數讀者來說,概述可能反而比后續章節更難讀,初讀時一知半解。
這次愛民要出第 3 版,寄贈我一些樣稿,我讀到第 4 章概述中論及“結構化的疑難”是“抽象層次過低”,而“面向對象”范式正是對此的應答時,頗有茅塞頓開之感。但后來重新翻閱 12 年前愛民贈我的本書第 1 版,才發現已包含了這段論述。可見當年我恐怕也是囫圇吞棗,雖讀之也并不能領會消化。
然而即使我現在提到了這個段落,讀者可能特意去認真閱讀該段落,記住了、理解了,也不見得能產生直接的“用處”。打個不一定恰當的比喻,金庸的《射雕英雄傳》中周伯通講《九陰真經》:“這上卷經文中所載,都是道家修煉內功的大道,以及拳經劍理,并非克敵制勝的真實功夫,若未學到下卷中的實用法門,徒知訣竅要旨,卻是一無用處。”
市面上大部分技術圖書,都是講“實用法門”的,偶爾講一點“拳經劍理”。愛民寫這本書的終極目標其實是傳授“內功大道”,為此拿了一門最流行的武功(語言)來拆解剖析,總結出其獨特的“拳經劍理”,以印證“大道”。在這個闡釋的過程中,“實用法門”講的不多,即使講了一些,也意不在此。事實上,很多人只是想要“實用法門”的書,最好還是速成的。那就最好不要選本書了。這種需求也不好說錯。或許先講“實用法門”,再講“拳經劍理”乃至“大道”,才是符合普通人的認知規律的。
另一方面,即使一個人也有意于“拳經劍理”乃至“大道”,如果市面上全是講“實用法門”的書,他一直以來熟悉的只有這個套路,就會對其他模式不太適應。比如說,對一個語言特性的解說和評論,絕大部分圖書的講法主要基于“實用”,也就是,有什么用,怎么用,用起來順手不順手。但愛民這本書的視角就很不一樣,主要是基于“大道”和“拳經劍理”的內在邏輯進行推演。
需要理解的是,這兩個方向可能互相印證,也可能產生矛盾。編程語言和一切復雜的人造事物一樣,是不完美的。
這也會延伸到語言設計上。作為程序員,雖然看到新語言特性的介紹通常還都是從“實用”角度講解(宣傳)的,但在設計階段,其實要接受各個維度、不同層面的需求和約束。語言特性要平衡多種不同因素,平衡不了就要做取舍。但這個取舍到底是不是合適,就見仁見智了。
愛民在這次新版的第 4 章中花了不少篇幅討論目前 stage 3 的類字段(class fields)提案和他設計的替代性方案。這個提案比表面上看起來要復雜得多,無論是在委員會還是在社區里,不同的人的看法會非常不同,而且這種分歧貫穿了“大道”“拳經劍理”“實用法門”各個層面。需要注意,即使持同樣立場的人,比方說同樣反對現有提案,其背后的原因也可能截然不同,對解決路徑的判斷也會截然不同。TC39 是基于一致同意的方式推進工作的。對于接受現有提案的人來說,即使其認知不同,但至少表面上是達成一致的。而對不同意現有提案的人,各有各的不同意,因而也無法達成一致。表現出來的結果,就是愛民在書中所說:“類字段提案提供了一個極具爭議的私有字段訪問語法,并成功地做對了唯一一件事情,讓社區把全部的爭議焦點放在了這個語法上”。這也是類字段提案的悲劇性之所在。
我認為,討論這個話題的價值,不在于給出一個答案(畢竟 TC39 都給不出令人滿意的答案),而是這個思考過程。在這個過程中,我們需要同時考慮“大道”(面向對象范式)、“拳經劍理”(JavaScript 現有的語法和語義約定和約束,與后續提案的關系和協調性等)、“實用法門”(使用方式、如何滿足各種需求、代碼風格、性能……)等不同的層面。這是一個極好的思維訓練,在這個過程中,無論你得到怎樣的結論,都會對 JavaScript 語言有更深
層次的認知和把握。而這樣的內容,也只能存在于“闡釋”之書中。
然后說說對“闡釋”可能存在的疑問。那就是多種不同的甚至矛盾的“闡釋”是否可以共存,有沒有一種解釋是最正確的,或者權威的。
舉一個小例子, typeof null 為什么返回 "object" ?從歷史事實來說,根據Brendan Eich 自己的說法,這是無心之失。但愛民的意見,這也可以理解為 null 實為對象類型的特殊值。6年前我在知乎上對這種“闡釋”做了較為詳細的解說。按照一般認知,Brendan Eich 自己的說法當然是最正確和權威的。然而有意思的是,前不久,在 Allen Wirfs-Brock 和 Brendan Eich 合作撰寫并提交給 HOPL 會議的論文 JavaScript : The First 20 Years 中寫道:
……令人困惑的是,typeof null 會返回字符串值"object"而不是"null"。其實也可以說這與Java 保持了一致,因為 Java 中的所有值都是對象,而 null 本質上是表達“沒有對象”的對象……根據 Brendan Eich 的回憶, typeof null 的值是原始 Mocha 實現中“抽象泄露”的結果。 null 的運行時值使用了與對象值相同的內部標記值進行編碼,因此 typeof 運算符的實現就直接返回了"object"。
——引自 doodlewind 的中文譯本,原文在預印本第 12 頁
這篇權威論文同時列出了這兩種解釋。所以愛民很多年前的闡釋也算被“官宣”了。有人可能要打破砂鍋問到底,到底哪一種才是“正確”的呢?其實我認為都是正確的。
Brendan Eich 的回憶可能是歷史真相,但當事人的回憶不一定是真相的全部。我們可以追問,為什么當初在實現的時候,對象和 null 共享了相同的標記值呢?一種解釋是,可能是當年有意識“根據 Java 的 null 值表示‘沒有對象’,來對 JavaScript 中的 null 值進行建模”的副產品,另一種解釋是編程中無意產生的結果。即使是后一種,如果考慮引擎是如何實現的,就會發現對象引用的內部表達肯定是一個內存地址,那么很自然就會以全0的地址代表 null 。
那么可以說,導致這種“抽象泄露”本身的源頭是高層模型到具體實現的自然映射,偶然性中蘊含了必然性。另外,我們也可以追問,為什么當初標準化的時候,沒有對 typeof null 的結果提出異議呢?不太可能委員會中的所有成員都沒有發現,所以一個合理猜想是,發現這個問題的人也采用了類似愛民的闡釋對這個行為進行了“合理化”。
其實在日常生活中,有大量這種既是“機緣巧合”又“冥冥中自有定數”的事例,在技術領域其實也一樣。
這當然不是說,任意一種“闡釋”都是正確的,“闡釋”本身得自洽,然后有足夠的解釋效力,具有普適性,不會引發反例,引入一種“闡釋”的成本不應該大于收益,最后還要經得起“奧卡姆剃刀”原則的考驗。要做到這些是非常困難的,有時候是難以判斷的。包括本書對 JavaScript 語言的各種“闡釋”,肯定不是所有人都認同的,包括我自己,對其中某些部分也會有不同意見。但是程序員從“碼農”成長起來,可以進行更大范圍、更高層次的設
計,乃至以成為像愛民那樣的“架構師”為職業目標,這就需要提升對各種不同“闡釋”的理解判斷及融會貫通的能力,并逐步形成自己對技術進行“闡釋”的能力。從這點來說,這本“硬書”在那么多 JavaScript 書中是獨具價值的。
當然,這樣的“闡釋”之書,啃起來不容易。豆瓣上有一些吐槽,這些評論絕不是惡意的,實際上這些評論者總體上都是贊許本書的。我自己當年讀本書第 1 版時也有同感。今天我讀樣稿時感覺倒是好了不少,可能是愛民做了一些優化,但估計更多是隨著年歲漸長,我本身的技術水平提升了,對“闡釋”之書的閱讀能力也提升了。尤其這一年以來,親身參與在 TC39 之中,感受到對JavaScript 的“闡釋”即使在委員會里本身也是具有多重性和不確定性的,這產生了很多問題,但也是活力的一部分。所以對不同“闡釋”的包容和理解,乃是必需的。
不過即使考慮閱讀能力有所提升,本書的閱讀體驗和“流暢”“閱讀快感”也是不搭界的。這是讀者在讀本書前需要有的心理準備。
最后總結,“闡釋”之書定然“不是所有人都需要的”,但我個人希望這樣的書可以多來幾本。
——賀師俊 2020 年 4 月

推薦序 2
寫給優秀程序員的一本書
很興奮愛民兄的書又更新了,它專注于講解 JavaScript 的語言精髓與編程實踐。
JavaScript 是世界上最火的編程語言之一,可以用來寫淘寶網站,也可以用來寫支付寶小程序,基于 Electron 等技術還可以用來寫桌面端應用。“能用 JavaScript 實現的東西,遲早會用 JavaScript 實現”,這句“狂妄”的話,正在實現著。
看愛民兄的書,有一種很過癮的感覺。JavaScript 的每一個知識點,淺學會覺得很簡單,跟著書一步步深入思考,才發現自己的理解很粗淺。書中的每一個章節,都是抽絲剝繭般層層深入,一個點串起了一個面,能讓知識觸類旁通,非常透徹。
閱讀本書會讓我思考一個問題:什么樣的程序員是優秀的程序員?程序員的優秀各有各的不同,但優秀的程序員有一個共同的特點,那就是充滿好奇心。愛民兄的這本書,非常形象地闡述了什么是好奇心。對于一個看似簡單的知識點,沒有好奇心,就會停留在知識點的使用層面;有了好奇心,則會不斷去深挖知識點背后的歷史和成因。JavaScript 是一門混合范型語言,帶著好奇心去學習,能看到的遠遠不止一門語言,而是語言的世界,因為它有 Java、
C、LISP 等各種語言的身影。每種語言的優劣,是怎么被帶入 JavaScript 并成為優點或者成為“深坑”的,這種跨語言的對比探究,會讓我們對語言特性有更深的了解,甚至能重塑我們的技術價值觀,對什么是好的語言特性,什么是不好的語言特性,有更全面的科學判定與選擇。
優秀的程序員,還有一個共同的特點 — 體系化的思維能力。愛民兄的文字,像是編織美妙錦緞的針線,每一針每一線的背后,都是體系化的思維架構。JavaScript 語言是怎么構建起來的,在執行引擎層面是如何運行的,如何面向對象承載大型應用,這些循序漸進的精髓講解與編程實踐,可以讓我們對 JavaScript 的整個“大廈”有全局性理解。任何知識,只有經過體系化的理解與運用,才能真正內化為一種基礎能力。體系化的基礎能力,可以讓程序員
自由遨游在編程的浩瀚宇宙里。
好奇心與體系化思維能力,是優秀程序員的兩大法寶。如果遠方是蔚藍星空,好奇心能讓我們駛向一個個星球,體系化思維則能讓我們的宇宙飛船不斷升級換代。編程的世界很精彩,期待每一位同學的太空揚帆。
——阿里巴巴研究員&體驗科技踐行者 王保平,阿里花名玉伯

推薦序 3
一個程序員到底需要掌握編程語言的多少特性?
每個程序員每天都在跟編程語言打交道,但是很少有人愿意真正了解這些“老朋友”。
有一些編程語言的行為非常反直覺,比如 JavaScript 中著名的“0.1 + 0.2 ≠ 0.3”的現象。它看似語言的 bug,但實際上來自浮點數的精度問題。正因如此,任何計算機語言的浮點類型都有著相似的問題。
而有些語言特性其實根本就是設計失誤,尤其是對于一門只花了兩個星期來設計的語言來說。比如 JavaScript 中的雙等號,它“踐踏”著一切的直覺,如“==”的傳遞性,又如 if(x) 與 if(x == true) 的等價性。
所以我相信很少有程序員想要了解“雙等號兩邊數據類型不一樣的時候”的轉換規則到底是什么。還有一些語言特性很有必要但是非常罕見,比如在最新的 JavaScript 標準中加入了code point 系列 API,用來處理 Unicode 基本平面(BMP)之外的字符。盡管在我十年的職業生涯里,一次都沒有遇到過 BMP 之外的字符,然而這并不意味著它們不重要。
在你不知道的角落,一些你不知道的特性在被你用不知道的方式使用著。以前我在參與es-discuss 郵件組討論時,曾經非常冒昧地建議“函數調用與其后括號間不得加入換行規則”(即 no line terminator 規則)。但是很快收到回復,這樣做會破壞一些既有代碼,比如一些奇思妙想的框架生成 HTML的方式。這讓我意識到,語言設計需要考慮的情況是極端和復雜的,一些特性對某些人來說無關緊要,但對另一些人來說是生命線。
今天,很多語言特性決定了技術產品的走向,比如 Vue 3.0,它非常依賴 Proxy 特性。比起 getter 和 setter 實現的 Vue 2.0,它對模型的侵入性更低,邊緣 case 也更少。而從歷史來看,在過去的幾十年里,多數開發者只需要掌握他們關心的一部分特性就可以了,甚至可以躲在框架之上進行開發。
現在情況正在發生變化。在 TC39,一些將會陪伴你職業生涯數十年的新特性,正在被針鋒相對地討論,而支持或者反對它們的理由可能與你不太相關 — 你必須從整體上理解語言的設計,才有參與其中的可能。即使多數程序員不需要參與這樣的討論,如今語言的更新速度也對程序員的素質提出了更高的要求,從 ES5 到 ES2018,語言標準的表述篇幅增加到原來的三倍,這樣的信息增長速度堪稱恐怖。
所以,今天,編程語言學習的門檻客觀上提高了,唯有站在更高的視角,跳出語言本身的約束,才能更好地應對莫測的未來。
我們討論或者解釋 JavaScript 的問題時,大多會引用標準的描述,比如執行上下文、變量環境、詞法環境等,但是這些概念始終在變化。然而若我們以結構化程序設計的角度去看,不論是原有的函數級、全局作用域,還是新加入的模塊、let 級別的作用域,都沒有跳出一般的結構化的思路。所以說,底層不等于本質,我們從更通用的角度去看待 JavaScript,就會更容易理解它。尤其是,JavaScript 是追求實用性的語言,它并沒有試圖像 Haskell、Rust 那樣從語言角度做本質創新。
在我和我周圍,很多前端工程師認識周愛民老師都是從他在 2007 年左右發布的系列博文“JavaScript 面向對象編程”開始的。從那時候起,周老師對語言的研究就總是早于這些特性的大規模應用。作為架構師,周老師的思路總是能夠給我們前端帶來一些新的靈感。在這一次的改版中,周老師又為圖書加入了不少新內容(比如關于并行特性的部分),我很期待這些新內容在未來成為每個前端程序員的新武器。
——程劭非(winter)

第 3 版 代序
什么叫“會編程”
— 《程序原本》節選 —
程序設計語言 — 這種工具有什么性質呢?又或者,究竟是什么決定了一門語言稱為Java,而另一門叫作 C#呢?它們之間存有何種不同,又存有哪些淵源呢?
有趣的是,通過分析現有的種種程序設計語言,(正因為這些語言是我們人類自己創造的,所以)我們發現如同人類的自然語言一樣,程序設計語言也總是有著三種基本性質:語法、語義與語用。正是這三種性質,使得一種語言區別于其他語言,而又能從其他語言那里有所借鑒,以及實現彼此溝通。
那么我們先來談談什么是語法。語法,是指我們表達內容的形式。這一形式首先與不同的表達手段有關,例如同一個意思,口頭表達和書面表達是不同的。其次,即使表達手段相同,也會因為介質的材料性質存有差異而導致形式不同,例如鐘鼎文和白話文都用于書寫,但顯然鐘鼎文不能像白話文那樣冗長。類似地,在我們的程序設計語言中,早期的程序輸入就是電子開關的開合,因此代碼會是一些操作命令,而現在我們可以將之輸入為接近自然語言的程序文本;早期的運行環境要求代碼必須盡量精簡,而現在我們則考慮通過規整而冗長的代碼來降低工程整體的復雜性。
所以,語法是對語言的表達手段,以及對該表達手段的條件限制加以綜合考慮而設定的
一種形式化的規則。而所謂語義,則是指我們表達內容的邏輯含義。語義有以下基本性質:
? 一、必須是一個含義。
? 二、該含義必須能夠以某種基本的邏輯形式予以闡述。
語義還有一項非必需的性質,即:
? 三、上述邏輯所表達的含義可以為語言的接受者所知。
略為討論一下第二項性質。為何語義必須要能闡述為一種基本邏輯呢?因為語義定義為內容的含義,而這種含義可以由多種形式來表現,因此,如果它不能用一種基本邏輯來表達,也就沒有辦法在多種表現形式之間對它互做驗證。例如,不能用書寫的方式來確定口頭轉述的正確性,反之也不能通過口傳心授來傳播書本知識。自然語言中的這種性質(部分地)可以由基本邏輯的矛盾律來約束,即“一個概念不能既是該概念,而又非該概念”。
正是因為我們的文字記錄與對話交流等內容中存在著這樣的一些基本邏輯,所以它才可能科學、嚴謹及正確。
第三項性質對自然語言來說是非必需的 — 如果一個人自言自語,那么他的言語可能仍然是有語義的,只是這個語義不為他人所知。但這一點對于程序設計語言來說卻是必需的,因為我們設計這樣一門語言的目的,正是要讓我們所表達的含義為計算機所知。
正是這第三項性質,加入了對“接受者的理解能力”的限制。出于語義的前兩項基本性質,這種理解能力也必然由兩個方面構成,一是指含義,二是指邏輯。于是我們看到了我們曾經討論過的全部內容:計算系統的要素,包括數、數據和邏輯,以及在此基礎上進行正確計算的方法的抽象,即計算范式。只有通過這些組織起來的語義,才可能被(我們此前所述的)計算系統理解。
這些語義與其表現形式(即語法)是無關的,有其基本邏輯存在。我們所謂的“會編程”,正是指對這種語義的理解,而非對某種語法的熟悉。正因如此,我們才可以在Java上實現某個程序,又在C#上同樣實現它,在(使用這些語言的)類似的仿制過程中,不變的是語義。因此再進一步觀察,編程這樣的行為也就無非是將我們的意圖表達為計算系統的理解能力范圍內的語義罷了。歸納起來看,這種語義:由計算系統與程序員共同確知的數據與邏輯構成;且,最終可以由某種計算方法在指定計算系統上實施以得到計算結果。
這里的計算方法并不是指“算法”,而是指對某種計算實施過程的抽象,例如“命令式”和“函數式”這兩種計算范式。所以,會編程與掌握某種語言的語法形式是無關的。
編程實質上是一種在語義描述上的能力修養。具備這種能力之后,語法也就只存在一些規則、限制和對不同計算系統的理解能力上的差別了。所以,“計算機程序設計”這門功課應該教你編程,而不是教你使用一門具體的語言 — 我們現在大多把它當成語言課,實在是本末倒置了。
——周愛民 2012 年 6 月于《程序原本》

第 2 版 代序
要有光
— 《世界需要一種什么樣的語言》節選 —
什么才是決定語言的未來的思想呢?或者我們也可以換個角度來提出這個問題:世界需要一種什么樣的語言?
特性眾多、適應性強,就是將來語言的特點嗎?我們知道,現在的 C#與 Java 都在向這一目標前進。與特定的系統相關,就是語言的出路嗎?例如,曾經的 VC++,以及它面向不同平臺的版本。當然,在類似的領域中,還有 C,以及匯編……
這些列舉其實都是在特定環境下的特定語言,所不同的無非是環境的大小。這其實也是程序員的心病:我們到底選 Windows 平臺,還是 Java 平臺,或者 Linux 系統,再或者是……
我們總是在不同的廠商及其支持的平臺中選擇,而最終這種選擇又決定了我們所使用的語言。這與喜好無關,也與語言的好壞無關,不過是一種趨利的選擇罷了。所以也許你在使用著的只是一種“并不那么‘好’”,以及并不能令你那么開心的編程語言。你越發辛勤地工作,越發地為這些語言搖旗鼓噪,你也就離語言的真相越來越遠。
當然,這也不過是一種假設。但是,真相不都是從假設開始的嗎?
語言有些很純粹,有些則以混雜著稱。如果編程世界只有一種語言,無論它何等復雜,也必因毫無比較而顯得足夠純粹。所以只有在多種語言之間進行比較,才會有純粹或混雜這樣的效果:純粹與混雜總是以一種或多種分類法為背景來描述的。
因此我們了解這些類屬概念的標準、原則,也就回溯到了語言的本質:它是什么、怎么樣,以及如何工作。在這本書中,將這些分類回溯到三種極端的對立:命令式與說明式,動態與靜態,串行與并行。分離它們,并揭示將它們混沌一物的方法與過程,如歷經涅槃。在這一經歷中,這本書就是我的所得。
多年以來,我在我所看不見的黑暗與看得見的夢境中追尋著答案。這本書是我最終的結論,或結論面前的最后一層表象:我們需要從純化的語言中領悟到編程的本質,并以混雜的語言來創造我們的世界。我看到:局部的、純化的語言可能帶來獨特的性質,而從全局來看,世界是因為混雜而變得有聲有色的。如果上帝不說“要有光”,那么我們將不能了解世象之表;而世象有了表面,便有了混雜的色彩,我們便看不見光之外的一切事物。我們依賴于光
明,而事實是光明遮住了黑暗。
如同你現在正在使用的那一種、兩種或更多種語言,阻礙了你看到你的未來。
——周愛民 2009 年 1 月于本書精簡版序

第 1 版 代序
學兩種語言
— 《我的程序語言實踐》節選 —
《程序設計語言 — 實踐之路》一書對“語言”有一個分類法,將語言分類為“說明式”與“命令式”兩種。Delphi 及 C、C++、Java、C#等都被分在“命令式”語言范型的范疇,“函數式”語言則是“說明式”范型中的一種。如今我回顧自己對語言的學習,其實十年也就學會了兩種語言:一種是命令式的 Pascal/Delphi,另一種則是說明式的 JavaScript。當然,從語言的實現方式來看,一種是靜態的,一種是動態的。
這便是我程序員生涯的全部了。
我畢竟不是計算機科學的研究者,而只是其應用的實踐者,因此我從一開始就缺乏在“程序”的某些科學的或學術層面上的認識是很正常的。也許有些人一開始就認識到程序便是如此,或者一種語言就應當是這樣構成和實現的,那么他可能是從計算機科學走向應用的,故而比我了解得多一些。而我,大概在十年前學習編程及在后來很多年的實踐中,僅被要求“寫出代碼”而從未被要求了解“什么是語言”。所以我才會后知后覺,才會在很長的時間里迷
失于那些精細的、溝壑縱橫的語言表面而不自知。然而一如我現在所見到的,與我曾相同地行進于那些溝壑的朋友,仍然在持續地迷惑著、盲目著,全然無覺于溝壑之外的瑰麗與宏偉。
前些天我寫過一篇博客,是推薦那篇《十年學會編程》的。那篇文章道出了我在十年編程實踐之后,對程序語言的最深刻的感悟。我們學習語言其實不必貪多,深入一兩種就可以了。如果在一種類型的語言上翻來覆去,例如,不斷地學 C、Delphi、Java、C#……無非是求生存、討生活,或者用以裝點個人簡歷,于編程能力提高的作用是不大的。更多的人,因為面臨太多的語言選擇而淺嘗輒止,多年之后仍遠離程序根本,成為書寫代碼的機器,把書寫
代碼的行數、程序個數或編程年限作為簡歷中最顯要的成果。這在明眼人看來,不過是熟練的“砌磚工”而已。
我在《大道至簡》中說“如今我已經不再專注于語言”。其實在說完這句話之后,我就已經開始了對 JavaScript 的深入研究。在如此深入地研究一種語言,進而與另一種全然有別的語言進行比較之后,我對“算法+結構=程序”有了更深刻的理解與認識 — 盡管這句名言從來未因我的認識而變化過,從來未因說明與命令的編程方式而變化過,也從來未因動態與靜態的實現方法而變化過。
動靜之間,不變的是本質。我之所以寫這篇文章,并非想說明這種本質是什么抑或如何得到,只是期望讀者能在匆忙的行走中,時而停下腳步,遠遠地觀望一下目標罷了。
而我此刻,正在做一個駐足觀望的路人甲。
——周愛民 2007 年 11 月于個人博客

前言
語言
語言是一種交流工具,這約定了語言的“工具”本質,以及“交流”的功用。“工具”的選擇只在于“功用”是否能達到,而不在于工具是什么。
在數千年之前,遠古祭師手中的神杖就是他們與神交流的工具。祭師讓世人相信他們敬畏的是神,而世人只需要相信那柄神杖。于是,假如祭師不小心丟掉了神杖,就可以堂而皇之地再做一根。甚至,他們可以隨時將舊的換成更新的或更舊的神杖,只要他們宣稱這是一根更有利于通神的神杖。對此,世人往往做出迷惑的表情或者歡欣鼓舞的姿態。今天,這種表情或姿態一樣會出現在大多數程序員聽聞新計算機語言被創生的時刻。
神杖換了,祭師還是祭師,世人還是會把頭叩得山響。祭師掌握了與神交流的方法(如果真如同他們自己說的那樣),而世人只看見了神杖。
所以,泛義的工具是文明的基礎,而確指的工具卻是愚人的器物。
分類法
在我看來,在 JavaScript 的進化中存在著兩種與分類法相關的核心動力:一種促使我們不斷地創造并特化某種特性,另一種則不斷地混合種種特性。更進一步地說,前者在于產生新的分類法以試圖讓語言變得完美,后者則通過混淆不同的分類法,以期望通過突變而產生奇跡。
二者相同之處在于都需要更多的分類法。于是我們看到,在二十年之后,JavaScript 語言中有了動態的、靜態的、命令式的、說明式的、串行的、并行的等特性。然而拋開所有這些致力于“創生一種新語言”的魔法,到底有沒有讓我們在這浩如煙海的語言家族中,找到學習方法的魔法呢?
我的答案是:看清語言的本質,而不是試圖學會一門語言。當然,這看起來非常概念化。
甚至有人說我可能是從某本教材中抄來的,另外一些人會說我試圖在這本書里宣講類似我那本《大道至簡》里的老莊學說 。
其實我感覺很冤枉。我想表達的意思不過是:如果你想把一副牌理順,最好的法子,是回到它的分類法上,要么從A到K整理,要么按四個花色整理。 2 畢竟,兩種或更多種分類法作用于同一事物,只會使事物混淆而不是弄得更清楚。
這本書
時至今日,離這本書第 1 版的發布已經過去十多年的時間了。我承認在之前的版本中,許多內容是通過對引擎或解釋器源碼的分析,以及對一些語言特性的表現進行反推而得來的。
因此早期的一些內容并不能深刻、準確地反映 JavaScript 語言中的事實,又或者存在錯誤。更關鍵的地方在于,并沒有什么資料可以給出確定的事實或指出這些錯誤。如此,以至于在本書第 2 版發布時,我也只是匆匆地添加了一些有關 ECMAScript 5 (ECMAScript 也簡稱為 ES)規范中的內容,而未能對全書進行及時更新。
事實上我當時并沒有看到 ES5 的偉大之處。后來隨著 ECMAScript 新版本的推出,ES5 中所蘊含的力量漸漸釋放出來,隨著 ES6 一直演進到現在的 ES2019,我們見證了 JavaScript 最有活力、最精彩的時光。然而這本書早期版本中的內容也漸漸蒙塵,成為故紙堆中的不實傳言。于是,我終于下定決心,圍繞 ECMAScript 來重新解釋整個 JavaScript 核心,并在整本書的“多語言范型的整合”的大框架下重新開始寫它的第 3 版,即你現在讀到的這個版本。
這顯然意味著我在這一版中將更加尊重 ECMAScript 規范,并同時降低了對引擎差異性的關注。
另外,我對書中“編程實踐”的部分也進行了重新規劃,讓它們分散于每章的末尾,以便讀者可以有選擇地閱讀,以及有針對性地分析這些實踐案例。
然而無論如何,這本書都不是一本讓你“學會某種語言”的書,也不是一本讓初學者“學會編程”的書。閱讀本書,你至少應該有一點編程經驗,而且要擯棄某些偏見(例如,C 語言天下無敵或 JavaScript 是新手玩具等)。
最后,你還要有很多耐心與時間。
問題
1. 這本書可能會在不同章節反復講同一個問題,這是因為從不同的視角看相同的問題,其實結論并不相同(而 JavaScript 正好是一門多范型的語言)。
2. 這本書會有極少數內容是與 ECMAScript 或現實中的特定運行期環境不同的,如果存在這種情況,本書一定會詳述其緣由。
3. 這本書重在解釋原理,而不是實踐。
4. 這本書與許多同類書相比,有著非常多的廢話 — 尤其對于那些打算通過這本書學習如何使用 JavaScript 的讀者來說。但這本書的特點,或許就在于說了一門具體語言之外的許多廢話。
5. 從純粹討論“如何使用 JavaScript”的角度來講,本書在功能點上寫得有些碎片化,這會導致許多內容中出現指向其他章節的交叉引用。這是本書從多語言范型角度對知識結構進行重新規劃的結果。
為什么
無論是出于作者的身份,還是之于讀者的期望,我都希望你在閱讀本書的過程中能多問幾個“為什么”。有疑與設疑,是本書與他書在立足與行文上的根本不同。
例如,在 JavaScript 中,函數參數是允許重名的,但是一旦使用了默認參數,就不允許重名了。關于這個問題,在 ECMAScript 中是有講解的,它詳細地用算法約定了這個語言特性。并且如果你深究一下,還會發現函數參數的初始化可以分成兩類:綁定變量名和綁定初始器,一旦啟用后一種方式,那么函數參數就不能重名了。再進一步,你會發現這個“初始器”是通過一個類似“鍵值對”的表來保存參數名和初始化表達式的,因為鍵不能重復,所以參數名也就不能重復……
是的,我們確實可以通過精讀 ECMAScript 來知道上述特性,了解它的成因和原理。然而倘若我們多問一下“為什么它這么設計”,大多數人就答不出來了。
因為 ECMAScript 中根本沒有寫。ECMAScript 只說了“如何實現”,從來沒說“為什么這么設計”。這就好像有一本制造手冊放在你的手邊,即便你精讀每一章、研習每一節,其最終結果也只能做到“精確地制造”,而這個東西為什么造成這個樣子,又或者某一個參數設置為什么是 5.01 而不是 4.99,你永遠也不知道。
例如這個問題:為什么“允許重名”這個特性不見了?
歷史
? 2020.06《JavaScript 語言精髓與編程實踐》(第 3 版)(當前版本)本書第 3 版邀請了賀師俊(hax)、王保平(玉伯)和程劭非(winter)三位老師為本書寫推薦序,另外節選了《程序原本》一書的內容作為本書代序,并大幅更新了前言。全書內容重寫與重校,數易其稿,歷時三年終成。
本書新添加了第 4 章和第 7 章,分別討論靜態語言特性(主要是指結構化的方法與基本原理),以及并行語言特性。后者的部分內容涉及并發特性及其實現。在這一版中,將之前版本中關于“編程實踐”的大章刪除,分散在各章后分別討論實踐案例。
? 2012.03《JavaScript 語言精髓與編程實踐》(第 2 版)
本書第 2 版節選了《動態函數式語言精髓》一書的序作為代序。主要添加了 ES5 相關的內容,并將“編程實踐”中的內容從 Qomo 項目替換為 QoBean 項目。這是一個改動較少的修訂版本。
? 2009.03《動態函數式語言精髓》(電子書)這本電子書是作為本書第 1 版的精簡版由 InfoQ 發布的。與《主要程序設計語言范型綜論與概要》類似,這本電子書回歸了我原本著述本書的目的,希望能通過對JavaScript 的深入研究來切入對語言本質的討論。正是這些關于語言的探索,最終幫助我完成了《程序原本》一書(2012—2016 年)。
? 2008.10《主要程序設計語言范型綜論與概要》(電子書)這是一份對本書第 1 版的摘引,并作為一本獨立電子書發布。主要側重于語言范型的綜論,針對 JavaScript 的相關討論較少。
? 2008.03《JavaScript 語言精髓與編程實踐》第 1 版 本書正式發行。
慣例
1. 類名或構造器函數名以大寫字符開始,例如,MyClass。
2. 變量名或一般函數名以小寫字符開始,例如,myObject。
3. 關鍵字或引用代碼中的變量名使用等寬字體,例如, yield 。
4. 正文中的英文采用一般字體,例如,JavaScript。
5. 方法名或函數名在引用時會加括號,如 apply() ,如果不使用括號,則表明它們在上下文中應該被理解為屬性、構造器名或類名,例如, obj.constructor 。
6. 對象內部方法(通常在 ECMAScript 中規定)會以一對方括號標示,例如, [[call]] 。
7. 若非特殊說明,ES5/ES5.1 分別指 ECMA-262 edition 5/5.1,ES6/ES7/ES8 分別指ECMA-262 edition 2015/2016/2017。
8. 為了避免行文中不斷出現“從 ESx 版本開始,支持某某特性”的字樣,本書將基于ES2019 講述,但在第 3 章與第 6 章的實踐中,部分 ECMAScript 規范章節編號指向的是 ES2017。
9. 所有在 JavaScript 示例代碼中出現的名稱都是按照 JavaScript 慣例命名的,如一般標識符以小寫字符開始,類、單例類或構造器以大寫字符開始;不使用下畫線作為分隔符。但是,如果一個變量聲明的目的是表明某種強調的語義效果,或是一個未經實現的工具函數/狀態,那么它將使用下畫線來分隔,并盡量表達一個有完整語義的定義。如果一個聲明以下畫線開始,那么它將表達 ECMAScript 規范中的例程,或引擎實現中的內部例程(使用下畫線的標識符,在源代碼中也可能會以斜體表示)。
10. 源代碼中的斜體字通常用于表達語法概念,而非一個實際可用的標識符或聲明。
內容簡介:

JavaScript 是一門包含多種語言特性的混合范型語言,在面向對象和函數式語言特性方面表現尤為突出,且在 ES6 之后所添加的并行語言特性也極為出色。本書基于 ES6,并涵蓋最新的 ES2019 規范,全面講述 JavaScript 在五個方面的語言特性,以及將這些特性融會如一的方法。本書不但完整解析了 JavaScript 語言,還逐一剖析了相關特性在多個開源項目中的編程實踐與應用,是難得的語言學習參考書。本書作者在前端開發領域經驗豐富、深耕不輟,一書三版,歷經十余年。書中對 JavaScript 語言的理解與展望,尤其適合期望精通這門語言的中高級程序員和語言實踐者閱讀。
目錄:

第 1 章 二十年來的 JavaScript ........... 1
1.1 網頁中的代碼 ................ 1
1.1.1 新鮮的玩意兒 .............. 1
1.1.2 寫在網頁中的第一段代碼 ............ 2
1.1.3 最初的價值 ................ 3
1.2 用 JavaScript 來寫瀏覽器上的應用 ............ 5
1.2.1 我要做一個聊天室 .............. 5
1.2.2 Flash 的一席之地 ............... 7
1.2.3 RWC 與 RIA 之爭.............. 8
1.3 沒有框架與庫的語言能怎樣發展呢 ............10
1.3.1 做一個框架 ...............10
1.3.2 重寫框架的語言層 .............13
1.3.3 富瀏覽器端開發(RWC)與 AJAX ..........14
1.4 語言的進化 .................16
1.4.1 Qomo 的重生 ..............16
1.4.2 QoBean 是對語言的重新組織 ...........17
1.4.3 JavaScript 作為一門語言的進化 ............18
1.5 大型系統開發 ...............20
1.5.1 框架與架構是不同的 .............20
1.5.2 大型系統與分布式的環境 .............21
1.5.3 劃時代的 ES6..............23
1.6 為 JavaScript 正名 ...............24
1.6.1 JavaScript ................25
1.6.1.1 Core JavaScript .............26
1.6.1.2 SpiderMonkey JavaScript ...........27
1.6.1.3 JScript ...............27
1.6.2 ECMAScript ..............28
1.7 JavaScript 的應用環境 ..............29
1.7.1 宿主環境 ...............30
1.7.2 外殼程序 ...............31
1.7.3 運行期環境 ...............32
1.7.4 兼容環境下的測試 .............34
第 2 章 JavaScript 的語法 ............. 36
2.1 語法綜述 .................36
2.1.1 標識符所綁定的語義 .............37
2.1.2 識別語法錯誤與運行錯誤 .............38
2.2 JavaScript 的語法:聲明 ..............40
2.2.1 變量的數據類型 ...............40
2.2.1.1 基本數據類型 ............41
2.2.1.2 宿主定義的其他對象類型 ..........42
2.2.1.3 值類型與引用類型 ............42
2.2.1.4 討論:ECMAScript 的類型系統 ..........43
2.2.2 變量聲明 ...............45
2.2.2.1 塊級作用域的變量聲明與一般 var 聲明 .........47
2.2.2.2 用賦值模板聲明一批變量 ..........48
2.2.3 使用字面量風格的值 .............48
2.2.3.1 字符串字面量、轉義符 ............49
2.2.3.2 模板字面量 ..............51
2.2.3.3 數值字面量 ..............52
2.2.4 其他聲明 ...............53
2.2.4.1 常量聲明 ..............53
2.2.4.2 符號聲明 ..............54
2.2.4.3 函數聲明 ..............55
2.3 JavaScript 的語法:表達式運算 ............56
2.3.1 一般表達式運算 ...............59
2.3.1.1 邏輯運算 ..............59
2.3.1.2 字符串運算 ..............60
2.3.1.3 數值運算 ..............61
2.3.2 比較運算 ...............61
2.3.2.1 等值檢測 ..............62
2.3.2.2 序列檢測 ..............64
2.3.3 賦值運算 ...............67
2.3.3.1 賦值的語義 ..............67
2.3.3.2 復合賦值運算符 ............68
2.3.3.3 解構賦值 ..............68
2.3.4 函數相關的表達式 .............69
2.3.4.1 匿名函數與箭頭函數 ............70
2.3.4.2 函數調用 ..............70
2.3.4.3 new 運算 ..............72
2.3.5 特殊作用的運算符 .............72
2.3.5.1 類型運算符(typeof) ............73
2.3.5.2 展開語法(spread syntax) ..........74
2.3.5.3 面向表達式的運算符 ............74
2.3.6 運算優先級 ...............76
2.4 JavaScript 的語法:語句 ..............78
2.4.1 表達式語句 ...............80
2.4.1.1 一般表達式語句 ............80
2.4.1.2 賦值語句與隱式的變量聲明 ..........81
2.4.1.3 函數調用語句 ............82
2.4.2 變量聲明語句 ...............86
2.4.3 分支語句 ...............87
2.4.3.1 條件分支語句(if 語句) ...........87
2.4.3.2 多重分支語句(switch 語句) .........88
2.4.4 循環語句 ...............89
2.4.5 流程控制:一般子句 .............91
2.4.5.1 標簽聲明 ..............91
2.4.5.2 break 子句 ..............92
2.4.5.3 continue 子句 .............94
2.4.5.4 return 子句 .............95
2.4.6 流程控制:異常 ...............96
2.5 JavaScript 的語法:模塊 ..............97
2.5.1 模塊的聲明與加載 .............98
2.5.1.1 加載模塊 ..............98
2.5.1.2 聲明模塊 ..............100
2.5.2 名字空間的特殊性 .............101
2.5.2.1 名字空間的創建者 ............102
2.5.2.2 名字空間中的名字是屬性名 ..........102
2.5.2.3 使用上的一些特殊性 ............103
2.6 嚴格模式下的語法限制 .............105
2.6.1 語法限制 ...............106
2.6.2 執行限制 ...............108
2.6.3 嚴格模式的范圍 ............. 110
2.6.3.1 有限范圍下的嚴格模式 .......... 110
2.6.3.2 非嚴格模式的全局環境 .......... 112
2.7 運算符的二義性 ............... 112
2.7.1 加號“+”的二義性 ............. 114
2.7.2 括號“( )”的二義性 .............. 114
2.7.3 冒號“:”與標簽的二義性 ............ 116
2.7.4 大括號“{ }”的二義性 ............. 117
2.7.4.1 復合語句/語句塊 ............. 117
2.7.4.2 聲明對象字面量 ............ 118
2.7.4.3 函數聲明 .............. 119
2.7.4.4 結構化異常 .............. 119
2.7.4.5 模板中的變量引用 ............120
2.7.4.6 解構賦值 ..............120
2.7.5 逗號“,”的二義性 ..............122
2.7.6 方括號“[ ]”的二義性 ............123
2.7.7 語法設計中對二義性的處理............127
第 3 章 JavaScript 的面向對象語言特性 ......... 130
3.1 面向對象編程的語法概要 .............130
3.1.1 對象聲明與實例創建 .............132
3.1.1.1 使用構造器創建對象實例 ..........132
3.1.1.2 聲明對象字面量 ............134
3.1.1.3 數組及其字面量 ............137
3.1.1.4 正則表達式及其字面量 ..........138
3.1.1.5 在對象聲明中使用屬性存取器 ..........141
3.1.2 使用類繼承體系 .............141
3.1.2.1 聲明類和繼承關系 ............141
3.1.2.2 聲明屬性 ..............143
3.1.2.3 調用父類構造方法 ............144
3.1.2.4 調用父類方法 ............145
3.1.2.5 類成員(類靜態成員) ..........146
3.1.3 對象成員 ...............147
3.1.3.1 成員的列舉,以及可列舉性 ..........147
3.1.3.2 對象及其成員的檢查 ............150
3.1.3.3 值的存取 ..............153
3.1.3.4 成員的刪除 ..............154
3.1.3.5 方法的調用 ..............157
3.1.4 使用對象自身 ...............157
3.1.4.1 與基礎類型數據之間的運算 ..........157
3.1.4.2 默認對象的指定 ............158
3.1.5 符號 .................158
3.1.5.1 列舉符號屬性 ............159
3.1.5.2 改變對象內部行為 ............159
3.1.5.3 全局符號表 ..............160
3.2 JavaScript 的原型繼承 ..............161
3.2.1 空(null)與空白對象(empty) ...........161
3.2.1.1 空白對象是所有對象的基礎 ..........162
3.2.1.2 構造復制?寫時復制?還是讀遍歷?.........163
3.2.1.3 構造過程:從函數到構造器 ..........166
3.2.1.4 內置屬性與方法 ............167
3.2.1.5 原型為 null:“更加空白”的對象 .........170
3.2.2 原型鏈的維護 ...............171
3.2.2.1 外部原型鏈與 constructor 屬性 ..........172
3.2.2.2 使用內部原型鏈 ............173
3.2.3 原型繼承的實質 .............175
3.2.3.1 簡單模型 ..............175
3.2.3.2 基于原型繼承的設計方法 ..........177
3.2.3.3 如何理解“繼承來的成員” ..........177
3.3 JavaScript 的類繼承 ..............179
3.3.1 類是靜態的聲明 .............179
3.3.2 super 是全新的語法元素 ............181
3.3.2.1 super 的作用 ............181
3.3.2.2 super 指向什么 ............182
3.3.2.3 super 對一般屬性的意義...........184
3.3.2.4 super 在兩種繼承關系中的矛盾 .........186
3.3.2.5 super 的動態計算過程 ............188
3.3.3 類是用構造器(函數)來實現的 ..........189
3.3.4 父類的默認值與 null 值 .............192
3.4 JavaScript 的對象系統 ..............196
3.4.1 封裝與多態 ...............196
3.4.1.1 封裝 ..............196
3.4.1.2 多態 ..............198
3.4.1.3 多態與方法繼承 ............200
3.4.2 屬性 .................201
3.4.2.1 方法 ..............201
3.4.2.2 事件 ..............205
3.4.3 構造對象系統的方法 .............206
3.4.3.1 類抄寫 ..............206
3.4.3.2 原型繼承 ..............209
3.4.3.3 類繼承 ..............210
3.4.3.4 直接創建對象 ............ 211
3.4.3.5 如何選擇繼承的方式 ............213
3.4.4 內置的對象系統 .............214
3.4.4.1 早期規范(ES5 之前)中的對象 .........216
3.4.4.2 集合對象 ..............218
3.4.4.3 結構化數據對象 ............221
3.4.4.4 反射對象 ..............223
3.4.4.5 其他 ..............225
3.4.5 特殊效果的繼承 .............226
3.5 可定制的對象屬性 ...............229
3.5.1 屬性描述符 ...............230
3.5.1.1 數據描述符 ..............230
3.5.1.2 存取描述符 ..............231
3.5.1.3 隱式創建的描述符:字面量風格的對象或類聲明 .......232
3.5.2 定制對象屬性 ...............233
3.5.2.1 給屬性賦值 ..............234
3.5.2.2 使用屬性描述符 ............235
3.5.2.3 取屬性或屬性列表 ............237
3.5.3 屬性表的狀態 ...............239
3.6 運行期侵入與元編程系統 .............242
3.6.1 關于運行期侵入 .............243
3.6.1.1 運行期侵入的核心機制 ..........243
3.6.1.2 可被符號影響的行為 ............244
3.6.1.3 內部方法與反射機制 ............251
3.6.1.4 侵入原型 ..............255
3.6.2 類類型與元類繼承 .............257
3.6.2.1 原子 ..............257
3.6.2.2 元與元類 ..............258
3.6.2.3 類類型系統 ..............260
3.6.2.4 類類型的檢查 ............261
3.6.2.5 類類型的聲明以及擴展特性 ..........263
3.6.3 元編程模型 ...............266
第 4 章 JavaScript 語言的結構化 .......... 269
4.1 概述 .................269
4.1.1 命令式語言 ...............270
4.1.1.1 存儲與數據結構 ............270
4.1.1.2 結構化編程 ..............271
4.1.1.3 結構化的疑難 ............272
4.1.2 面向對象語言 ...............275
4.1.2.1 結構化的延伸 ............275
4.1.2.2 更高層次的抽象:接口 ..........278
4.1.2.3 面向接口的編程方法 ............280
4.1.3 再論語言的分類 .............281
4.1.3.1 對語言范型的簡化 ............281
4.1.3.2 結構化的性質 ............282
4.1.4 JavaScript 的語源 ..............283
4.2 基本的組織元素 ...............284
4.2.1 標識符 ...............285
4.2.2 表達式 ...............286
4.2.2.1 字面量 ..............287
4.2.2.2 初始器 ..............287
4.2.3 語句 .................288
4.2.4 模塊 .................289
4.2.5 組織的原則 ...............290
4.2.5.1 原則一:抑制數據的可變性 ..........290
4.2.5.2 原則二:最小邏輯和最大復用 ..........291
4.2.5.3 原則三:語法在形式上的清晰與語義一致性 .......293
4.3 聲明 .................294
4.3.1 聲明名字 ...............295
4.3.2 確定性 ...............296
4.3.3 頂層聲明 ...............297
4.4 語句與代碼分塊 ...............300
4.4.1 塊 .................301
4.4.1.1 簡單語句 ..............302
4.4.1.2 單值表達式 ..............302
4.4.2 塊與語句的語法結構 .............303
4.4.2.1 語義上的代碼分塊 ............303
4.4.2.2 分支邏輯中的代碼分塊 ..........303
4.4.2.3 多重分支邏輯中的代碼分塊 ..........304
4.4.2.4 循環邏輯中的代碼分塊 ..........306
4.4.2.5 異常中的代碼分塊 ............308
4.4.3 塊與聲明語句 ...............309
4.4.3.1 只能在塊中進行數據聲明 ..........309
4.4.3.2 能同時聲明塊的聲明語句 ..........310
4.4.3.3 聲明語句與塊的組織 ............ 311
4.4.4 塊與語句的值 ...............312
4.4.4.1 語句的執行狀態 ............314
4.4.4.2 語句無值 ..............315
4.4.4.3 語句有值 ..............316
4.4.5 標簽化語句與塊 .............317
4.5 組織形式分塊的方法 ...............318
4.5.1 詞法作用域 ...............319
4.5.1.1 不存在“級別 1:表達式” ...........320
4.5.1.2 級別 2:語句 .............320
4.5.1.3 級別 3:函數 .............324
4.5.1.4 級別 4:模塊 .............325
4.5.1.5 級別 5:全局 .............327
4.5.2 執行流程及其變更 .............328
4.5.2.1 級別 1:可能的逃逸 ...........329
4.5.2.2 級別 2:“break <label>;”等語法 ........331
4.5.2.3 級別 3:return 子句 ...........333
4.5.2.4 級別 4:動態模塊與 Promise 中的流程控制.......335
4.5.2.5 級別 5:throw 語句 ...........335
4.5.3 詞法作用域之間的相關性 .............336
4.5.4 執行流程變更的內涵 .............337
4.6 層次結構程序設計 ...............340
4.6.1 屬性的可見性 ...............341
4.6.1.1 屬性在繼承層次間的可見性 ..........342
4.6.1.2 屬性在繼承樹(子樹)間的可見性 ........343
4.6.2 多態的邏輯 ...............343
4.6.2.1 super 是對多態邏輯的綁定 ...........344
4.6.2.2 super 是一個作用域相關的綁定 .........345
4.6.3 私有作用域的提出 .............347
4.7 歷史遺產:變量作用域 .............349
4.7.1 變量作用域 ...............350
4.7.1.1 級別 3:函數(局部變量) ...........351
4.7.1.2 級別 4:模塊 .............352
4.7.1.3 級別 5:全局變量 ...........352
4.7.2 變量的特殊性與變量作用域的關系 ..........353
4.7.2.1 變量提升 ..............353
4.7.2.2 變量動態聲明 ............354
4.7.2.3 變量隱式聲明(全局屬性) ..........355
4.8 私有屬性與私有字段的紛爭..............356
4.8.1 私有屬性的提出 .............357
4.8.1.1 對象字面量中的作用域問題 ..........357
4.8.1.2 類聲明中的作用域問題 ..........359
4.8.1.3 識別“對象自己(訪問)” ..........360
4.8.1.4 識別“對象訪問(自己)” ..........361
4.8.2 從私有屬性到私有成員 .............361
4.8.2.1 私有屬性與私有字段 ............361
4.8.2.2 私有字段與私有變量 ............363
4.8.2.3 再論私有成員 ............364
4.8.3 “類字段”提案的實現概要............364
4.8.3.1 語法設計 ..............365
4.8.3.2 實現框架 ..............366
4.8.3.3 概要分析 ..............368
4.8.4 “私有屬性”提案的設計與提議 ..........368
4.8.4.1 語法設計 ..............368
4.8.4.2 語法與語義的關系 ............371
4.8.5 “私有屬性”提案的實現 .............373
4.8.5.1 核心的實現邏輯 ............373
4.8.5.2 一個簡短的回顧 ............374
4.8.5.3 保護屬性的實現 ............375
4.8.5.4 可見性的管理(unscopables) .........376
4.8.5.5 避免侵入(thisValue) ...........377
4.8.5.6 內部訪問(internal) ............378
4.8.5.7 概要分析 ..............380
第 5 章 JavaScript 的函數式語言特性 ......... 381
5.1 概述 .................381
5.1.1 從代碼風格說起 .............382
5.1.2 為什么常見的語言不贊同連續求值 ..........383
5.1.3 函數式語言的淵源 .............384
5.2 從運算式語言到函數式語言..............386
5.2.1 JavaScript 中的幾種連續運算 ............386
5.2.1.1 連續賦值 ..............386
5.2.1.2 三元表達式的連用 ............387
5.2.1.3 連續邏輯運算 ............388
5.2.1.4 逗號運算符與連續運算 ..........389
5.2.1.5 解構賦值 ..............389
5.2.1.6 函數與方法的調用 ............390
5.2.2 如何消滅語句 ...............391
5.2.2.1 通過表達式消滅分支語句 ..........391
5.2.2.2 通過函數遞歸消滅循環語句 ..........393
5.2.2.3 其他可以被消滅的語句 ..........394
5.2.3 運算式語言 ...............394
5.2.3.1 運算的實質是值運算 ............394
5.2.3.2 運算式語言的應用 ............396
5.2.4 重新認識函數 ...............397
5.2.4.1 函數是對運算式語言的補充 ..........398
5.2.4.2 函數是代碼的組織形式 ..........398
5.2.4.3 當運算符等義于某個函數時 ..........399
5.2.5 函數式語言 ...............401
5.2.5.1 “函數”===“Lambda” ..........402
5.2.5.2 函數是操作數 ............402
5.2.5.3 在函數內保存數據 ............403
5.2.5.4 函數內的運算對函數外無副作用 ..........404
5.2.5.5 函數式的特性集 ............405
5.3 JavaScript 中的函數 ..............405
5.3.1 參數 .................405
5.3.1.1 可變參數 ..............406
5.3.1.2 默認參數 ..............408
5.3.1.3 剩余參數 ..............409
5.3.1.4 模板參數 ..............410
5.3.1.5 參數對象 .............. 411
5.3.1.6 非簡單參數 ..............413
5.3.1.7 非惰性求值 ..............414
5.3.1.8 傳值參數 ..............416
5.3.2 函數 .................418
5.3.2.1 一般函數 ..............419
5.3.2.2 生成器函數 ..............421
5.3.2.3 類 ..............423
5.3.2.4 方法 ..............425
5.3.2.5 箭頭函數 ..............426
5.3.2.6 綁定函數 ..............427
5.3.2.7 代理函數 ..............431
5.3.3 函數的數據性質 .............431
5.3.3.1 函數是第一型 ............432
5.3.3.2 數據態的函數 ............433
5.3.3.3 類與對象態的函數 ............434
5.3.3.4 代理態的函數 ............438
5.3.4 函數與邏輯結構 .............439
5.3.4.1 遞歸 ..............439
5.3.4.2 函數作為構造器的遞歸 ..........441
5.3.4.3 塊級作用域中的函數 ............442
5.4 函數的行為 .................443
5.4.1 構造 .................444
5.4.1.1 this 引用的創建 .............444
5.4.1.2 初始化 this 對象 ............446
5.4.2 調用 .................448
5.4.2.1 不使用函數調用運算符 ..........449
5.4.2.2 callee:我是誰 .............452
5.4.2.3 caller:誰調用我 .............453
5.4.3 方法調用 ...............455
5.4.3.1 屬性存取與 this 引用的傳入...........456
5.4.3.2 this 引用的使用 .............457
5.4.3.3 在方法調用中理解 super ...........458
5.4.3.4 動態地添加方法 ............459
5.4.4 迭代 .................461
5.4.4.1 可迭代對象與迭代 ............461
5.4.4.2 可迭代對象在語法層面的支持 ..........462
5.4.4.3 迭代器的錯誤與異常處理 ..........464
5.4.5 生成器中的迭代 .............466
5.4.5.1 生成器對象 ..............466
5.4.5.2 生成器的錯誤與異常處理 ..........469
5.4.5.3 方法 throw()的隱式調用 ...........472
5.4.5.4 向生成器中傳入的數據 ..........474
5.5 閉包 .................475
5.5.1 閉包與函數實例 .............476
5.5.1.1 閉包與非閉包 ............476
5.5.1.2 什么是函數實例 ............477
5.5.1.3 看到閉包 ..............478
5.5.1.4 閉包的數量 ..............479
5.5.2 閉包的使用 ...............481
5.5.2.1 運行期的閉包 ............482
5.5.2.2 閉包中的可訪問標識符 ..........483
5.5.2.3 用戶代碼導致的閉包變化 ..........484
5.5.2.4 函數表達式的特殊性 ............485
5.5.2.5 嚴格模式下的閉包 ............486
5.5.3 與閉包類似的實例化環境 .............487
5.5.3.1 全局環境 ..............487
5.5.3.2 模塊環境 ..............490
5.5.3.3 對象閉包 ..............491
5.5.3.4 塊 ..............492
5.5.3.5 循環語句的特殊性 ............493
5.5.3.6 函數閉包與對象閉包的混用 ..........495
5.5.4 與閉包相關的一些特性 .............496
5.5.4.1 變量維護規則 ............496
5.5.4.2 引用與泄露 ..............497
5.5.4.3 語句或語句塊中的閉包問題 ..........499
5.5.4.4 閉包中的標識符(變量)特例 ..........502
5.5.4.5 函數對象的閉包及其效果 ..........504
第 6 章 JavaScript 的動態語言特性 .......... 506
6.1 概述 .................506
6.1.1 動態數據類型的起源 .............507
6.1.2 動態執行系統 ...............507
6.1.3 腳本系統的起源 .............509
6.1.4 腳本只是表現形式 .............510
6.2 動態類型:對象與值類型之間的轉換 ............512
6.2.1 包裝類:面向對象的妥協 .............512
6.2.1.1 顯式創建 ..............513
6.2.1.2 顯式包裝 ..............514
6.2.1.3 隱式包裝的過程與檢測方法 ..........514
6.2.1.4 包裝值類型數據的必要性與問題 ..........517
6.2.1.5 其他字面量與相應的構造器 ..........519
6.2.1.6 函數特例 ..............519
6.2.2 從對象到值 ...............520
6.2.2.1 對象到值的隱式轉換規則 ..........520
6.2.2.2 直接的值運算不受包裝類的方法影響.........522
6.2.2.3 什么是“轉換的預期” ..........524
6.2.2.4 深入探究 valueOf()方法 ..........525
6.2.2.5 布爾運算的特例 ............527
6.2.2.6 符號 Symbol.toPrimitive 的效果 .........528
6.2.3 顯式的轉換 ...............529
6.2.3.1 顯式轉換的語法含義 ............530
6.2.3.2 對“轉換預期”的顯式表示 ..........531
6.2.3.3 關于符號值的補充說明 ..........531
6.3 動態類型:值類型的轉換 .............532
6.3.1 值運算:類型轉換的基礎 .............532
6.3.1.1 完整過程:運算導致的類型轉換 ..........533
6.3.1.2 語句或語義導致的類型轉換 ..........535
6.3.2 值類型之間的轉換 .............535
6.3.2.1 undefined 的轉換 .............536
6.3.2.2 number 的轉換 .............537
6.3.2.3 boolean 的轉換 ............537
6.3.2.4 string 的轉換 ............538
6.3.2.5 symbol 的轉換 .............539
6.3.3 值類型之間的顯式轉換 .............540
6.3.3.1 到數值的顯式轉換 ............540
6.3.3.2 到字符串類型的顯式轉換 ..........541
6.3.3.3 到 undefined 值的顯式處理 ..........544
6.3.3.4 到布爾值的顯式處理 ............544
6.4 動態類型:對象與數組的動態特性 ............545
6.4.1 關聯數組與索引數組 .............545
6.4.2 索引數組作為對象的問題 .............546
6.4.2.1 索引數組更加低效 ............547
6.4.2.2 屬性 length 的可寫性 ............549
6.4.2.3 類型化數組的一些性質 ..........550
6.4.3 類數組對象:對象作為索引數組的應用 ..........552
6.4.4 其他 .................554
6.5 重寫 .................555
6.5.1 標識符的重寫及其限制 .............555
6.5.1.1 早于用戶代碼之前的聲明與重寫 ..........556
6.5.1.2 聲明對標識符可寫性的影響 ..........559
6.5.1.3 賦值操作帶來的重寫 ............560
6.5.1.4 對象內部方法對重寫的影響 ..........563
6.5.1.5 非賦值操作帶來的重寫 ..........564
6.5.1.6 條件化聲明中的重寫 ............565
6.5.1.7 運算優先級與引用的暫存 ..........566
6.5.2 原型重寫 ...............567
6.5.3 構造器重寫 ...............569
6.5.3.1 重寫 Object() ............569
6.5.3.2 使用類聲明來重寫 ............571
6.5.3.3 繼承關系的丟失 ............572
6.5.4 對象成員的重寫 .............573
6.5.4.1 成員重寫的檢測 ............574
6.5.4.2 成員重寫的刪除 ............575
6.5.4.3 成員重寫對作用域的影響 ..........576
6.5.5 引擎對重寫的限制 .............578
6.5.5.1 this 與 super 等關鍵字的重寫 .........579
6.5.5.2 語句中的重寫 ............580
6.5.5.3 結構化異常處理中的重寫 ..........580
6.6 動態執行 .................582
6.6.1 eval()作為函數名的特殊性 ............582
6.6.2 eval()在不同上下文環境中的效果 ..........584
6.6.2.1 eval 使用全局環境 ............584
6.6.2.2 eval 使用對象閉包或模塊環境 .........585
6.6.2.3 eval()使用當前函數的閉包 ...........585
6.6.3 Eval 環境的獨特性 .............586
6.6.3.1 默認繼承當前環境的運行模式 ..........587
6.6.3.2 例外:obj.eval()的特殊性 ...........588
6.6.3.3 執行代碼可以自行決定運行模式 ..........589
6.6.3.4 聲明實例化過程與其他可執行結構不同 .......591
6.6.3.5 環境的回收 ..............592
6.6.4 動態執行過程中的語句、表達式與值 ..........593
6.6.5 序列化與反序列化 .............595
6.6.5.1 在對象與函數上的限制 ..........596
6.6.5.2 對象深度與循環引用 ............597
6.6.5.3 不太現實的替代品 ............599
6.6.6 eval 對作用域的影響 ..............600
6.6.7 其他的動態執行邏輯 .............601
6.6.7.1 動態創建的函數 ............601
6.6.7.2 模板與動態執行 ............603
6.6.7.3 宿主的動態執行邏輯 ............604
6.7 動態方法調用(call、apply 與 bind) ............605
6.7.1 動態方法調用以及 this 引用的管理 ..........605
6.7.2 丟失的 this 引用 .............608
6.7.3 bind()方法與函數的延遲調用 ............610
6.7.4 棧的可見與修改 .............612
6.7.5 嚴格模式中的 this 綁定問題 ............614
6.8 通用執行環境的實現 ...............615
6.8.1 通用 DSL 的模型 ..............616
6.8.1.1 概念設計 ..............616
6.8.1.2 被依賴的基礎功能 ............616
6.8.1.3 一個基本實現 ............619
6.8.1.4 應用示例 ..............621
6.8.1.5 其他 ..............623
6.8.2 實現 ECMAScript 引擎 ............624
6.8.2.1 簡單入手 ..............624
6.8.2.2 引擎中的環境 ............625
6.8.2.3 對用戶代碼的語法分析 ..........628
6.8.2.4 執行前的準備工作 ............630
6.8.2.5 從語法樹節點開始執行 ..........631
6.8.2.6 數據的交換 ..............633
6.8.2.7 上下文的使用與管理 ............634
6.8.3 與 DSL 的概念整合 ..............635
第 7 章 JavaScript 的并行語言特性 .......... 638
7.1 概述 .................638
7.1.1 并行計算的思想 .............638
7.1.1.1 并行計算范型的抽象 ............639
7.1.1.2 分布與并行邏輯 ............639
7.1.1.3 并發的討論背景 ............640
7.1.1.4 分支也可以不是時序邏輯 ..........641
7.1.2 并行程序設計的歷史 .............642
7.1.2.1 從“支持并行”到并行程序語言 ..........643
7.1.2.2 用并發思想處理數據的語言 ..........643
7.1.2.3 多數傳統程序設計語言是“偽并行”的 .......644
7.1.2.4 真正的并行:在語言層面無視時間 ........644
7.1.3 并行語言特性在 JavaScript 中的歷史 ...........645
7.2 Promise 的核心機制 ...............647
7.2.1 Promise 的核心過程 .............647
7.2.1.1 Promise 的構造方法 ............647
7.2.1.2 需要清楚的事實:沒有延時 ..........648
7.2.1.3 Then 鏈 .............649
7.2.1.4 Then 鏈中 promise2 的置值邏輯 ..........650
7.2.1.5 Then 鏈對值的傳遞以及.catch()處理 .........652
7.2.2 Promise 類與對象的基本應用 ...........654
7.2.2.1 Promise 的其他類方法 ............654
7.2.2.2 Promise.resolve()處理 thenable 對象的具體方法 .......656
7.2.2.3 promise 對象的其他原型方法 ..........658
7.2.2.4 未捕獲異常的 promise 的檢測 .........660
7.2.2.5 特例:將響應函數置為非函數 ..........662
7.2.3 Promise 的子類 ...............663
7.2.3.1 由 Promise()派生的子類 ...........663
7.2.3.2 thenable 對象或其子類 ............664
7.2.4 執行邏輯 ...............666
7.2.4.1 任務隊列 ..............666
7.2.4.2 執行棧 ..............667
7.3 與其他語言特性的交集 .............668
7.3.1 與函數式特性的交集:異步的函數 ..........669
7.3.1.1 異步函數的引入 ............669
7.3.1.2 異步函數的值 ............670
7.3.1.3 異步函數中的 await ............671
7.3.1.4 異步生成器函數 ............673
7.3.1.5 異步生成器函數中的 await ...........674
7.3.1.6 異步生成器函數與 for await.of 語句 ........676
7.3.2 與動態特性的交集 .............677
7.3.2.1 await 在語義上的特點...........677
7.3.2.2 resolve 行為與類型模糊 ..........678
7.3.2.3 then 方法的動態綁定 ............679
7.3.2.4 通過接口識別的類型(thenable) .........680
7.3.2.5 通過動態創建函數來驅動異步特性 ........682
7.3.3 對結構化特性帶來的沖擊 .............683
7.3.3.1 對執行邏輯的再造 ............683
7.3.3.2 遲來的 finally.............684
7.3.3.3 new Function()風格的異步函數創建 ........686
7.3.3.4 異步方法與存取器 ............687
7.4 JavaScript 中對并發的支持 ..............690
7.4.1 Agent、Agent Cluster 及其工作機制 ...........691
7.4.1.1 工作線程及其環境 ............691
7.4.1.2 線程及其調度 ............692
7.4.1.3 與誰協商 ..............693
7.4.1.4 多線程的可計算環境 ............694
7.4.1.5 通過消息通信完成協商 ..........695
7.4.2 SharedArrayBuffer ..............698
7.4.3 Atomics................701
7.4.3.1 鎖 ..............701
7.4.3.2 置值:鎖的狀態切換 ............704
7.4.3.3 其他原子操作 ............705
7.4.3.4 原子操作中的異常與鎖的釋放 ..........705
7.5 在分布式網絡環境中的并行執行 ............706
7.5.1 分布式并行架構的實踐 .............707
7.5.1.1 N4C 的背景 .............707
7.5.1.2 N4C 的架構設計 .............707
7.5.1.3 N4C 架構的實現 .............708
7.5.2 構建一個集群環境 .............709
7.5.2.1 N4C 集群與資源中心的基本結構 ..........709
7.5.2.2 啟動集群 .............. 711
7.5.2.3 在集群中創建任務中心 ..........712
7.5.2.4 將計算節點加入集群 ............713
7.5.3 使用 PEDT 執行并行任務 .............713
7.5.3.1 本地任務、遠程任務與并行任務 ..........714
7.5.3.2 使用 PEDT 來管理并行任務 ..........714
7.5.3.3 任務的執行 ..............715
7.5.3.4 并行的方法 ..............716
7.5.4 可參考的意義 ...............718
附錄 A 術語表 ............. 719
附錄 B 參考書目 ............. 723
附錄 C 圖表索引 ............. 725
附錄 D 本書各版次主要修改 ........... 731
序: