冰与火之歌: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代表。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/3P1Watv3Iys9LJkAHZaW
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券