最近几年以来,伴随着各个端平台的迅猛发展,以 TypeScript、Swift、Kotlin 和 Dart 为代表的新一代应用编程语言纷纷浮现。群雄环伺之下,JavaScript 也在不断演进。在今天正在深圳召开的 GMTC2019 全球大前端技术大会上,360 高级前端架构师贺师俊发表《JavaScript 的困境与挑战》的主题演讲,分析 JavaScript 目前面对的问题以及下一步的发展趋势。
我从 1998 年就开始写 JavaScript 了,那时候做的是 IE4,在座很多人可能没有用过这个东西。所以我经历了整个时代的变化。
在过去十多年里面,我经常出来讲 JavaScript 的内容,2010 年我就讲过 ES5 的话题,我当时对 JavaScript 的发展做了判断:发展方向可能有三个:API 的扩展和标准化;通用化;还有适应于 PITL(programming-in-the-large)。
2014 年我也做过演讲,当时对 ES6 做了一些总结:
在 2015 年到 2017 年,我做过一个演讲,题目叫做《JS——世界第一程式设计语言》。在这个演讲里面,我特别提到:根据当时在推特上的统计,有 33% 的人直接在生产环境使用 Babel stage0/1 presets。
传统上 JavaScript 是一个非常难以升级的语言,因为要保持线上兼容性,但是有了 Babel 之后,就变成了永远在使用最新特性。从生态上来讲,JavaScript 有浏览器,有非常多的大公司一起在这个委员会里,有世界上最大的开发者生态。
我为什么把这个拿出来说一下呢?之前的演讲,我觉得其实比较乐观,某种意义上是立了 flag。但当时 JS 已经有一些隐忧浮现了,最直接的体现是什么呢?就是所谓 js fatigue——”学不动了“。不单单包括语言,框架、工具也不断地改变,我们会觉得要学的东西很多。
当然如果大家看最近一两年会发现框架也好、工具也好,整个演进已经变慢了,已经稳定了、成熟了,如果今天我还说学不动的话,很多情况下可能会指语言本身。比如 JavaScript 一直在加新语言特性,2015 年之后,每一年都会发一个新的带年份版本的标准,每一年大概会加六到八个新的提案。包括 ES2020 也已经定了,大概会增八到九个新特性。所以这个并没有慢下来。
那为什么我们会有所谓的 JavaScript 学不动的感觉?我理解这个事情是一个边际效用下降。原本你学一个东西马上会给你带来非常大的收益,但是现在你好像每学一个新的框架,或者学一个新的语言特性,在收益上可能要打一个问号。
如果你学的东西并不能给你直接带来一个非常强的收益,包括你在生产环境里面,你觉得也不是很多地方能用到它,那么它的整个性价比可能就会下降,所以你就会发出一个好像学不动的感叹。
所以我今天的内容,其实就是在反思,经过狂飙后,我们停下来看一看我们眼前所遇到的问题。在今年 6 月份,也就是在北京站的 GMTC 上我做过演讲,叫做《前端开发编程语言的过去、现在和未来》,讲了 TS、JS 未来面临的挑战。简单来讲就是两句话:TS 背着 JS 的包袱;JS 背着历史的包袱。
很多人认为我只要 TypeScript 就好了,但是大家要理解,TypeScript 在设计目标上要全兼容,所以如果 JavaScript 有什么问题的话,TypeScript 也跑不了。
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 是对生态非常重要的东西。大家知道我们之前都用 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。所以会遇到非常多的矛盾问题。
讲了这么多,可能最重要的问题就是:到底我们整个发展有没有 Roadmap?答案是没有。这就是委员会里面的一个问题,进一步来讲,它也没有明确的主导者,如果没有 Roadmap,全局上怎么解决这个事情就不好说了。
所以我们今天进展到这样一个地步——JavaScript 是非常成功的语言,但到现在是一个转折点,我们要停下来看一看:它再往下怎么样发展才会更好?
嘉宾介绍:
贺师俊(网名 Hax),360 高级前端架构师,十多年来一直活跃在前端和 JavaScript 社区。对多项 Web 标准有微小贡献,对 Groovy 语言并间接对 Swift 语言有微小贡献,近年来参与了诸多 ECMAScript 新草案的讨论。2019 年 7 月起成为 360 的 TC39 代表。