五个强大的 JavaScript 特性技巧

作者 | Alexander T. Williams

译者 | 明知山

策划 | Tina

JavaScript 是现代 Web 开发中一个必不可少的工具,它不断发展和演变,不断建立新的标准。在本文中,我们专注于介绍五种前沿的 JavaScript 技术,向开发者展示构建高交互性和高性能动态 Web 应用程序的创新性方法。从单子(Monad)到模式匹配,我们将带你了解最新、最伟大、适用于高级开发者的 JS 技术。

JavaScript 因其灵活性获得了巨大普及,并成为世界上使用最为广泛的编程语言。JS 通常被用于构建具有高交互性的动态 Web 应用程序 —— 例如实时更新、直观、功能丰富的用户界面等。此外,JavaScript 还允许应用程序跨平台运行。

JS 可用于各种项目,例如为电子商务服务提供支持或制作动画和手机游戏。然而,这只是 JavaScript 能力的一个缩影。我们还看到 JS 被用于企业环境中,特别是在关键的 ERP 过程中,如 SAP 人员增补,它允许在原生 Web 平台之上创建自定义仪表盘和 UI。

许多领先的平台,如 Facebook,使用了开源用户界面框架 React Native,它就是基于 JavaScript 构建的。这使得他们能够在使用单一代码库的情况下构建可同时在 iOS 和 Android (甚至是 Apple Vision Pro)上运行的移动应用程序。其结果就是开发时间大大缩短,使用的资源更少,所有平台和设备上的用户体验都保持一致。

服务器端运行时环境(如 Node.js)让 JavaScript 可以在 Web 浏览器之外运行,这进一步提升了应用程序的可扩展性和部署可能性。为了使 JS 更加通用和强大,大量 JS 兼容的 API 将 Web 应用程序链接到了外部服务。

最后,JavaScript 生态系统有了众多强大的库和框架,这有助于简化和加速开发,使开发者能够选择预先写好的代码来执行特定的功能。

我们选择了开发者在使用的五种前沿的 JavaScript 技术,希望可以帮你解决一些开发问题,构建更有效、用户友好的应用程序。

单子可用于组合需要上下文的函数,并返回一个值,它在简化错误管理和减少意外结果方面非常有效。

单子就是让代码中的函数组合变得尽可能简单。它们通常用于构建对精度要求很高的企业级应用程序。单子使代码更易于管理,从而可以实现更复杂的回调、嵌套条件分支等。本质上,单子就是让代码中的函数组合尽可能简单。

单子可以分为三种类型的函数组合:

函数映射:a => b

带上下文的函数映射:Functor(a) => Functor(b)

单子扁平化(从上下文中解包值)并带上下文的函数映射:Monad(Monad(a)) => Monad(b)

函数组合是创建函数管道的基础,可以实现高效的数据流。管道的第一个阶段是输入,最后一个阶段是经过转换的输出。为了实现管道,每一个阶段都必须能够预测到前一个阶段将返回的数据类型。

这时候单子就可以发挥作用,它们能够有效地映射函数并创建智能的管道。它们 与 Promise 的工作方式类似,并且可以无缝地放在一起使用。

下面是一个使用单子从 异步 API 获取用户数据,然后将用户数据传给另一个异步 API 执行计算的示例:

复制代码

当开发者希望代码更简洁、更具表达性时,通常会使用声明式方法。JavaScript 的声明式编程关注的是代码的总体目标,而不是如何实现这些目标。这使得代码更简单、更易读,也更易于维护。当开发者希望代码更简洁、更具表达性以快速交付项目时,通常会使用声明式方法。

我们将声明式方法与命令式方法做一番比较:

命令式:

复制代码

声明式:

复制代码

服务器端缓存可用于根据使用情况自动伸缩资源。

缓存并不是什么新鲜事物,也不会被认为是什么前沿技术,但由于客户端和服务器端 Web 应用程序都可以使用缓存,因此它成了一个强大的提升性能的工具,特别是服务器端缓存可以通过加速数据检索来提高 Node.js 的性能。

我们来看一个使用内存缓存技术的例子:

复制代码

服务器端缓存可以用于根据使用情况自动伸缩资源。AWS Lambda、Azure Functions 或 Google Cloud Functions 可以通过编程动态调整服务,而 AWS JavaScript SDK 允许你监控使用情况、优化云成本和自动伸缩资源,确保只为所需资源付费。

