冰與火之歌:JavaScript 的困境與挑戰

最近幾年以來,伴隨着各個端平臺的迅猛發展,以 TypeScript、Swift、Kotlin 和 Dart 爲代表的新一代應用編程語言紛紛浮現。羣雄環伺之下,JavaScript 也在不斷演進。在今天正在深圳召開的GMTC2019 全球大前端技術大會上,360高級前端架構師賀師俊發表《JavaScript 的困境與挑戰》的主題演講,分析 JavaScript 目前面對的問題以及下一步的發展趨勢。

JavaScript的時代變遷

我從1998年就開始寫JavaScript了,那時候做的是IE4,在座很多人可能沒有用過這個東西。所以我經歷了整個時代的變化。

  • 1995年到1999年,是前ES3的時代;
  • 2000年到2010年,是ES3的時代,我們在整個開發當中使用的都是ES3的版本;
  • 2008年到2016年,是Harmony時代,今天我們基於JavaScript的開發,主要工具也好、方式也好,都是在Harmony時代積累下來的;
  • 2014年到2020年,就已經進入了ES6/Babel的時代,Babel在裏面扮演了非常重要的角色,使我們可以在生產環境裏面去使用ES6的特性;
  • 最後一個是我個人的預期:我認爲我們下面新的時代,可能就是TS、JS共同構成的生態新時代。

在過去十多年裏面,我經常出來講JavaScript的內容,2010年我就講過ES5的話題,我當時對JavaScript的發展做了判斷:發展方向可能有三個:API的擴展和標準化;通用化;還有適應於PITL(programming-in-the-large)。

2014年我也做過演講,當時對ES6做了一些總結:

  • Module 使得 JS 生態系統能重新統一;
  • Module / Class 等讓 JS 更適合大型編程;
  • Promise 等將最佳實踐標準化;
  • Generator / Proxy 等提供了現代語言所需的重要設施;
  • Arrow function / Destructring 等不僅是語法糖,而且填了長期以來的一些坑。

在2015年到2017年,我做過一個演講,題目叫做《JS——世界第一程式設計語言》。在這個演講裏面,我特別提到:根據當時在推特上的統計,有33%的人直接在生產環境使用Babel stage0/1 presets。

傳統上JavaScript是一個非常難以升級的語言,因爲要保持線上兼容性,但是有了Babel之後,就變成了永遠在使用最新特性。從生態上來講,JavaScript有瀏覽器,有非常多的大公司一起在這個委員會裏,有世界上最大的開發者生態。

爲什麼會覺得JavaScript學不動了?

我爲什麼把這個拿出來說一下呢?之前的演講,我覺得其實比較樂觀,某種意義上是立了flag。但當時JS已經有一些隱憂浮現了,最直接的體現是什麼呢?就是所謂js fatigue——”學不動了“。不單單包括語言,框架、工具也不斷地改變,我們會覺得要學的東西很多。

當然如果大家看最近一兩年會發現框架也好、工具也好,整個演進已經變慢了,已經穩定了、成熟了,如果今天我還說學不動的話,很多情況下可能會指語言本身。比如JavaScript一直在加新語言特性,2015年之後,每一年都會發一個新的帶年份版本的標準,每一年大概會加六到八個新的提案。包括ES2020也已經定了,大概會增八到九個新特性。所以這個並沒有慢下來。

那爲什麼我們會有所謂的JavaScript學不動的感覺?我理解這個事情是一個邊際效用下降。原本你學一個東西馬上會給你帶來非常大的收益,但是現在你好像每學一個新的框架,或者學一個新的語言特性,在收益上可能要打一個問號。

如果你學的東西並不能給你直接帶來一個非常強的收益,包括你在生產環境裏面,你覺得也不是很多地方能用到它,那麼它的整個性價比可能就會下降,所以你就會發出一個好像學不動的感嘆。

2020:JavaScript 的困境與挑戰

所以我今天的內容,其實就是在反思,經過狂飆後,我們停下來看一看我們眼前所遇到的問題。在今年6月份,也就是在北京站的GMTC上我做過演講,叫做《前端開發編程語言的過去、現在和未來》,講了TS、JS未來面臨的挑戰。簡單來講就是兩句話:TS揹着JS的包袱;JS揹着歷史的包袱。

