Skip to content

ch1

目录


内容摘抄

In some ways, legal relationships run even deeper than the syntax. Oracle (via Sun), the company that still owns and runs Java, also owns the official trademark for the name "JavaScript" (via Netscape). This trademark is almost never enforced, and likely couldn't be at this point. 在某些方面,法律关系甚至比语法更深。Oracle(通过 Sun 公司)仍然是 Java 的所有者和运营者,同时也拥有“JavaScript”名称的官方商标(通过 Netscape 公司)。这个商标几乎从未被强制执行,而且现在可能也无法强制执行了。

For these reasons, some have suggested we use JS instead of JavaScript. That is a very common shorthand, if not a good candidate for an official language branding itself. Indeed, these books use JS almost exclusively to refer to the language. 由于这些原因,一些人建议我们使用 JS 而不是 JavaScript。这是一种非常常见的缩写,如果不是一个适合作为官方语言品牌的候选名称。事实上,这些书籍几乎专门使用 JS 来指代这种语言。

Further distancing the language from the Oracle-owned trademark, the official name of the language specified by TC39 and formalized by the ECMA standards body is ECMAScript. And indeed, since 2016, the official language name has also been suffixed by the revision year; as of this writing, that's ECMAScript 2019, or otherwise abbreviated ES2019. 进一步将语言与 Oracle 拥有的商标保持距离,TC39 指定的并由 ECMA 标准机构正式化的语言官方名称是 ECMAScript。确实,自 2016 年以来,官方语言名称也附加了修订年份;截至本文写作时,这是 ECMAScript 2019,或简称为 ES2019。

In other words, the JavaScript/JS that runs in your browser or in Node.js, is an implementation of the ES2019 standard. 换句话说,你在浏览器或 Node.js 中运行的 JavaScript/JS,是 ES2019 标准的实现

NOTE: 注意: Don't use terms like "JS6" or "ES8" to refer to the language. Some do, but those terms only serve to perpetuate confusion. "ES20xx" or just "JS" are what you should stick to. 不要使用"JS6"或"ES8"等术语来指代该语言。虽然有些人会使用,但这些术语只会加剧混淆。"ES20xx"或直接使用"JS"才是你应该坚持的。

ES6-ES10 对应指定年份的 ECMAScript 标准。 ES6:2015 ES7:2016 ES8:2017 ES9:2018 ES10:2019

JS6 是更为奇怪的称呼


JS's syntax and behavior are defined in the ES specification. JS 的语法和行为在 ES 规范中定义。

ES2019 happens to be the 10th major numbered specification/revision since JS's inception in 1995, so in the specification's official URL as hosted by ECMA, you'll find "10.0": 自 1995 年 JS 诞生以来,ES2019 恰好是第 10 个主要编号的规范/修订版本,因此在由 ECMA 托管的规范官方 URL 中,你会看到"10.0":

ES6-ES10 的数字部分取自于版本号,但为啥作者坚持用年份?


All major browsers and device makers have committed to keeping their JS implementations compliant with this one central specification. Of course, engines implement features at different times. But it should never be the case that the v8 engine (Chrome's JS engine) implements a specified feature differently or incompatibly as compared to the SpiderMonkey engine (Mozilla's JS engine). 所有主要浏览器和设备制造商都承诺保持其 JS 实现与这一核心规范一致。当然,引擎在不同时间实现功能。但绝不应该出现 v8 引擎(Chrome 的 JS 引擎)与 SpiderMonkey 引擎(Mozilla 的 JS 引擎)在实现指定功能时存在不同或不相容的情况。

浏览器是 JS 的实现方

Nodejs 也只是对 ES 规范的一种实现


Is this code a JS program? 这段代码是 JS 程序吗?

alert("Hello, JS!"); Depends on how you look at things. The alert(..) function shown here is not included in the JS specification, but it is in all web JS environments. Yet, you won't find it in Appendix B, so what gives? 取决于你如何看待事物。这里展示的 alert(..) 函数并未包含在 JS 规范中,但它存在于所有 Web JS 环境中。然而,你在附录 B 中找不到它,那么这到底是怎么回事呢?

