节选翻译自(You-Dont-Know-JS - 2nd Edition )
序
很多人认为JS是很糟糕的语言,设计的不好,有严重缺陷,这是个很荒谬的说法。
和任何很棒的语言一样,JS有精妙的设计,也有一些坑。即使是JS的创造者Brendan Eich也遗憾地认为有些部分是错误的。但是他错了,那些并不是错误。正是由于JS的全部部分,才让它成了这世上最普遍,最有影响力的语言。
给前端开发者的建议: 只让代码达到想要的效果是不够的,如果能完全理解代码是怎么运行的,你就能在开发工作中更加高效。
good enough to work is not, and should not be, good enough.
你不知道的JS
问题:
- 什么是JavaScript?
alert("hello")
是JS 吗?- 编程范式有哪几种,JS属于哪一种?
- 什么是向后/向前兼容,HTML, CSS JS 分别是哪种,如果不兼容,解决方案是什么?
- 解释型语言和编译型语言有什么特点,JS是哪一种?JS引擎在运行代码前做了什么?
- WebAssembly是什么(WASM)
关于JavaScript
这个语言和Java没关系,也不是用来写脚本的,为了迎合市场,吸引Java程序员才这样命名。
这个语言的官方名称是ECMAScript,由TC39委员会指定,归属于ECMA组织
ECMAScript定义了语法和行为,运行在浏览器/Node.js上的JavaScript(简称JS) 是ECMAScript规范(简称ES)的实现。
运行JS的环境有很多,除了浏览器,Node.js之外,还有机器人,电灯泡……
这么多环境里,web浏览器占着主导地位,JS在web浏览器里是如何实现的,这一点至关重要。
大多数情况下,浏览器JS引擎实现的JS 和规范里的是一致的,不过也有些差异。ECMAScript附录B中记录了只针对web浏览器的规范, 不过不建议使用,因为它们只在特定JS引擎环境下有用。开发中还是应该尽可能遵从JS规范。
alert("hello")
是JS 吗?
你是怎么看这个代码的呢?JS规范中并没有定义 alert(...)
函数,但是所有的web环境都有它
很多JS环境(比如浏览器JS引擎, Node.js)在全局作用域中添加了API, 为你的JS程序提供了特定环境的能力,比如alert()就是在用户浏览器弹出一个警告框
fetch(..)
, getCurrentLocation(..)
,getUserMedia(..)
看上去很像JS,其实是Web API。 还有Node.js环境里, 提供了很多内置模块的API方法,比如fs.write(..)
console.log()
不在JS规范里,但是他们太通用了,几乎所有JS环境都会定义他们。
所以alert(..)
和 console.log(..)
并不是JS,只是像JS。他们遵从JS语法。他们的行为由正在运行的JS引擎控制。
人们总是抱怨JS不一致,其实大多这些跨浏览器的差异并不是JS导致的,而是环境自己的行为差异。
alert(..)
这个调用算是JS, 但是 alert
函数本身并不是JS规范。
编程范式
“范式”术语指的是组织代码的思路和方法,范式里有很多风格,包含各种库和框架。
典型的范式分类包括: 过程式,面向对象和函数式。
- 过程式风格在组织代码时,自上而下,线性处理,一系列操作结合成过程
- 面向对象风格在组织代码时,把逻辑和数据放到 类 里
- 函数式风格把代码组织成函数,(和过程不一样,纯计算), 并把这些函数映射出值
范式没有对错之分,它们指导程序员如何组织代码,维护代码,解析问题。
有些语言是偏向单一范式的,比如C是过程式,Java/C++是面向对象, Haskell是函数式。
多范式语言更加灵活,一个程序甚至也能有2个以上的范式
JS是多范式语言
向后兼容 向前兼容
JS 向后兼容,不向前兼容(不支持在旧的JS引擎上使用新的特性)
HTML和CSS相反, 是向前兼容(在2010的浏览器上,使用2019年的特性),但不是向后兼容 (以前写的html和css可能不可用了)
编程语言向前兼容是不实际的, HTML 和CSS是声明式的,很容易跳过不认识的声明,对已认识的声明影响较小。
如果特性是新的语法,程序可能会无法编译运行,抛出语法错误; 如果特性是一个新的API,比如ES6的Object.is(),程序会运行到一个地方,然后当它遇到这个未知API的引用时,抛出运行时异常
对于新的不兼容的语法,解决方案就是转译transpile, (使用工具,把程序的源码从一种形式转换成另一种形式,仍然是文本源码的形式)。通常,可以用转译器来解决由语法导致的前向兼容问题。最常见的就是使用Babel将新的JS语法转换到相对旧的语法
为什么要使用工具把新的语法版本转换成老的版本? 原因是,强烈推荐开发者使用最新版本的JS,这样他们的代码简洁高效。
开发者应该把精力集中在清晰的新语法格式,让工具处理,生成一个向前兼容的适用于部署运行在旧的JS引擎环境上的代码版本。
例子:let关键字是ES6的特性
if (something) {
let x = 3;
console.log(x);
}
else {
let x = 4;
console.log(x);
}
// 转译后:
var x$0, x$1;
if (something) {
x$0 = 3;
console.log(x$0);
}
else {
x$1 = 4;
console.log(x$1);
}
如果向前兼容的问题和新语法无关,而是缺少了新加的API,最常用的解决方法就是为缺失的API提供一个定义,就像是在旧环境已经定义过它。这种模式叫做polyfill 填充。
例子: finally属于ES2019规范
// 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
// 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;
});
}
);
};
}
解释型 编译型
JS是解释型语言还是编译后的程序,这是个一直有争论的问题,大部分意见认为它是解释型。
相比于编译型语言,解释型/脚本语言通常被认为更差,原因有很多,大家觉得它们缺少性能优化,不是成熟的静态类型语言(脚本语言通常使用动态类型)。
被认为是编译型的语言,通常会生成程序的二进制格式用于之后执行。 很多人因为这个觉得JS不是编译型语言(没有二进制),实际上,最终程序的格式并不是很重要。
JS到底是解释型语言还是编译型语言,和它怎么处理错误有关系。
脚本/解释型语言通常从上至下逐行执行,比如第五行有错误,直到1-4行执行完后,程序第五行的错误才会被发现。(图1)
与之相比,编译型语言在执行前,会先经理一个解析的步骤(图2)
图2: 解析 + 编译 + 执行
在执行前,一个非法的命令可以在解析的阶段被捕获。程序不会运行。对于静态错误来说,在执行代码前发现它们会更好。
所有编译型语言都经过了解析。在经典编译理论中,解析后就是代码生成,生成一个可执行的形式。
JS源代码在被执行前,会先被解析,解析后是抽象语法树AST(Abstract Syntax Tree)
所以JS是被解析的语言, 但它是不是编译的呢?
答案接近于 是
具体来说,JS引擎 “编译” 生成了二进制字节码(bytecode),然后交给“JS虚拟机”去执行。有些人说VM虚拟机在解释字节码。那样的话,java也算是解释型语言了。当然,这和传统的JAVA是编译型语言相违背
JS引擎可以对解析后的代码进行多次JIT(Just-In-Time)即时处理/优化
从源代码到JS执行的,JS引擎做了什么?
- 源程序经过Babel转译(transpile),webpack打包(或者其他构建工具),然后以各种不同的形式给到JS引擎
- JS引擎把代码解析成
AST
抽象语法树(parse the code to an AST) (通过词法分析tokenize,语法分析) - JS引擎把
AST
转换成字节码
(a binary intermediate representation),然后由优化的JIT编译器来进一步提炼/转换 - 最后,JS VM虚拟机执行程序
JS是编译型语言
WebAssembly简介
WASM类似于汇编的格式,它跳过了解析编译阶段(通常由JS引擎完成),二进制打包的程序不需要很多处理就能被JS引擎执行。
刚开始它的目的是性能提升,此外,它还能让非JS的语言进入web平台。举个例子,它可以将Go语言的程序(支持多线程)转换为JS引擎能理解的形式,尽管JS引擎没有线程的特性。
还有个观点认为WASM正在成为一个跨平台的虚拟机,程序被编译后可以运行在不同的环境里
总结
JS的定义
- JS是ECMAScript规范的实现 (ECMAScript规范由TC39委员会制定,归属于ECMA组织)
JS is an implementation of the ECMAScript standard , which is guided by the TC39 committee and hosted by ECMA. It runs in browsers and other JS environments such as Node.js.
- JS是多范式语言: 开发者可以混合使用过程式,面向对象,函数式这些范式
JS is a multi-paradigm language, meaning the syntax and capabilities allow a developer to mix and match (and bend and reshape!) concepts from various major paradigms, such as procedural, object-oriented (OO/classes), and functional (FP).
- JS是编译型语言,在执行JS前,工具(JS引擎)会先进行处理,并识别一些错误。
JS is a compiled language, meaning the tools (including the JS engine) process and verify a program (reporting any errors!) before it executes.
收获
- 什么是JavaScript?
JS是ECMAScript规范的实现,很多环境都能运行JS,比如web浏览器。
alert("hello")
是JS 吗?
alert
, console.log()
,这些只是看上去像JS,他们遵守了JS的语法,但是并不属于JS规范。由于console.log的通用性,每个环境都会定义它
- 编程范式有哪几种,JS属于哪一种?
JS是多范式语言,可以混合使用 过程式,面向对象,函数式编程
- 什么是向后/向前兼容,HTML, CSS JS 分别是哪种,如果不兼容,解决方案是什么?
JS向后兼容;HTML和CSS向前兼容
向前兼容的解决方案有两个,针对语法syntax,转译transpile; 针对新的API方法,填充polyfill。
babel是最常见的转译器。
- 解释型语言和编译型语言有什么特点,JS是哪一种?JS引擎在运行代码前做了什么?
(有争议)本文中,JS是更像编译型语言。
- WebAssembly是什么(WASM)
WebAssembly 是一门类汇编语言, 为其他语言(Go, Rust, C++)提供了移植进web的道路
下一章《学习JS最好的方式是?》 未完成,持续更新中……
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!