很多人認爲我只要TypeScript就好了,但是大家要理解,TypeScript在設計目標上要全兼容,所以如果JavaScript有什麼問題的話,TypeScript也跑不了。

JavaScript的新包袱和歷史包袱

JavaScript大家知道,揹着歷史包袱。歷史包袱不僅指老的問題,今天也改不掉,而且更重要的是,因爲JavaScript的歷史包袱導致它有一些新的設計,仍然會增加新的包袱:

第一個,JavaScript的應用場景非常多,導致JavaScript的開發者社區非常複雜,有非常巨大的差異性。當我們要去對JS進行改進或者要加入新東西的時候,不同社區的想法、需求可能會不一樣。怎麼做tradeoff?非常困難。

另外,還有像委員會語言的弊病,這個不用諱言比如C++就是明顯的委員會的語言,委員會的語言因爲參與的人多,不像很多由公司主導的語言,本身的發展比較有秩序。

歷史包袱我們可以展開講一下,歷史包袱簡單來講,最基本的就是,因爲JavaScript用了這麼多年,不能破壞它的兼容性,而且在所有語言裏面是最沒有辦法改的——我們有那麼多的網站跑到線上,加了任何東西都不能把原本的網站搞掛了,所以只能增加特性解決以前的問題。

比如箭頭函數,它加了之後解決了很多以前ES3時代的函數裏面的問題,也就是增加了一個所謂的Lexical this,但是不可避免的是使this的語義變得更加複雜。

另外一個例子,因爲我們這麼多年以來,整個JavaScript的發展,使得我們在真正的工程當中,會存在幾種不同的東西:一個叫工程方案;第二個叫事實標準;第三個才叫真的標準。這三個東西會長期共存,所以使得整個生態裏面會有些複雜的問題。

對於歷史包袱,我們現在的解決方法是Polyfill的方案。Polyfill有廣義Polyfill和狹義Polyfill,到底什麼叫Polyfill?Polyfill是已經成爲了標準了,然後我們把它做出來,在沒有實現的瀏覽器上可以用,這才叫Polyfill。如果你是一個實驗性的實現,你就不應該去修改global和prototype上的東西,因爲它其實會造成很多潛在的問題。但是如果已經是一個標準了,那麼去改並沒有什麼錯。但是這個問題就是,很多開發者並不能區分這個,而且在我們的日常當中,當我們講Polyfill的時候也沒有刻意區分,但就會在生態中造成這樣一個非常嚴重的問題。

Babel帶來的挑戰

接下來講Babel,Babel是對生態非常重要的東西。大家知道我們之前都用presets,在v7的時候被取消了。主要問題是我們會無意中在Production中使用了unstable features,當它發生改變,如果你在生產環節裏用的話,對你本身來講就會是非常大的挑戰,對於標準本身來講也會有很大的挑戰。甚至到stage3都會有這樣的問題。但這個事情是雙刃劍,有很多東西你只有在真正的生產環境裏面才能得到真的反饋,不是說光寫一些demo就可以看出來什麼問題。

第二個問題是,很多人說我今天寫的不是JavaScript,而是BabelScript,這些特性是自己定製的語法特性。這裏面有個特別的例子,是babel-plugin-macros,這個插件非常好,原本要單獨寫插件,現在可以用macros去寫,它相對是更顯性的方式,明確的知道我這裏用了自定義的東西。但是本質上,當你導入一個像函數的東西,它其實並不是函數。這個仍然存在一個挑戰,對很多開發者來講,是否理解是函數和不是函數的差異?

再看下TypeScript,TypeScript的設計原則是隻有到Stage3纔可以,TypeScript是從2012年就誕生了,2012年ES6還沒有定案,所以TypeScript當年的很多特性都是ES6還沒有定案的。如果我們看一下,這是當年TypeScript增加的東西,本質上TypeScript只是加了類型,但是在當年,因爲2012年ES6還沒有定案,除了類型之外,這些東西是加進去的東西。

這些東西稍微看一下,arrow function、class沒有什麼問題,es module就有問題了。它的語法不一樣,這已經開始有坑了。decorator也有問題,這是現在的提案,和TypeScript的提案非常不一樣,所以TypeScript需要一個編譯開關才能使用,這也是decorator不太好的狀態。包括最後這兩個,private property和public property語法語義和現在的stage3的class fields完全不一樣,對TypeScript來說非常痛苦。