Various JS environments (like browser JS engines, Node.js, etc.) add APIs into the global scope of your JS programs that give you environment-specific capabilities, like being able to pop an alert-style box in the user's browser. 各种 JS 环境(如浏览器 JS 引擎、Node.js 等)将 API 添加到 JS 程序的全局范围内,为您提供特定于环境的功能,例如能够在用户的浏览器中弹出警报样式的框。

In fact, a wide range of JS-looking APIs, like fetch(..), getCurrentLocation(..), and getUserMedia(..), are all web APIs that look like JS. In Node.js, we can access hundreds of API methods from various built-in modules, like fs.write(..). 事实上,许多看起来像 JS 的 API,例如 fetch(..) 、 getCurrentLocation(..) 和 getUserMedia(..) ,都是看起来像 JS 的 Web API。在 Node.js 中,我们可以从各种内置模块访问数百种 API 方法,例如 fs.write(..) 。

Another common example is console.log(..) (and all the other console._ methods!). These are not specified in JS, but because of their universal utility are defined by pretty much every JS environment, according to a roughly agreed consensus. 另一个常见的例子是 console.log(..) (以及所有其他 console._ 方法!)。这些方法在 JS 中没有明确规定,但由于它们的通用性,几乎所有 JS 环境都根据大致一致的共识定义了它们。

So alert(..) and console.log(..) are not defined by JS. But they look like JS. They are functions and object methods and they obey JS syntax rules. The behaviors behind them are controlled by the environment running the JS engine, but on the surface they definitely have to abide by JS to be able to play in the JS playground. 所以 alert(..) 和 console.log(..) 不是由 JS 定义的。但它们看起来像 JS。它们是函数和对象方法,并且遵循 JS 语法规则。它们背后的行为由运行 JS 引擎的环境控制,但表面上看,它们必须遵循 JS 才能在 JS 游乐场中发挥作用。

Most of the cross-browser differences people complain about with "JS is so inconsistent!" claims are actually due to differences in how those environment behaviors work, not in how the JS itself works. 人们抱怨的大多数跨浏览器差异“JS 太不一致了!”实际上是由于这些环境行为的工作方式不同,而不是 JS 本身的工作方式不同。

So an alert(..) call is JS, but alert itself is really just a guest, not part of the official JS specification. 因此 alert(..) 调用是 JS,但 alert 本身实际上只是一个客人,而不是官方 JS 规范的一部分。


Using the console/REPL (Read-Evaluate-Print-Loop) in your browser's Developer Tools (or Node) feels like a pretty straightforward JS environment at first glance. But it's not, really. 乍一看,在浏览器的开发者工具(或 Node)中使用控制台/REPL(读取-求值-打印-循环)似乎是一个非常简单的 JS 环境。但事实并非如此。

Developer Tools are... tools for developers. Their primary purpose is to make life easier for developers. They prioritize DX (Developer Experience). It is not a goal of such tools to accurately and purely reflect all nuances of strict-spec JS behavior. As such, there's many quirks that may act as "gotchas" if you're treating the console as a pure JS environment. 开发者工具是……为开发者提供的工具。它们的主要目的是让开发者的工作更轻松。它们优先考虑 DX(开发者体验)。这类工具的目标并非准确、纯粹地反映严格规范的 JS 行为的所有细微差别。因此,如果你将控制台视为纯 JS 环境,那么许多怪异之处可能会成为“陷阱”。

This convenience is a good thing, by the way! I'm glad Developer Tools make developers' lives easier! I'm glad we have nice UX charms like auto-complete of variables/properties, etc. I'm just pointing out that we can't and shouldn't expect such tools to always adhere strictly to the way JS programs are handled, because that's not the purpose of these tools. 顺便说一句,这种便利是件好事!我很高兴开发者工具让开发者的生活变得更轻松!我很高兴我们拥有一些很棒的用户体验功能,比如变量/属性的自动补全等等。我只是指出,我们不能也不应该期望这些工具总是严格遵循 JS 程序的处理方式,因为这不是这些工具的目的。

