前言
從這篇起我們來一起學習 JS,
在二十一世紀二十年代的今天,想必不會有人再對 JS 作為一門正兒八經的編程語言的合理地位提出質疑了,而想要獲得一門編程語言的比較完備的知識,我們就至少需要從文法(語法詞法)、語意、編譯時、運行時四個角度去考慮,
由于 JS 在大部分情況下是 解釋執行 的,一般不考慮編譯時的東西,因此它的基礎知識結構就如下圖所示:

當然,我們不是語言學家,作為工程技術人員對語意這個東西并不需要過分解釋,它就是指一段代碼的意思,所以我們學 JS ,說白了就是在學它的文法和運行時,
我們之后的內容也會圍繞著這兩部分展開,但為了真正弄明白 JS 這門語言的設計思路,我覺得還是有必要先從它的歷史沿革說起,
1 JS 簡史
1.1 倉促誕生
JavaScript 誕生于網景(Netscape)公司,最初 Netscape Navigator 瀏覽器只能展示靜態頁面,缺乏動態互動的能力,因此在 1995 年,網景公司決定向瀏覽器中加入一個 “腳本語言”,他們設想了兩條實作路徑:
- 與 Sun 公司合作,嵌入 Java
- 嵌入 Scheme 語言
然而網景的管理者最終覺得最好的辦法是設計一門語法 類似 Java,而不是像現存的 Scheme 等腳本語言的新語言出來,
因此,原本被招來負責嵌入 Scheme 的 Brendan Eich 就花了幾周時間搞出來了這么個東西,一開始被稱為 Mocha,然后改為 LiveScript,三個月又后改為 JavaScript,
JavaScript 這個名字使它看起來像 Java 派生出來的,誤導了不少小白,其實兩者的關系也僅僅在語法設計層面,起這么個名字往難聽了說就是蹭熱度,只能說當時格局小了,
另外據說 Brendan 對要求自己照著 Java 語法設計語言這件事有些不爽,還在網上吐槽過自己的某個“光頭老板”,
1.2 明爭暗斗
為了與網景分庭抗禮,1996 年,微軟在自己的 IE 瀏覽器中內置了一門新的腳本語言,稱為 JScript,去蹭 JavaScript 的熱度,這個 JScript 跟 JS 在實作方式上又很大不同,因此開發者很難解決兼容性問題,使得當年許多網頁上都會有類似 “best viewed in Netscape” 和 “best viewed in Internet Exploer” 這也的 logo,
同年,網景把 JavaScript 提交到了 ECMA 國際,意在形成一個讓所有瀏覽器廠商遵守的標準規范,1997 年,ECMA 基于 JavaScript 發布了 ECMAScript 的第一個正式版本,
雖然自此有了一個規范擺在那里,但代碼怎么執行還是瀏覽器說了算,在實作上多一些少一些原創一些都有可能,因此 JavaScript 這個稱呼還是保留了下來,代指這些差不多又不那么規范的東西,
此后幾年,ECMAScript 陸續發布了 2、3、4 版本,然而到 2000 年時,IE 瀏覽器已經占據了 95% 的市場,可以說 JScript 反倒成了事實上的客戶端腳本標準,
其實在 ECMAScript 制定的初期,微軟還依據 JScript 提出了一些建議,但后來逐漸停止了跟 ECMA 的合作,而 ES 4 由于爭議性比較大最終被棄用了,看起來似乎 JScript 要一統江山了,(有意思的是,ES 4 里最具爭議的幾個特性,比如生成器、迭代器、解構賦值等在最近的版本里又被加了回來)
1.3 融合統一
天道好輪回,Netscape 的后繼者 Mozilla 基金會又發行了 Firefox 瀏覽器,由于廣受好評,沒過幾年又搶占了 IE 的許多市場份額,
2005 年,Jesse James Garrett 發布了論文:《Ajax: A New Approach to Web Applications》,描述了一種以 JavaScript 為基礎的技術,使得頁面無需全部重新加載即可回應變化,這種技術的出現極大地豐富了網頁的表現能力,引發了 JavaScript 的復興,在開源社區的主導下誕生了 jQuery、Prototype、Dojo Toolkit、MooTools 等著名的庫或工具集,
2008 年,Google 推出 Chrome 瀏覽器,由于其 JavaScript 引擎在性能上的優勢,又一次引發了瀏覽器市場的洗牌,
也許是終于意識到在內置語言這個問題上爭論不休不利于 Web 技術的發展,2008 年 7 月,相關廠商在奧斯陸(Oslo)舉辦了會議,認為應當攜手推動語言的發展,并基于 ES 4 開展了標準的重修作業,
會議后不久,ES 5 正式發布,同時,在這次會議上提出的代號為 Harmony 的專案最終于 2015 年開花結果,也就是我們熟知的 ES 6(ES 2015),
可以說,直到 ES 6 發布,用于瀏覽器端的編程語言 才真正有了統一的廣受認可的標準,實際上從此時開始,ECMAScript 每年都會發布新的版本,其雖然脫胎于 JavaScript,但顯然背后的推動力量已經上了一個新臺階,
ES 6 在技術層面也是具有里程碑意義的重要版本,它補充了一些十分關鍵但不被舊版瀏覽器支持的特性,促進了瀏覽器的新一輪迭代,豐富了語言的能力,甚至形成了新的編程風格,在過去的幾年里,使用 ES 6 改寫代碼、處理使用 ES 6 導致的兼容性問題也是前端的重要作業之一,
雖然嚴格意義上一統江湖的是 ECMAScript,但由于 JavaScript 這個名字已經太過深入人心,我們在大部分情況下還是習慣性地用它來代指這門語言,
前面提到,最初對 JS 的定位就是一門 “腳本語言(Script,解釋執行的語言)”,但越來越多的人不再把 JS 當作一門腳本語言,實際上 “解釋執行” 和 “編譯執行” 并非是語言本身的特性,而是 編譯器/解釋器/引擎 需要做的事情,因此 腳本語言/非腳本語言 這種分類方式其實并不嚴謹,
現代瀏覽器的顛覆性革新之一就是使用 JIT(Just In Time)技術,對一段 JS 代碼動態地選擇究竟使用解釋還是編譯執行,所以忘掉 JavaScript 叫 Script 這件事吧,(另外其實 Java 也采用了類似的技術,使得它可以被解釋執行)
2 JS 面向物件
2.1 面向物件與基于物件
在 JavaScript 誕生至今的二十五六年里,對于它到底是不是面向物件的編程語言這件事經常有爭議,也有不少人強調,JS 不是 “面向物件(Object-Oriented)”,而是 “基于物件(Object-Based)”,
這個問題,怎么說呢,一部分是文字游戲,一部分又涉及語言的本質,需要認真理解,
首先,ECMAScript 規范中明確指出 ES 是面向物件的編程語言,
ECMAScript is an object-oriented programming language for performing computations and manipulating computational objects within a host environment.
當然,我們可以暫且認為這只代表了撰寫規范的人的想法,究竟是不是面向物件,還要看面向物件的定義又是什么,
oh,不好意思,面向物件至今仍然沒有統一的定義,
實際上計算機領域里少有公理一般的概念,同一個術語在一個時期里并存多種解釋的情況是廣泛存在的,這些解釋里有的認可度高,有的認可度低,有的更抽象,有的更貼切,有的全面一些,有些片面一些,
如何回答什么是面向物件,與問題的語境密切相關,在 Java 領域,你就得咬死面向物件三大特性——封裝、繼承、多型,最近一朋友去面位元組,反饋還是有這種問題,他稱之為 “Java 八股”,😂
說白了就是,一部分工程師(以 Java 為代表)認為 “面向物件” 是一個內涵確定的概念,判斷一個語言是否是面向物件,存在一組必要條件,不符合其中之一,就不是面向物件,即使有物件這個東西在,也只能稱為 “基于物件”,
然而面向物件,其實屬于不斷被豐富又不斷被抽象的一個概念,
豐富,意思是不斷地有新的特性被加入,這些特性使得面向物件這種設計能夠展現更大的能力,
而抽象,意思是面向物件的核心概念在收斂,從而擴大了滿足面向物件基本要求的語言范圍,
但作為前端工程師,對面向物件這個問題就可以認識地更靈活一些,面向物件語言的基本條件就是,它是 “面向” -> “物件” 的,面向,就是針對或以···為主體的意思,而物件這個東西,我們認為它具有以下特征:
- 唯一標識性:每個物件是唯一且可以標識的,即使看起來完全相同,也并非同一個
- 物件有狀態:物件具有狀態,同一物件可能處于不同狀態之下
- 物件具有行為:即物件的狀態,可能因為它的行為產生變遷
“物件” 又是一個可能變化的概念,但上述特征是比較貼近我們對現實世界中的事物的認知的本質的,因此我們姑且可以遵循這個定義去判斷一個語言是否實作了完備的物件系統,
對于 JS 來說,每個物件在記憶體中都有獨立的地址,且可以通過物件名訪問,符合唯一標識性,JS 的物件有狀態屬性、有行為屬性,符合后兩個特征,它的物件系統是完備的,
只不過,JS 走了一條與 C++、Java 等老大哥不同的道路,那就是基于 “原型” 來描述物件,
2.1 基于原型的面向物件系統
在面向物件的世界里,使用 “類(Class)” 這個概念描述物件是如此成功,以至于人們認為面向物件一定要有類這個東西,
然而,JS 的設計者,Brendan 老哥使用了另一套理念去實作物件系統,那就是基于 “原型” 描述物件,
我們前面提到,JS 在設計之初被要求貼近 Java 的語法,因此 Brendan “被迫” 引入了一些語言特性,使得它能夠用類似 Java 語言的方式去操縱物件,這些特性只是一種模擬,或者說是語法糖,并沒有運行時的支撐,
什么意思呢?Java 的每個類都是一種真正存在的型別,而 JS 中的物件均屬于 Object 這一種型別,所謂的 Class 只不過是物件的一個私有屬性,
而且,JS 初期對 “類系統” 的模擬主要依靠 new、this 等關鍵字,不但無法模仿繼承等關鍵特性,還存在一些反直覺的現象,而崇尚類系統的開發者們又折騰出了不同的解決方案,使得 JS 中對物件的使用五花八門,造成了許多的麻煩,直到 ES 6 問世,提供了 class 關鍵字來定義類,才使得 JS 對類的模擬方式終于統一起來,
但是我們大可不必非要去模仿類系統,對于 ES 6 之后版本的 JS 來說,使用原型系統本身的特性是十分自然的,
原型這個概念乍一看不太好理解,感覺背后有很深的含義在,但其實原型就是原型的字面意思,
在 Java 中,假如我們想要獲得一個英短金漸層物件,需要先定義一個英短金漸層類,這個類可能繼承自貓類,然后根據類 new 一個物件出來,
而 JS 中,我們可以直接根據已經存在的英短銀漸層這個原型(也是一個物件)去創建金漸層物件,不需要再定義一個類,物件與原型之間也不必有抽象層次的限制,只需要描述與原型的區別即可,
而且,從運行時的角度來說,根據原型創建的物件不需要保存原型的屬性,但卻可以順著 “原型鏈” 訪問自己沒有而原型有的屬性,
什么是原型鏈?一個屬性如果物件沒有,就會根據 [[prototype]] 欄位訪問原型,查找原型中的欄位,在 JS 中,所有物件的原型都可以追溯到 Null,到 Null,或者說其實是 Object,但 Object 本身的原型是 Null,
因此,我們也可以通過原型物件控制所有基于它實作的物件的行為,這是基于類的系統所不具備的能力,JS 也算是在一定程度上證明了基于原型這條路一樣走得通,孰優孰劣目前還下不了定論,
在語法層面,今天的 JS 有四套操縱物件的方式:
- {} / . / [] / Obejct.defineProperty,不依賴類或原型創建物件、訪問屬性、定義新屬性的基礎方法
- Obejct.create / Object.setPrototypeOf / Object.getPrototypeOf,基于原型的描述方法
- new / class / extends,模擬類的方式
- new / function /prototype,原始的模擬類的方式,ES 3 版本之后不推薦使用
這就造成了同樣的一門語言,不同的公司、不同的人寫起來可能完全是不同的風格,新手可能會因為這種現象大為頭疼,但是我們應該認識到,幾種寫法背后其實是同一套機制在支撐,
好了,這篇文章我們先從 JS 原型系統這么一個關鍵點切入,踏出理解 JS 本質的第一步,在之后的文章中,我們會繼續深入剖析 JS 的運行時,
下一步
下一篇文章,我們就來詳細探討一下 JavaScript 的型別系統,理解各種型別在記憶體中的組織方式以及使用時的注意事項,
記憶拾遺:
JS 這門語言,有太多的眾說紛紜、似是而非,我們雖然可以通過 ECMA 標準來論證真偽,但有時候一些錯誤的說法在實踐上卻可以取得正確的結果,因此我們自己可能都感覺不出來有問題,
也是受到 winter 大神的啟發,我才意識到理解 JS 的本質應該從運行時入手,縱使語法特性不斷迭代,JS 的運行時依舊遵循著最初的設計路線,
然而,各類面經又熱衷于傳遞拗口的語法層面的概念,大家如果自己參與過出題這種活動就會明白,有些題的出處可能就是哪個人在博客或者什么地方隨便說了一句話,被出題的人搜到了拿來湊數,沒想到一傳十十傳百,最后比定義還像定義,你不知道還顯得像個菜雞,
網上嚴謹一些的教程,比如 MDN,大部分時候解釋的是對的,但就是遇到稍微復雜一點的問題,看著感覺十分費勁,這也是因為 MDN 的中文并非原創,而是從英文原版翻譯而來,首先外國人的思維模式跟我們有出入,再加上可能并不那么專業的翻譯(MDN 是一個開源專案),相當于被加了兩層 debuff,
所以有時候我們學習知識確實比英語國家要費勁一些,希望有一天我們能在更多領域讓別人試圖理解我們的意思吧,嗯,從這個層面來說,對尤雨溪大神,我真的 respect,同叫 Evan,我很慚愧,😝
年后的第一個三天小長假,大半天算是陪各位度過了,明天好好休息一天,下周見,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/272505.html
標籤:其他