然後我們再來看一個例子,叫做ESLint。它採用的方針更加保守了,它只是在Stage4的時候纔會做跟這些新特性有關的新規則。這種方式也避免了前面很多問題。

ESLint會導致兩個問題:第一個我們很多人已經在做更早的Stage3的東西,但Stage3的東西也需要保護,而且更需要保護;第二個問題就是,在制訂標準的時候,缺乏lint社區的反饋。

有一個有趣的事情是,在整個ES6的發展當中,很多的提案是遵循一個叫做Maximally Minimal的設計哲學,最大化的最小化,我們只加那些最最重要的東西,如果翻譯過來的話叫先解決溫飽再考慮小康,所以ES6很多東西確實解決痛點,但很多時候ES6的東西並不能完全滿足你的所有需要。

比如ES6加了Map和Set,但這兩API只能滿足你最基本的需求。所以這也是一個雙刃劍,好的地方是,我們只有通過這樣的設計,才能在當時那麼快的把ES6做出來,否則可能永遠都做不出來。到底怎麼樣纔是真正滿足所有人需求?這個事情定義不清楚。

今天來講,我們會發現,很多時候我們溫飽都已經解決了,現在大家討論的都是小康問題。怎麼讓特性用得更好更爽。但這個問題就是前面講的,因爲開發者社區非常複雜,每個人的需求都不一樣,最大的問題是誰能代表開發者?這其實一個非常難以回答的問題。

舉個例子——Map.prototype.upsert(),看名字猜得出來嗎?估計猜不太出來。我個人認爲只有非常有經驗的開發者,纔可能比較需要這個東西。一般的開發者其實是不是真的需要,要打個問號。同時對新手來講,理解的成本也比較高。所以這裏有個問題,我們怎麼衡量成本和收益?其實是非常困難的。

最後,我講一下有爭議的事,比如說我們現在對Top-level Await、Class fields這兩個提案都不太滿意,當然我們技術上可以有很多的討論,但實際上這個事情一句話講就是改革進入深水區,就是好做的都做完了,剩下的都是難搞的。這兩個其實都是ES6時代遺留至今的問題。

最後再舉一個例子——Pipeline Operator。現在Pipeline有兩種不同的競爭提案,第一種叫F# Style,另外一種方式叫Smart style。所以這其實是兩難選擇,我們是希望更符合FP的主流還是更普適現有整個JavaScript的生態,這是非常難做的選擇。

還有Binary AST,也是一個提案,它相當於字節碼,它有個很大的好處就是整個加載速度會非常快的提升。所以這樣一個提案肯定是我們所有人都很喜歡的,特別是做Web的人會非常喜歡。但是這個現在也處於一個比較難推進的狀態,爲什麼呢?講一個很簡單的原因就是,當你有了Binary AST之後,所有語言特性都得考慮,在這裏面怎麼把它加進去,字節碼裏面怎麼把它加進去,所以困難程度直接翻番了。

大家看到很多問題都是有兩面性,這是改革進入深水區遇到的很多問題。包括不同平臺的需求,比如Web和Node的平臺,這兩個平臺的需求不一樣。

還有性能和動態性的矛盾,我們做一個東西既希望性能好,又希望符合JavaScript的傳統,這裏面最簡單的例子就是decorator。所以會遇到非常多的矛盾問題。

JavaScript的轉折點:未來何去何從?

講了這麼多,可能最重要的問題就是:到底我們整個發展有沒有Roadmap?答案是沒有。這就是委員會裏面的一個問題,進一步來講,它也沒有明確的主導者,如果沒有Roadmap,全局上怎麼解決這個事情就不好說了。

所以我們今天進展到這樣一個地步——JavaScript是非常成功的語言,但到現在是一個轉折點,我們要停下來看一看:它再往下怎麼樣發展纔會更好?

嘉賓介紹:

賀師俊(網名 Hax),360 高級前端架構師,十多年來一直活躍在前端和 JavaScript 社區。對多項 Web 標準有微小貢獻,對 Groovy 語言並間接對 Swift 語言有微小貢獻,近年來參與了諸多 ECMAScript 新草案的討論。2019年7月起成爲360的TC39代表。

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