Since such tools vary in behavior from browser to browser, and since they change (sometimes rather frequently), I'm not going to "hardcode" any of the specific details into this text, thereby ensuring this book text is outdated quickly. 由于此类工具在不同浏览器的行为上有所不同,而且它们会发生变化(有时相当频繁),因此我不会将任何具体细节“硬编码”到本书中,从而确保本书的文本很快就会过时。


implications of this term, and often confuse it with a related but different term: forwards compatibility. JavaScript 最基本的原则之一是保持向后兼容性 。许多人对这个术语的含义感到困惑,并且经常将其与一个相关但不同的术语混淆: 向前兼容性 。

Let's set the record straight. 让我们澄清事实。

Backwards compatibility means that once something is accepted as valid JS, there will not be a future change to the language that causes that code to become invalid JS. Code written in 1995—however primitive or limited it may have been!—should still work today. As TC39 members often proclaim, "we don't break the web!" 向后兼容意味着,一旦某些代码被接受为有效的 JS,将来就不会再对语言进行任何修改,导致该代码变为无效的 JS。1995 年编写的代码——无论它多么原始或有限!——今天都应该仍然有效。正如 TC39 成员经常宣称的那样:“我们不会破坏网络!”

The idea is that JS developers can write code with confidence that their code won't stop working unpredictably because a browser update is released. This makes the decision to choose JS for a program a more wise and safe investment, for years into the future. 其理念是,JS 开发者可以安心地编写代码,确保他们的代码不会因为浏览器更新而意外停止运行。这使得选择 JS 开发程序成为一项更明智、更安全的投资,并在未来数年内持续受益。


Jumping the Gaps 跨越差距 Since JS is not forwards-compatible, it means that there is always the potential for a gap between code that you can write that's valid JS, and the oldest engine that your site or application needs to support. If you run a program that uses an ES2019 feature in an engine from 2016, you're very likely to see the program break and crash. 由于 JS 不向前兼容,这意味着您编写的有效 JS 代码与您的网站或应用程序需要支持的最旧引擎之间始终存在潜在差异。如果您在 2016 年的引擎中运行使用了 ES2019 功能的程序,则很可能会看到程序中断并崩溃。