不可变性是指不能被改变的东西。在 JavaScript(以及一般的编程)中,它指的是在设置后就不能被修改的值。由于应用程序不断变化和更新,不可变性看起来似乎是不必要的 —— 但事实并非如此。

不可变性可以减少调试和意外结果。

数据的不可变性很重要,因为它有助于通过代码库实现一致性,并有助于状态管理。创建新值,而不是修改已存在的值,这让事情变得更可预测,从而减少错误 —— 比如当数据结构被意外修改时发生的错误。这样可以减少调试和意外结果。

将不可变性用于命名变量的示例:

复制代码

不可变性是一项重要的技术,已经越来越多地被用在数据科学任务和人工智能项目中,这再次证明 JavaScript 无法处理的问题越来越少了

模式匹配是一种条件分支,它可以在绑定变量的同时简洁地匹配数据结构模式。在编写 XSLT 样式表转换 XML 文档时,通常可以使用模式匹配。

模式匹配比标准的 switch 语句高效得多。

当需要匹配一个值与给定模式时,模式匹配比标准的 switch 语句更加高效,它提供了更多的控制,让开发者可以编写更复杂的表达式。

下面是使用 JUnify 库实现阶乘函数的示例,其中就用到了匹配模块:

复制代码

JavaScript 不仅灵活、多功能性,还可以部署在各种平台上。通过使用上述技术,开发者可以为他们的应用程序开发出功能强大且简洁的代码。

原文链接:

https://thenewstack.io/top-5-cutting-edge-javascript-techniques/

声明:本文为 InfoQ 翻译整理,未经许可禁止转载。

原文链接:

图解实例讲解JavaScript算法,让你彻底搞懂

你好程序员,我们大多数人都害怕算法,并且从未开始学习它。但我们不应该害怕它。算法只是解决问题的步骤。

今天让我们以简单和说明性的方式介绍主要算法。

不要试图记住它们,算法更多的是解决问题。所以,坐下来用纸和笔。目录中的术语可能看起来很吓人,但只要和我在一起,我保证会以尽可能简单的方式解释所有内容。

目 录

  • 大 O 表示法
    • 理解大 O 符号
  • 算法
    • 什么是算法,为什么要关心?
    • 递归
    • 线性搜索算法
    • 二进制搜索算法
    • 朴素搜索算法
    • KMP算法
    • 冒泡排序
    • 合并排序
    • 快速排序
    • 基数排序

Big O Notation 是一种表示算法时间和空间复杂度的方法。

  • 时间复杂度:算法完成执行所花费的时间。
  • 空间复杂度:算法占用的内存。

表示算法时间复杂度的表达式(符号)很少。

  • O(1):常数时间复杂度。这是理想情况。
  • O(log n):对数时间复杂度。如果`log(n) = x`那么它与`10^x`
  • O(n):线性时间复杂度。时间随着输入的数量呈线性增加。例如,如果一个输入需要 1 毫秒,则 4 个输入将花费 4 毫秒来执行算法。
  • O(n^2):二次时间复杂度。这主要发生在嵌套循环的情况下。
  • O(n!):阶乘时间复杂度。这是最坏的情况,应该避免。

您应该尝试编写您的算法,使其可以用前 3 个符号表示。最后两个应尽可能避免。

您希望尽可能地降低复杂性,最好避免超过 O(n) 的复杂性。

在本文的后续部分中,您将看到每种表示法的示例。现在,这就是您需要知道的全部内容。

解决问题的方法,或者我们可以说解决问题的步骤、过程或规则集被称为算法。

例如:用于查找与搜索字符串相关的数据的搜索引擎算法。

作为一名程序员,您会遇到许多需要使用这些算法解决的问题。因此,如果您已经了解它们会更好。

调用自身的函数是递归的。将其视为循环的替代方案。

在上面的代码片段中,请看第 3 行recursiveFnrecursiveFn 本身中被调用。正如我之前提到的,递归是循环的替代方法。

那么,这个函数到底要运行多少次呢?

好吧,这将创建一个无限循环,因为在任何时候都无法阻止它。

假设我们只需要运行循环 10 次。在第 11 次迭代函数应该返回。这将停止循环。

在上面的代码片段中,第 4 行返回并在计数为 10 时停止循环。

