文已经原作者授权
你可能会经常听到一些人在抱怨 JS 很奇怪,有时甚至是一文不值。之所以有这种想法,是因为他们不太了解 JS 背后的运作方式。我也觉得 JS 在某些情况处理方式与其它语言不太一样,但这并不能怪它,它也只是以自己的方式展现给大家而已。
如果,你热爱一门编程语言,那么应该就会想深入了解并逐个掌握它的概念。
这里列出了36个JavaScript概念,你需要掌握这些概念才能成为一个更懂 JS 的前端开发者。
我们都知道堆栈溢出,但是你知道堆栈溢出是由什么原因导致的吗? 堆栈溢出是与调用堆栈一些操作错误相关联的。
理解了调用堆栈,你就会清楚解像是JS 这们的编程语言是如何执行的。
const foo = "bar";
foo.length; // 3
foo === "bar"; // true
这里,我们将值bar分配给常量foo时,它属于原始类型string。这个每个人都知道。但是各位少侠想没想过一个问题,string是基本数据类型,怎么能调用方法了?
奇怪吗? 不。
这个特性称为自动装箱。每当读取一个基本类型的时候,JS 后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。
还是拿上面的例子开始:
const foo = "bar";
foo.length; // 3
foo === "bar"; // true
变量 foo 是一个基本类型值,它不是对象,它不应该有方法。但是 JS 内部为我们完成了一系列处理(即装箱),使得它能够调用方法,实现的机制如下:
const foo = new String("bar");
foo.length
foo === 'bar'
foo = null
通过对原始数据类型有深入的了解,我们就应该知道这些“怪异”情况是如何发生的,以及它们背后的逻辑原因。
最近,我对**“引用传递"** 在 JS 是怎么工作的感到困惑。尽管我知道 C 和 Java 等语言中有“按引用传递”和“按值传递”的概念,但是我不确定它在 JS 中如何工作。
你是否知道分配给 JS 中非原始值的变量对该值的引用?引用指向存储值的内存位置。。
var arr1 = [1,2,3];
var arr2 = arr1;
arr2.push(10);
console.log(arr2);
//[1, 2, 3, 10]
console.log(arr1);
//[1, 2, 3, 10]
上面的示例中可以看到,对arr2所做的任何修改也将体现在arr1上。这是因为它们仅保存值对应的内存地址的引用,而非值本身。
通过了解值类型和引用类型的概念,你就会更好地了解如何为变量分配值和内存引用。
这个概念主要解释了隐式和显式类型强制之间的区别。这是前端开发中对 JS 迷惑少数几个领域之一。对于隐式强制转换的概念尤其如此,因为它对不同的数据类型以不同的方式表现。
这是 JS 面试中最常被考的。
Number('789') // 显式
+'789' // 隐式
789 != '456' // 隐式
9 > '5' // 隐式
10/null // 隐式
true | 0 // 隐式
掌握了类型显隐转换,恭喜你对 JS 了解就进一步了。
双等与三等,它们在大多数情况下在表面上看起来相同并且给出相同的结果,但是,它们有时候可能会给带来意想不到的错误。
为了了解这两亲兄弟的区别,我们可以借助 typeof 来查看被比较的值的类型。
typeof 3 // "number"
typeof "abc" // "string"
typeof {} // "object"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"
typeof [] // "object"
typeof null // "object"
作用域是 JS 中一个很重要的尴尬,JS 也一直在不断完善自己的作用域。根据Wissam的说法,作用域的简单定义是,编译器在需要时查找变量和函数。
了解作用域有助于我们有效地使用JavaScript。我们还需要了解全局作用域以及块和函数作用域,也称为词法作用域。JS 作用域一开始接触会感到很困惑,但是一旦你了解了事情的幕后原理,使用它就会非常令人兴奋。
JavaScript 程序是一系列可执行语句的集合。所谓语句,就是一个可执行的单元,通过该语句的执行,从而实现某种功能。通常一条语句占一行,并以分号结束。默认情况下,JavaScript 解释器按照语句的编写流程依次执行。如果要改变这种默认执行顺序,需要使用判断、循环等流程控制语句。
我们应该要知道 语句和声明 的区别,这对我们全面了解 JS 是很有帮助的。
IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。它主要用于避免污染全局作用域。后来,引入了ES6模块,为避免全局作用域的污染提供了一种标准方法,尽管有人认为它不是IIFE的直接替代。
通过理解IIFE和模块,你可以构建较少由于全局空间处理不当而导致的错误的应用程序。当然,使用模块,我们还可以做很多事情。
正如MDN文档所说,JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。
在并发模型中,消息队列用于处理最早的消息。只要有事件发生,便会加入消息队列中。通过理解这些概念,你可以更好地理解JS在底层是如何工作的,以及知道你的代码是如果运行的。
想在 JS 有计划的调用的函数,可以使用下面两个函数:
这些与前面的消息队列和事件处理程序的概念有些关联。因此,通过理解时间间隔方法,我们可以理解它们是如何工作的,并在我们的用例中有效地使用它们。
JavaScript引擎是执行 JS 代码的计算机程序或解释器。JS 引擎可以用多种语言编写。例如,驱动Chrome浏览器的V8引擎是用 c++ 编写的,而驱动Firefox浏览器的SpiderMonkey引擎是用 C 和 c++编 写的。
要想编写高效的代码,你必须了解所使用的 JS 引擎。使用webview的移动开发人员要特别注意这一点。
按位运算操作将值视为位(0和1),而不是十进制,十六进制或八进制数字。按位运算符对此类二进制表示形式执行其操作,但是它们返回标准JavaScript数值。
通常,很少会在代码中使用这些操作,但是它们确实有一些用例。比如,可以使用它们来查找偶数和奇数值,颜色转换,颜色提取等等。
通过全面了解这些按位操作,您可以很好地使用 WebGL 之类的技术,因为它包含许多像素操作。
我们大多数人都听说过文档对象模型(DOM),但只有少数人对此有深入的了解。你知道在浏览器中看到的不是DOM吗?而是渲染树,它实际上是DOM和CSSOM的组合。
通过理解DOM的工作方式、结构以及页面的渲染方式,我们就能够在 JS 的帮助下动态地操作web页面。这对于确保我们的应用程序具有高标准的性能尤为必要。
JavaScript 不是一种面向对象的语言。但是,为了模仿OOP属性,使用了构造函数。根据Tania的说法,“ JavaScript中的类实际上并没有提供其他功能,只是在原型和继承上提供语法糖,因为它们提供了更简洁,更优雅的语法。由于其他编程语言都使用类,因此 JS 中的类语法使开发人员在各种语言之间移动变得更加简单。”
工厂函数是不是返回对象的类或构造函数的函数。根据JS专家Eric Elliot的说法,“在JavaScript中,任何函数都可以返回一个新对象。如果它不是构造函数或类,则称为工厂函数。”
当开始开发规模更大的应用程序时,理解这两个概念是很有必要的。
就我个人而言,我认为对于一个JS开发人员来说,理解this 关键字是至关重要的。如果你不能正确地理解它,将来你开发的项目也会经常遇到this相关的问题。
如果你对this关键字很清楚,则可以看看apply,call和bind方法,这些都可以解决 this 指向引发的问题。
构造函数就像常规函数一样。但是它们有很多差异,函数名称以大写字母开头,并且只能由new运算符执行。具有OOP开发经验的程序员会熟悉new关键字。
为了正确识别对象的类型,我们使用instanceOf运算符。简单来说,它检查一个对象是否是另一个对象的实例。
这才助于你理解对象如何相互继承,继承是通过原型实现的。
这是 JS 中最令人困惑的概念之一,即使对于有十年经验的人来说也是如此。
JavaScript中的原型是在对象之间共享通用功能的机制。JavaScript中几乎所有对象都是Object的实例。对象会从Object.prototype继承所有属性和方法。
简单来说,原型是 JS 对象从中继承方法和属性的对象。
理解了原型,你就可以构建高效,快速的应用程序。
创建对象有很多方法。但是,大都会选择Object.create方法而不是new关键字。这是有原因的,因为 使用Object.create方法时,可以将现有对象用作新创建的对象的原型。这样就可以重用现有对象的属性和功能,有点像OOP中的继承概念。
使用Object.assign方法时,可以将可枚举的自身属性从一个或多个源对象复制到目标对象。在这种情况下,目标对象的原型不包含源对象的属性。这是这两种方法之间的主要区别。
通过了解对象创建的这三种方式,可以根据实际情况适当地使用它们,以创建效率更高的程序。
当涉及到数组操作时,这三种方法非常有用。它们可以在Array原型中找到。
如果你有一个数组,并且想对每个元素做一些事情,那么您可以使用map方法。
如果你有一个数组,并且想通过某些条件来过滤一些值时,则可以使用filter方法。
reduce() 方法对数组中的每个元素执行一个由你提供的reducer函数(升序执行),将其结果汇总为单个返回值。
典型的例子就是对数组的所有元素进行求和:
let numbers = [1,2,3,4,5,6]
const reduced = numbers.reduce( (accumulator, currentValue) => accumulator + currentValue )
console.log(reduced)
// 21
请注意,上述三种方法不会更改原始数组的值。
这三个概念对于 JS 开发人员而言非常重要,状态变更对于使用 React 的开发人员尤其重要。
纯函数指的是一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用。
函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。副作用的函数不仅仅只是返回了一个值,而且还做了其他的事情,比如:
状态变更是指你更改变量值的地方。如果你对变量进行更改,则可能会影响到其他函数,具体取决于变量被更改之前的值。在React环境中,建议我不要改变状态。
闭包很难理解。但是一旦理解,你会觉得 JS 其实也挺好的。在线上有足够的资源。你花足够的时间学习闭包,掌握理解它并不难。
使用闭包可以访问内部作用域中外部作用域的作用域。每次创建函数时都会在函数创建时创建JavaScript闭包。
高阶函数是将其他函数作为参数或返回结果的函数。你可以创建仅负责一项任务的较小函数,然后在这些较小函数的帮助下构造复杂函数。这也会提交代码的可重用性。
递归是所有编程语言中的一个常见概念。简单地说,递归就是把大问题分解成小问题,然后解决小问题一种思路。
尽管递归可能是一个让你头疼的令人困惑的概念,但是通过大量的练习,从一些小问题开始,你可以更好地理解它。
ES6 中新引入了集合和生成器。新引入的集合有Map,Set,WeakSet和WeakMap。这些集合为我们提供一些很方便的操作。了解它们的方式至关重要,尤其是对于现代JavaScript。
生成器有时很难理解,特别是对于初学者。生成器允许我们编写代码函数,从而能够暂停和重新启动函数,而不会阻止其他代码的执行,这在JavaScript中是很不常见的。
Jecelyn对 Promises 的解释如下:“想象一下你是个孩子。你妈妈向你保证,她下周会买一部新手机给你。”
你要到下周才能知道你是否能屋那部手机。你的妈妈要么真的给你买了一个全新的手机,要么因为不开心就不给你买。
这算是一个承诺。一个 Promise 有三个状态,分别是:
要了解什么是异步编程,首先要先积善成德什么是同步编程。同步编程是线程阻塞的,由于 JS 是单线程的,因此代码将逐行执行。
但是使用异步代码,你可以执行一些比较耗时的任务。当你必须执行花费很长时间才能完成的多个任务时,此功能特别有用。但是在某些情况下,即使是需要执行很长时间的代码,也可能需要用同步的方式,这时就可以使用async/await。
箭头函数是 ES6 的新增功能,是常规函数的语法替代。区别在于箭头函数不绑定到this,arguments,super或new.target关键字。这使得箭头函数在某些情况下是一个不错的选择,而在另一些情况下则是一个非常糟糕的选择。
因此,不要一上来就使用箭头函数。需要根据你实际情况还使用它们。
无论使用哪种编程语言,数据结构都是开发人员应具备的基本知识之一。
糟糕的程序员担心代码,好的程序员担心数据结构和它们之间的关系。
数据结构方面,你应该了解链表,队列,堆栈,树,图和哈希表。
不管编程语言如何,时间复杂度分析也是计算机编程的另一个基础。为了构建更好的应用程序,你应该编写更好的解决方案。为此,你需要了解时间复杂度的概念。有时也称为BigO。
这也是在计算机基础课程中首先要教的内容之一。简而言之,算法是逐步实现目标的过程。程序员应该能够从算法的角度看任何问题。
尽管有成千上万个用例的大量算法,但是下面两个很常见:
这两个用例对程序员来说是非常常见的,至少应该了解实现它们的已知算法。没有固定的规则规定你应该使用这些算法之一,但是这些算法在性能方面是众所周知的,并且有很好的文档证明。
你甚至可以创建自己的算法,并将其介绍给世界。如果它比目前已知的算法更好,你可能会成为下一个编程明星
JS 中的继承可用于原型来实现。这是因为 JS 是非OOP语言。但是 JS 通过提供原型继承来提供OOP的某些功能。
多态是对象、变量或函数可以采用多种形式的概念。在 JS 中,要看到多态的效果有点困难,因为在静态类型的系统中,多态的经典类型更明显。
以上两个概念都可以帮助我们在 JS 中实现更好代码重用。
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变。
你需要掌握函数式编程的几个概念:
无论使用哪种编程语言,这都是每个开发人员都应该掌握的一项基本技能。每种编程语言都有一套单独的良好实践。尽管这些“良好”做法是主观的,并且在工作场所之间存在差异,但有些惯例被认为是“良好”。
通过遵循这些代码原则,可以确保每个人都可以阅读和维护你的代码。这也会帮助你和你的团队在应用程序开发过程中顺利合作。
在ES6中引入了解构赋值操作符,它非常有用。对于相同的用例,它们比以前的实现更简单、更有效。
编程的优点之一是,如果你不去不断学习,你永远不会成为该领域专家。编程语言会随着时间不断发展,因为每个主要版本中都引入了其他新的功能。
这也说明了你对某个概念的专业知识很可能在将来的10年后会过期,因为会有更好的替代版本与版本更新一起发布。对于任何编程语言,这都是非常常见的情况。
ES202 0发布了几个新特性,包括可选链接、空值合并、动态导入等等。你必须学习这些新概念,以跟上快速变化的It世界。
掌握一门语言需要多年的经验和时间,但是知道要掌握什么会让事情变得更容易,希望这 36 个概念能对你有所帮助。
人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。
作者:Mahdhi Rezvi 译者:前端小智 来源:medium
原文:https://medium.com/better-programming/36-javascript-concepts-you-need-to-master-to-become-an-expert-c6630ac41bf4
于有人站出来,打算跟 JavaScript 生态系统正面交锋了。这家伙知道自己在干什么,而且也描绘出了干掉 JS 之后要创造的美好新世界。
2022 年,前 Stripe 开发人员 Jared Sumner 发布了Bun,一种用 Zig 编程语言开发的运行时。据我所知,Bun 最初只是种 JavaScript webserver,但在后续发展中逐渐酝酿出了全面颠覆 JS 生态系统的野心。
按我个人的关注度排序,Bun 的优势主要有以下几点:
Bun 改朝换代的思路看着非常简单粗暴——JS 有的我也要有,而且我的要更简单、更高效。这里没有小聪明、没有曲线救国,要的就是正面对抗而且样样比 JS 强。用一种低级语言,编写出运行极快的代码,这就是 Bun。
Bun 还很年轻,也许还没准备好迎接那些令人头大的真实生产用例。但它确实发展迅速,所以如果 Bun 真能在几年后快速占据市场份额,我也觉得完全在情理之中。
不知道大家在实际工作中有没有编写过 JS 或 TS 生产代码,那种体验挺难受的。多数情况下,开源工具和小项目也能良好运转,但一到商业和企业级用例上就经常掉链子。而因为传统、常规的路线走不通,企业只能试遍各种办法让项目能在生产环境中正常起效。
例如,TypeScript 在涉及多位开发者的项目中解决了不少老大难问题,所以只要 JS 的路子走不通,我们就能随时引入 TS 进行代码转换。这里要真心感谢微软。NPM 对大型项目和单体 repo 来说速度太慢,所以公司可能需要转向 Yarn。这里又要谢谢 Facebook。总之,我们就是在拼了命地东拼西凑,最终搞出性能勉强说得过去的成果。
作者提到自己所在企业的整个单体 repo 执行 eslint 需要耗费 79 秒,所以只能单独配置,保证只对发生变更的文件执行 lint。虽然会引入更多复杂元素,但也没有办法。
总的来说,无数开发者都在用自己的办法加速 JS 工具链中的某些特定部分。比如用 Yarn 3 那疯狂的“即插即用”节点模块虚拟化速度来替代 NPM,或者用基于 JSON Schema 的请求解析器解决 Express 的低速问题。其实大多数原有工具都有类似的问题,而且它们是由 JS 开发者编写、专为 JS 开发者服务的。用 JS 编写,就等同于速度很慢……
于是,一些用更快语言编写的高速工具开始流行起来。每家拥有大型 React 应用程序的企业,肯定都经历过 WebPack 构建要花掉整整一分钟的折磨。为此,他们必须转向用 Go 语言编写 esbuild。同样的,其他语言版本的 eslint 替代方案也开始出现,比如用 Rust 重写 Rome。
Bun 是这种趋势的自然延续,但采取的却是自下而上的推进路径。这个项目的核心思路就是从零起步、以内置“batteries”的方式,用低级语言重写整个 JavaScript 生态系统。而且到目前为止,效果还真心不错。
如果 Bun 只是对所有 JS 辅助工具进行重写,我当然也很欢迎,但那样的它只能算是 Node.js 的又一个替代品。Bun 并没有这样偷懒,它努力让解释器本身也快起来。
Bun 是用 Zig 编写的,而且配合苹果开发的 JavaScriptCore,类似于 Node 使用 v8。Zig 是一种新兴的低级语言,主要活跃在 C++占主导地位的场景。我不是低级开发者,所以没亲自用过,更多细节就留给其他技术更强的博主吧。在本文中,大家只要知道 Zig 写的代码很快就行了。至于 JavaScriptCore,它的作用跟 v8 一样,只是 v8 来自谷歌、而它来自苹果。Safari 和苹果的很多其他项目都有用到 JavaScriptCore。
Bun 比 Node 到底快多少还没有定论,但据称在某些特定场景下要快得多。很多朋友可能没经历过 io.js 刚诞生的时代,总结来说,那时候一个单纯能提高解释器速度的分叉就足以撼动整个 JS 生态系统。而 Bun 的启动速度又比 Node 快得多。我自己的亲身实验是 7 毫秒左右,大概比 Node.js 快了 10 倍,所以特别适合无服务器环境和边缘计算场景。
这一波颠覆依靠的不只是速度优势,Bun 还添加了不少优秀的标准库函数。例如,Bun.write()就是用于编写文件的新函数,它会返回一个承诺,而且号称可以通过更适合的系统调用进一步加快速度。
说起 Node API,Bun 目前已经能支持约 90%的现有 Node API。Node 规模很大,其中总有一些别说没用过、可能大家听都没听过的东西(比如 new AsyncLocalStorage() ),所以能支持 90%已经很好了。谁会运行 NPM 上的所有包呢?根本不需要,而且基本不影响我们的日常开发。
顺便说一句,TypeScript 在 Bun 这边可是相当有排面,直接调用 bun my-ts-file.ts 就行。Deno 对 TS 的支持也就这个水平了。使用 Bun 对新项目进行模板化,或者把 bun-types 添加到 tsconfig 当中,IDE 中的自动补全功能就将适用于这些新函数!
Bun 项目最初目标之一就是创建一种更快、更强大的 TypeScript 编译器。这个目标现在已经实现,同时被淹没在其他众多功能中。但目前,它仍然无法支持某些比较高级的 TypeScript 配置和功能,例如装饰器、tsconfig 中将多个配置合并起来的扩展功能等。
下面来聊 Bun 最振奋人心的能力之一——替代 NPM。它真的很快,能让人人都满意那种快。
在 Linux 上,bun install 的包安装速度可以达到 npm install 的 20 倍到 100 倍。在 macOS 上,也能达到 4 倍到 80 倍。
我敢肯定,没 cache 快,有 cache 更快,总之就是快。
之前就已经有很多方案在努力帮 NPM 提速了。比如大家熟悉的 Yarn Plug-n-Play,它的思路就是彻底放弃 node_modules 文件夹来加快包安装速度。虽然有一定效果,但在实际使用中,提速并没有那么显著,而且还需要处理大量 polyfill 和 escape-hatches 操作。能用是能用,但我个人实在是不想再用、也不打算向大家推荐。
Pnpm 是另一种新兴的 NPM 替代方案,在继续使用 TypeScript 编写的同时实现了一部分智能优化。在 pnpm 中,node_modules 是通过符号链接从全局缓存中访问的,每个包都能在自己的独立时间内完成安装,无需等待其他包完成当前操作。
Bun 的基本思路跟 NPM 一样,但速度却更快。它有自己的 lockfile 格式,而且其中的 node_modules 和 package.json 看起来没什么变化。如果大家对文件系统调用比较熟悉,可以结合低级访问和快速语言实现极快的安装效果,而且无需任何花哨的技巧。
现在,Bun 还不提供工作空间支持,所以暂时没法对接那些期待它来拯救的大型单体 repo(我们的项目也属于这类)。但好在 Bun 正保持着迅猛的发展速度,几周前刚公布的路线图也提到了工作空间支持。
请注意,大家不用全面转向 Bun 也能把它当成包管理器、转译器或者解释器。只需要选择我们需要的部分,丢弃其余的部分就行。我猜 Bun 的初步普及可能也会走这条道路,就是先当个好用的包管理器,其他的以后再说。这样接受门槛会变得更低一些。
Bun 当中包含一个用于网络浏览器的转译器,这明显是把矛头指向了 webpack 和 esbuild。顺带一提,Bun 中的解析器就是 esbuild 解析器的一个 Zig 端口,轻松愉快。
Bun 已经支持多种文件类型,css、svg、tsx、jsx、ts 之类的都行。JS 中的 CSS 等高级选项似乎也能在 Bun 上正常工作。
由于 Bun 包含一个带有几套内置模板的项目脚手架,所以这里我们可以直接调用:
bun create react my-app
之后,我运行 bun dev 并在浏览器里运行了一个 react 应用程序。我猜可以把 react-scripts 直接添加到 Bun 替换过的工具列表当中。
把文件扩展名从 jsx 改成 tsx,程序就立刻生效了。导入 svg,没有问题。开发模式似乎还支持 HMR,也就是前端开发者在使用 webpack 时的一大必备工具。
那么,转译器方面还缺什么吗?缺的还多,毕竟生产环境的要求可不简单。首先就是最小化了,这是实际用户最希望在后续发展路线图上看到的功能。对于大型插件生态系统来说,还必须要有能够支持不同文件格式的打包工具。例如,目前.vue 文件和.scss 还没有实际落地,特别是.scss,这东西几代开发者都在用,必须赶紧实现。目前我还不确定 Bun 捆绑器的可插拔性怎么样,而且最重点的是要直接在框架之内解决问题,不要依赖大量外部开源包。
Bun 还把不少传统意义上的框架元素添加到了标准库当中。就个人而言,我对那些库类型功能不太感兴趣,毕竟 Node 中已经有很多适用于 http server 的功能长城了。
Bun 的 webserver 看起来非常简单。Express 虽然有点落后于时代,但对大多数开发者来说仍然够用(开发团队今年还刚刚提供了对承诺的支持)。Bun server 好像跟 Cloudflare Worker 颇为相信。只要 JavaScript 生态中的其他问题逐一得到解决,也许 Bun 的开发团队会转回头好好打磨一下 webserver 吧。需要注意的是,在某些情况下,巧用系统调用可以让 Bun webserver 的速度提高一倍,特别是在文件处理过程中。
至于新的 SQLite 适配器,我觉得之前 Node 中的 sqlite 实现思路有点脱离正常人的脑洞。现在大多数开发者会把旧有 sqlite 3 包跟 sqlite 打包器结合使用,借此实现对承诺的支持。Bun 的解决方案看起来更简洁,所以就算速度上没啥大优势,我也愿意用。
我最担心的是,Bun 的这么多优点难以转化成对社区成员的实际吸引力。Bun 本身就是 JS 生态系统的完整替代品,这么巨大的转变一般人恐怕很难快速接受。
Bun 还很年轻,目前没有完整的说明文档。对于大多数问题,我们只能查阅长长的自述文件。但创建一个 docusaurus 站点,再配合具备完整内联注释的 TypeScript 类型生成相应的 typedoc 并不困难,所以我猜这一点应该很快就能解决。
服务端渲染 React 每秒 HTTP 请求数 (Linux AMD64) 对比,来自 Bun 官网
如果你从来没听说过 Deno、也不打算了解,直接跳过这章也行。而且就个人而言,我觉得 Bun 比 Deno 更有搞头、更有前途。
来自 Node 缔造者的 Deno 宣称解决了一些长期困扰开发者的老大难问题。它把 es-modules 设定成默认值,引入了第一方 TypeScript 支持(无需在发布前转译 NPM 模块)等等。但在我看来,Deno 在解决老问题的同时,也引入了不少新问题。
首先,Deno 对包解析和语法做的变更过于大刀阔斧,导致没法跟原有 NPM 生态系统兼容。换言之,Deno 需要培养起自己的全新库生态。虽然 Deno 慢慢开始支持一些早期库,但我觉得一个项目的影响力会直接决定它的发展上限,所以 Deno 的边界估计也就到这了。当然也有一些变通方法,比如把 NPM 包转换成 Deno 包的 CDN,但我觉得这不是什么好招。
Deno 还有不少在我看来暴露其半成品身份的问题,比如缺少 package.json。无论是从模块解析的角度来看,还是从缺少 manifest 文件出发,Deno 都不允许开发者为自己的包编写可扩展元数据。GoLang 甚至专门为此引入了 go.mod。
另外,我觉得 Deno 设计中的沙箱/权限系统应该是正确的思路,只是粒度不够细。它位于整个项目的顶层、脱离了包层次,这意味着大型应用程序最终还是需要所有权限,于是问题又回到了原点。而且作为一家安全公司,我们对 Deno 无法保护大型应用免受供应链攻击而颇感失望。当然,Bun 也没说打算如何解决这个问题,我这里只是发泄一下自己的不满。
所以总结起来:Bun 拥有远超 Deno 的发展潜力。具体原因如下:
如上所述,Rome 就是个验证器。Rome 的维护者们已经开始用 Rust 代替 JS 进行重写了,而且 79 秒的验证时长也有点夸张。(不骗人,我们的 eslint 就是用了 79 秒。)
从路线图来看,Rome 还打算引入捆绑器、文档生成器、压缩器、类型检查器、测试框架等等。但这一切尚未完成,而 Bun 明显已经走得更远。至少 Rome 还没开始重写 Node 核心本身,所以我觉得它的影响力也就差不多这样了。
总之,很多项目都发现了 Node 生态系统中的现有问题,而且各自尝试在统一的高性能框架中将其一举解决。接下来,就看谁发展得更快了。
这里我想把视野缩小一点,通过具体案例聊聊开源世界中的生态阵营是怎么产生的。
相信很多 Node 开发者都知道 Jest 是怎样力压 Mocha 测试框架,一路迅猛崛起的。Mocha 想当年也是人们首选的测试运行程序,效果不错而且语法优秀,但只要涉及更复杂的需求或者断言,就得引入其他模块和插件。好在有了社区协作,插件也不算太难找。总之,开发者需要具备更广泛的知识才能引入相应的库。
后来 Facebook 搞出了 Jest,一套内含“batteries”的测试框架。它借鉴了 Mocha 语法和库,并把一切整合到了单一框架中。Jest 什么都能解决,从伪造时间到需求的检测和模拟。Jest 也有扩展空间,但我在实际工作中就用过一次。大部分概念验证和设计都是由 Mocha 承担的,作为后来者的 Jest 只是把成果统一了起来并使其变得更易于访问。虽然 Mocha 也不乏铁杆粉丝,但 Jest 确实更受欢迎。
开源世界中有很多这样的案例。首创解决方案拿下先发优势,而后续一旦增长乏力,就会有热心的开发商把功能整合起来。这也让我想到了 Linux 大家族还未统一时的 systemd。如今,systemd 几乎可以管理大多数 Linux 发行版上的所有内容,而 Bun 也许会以同样的方式席卷整个 JavaScript 世界。
我意识到从开源的角度来看,这种合并和统一似乎与开源精神相悖,但用大量库实现简单需求确实已经成为折磨开发者们的痛点。而且如果每个库都有相应的维护团队,那恶意黑客通过简单的伪造邮件域就能实施供应链攻击。我们不想这样,但现实就是如此残酷。老手尚且容易中招,更遑论刚接触大量名称、还不熟悉种种语言的新人了。所以从务实的角度出发,我觉得很多朋友应该跟我一样,并不觉得把更多常用功能引入标准库、将多种开发工具整合进统一框架属于历史的倒退。
截至 2022 年 7 月,Bun 还是没有做好进军生产环境的准备,但我强烈建议大家自己装上试一试。整个流程非常便捷,而且我觉得现在的 Bun 已经足够应付小型子项目或者公司里的简单内部仪表板了。
我不敢说 Bun 在未来几年能否甚至如何重塑 JavaScript 的面貌,但我真心对它的发展充满期待。
原文链接:
https://www.lunasec.io/docs/blog/bun-first-look/
录
1.序言
var、let 和 const 都是 JavaScript 中用来声明变量的关键字,并且 let 和 const 关键字是在 ES6 中才新增的。既然都是用来声明变量的,那它们之间有什么区别呢?让我们来一探究竟。
2.var 与 let 的区别
(1)作用域
用 var 声明的变量的作用域是它当前的执行上下文,即如果是在任何函数外面,则是全局执行上下文,如果在函数里面,则是当前函数执行上下文。换句话说,var 声明的变量的作用域只能是全局或者整个函数块的。
而 let 声明的变量的作用域则是它当前所处代码块,即它的作用域既可以是全局或者整个函数块,也可以是 if、while、switch等用{}限定的代码块。
另外,var 和 let 的作用域规则都是一样的,其声明的变量只在其声明的块或子块中可用。
示例代码:
*请认真填写需求信息,我们会在24小时内与您取得联系。