If the feature is a new syntax, the program will in general completely fail to compile and run, usually throwing a syntax error. If the feature is an API (such as ES6's Object.is(..)), the program may run up to a point but then throw a runtime exception and stop once it encounters the reference to the unknown API. 如果该特性是一个新语法,程序通常会完全无法编译和运行,通常会抛出语法错误。如果该特性是一个 API(例如 ES6 的 Object.is(..) ),程序可能会运行到一定程度,但一旦遇到对未知 API 的引用,就会抛出运行时异常并停止。

Does this mean JS developers should always lag behind the pace of progress, using only code that is on the trailing edge of the oldest JS engine environments they need to support? No! 这是否意味着 JS 开发者应该永远落后于时代的步伐,只能使用他们需要支持的最老的 JS 引擎环境的底层代码?不!

But it does mean that JS developers need to take special care to address this gap. 但这确实意味着 JS 开发人员需要特别注意解决这一差距。

For new and incompatible syntax, the solution is transpiling. Transpiling is a contrived and community-invented term to describe using a tool to convert the source code of a program from one form to another (but still as textual source code). Typically, forwards-compatibility problems related to syntax are solved by using a transpiler (the most common one being Babel (https://babeljs.io)) to convert from that newer JS syntax version to an equivalent older syntax. 对于新的和不兼容的语法,解决方案是使用转译。转译是一个由社区发明的术语,用于描述使用工具将程序源代码从一种形式转换为另一种形式(但仍为文本源代码)。通常,与语法相关的前向兼容性问题可以通过使用转译器(最常见的是 Babel ( https://babeljs.io ) )来解决,将较新的 JS 语法版本转换为等效的较旧语法。

For example, a developer may write a snippet of code like: 例如,开发人员可能会编写如下代码片段:

if (something) { let x = 3; console.log(x); } else { let x = 4; console.log(x); } This is how the code would look in the source code tree for that application. But when producing the file(s) to deploy to the public website, the Babel transpiler might convert that code to look like this: 代码在该应用程序的源代码树中看起来是这样的。但是在生成要部署到公共网站的文件时,Babel 转译器可能会将该代码转换为如下所示的形式:

var x$0, x$1; if (something) { x$0 = 3; console.log(x$0); } else { x$1 = 4; console.log(x$1); } The original snippet relied on let to create block-scoped x variables in both the if and else clauses which did not interfere with each other. An equivalent program (with minimal re-working) that Babel can produce just chooses to name two different variables with unique names, producing the same non-interference outcome. 原始代码片段依赖 let 在 if 和 else 子句中创建块级作用域的 x 变量,这两个变量互不干扰。Babel 可以编写一个等效程序(只需极少的修改),只需选择使用唯一名称命名两个不同的变量,即可获得相同的互不干扰结果。

好像编译原理!

NOTE: 笔记: The let keyword was added in ES6 (in 2015). The preceding example of transpiling would only need to apply if an application needed to run in a pre-ES6 supporting JS environment. The example here is just for simplicity of illustration. When ES6 was new, the need for such a transpilation was quite prevalent, but in 2020 it's much less common to need to support pre-ES6 environments. The "target" used for transpilation is thus a sliding window that shifts upward only as decisions are made for a site/application to stop supporting some old browser/engine. let 关键字是在 ES6 中(2015 年)添加的。只有当应用程序需要在支持 ES6 之前的 JS 环境中运行时,才需要应用前面提到的转译示例。此处的示例只是为了方便说明。在 ES6 刚推出时,这种转译的需求非常普遍,但到了 2020 年,支持 ES6 之前环境的需求已经大大减少。因此,用于转译的“目标”是一个滑动窗口,只有当网站/应用程序决定停止支持某些旧版浏览器/引擎时,它才会向上移动。 You may wonder: why go to the trouble of using a tool to convert from a newer syntax version to an older one? Couldn't we just write the two variables and skip using the let keyword? The reason is, it's strongly recommended that developers use the latest version of JS so that their code is clean and communicates its ideas most effectively. 你可能会想:为什么要费力地使用工具将新语法版本转换为旧语法版本?难道我们不能只写这两个变量,而不用 let 关键字吗?原因是,强烈建议开发人员使用最新版本的 JS,这样他们的代码才能简洁,并能最有效地传达其思想。

Developers should focus on writing the clean, new syntax forms, and let the tools take care of producing a forwards-compatible version of that code that is suitable to deploy and run on the oldest-supported JS engine environments. 开发人员应该专注于编写干净、新的语法形式,并让工具负责生成适合在最老的受支持的 JS 引擎环境中部署和运行的向前兼容版本的代码。

Filling the Gaps 填补空白 If the forwards-compatibility issue is not related to new syntax, but rather to a missing API method that was only recently added, the most common solution is to provide a definition for that missing API method that stands in and acts as if the older environment had already had it natively defined. This pattern is called a polyfill (aka "shim"). 如果向前兼容性问题与新语法无关,而与最近添加的缺失 API 方法有关,那么最常见的解决方案是为该缺失的 API 方法提供一个定义,使其能够替代旧环境并像旧环境一样原生定义该方法。这种模式称为 polyfill(又称“shim”)。

Consider this code: 考虑以下代码:

// getSomeRecords() returns us a promise for some // data it will fetch var pr = getSomeRecords();

// show the UI spinner while we get the data startSpinner();

pr .then(renderRecords) // render if successful .catch(showError) // show an error if not .finally(hideSpinner) // always hide the spinner This code uses an ES2019 feature, the finally(..) method on the promise prototype. If this code were used in a pre-ES2019 environment, the finally(..) method would not exist, and an error would occur. 这段代码使用了 ES2019 的一个特性,即 Promise 原型上的 finally(..) 方法。如果在 ES2019 之前的环境中使用这段代码,则 finally(..) 方法将不存在,并且会发生错误。

A polyfill for finally(..) in pre-ES2019 environments could look like this: ES2019 之前的环境中 finally(..) 的 polyfill 可能如下所示:

if (!Promise.prototype.finally) { Promise.prototype.finally = function f(fn){ return this.then( function t(v){ return Promise.resolve( fn() ) .then(function t(){ return v; }); }, function c(e){ return Promise.resolve( fn() ) .then(function t(){ throw e; }); } ); }; } WARNING: 警告: This is only a simple illustration of a basic (not entirely spec-compliant) polyfill for finally(..). Don't use this polyfill in your code; always use a robust, official polyfill wherever possible, such as the collection of polyfills/shims in ES-Shim. 这只是 finally(..) 的一个基础 polyfill(并非完全符合规范)的简单示例。请勿在你的代码中使用此 polyfill;请尽可能使用健壮的官方 polyfill,例如 ES-Shim 中的 polyfill/shim 集合。 The if statement protects the polyfill definition by preventing it from running in any environment where the JS engine has already defined that method. In older environments, the polyfill is defined, but in newer environments the if statement is quietly skipped. if 语句通过阻止 polyfill 定义在 JS 引擎已定义该方法的任何环境中运行来保护 polyfill 定义。在较旧的环境中,polyfill 已定义,但在较新的环境中, if 语句会被悄悄跳过。

Transpilers like Babel typically detect which polyfills your code needs and provide them automatically for you. But occasionally you may need to include/define them explicitly, which works similar to the snippet we just looked at. 像 Babel 这样的转译器通常会检测你的代码需要哪些 polyfill,并自动为你提供。但有时你可能需要显式地包含/定义它们,其工作原理类似于我们刚刚看到的代码片段。

Always write code using the most appropriate features to communicate its ideas and intent effectively. In general, this means using the most recent stable JS version. Avoid negatively impacting the code's readability by trying to manually adjust for the syntax/API gaps. That's what tools are for! 始终使用最合适的功能编写代码,以便有效地传达其思想和意图。通常,这意味着使用最新的稳定 JS 版本。避免因尝试手动调整语法/API 差异而对代码的可读性产生负面影响。这就是工具的作用所在!

Transpilation and polyfilling are two highly effective techniques for addressing that gap between code that uses the latest stable features in the language and the old environments a site or application needs to still support. Since JS isn't going to stop improving, the gap will never go away. Both techniques should be embraced as a standard part of every JS project's production chain going forward. 转译和 polyfill 是两种非常有效的技术,可以弥补使用语言最新稳定功能的代码与网站或应用程序仍需支持的旧环境之间的差距。由于 JS 不会停止改进,这种差距永远不会消失。这两种技术都应该成为每个 JS 项目未来生产链的标准组成部分。


A long-debated question for code written in JS: is it an interpreted script or a compiled program? The majority opinion seems to be that JS is an interpreted (scripting) language. But the truth is more complicated than that. 关于用 JS 编写的代码,一个长期存在争议的问题:它是解释型脚本还是编译型程序?大多数人的观点似乎认为 JS 是一种解释型(脚本)语言。但事实远比这复杂得多。

For much of the history of programming languages, "interpreted" languages and "scripting" languages have been looked down on as inferior compared to their compiled counterparts. The reasons for this acrimony are numerous, including the perception that there is a lack of performance optimization, as well as dislike of certain language characteristics, such as scripting languages generally using dynamic typing instead of the "more mature" statically typed languages. 在编程语言发展的大部分历史中,“解释型”语言和“脚本型”语言一直被人们看不起,被认为比编译型语言低劣。造成这种负面评价的原因有很多,包括人们认为它们缺乏性能优化,以及不喜欢某些语言特性,例如脚本语言通常使用动态类型,而不是“更成熟”的静态类型语言。

Languages regarded as "compiled" usually produce a portable (binary) representation of the program that is distributed for execution later. Since we don't really observe that kind of model with JS (we distribute the source code, not the binary form), many claim that disqualifies JS from the category. In reality, the distribution model for a program's "executable" form has become drastically more varied and also less relevant over the last few decades; to the question at hand, it doesn't really matter so much anymore what form of a program gets passed around. 被视为“编译型”的语言通常会生成可移植的(二进制)程序表示,以便稍后分发执行。由于我们在 JS 中并没有观察到这种模型(我们分发的是源代码,而不是二进制形式),许多人声称这使得 JS 不属于这一类别。实际上,在过去的几十年里,程序“可执行”形式的分发模型已经变得极其多样化,也越来越不重要了;对于我们目前的问题来说,程序以何种形式分发已经不再那么重要了。

These misinformed claims and criticisms should be set aside. The real reason it matters to have a clear picture on whether JS is interpreted or compiled relates to the nature of how errors are handled. 这些误导性的说法和批评应该被搁置。真正重要的是要清楚 JS 是解释型的还是编译型的,这与错误处理方式的本质有关。

Historically, scripted or interpreted languages were executed in generally a top-down and line-by-line fashion; there's typically not an initial pass through the program to process it before execution begins (see Figure 1). 从历史上看,脚本语言或解释语言通常以自上而下、逐行的方式执行;在执行开始之前通常不会对程序进行初始处理(参见图 1)。

Interpreting a script to execute it

Fig. 1: Interpreted/Scripted Execution 图 1:解释/脚本执行

In scripted or interpreted languages, an error on line 5 of a program won't be discovered until lines 1 through 4 have already executed. Notably, the error on line 5 might be due to a runtime condition, such as some variable or value having an unsuitable value for an operation, or it may be due to a malformed statement/command on that line. Depending on context, deferring error handling to the line the error occurs on may be a desirable or undesirable effect. 在脚本语言或解释型语言中,程序第 5 行的错误直到第 1 行到第 4 行执行完毕后才会被发现。需要注意的是,第 5 行的错误可能是由于运行时条件导致的,例如某个变量或值的值不适合某个操作,也可能是由于该行语句/命令格式错误。根据具体情况,将错误处理推迟到发生错误的行可能会产生理想的效果,也可能会产生不良后果。

Compare that to languages which do go through a processing step (typically, called parsing) before any execution occurs, as illustrated in Figure 2: 将其与在执行任何操作之前都要经过处理步骤(通常称为解析)的语言进行比较,如图 2 所示:

Parsing, compiling, and executing a program

Fig. 2: Parsing + Compilation + Execution 图 2:解析+编译+执行

In this processing model, an invalid command (such as broken syntax) on line 5 would be caught during the parsing phase, before any execution has begun, and none of the program would run. For catching syntax (or otherwise "static") errors, generally it's preferred to know about them ahead of any doomed partial execution. 在这个处理模型中,第五行的无效命令(例如语法错误)会在解析阶段(任何执行开始之前)被捕获,程序将不会运行。对于捕获语法(或其他“静态”)错误,通常最好在任何注定失败的部分执行之前就知晓它们。

So what do "parsed" languages have in common with "compiled" languages? First, all compiled languages are parsed. So a parsed language is quite a ways down the road toward being compiled already. In classic compilation theory, the last remaining step after parsing is code generation: producing an executable form. 那么,“解析型”语言和“编译型”语言有什么共同之处呢?首先,所有编译型语言都是经过解析的。因此,解析型语言距离编译型语言还有很长的路要走。在经典编译理论中,解析之后的最后一步是代码生成:生成可执行文件。

Once any source program has been fully parsed, it's very common that its subsequent execution will, in some form or fashion, include a translation from the parsed form of the program—usually called an Abstract Syntax Tree (AST)—to that executable form. 一旦任何源程序被完全解析,其后续执行通常会以某种形式或方式包括从程序的解析形式(通常称为抽象语法树 (AST))到该可执行形式的转换。

In other words, parsed languages usually also perform code generation before execution, so it's not that much of a stretch to say that, in spirit, they're compiled languages. 换句话说,解析语言通常也会在执行之前执行代码生成,因此从本质上说,它们是编译语言,这并不夸张。

JS source code is parsed before it is executed. The specification requires as much, because it calls for "early errors"—statically determined errors in code, such as a duplicate parameter name—to be reported before the code starts executing. Those errors cannot be recognized without the code having been parsed. JS 源代码在执行之前会被解析。规范也提出了同样的要求,因为它要求在代码开始执行之前报告“早期错误”(代码中静态确定的错误,例如参数名称重复)。如果没有解析代码,这些错误就无法识别。

So JS is a parsed language, but is it compiled? 所以 JS 是一种解析语言 ,但它是编译语言吗?

The answer is closer to yes than no. The parsed JS is converted to an optimized (binary) form, and that "code" is subsequently executed (Figure 2); the engine does not commonly switch back into line-by-line execution (like Figure 1) mode after it has finished all the hard work of parsing—most languages/engines wouldn't, because that would be highly inefficient. 答案是肯定的,而不是否定的。解析后的 JS 会被转换为优化的(二进制)形式,然后执行该“代码”(图 2);引擎在完成所有复杂的解析工作后,通常不会切换回逐行执行模式(如图 1 所示)——大多数语言/引擎都不会这样做,因为那样效率非常低。

To be specific, this "compilation" produces a binary byte code (of sorts), which is then handed to the "JS virtual machine" to execute. Some like to say this VM is "interpreting" the byte code. But then that means Java, and a dozen other JVM-driven languages, for that matter, are interpreted rather than compiled. Of course, that contradicts the typical assertion that Java/etc are compiled languages. 具体来说,这种“编译”会产生一个二进制字节码(某种意义上),然后交给“JS 虚拟机”执行。有些人喜欢说这个虚拟机是在“解释”字节码。但这意味着 Java 以及其他十几种 JVM 驱动的语言是解释执行的,而不是编译执行的。当然,这与 Java 等语言是编译型语言的典型说法相矛盾。

Interestingly, while Java and JavaScript are very different languages, the question of interpreted/compiled is pretty closely related between them! 有趣的是,虽然 Java 和 JavaScript 是非常不同的语言,但它们之间的解释/编译问题却密切相关!

Another wrinkle is that JS engines can employ multiple passes of JIT (Just-In-Time) processing/optimization on the generated code (post parsing), which again could reasonably be labeled either "compilation" or "interpretation" depending on perspective. It's actually a fantastically complex situation under the hood of a JS engine. 另一个问题是,JS 引擎可以对生成的代码(解析后)进行多次 JIT(即时)处理/优化,根据不同的视角,这些代码也可以合理地被称为“编译”或“解释”。实际上,在 JS 引擎内部,这是一个极其复杂的情况。

So what do these nitty-gritty details boil down to? Step back and consider the entire flow of a JS source program: 那么这些细节归结起来是什么呢?退一步考虑一下 JS 源程序的整个流程:

After a program leaves a developer's editor, it gets transpiled by Babel, then packed by Webpack (and perhaps half a dozen other build processes), then it gets delivered in that very different form to a JS engine. 程序离开开发人员的编辑器后,会由 Babel 进行转换,然后由 Webpack(以及可能六个其他构建过程)打包,然后以非常不同的形式交付给 JS 引擎。

The JS engine parses the code to an AST. JS 引擎将代码解析为 AST。

Then the engine converts that AST to a kind-of byte code, a binary intermediate representation (IR), which is then refined/converted even further by the optimizing JIT compiler. 然后,引擎将 AST 转换为一种字节码,即二进制中间表示(IR),然后由优化 JIT 编译器进一步细化/转换。

Finally, the JS VM executes the program. 最后,JS VM 执行该程序。

To visualize those steps, again: 为了再次形象化地展示这些步骤:

Steps of JS compilation and execution

Fig. 3: Parsing, Compiling, and Executing JS 图 3:解析、编译和执行 JS

Is JS handled more like an interpreted, line-by-line script, as in Figure 1, or is it handled more like a compiled language that's processed in one-to-several passes first, before execution (as in Figures 2 and 3)? JS 的处理方式是否更像一个逐行解释的脚本(如图 1 所示),还是更像一个编译语言(先经过一到几次处理,然后再执行)(如图 2 和 3 所示)?

I think it's clear that in spirit, if not in practice, JS is a compiled language. 我认为很明显,从精神上讲(如果不是从实践上讲), JS 是一种编译语言

And again, the reason that matters is, since JS is compiled, we are informed of static errors (such as malformed syntax) before our code is executed. That is a substantively different interaction model than we get with traditional "scripting" programs, and arguably more helpful! 再次强调,重要的原因在于,由于 JS 是经过编译的,所以我们在代码执行之前就会收到静态错误(例如语法错误)的通知。这与传统的“脚本”程序的交互模型有着本质的不同,而且可以说更有用!

从编译原理的角度来看,JS 通过从编写代码到执行之间的多次整理,实现了与编译语言类似的效果

JS 代码 -> 打包称紧凑的 js 代码 -> 引擎运行(解析代码 -> 生成 AST -> 优化 AST -> 运行 AST)

摘抄内容小结

ECMAScript 是 js 代码标准,浏览器引擎(Chrome 的 V8 引擎以及 Firefox 的 SpiderMonkey 引擎)和 Node.js(也是 V8 引擎) 都是 ECMAScript 的实现

node 版本和 js 版本不是一个东西

ES 版本是对某年的标准的简称,比如 ES6 就是 2015 年 6 月 18 日发布的 ECMAScript 标准,

 ECMAScript (ES标准)

   V8 引擎实现

Node.js 使用 V8

以 V8 引擎为例,V8 会逐步实现 ECMAScript 最新的标准,然后 nodejs 会将 V8 引擎的实现作为 nodejs 的实现


alertconsole.log等代码并非 ES 规范中的语法,只是各家引擎在实现的时候不约而同规定的

alert 本身实际上只是一个客人,而不是官方 JS 规范的一部分


开发者工具不是严格的 JS 环境,只是为了方便调试实现的一个工具,不能指望开发者工具完美符合 ES 的标准


ES 的核心理念之一是向后兼容,意味着以前的代码可以在最新的引擎中运行,而新的代码则不能在老的引擎中运行

与之相反,另一个理念是向前兼容,意味着新的代码可以在老的引擎中运行,而以前的代码则不能在最新的引擎中运行

转译和 polyfill 是两种非常有效的技术,可以让新的代码运行在老的引擎中。其理念是,JS 开发者可以安心地编写代码,确保他们的代码不会因为浏览器更新而意外停止运行。这使得选择 JS 开发程序成为一项更明智、更安全的投资,并在未来数年内持续受益。


JS 看上去是一种脚本语言,但是在 JS 从编写到执行中间经历了非常多过程(不断地进化),所以实际上 JS 应该是一种编译语言

TS/JS 代码 -> 压缩优化后紧凑的 js 代码 -> 引擎运行(解析代码 -> 生成 AST -> 优化 AST -> 运行 AST)

————————————————
版权声明:本文为 田园幻想乡 的原创文章,遵循 CC 4.0 BY-NC-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接: http://truraly.fun/学习笔记/JavaScript/《你并不了解 JavaScript》/Get Started/ch1.html


发布时间:

最后更新时间:

Copyright © 2022 田园幻想乡 浙ICP备2021038778号-1