现在让我们看一个更现实的例子。我们的任务是从给定的数组中返回奇数数组。这可以通过多种方式实现,包括 for-loopArray.filter 方法等

但是为了展示递归的使用,我将使用 helperRecursive 函数。

这里的递归函数是helperRecursiveFn

例如:第一次 helperRecursiveFn 将被调用[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。下次它将被调用,[2, 3, 4, 5, 6, 7, 8, 9, 10]依此类推,直到数组长度为 0。

线性搜索算法非常简单。假设您需要查找给定数组中是否存在某个数字。

您将运行一个简单的 for 循环并检查每个元素,直到找到您要查找的元素。

这就是线性搜索算法。您以线性方式逐一搜索数组中的每个元素。

只有一个 for 循环会运行 n 次。其中 n(在最坏的情况下)是给定数组的长度。这里的迭代次数(在最坏的情况下)与输入(长度数组)成正比。

因此,线性搜索算法的时间复杂度是线性时间复杂度:O(n)。

在线性搜索中,您一次可以消除一个元素。但是使用二进制搜索算法,您可以一次消除多个元素。这就是二分查找比线性查找快的原因。

这里要注意的一点是,二分查找只对排序好的数组有效。

该算法遵循分而治之的方法。让我们在 [2, 3, 6, 8, 10, 12] 中找到 8 的索引。

第 1 步:找到数组的中间索引。

第 2 步:检查middleIndex元素是否 > 8。如果是,则说明 8 在middleIndex的左侧。因此,将lastIndex更改为 (middleIndex – 1)。

第 3 步:否则如果 middleIndex元素 < 8。这意味着 8 在middleIndex的右边。因此,将firstIndex更改为 (middleIndex+ 1);

第 4 步:每次迭代都会根据新的firstIndexlastIndex 再次设置middleIndex

让我们以代码格式一起查看所有这些步骤。

这是上述代码的可视化表示。

步骤1

第2步

步骤:3

只有一个 while 循环会运行 n 次。但是这里的迭代次数不依赖于输入(数组长度)。

因此,二进制搜索算法的时间复杂度是对数时间复杂度:O(log n)。你可以检查 O 符号图。O(log n) 比 O(n) 快。

朴素搜索算法用于查找字符串是否包含给定的子字符串。例如,检查“helloworld”是否包含子字符串“owo”。

  1. 首先循环主字符串(“helloworld”)。
  2. 在子字符串 (\”owo\”) 上运行嵌套循环。
  3. 如果字符不匹配,则中断内部循环,否则继续循环。
  4. 如果内循环完成并匹配,则返回 true 否则继续外循环。

这是一个视觉表示。

这是代码中的实现。

现在,让我们试着理解上面的代码。

  • 在第 2 行,如果 subString长度大于 mainString长度,则返回false
  • 在第 4 行,开始在mainString 上循环。
  • 在第 5 行,在subString上开始嵌套循环。
  • 在第 6 行,如果没有找到匹配项,则中断内循环,并继续进行外循环的下一次迭代。
  • 在第 7 行,在内循环的最后一次迭代中返回true

朴素搜索的时间复杂度

循环中有循环(嵌套循环)。两个循环都运行 n 次。因此,朴素搜索算法的时间复杂度是 (n * n) Quadratic Time Complexity: O(n^2)。

如上文所述,如果可能,应避免超过 O(n) 的任何时间复杂度。在下一个算法中,我们将看到一种时间复杂度更低的更好方法。

KMP算法是一种模式识别算法,理解起来有点费劲。好的,让我们尝试查找字符串“abcabcabspl”是否包含子字符串“abcabs”。

如果我们尝试使用Naive Search Algo来解决这个问题,它将匹配前 5 个字符但不匹配第 6 个字符。我们将不得不从下一次迭代重新开始,我们将失去上一次迭代的所有进展。

所以,为了保存我们的进度并使用它,我们必须使用一个叫做 LPS 表的东西。现在在我们匹配的字符串“abcab”中,我们将找到最长的相同前缀和后缀。

在这里,在我们的字符串“abcab”中,“ab”是最长的相同前缀和后缀。

现在,我们将从索引 5(对于主字符串)开始下一次搜索迭代。我们从之前的迭代中保存了两个字符。

为了找出前缀、后缀以及从哪里开始下一次迭代,我们使用 LPS 表。

我们的子串(“abcabs”)的 LPS 是“0 0 0 1 2 0”。

下面是如何计算 LPS 表。

下面是使用 LPS 表的代码实现。

只有一个循环运行 n 次。因此,KMP 算法的时间复杂度是线性时间复杂度:O(n)。

请注意,与 Naive 搜索算法相比,时间复杂度是如何提高的。

排序意味着按升序或降序重新排列数据。冒泡排序是众多排序算法中的一种。

在冒泡排序算法中,我们通过将每个数字与前一个数字进行比较,将较大的数字交换到末尾。这是一个视觉表示。

冒泡排序代码实现。

让我们试着理解上面的代码。

  • 从带有变量 i 的数组末尾开始循环。
  • 以变量 j 开始内循环,直到 (i – 1)。
  • 如果 array[j] > array[j + 1] 交换它们。
  • 返回排序数组。

有一个嵌套循环,两个循环都运行 n 次,因此该算法的时间复杂度为 (n * n) 即二次时间复杂度 O(n^2)。

合并排序算法遵循分而治之的方法。它是两件事的结合——合并和排序。

在这个算法中,我们首先将主数组分成多个单独的排序数组。

然后我们将单独排序的元素合并到最终数组中。

让我们看看代码中的实现。

合并排序数组

上面的代码将两个排序数组合并为一个新的排序数组。

合并排序算法

上述算法使用递归将数组划分为多个单元素数组。

让我们尝试计算归并排序算法的时间复杂度。因此,以我们之前的示例([6, 3, 5, 2])为例,将其划分为多个单元素数组需要 2 个步骤。

现在,如果我们将数组 (8) 的长度加倍,则需要 3 个步骤来划分 – (2^3)。意味着将数组长度加倍并没有使步骤加倍。

因此合并排序算法的时间复杂度是对数时间复杂度 O(log n)。

快速排序是最快的排序算法之一。在快速排序中,我们选择一个称为 pivot 的元素,我们会将所有元素(小于 pivot)移动到 pivot 的左侧。

视觉表示。

我们将对枢轴左侧和右侧的数组重复此过程,直到对数组进行排序。

代码实现:枢轴效用

上面的代码标识了 pivot 的正确位置并返回该位置索引。

上面的代码使用递归将枢轴移动到左右枢轴数组的正确位置。

最佳情况:对数时间复杂度 – O(n log n)

平均情况:对数时间复杂度 – O(n log n)

最坏情况:O(n^2)

基数排序也称为桶排序算法。

这里首先我们构建 10 个索引桶,从 0 到 9。然后我们取每个数字中的最后一个字符,并将该数字推送到相应的桶中。检索新顺序并重复每个数字的倒数第二个字符。

不断重复上述过程,直到数组排序完毕。

在代码中实现。

// Count Digits: 下面的代码计算给定元素的位数。

// 获取数字:下面的代码从右边给出索引 i 处的数字。

// MaxDigit:下面的代码片段找到了最大位数的数字。

// Radix 算法:利用上述所有代码段对数组进行排序。

有一个嵌套的for循环,我们知道嵌套的for循环的时间复杂度是O(n^2)。但是在这种情况下,for 循环都不会运行 n 次。

外循环运行 k (maxDigitCount) 次,内循环运行 m (数组长度) 次。因此,基数排序的时间复杂度为 O(kxm) – (其中 kxm = n)线性时间复杂度 O(n)

算法和计算机原理是如今在企业面试和进入互联网大厂必要的技能,如果你正在学前端,你也可以来咨询我们,我们的JavaScript系统课程中针对算法和基础原理也有详细的视频简介!!

冰与火之歌:JavaScript 的困境与挑战

最近几年以来,伴随着各个端平台的迅猛发展,以 TypeScript、Swift、Kotlin 和 Dart 为代表的新一代应用编程语言纷纷浮现。群雄环伺之下,JavaScript 也在不断演进。在今天正在深圳召开的 GMTC2019 全球大前端技术大会上,360 高级前端架构师贺师俊发表《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 有浏览器,有非常多的大公司一起在这个委员会里,有世界上最大的开发者生态。

我为什么把这个拿出来说一下呢?之前的演讲,我觉得其实比较乐观,某种意义上是立了 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 代表。

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。