前端之十万个为什么
第一次解决前端项目的时候还在使用 bootstrap 和 jQuery,后续慢慢地开始接触 react 和 vue ,一晃这么多年过去,整个前端圈已经发生了天翻地覆的变化。
有一天自己心血来潮想要开发小的前端项目,结果发现很多概念作为一个后端程序员的自己已经不怎么了解了,真是前端一天,后端一年。因此在这里对自己有疑问的地方做一个简单的梳理。因为本人对前端了解比较有限,因此内容比较杂,而且先就不求甚解了,如果理解有偏差,欢迎看到的同学可以随时指出。

Node.js 的出现解决了什么问题?
Node.js 是一个开源和跨平台的 JavaScript 运行时环境,基于 Google 的 V8 引擎。
这个环境下,使得 JS 可以脱离浏览器环境运行,同时可以承担一些后端任务。主要思想还是基于非阻塞和事件驱动。
ES 是什么?和 JS 区别是啥?
JS 全名是 JavaScript ,这个大家都很清楚。ES 其实是 ECMAScript 的简称。ECMA(欧洲计算机制造商协会 European Computer Manufacturers Association)制定了浏览器脚本语言的标准,之后又因为版权的原因将这个语言命名为 ECMAScript (Netscape 将 JavaScript 注册成了商标),意思是 ECMA 官方制定的脚本语言,不用 JavaScript 这个名字也体现了语言的开放和中立。实际上现在 ES 更多的时候是指一个标准,在这个标准之下,各个厂商可能又会有一些自己的实现。比如现在的 JS 就是 Netscape 的实现,而 JScript 就是 Microsoft 的实现。
ES 作为一个标准,各个实现自然就要遵从 ES 这个标准,比如:
- IE8 中使用的 JScript5.8
- Firefox 中使用的 JavaScript1.5
都是兼容 ES3 的这个标准的。
Edition | Date published | Name |
---|---|---|
1 | 1997.6 | |
2 | 1998.6 | |
3 | 1999.12 | |
4 | 2003.6(为最后一个 draft 的时间,已废弃) | |
5 | 2009.12 | |
5.1 | 2011.6 | |
6 | 2015.6 | ECMAScript 2015 (ES2015) |
7 | 2016.6 | ECMAScript 2016 (ES2016) |
8 | 2017.6 | ECMAScript 2017 (ES2017) |
9 | 2018.6 | ECMAScript 2018 (ES2018) |
10 | 2019.6 | ECMAScript 2019 (ES2019) |
11 | 2020.6 | ECMAScript 2020 (ES2020) |
12 | 2021.6 | ECMAScript 2021 (ES2021) |
表上我们可以看到 ES4 被废弃了,2007 年 10 月,ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。因此 ES4 没有被通过。其中的一些特性被 ES5 和 ES6 继承。
特别地,由于 ES6 相比较之前,引入的新特性很多,而且从发布日志我们也可以看到,标准制定委员会的规范指定流程也更加规范了起来,各方面来看 ES6 都是一个大的 milestone ,因此有些上下文里,ES6 也指 5.1 之后的版本,包含后续的 ES2016、ES2017 等等。
实际上浏览器或者说运行时永远都最不上 ES 的发展速度,各大浏览器目前对 ES6 的支持情况可以参照这里进行查看,主流的浏览器比较新的版本对 ES6 的绝大部分特性都可以适配得比较好,但是也不能保证用户的浏览器版本都比较新。因此就需要 Babel 这样的工具来进行转译。
Babel[ˈbeɪbl]
是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
TypeScript 是什么?和 JS 区别是什么?
至于 TS,TS 全称是 Typescript,是 Microsoft 推出的全新的开源语言,基于 Apache License,本身是 JS 的超集。

不过浏览器并没有原生支持 TypeScript ,因此需要将 TypeScript 编译生成 JavaScript 。从下图中我们也可以看到实际上 TypeScript 也是 ES6 的超集,TypeScript ES6 和 JS 之外还增加了强类型,编译器也会进行类型检查。
相比较于 JS 新增的一些语法糖包括并不限于:
- 类型签名/编译期静态检查
- 类型推断与预测
- 接口
- 泛型
- async/await
JavaScript 模块化
我们最原始的在前端引入 JavaScript 依赖的方式就是通过 script 标签的方式。这样带来的缺点是显而易见的:
- 多个 JavaScript 文件的引入污染了全局作用域,进而开发人员就要自己解决这些问题。
- 依赖问题,默认情况下,script 标签是顺序往下加载的,如果各个 JavaScript 文件存在相互依赖的关系,就需要考虑 JavaScript 文件的顺序问题。
以上问题在大型项目中会非常突出。
特别地,浏览器是同步加载 JavaScript 脚本的,渲染引擎遇到 <script>
标签就会先停下来,等到脚本执行完再继续往下渲染,如果是外部脚本,而且外部脚本体积也比较大的话,就可能会导致下载和执行的时间很长。因此造成浏览器阻塞,用户的体感就会很不好,因此浏览器也是允许脚本异步加载的。
1 | <script src="path/to/module_a.js" defer></script> |
script 标签添加 defer 和 async 属性,脚本就会异步加载。
- defer: 等到整个页面在内存中正常渲染完才会执行,当有多个脚本时则按照顺序执行。
- async: 一旦脚本下载完成,渲染引擎就会中断,执行完这个脚本再继续渲染,因此有多个脚本时不能保证其都按顺序执行。
回到我们模块化上面。模块化通常方式一个代码文件都是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前比较流行的 JavaScript 模块化规范有:CommonJS/AMD/CMD/UMD/ESM 。
CommonJS
Node.js 是 CommonJS 规范的主要实践者,CommonJS 也主要应用在服务端,当然因为后续 CommonJS 在服务端表现出色,后续也被移植到了浏览器端。
CommonJS 使用同步地方式加载模块。在服务端编程时候这样处理是合适的,因为这些模块文件都是在问题,本地磁盘 IO 的速度还是很快而且稳定可控的,但是对于前端领域,就不那么合适了,客户的网络环境往往是不稳定的,使用异步加载的方式显然会更合适一些。Browserif 就是浏览器端的实现,也被称为 CommonJS 浏览器端的打包工具。
CommonJS 中定义的几个特别的变量:module
、exportes
、require
、global
以及 __filename
和 __dirname
。这些变量在 CommonJS 中是可以直接打印的。
1 | console.log(module); |
在 CommonJS 中,可以使用 module.exports
(最好不直接使用 exports
)定义当前模块对外暴露的接口与变量,然后使用 require 来加载模块。
一个典型的定义和使用 CommonJS 模块的样例:
1 | // 定义模块 test.js |
1 | // 引用自定义的模块时,参数包含路径,可省略后续的.js |
Node.js 并非完全按照 CommonJS 的规范进行实现。
AMD(Asynchronous Module Definition)
AMD 即 Asynchronous Module Definition,即异步加载模块定义,在这个规范之下,一个单独的文件就是一个模块,模块本身的加载不会影响它后续语句的运行,而所有依赖这个模块的语句都定义在一个回调函数之中,等待模块加载完成了,这个回调函数才会运行。
AMD 也使用 require() 语句来加载模块,但是不同于 CommonJS,AMD 在使用 require 语句的时候是下面这样的:
1 | require([module], callback); |
示例如下:
1 | require(['math'], function (math) { |
那么因为是异步的,所以 AMD 是比较适合浏览器环境的。现在主要有两个 JavaScript 库实现了 AMD 规范,分别是 RequireJS 和 curl.js。
另外关于 CMD(Common Module Definition),我这里就不做过多介绍,借用玉伯的一句话
- AMD 本身是 RequireJS 在推广过程中对模块定义的规范化产出。
- CMD 则是 SeaJS 在推广过程中对模块定义的规范化产出。
对于 AMD 和 CMD 的不同点,主要在于:
- AMD 推崇依赖前置,提前执行。
- CMD 推崇依赖就近,延迟执行。
UMD(Universal Module Definition)
UMD 即 Universal Module Definition ,这里的通用表示的就是同时兼容了 CommonJS 和 AMD 两种规范。那么这里也就意味着无论是在使用 CommonJS 规范还是使用 AMD 规范的项目中,都可以直接引用 UMD 规范的模块使用。
原理其实很简单,就是判断全局是否存在 exports 和 defines ,如果存在 exports ,那么就以 CommonJS 的方式来暴露模块,如果存在 define 就使用 AMD 的方式来暴露模块。
ESM(ECMAScript Modules)
ESM 是 ES6 官方推出的模块管理方式,既然是官方推出的,那么就是出来一统江湖的了。主要有 import
和 export
这两个关键词,这两个关键词是语法层面的关键词,因此不能像 CommonJS 一样直接打印了。其中:
- export 用于规定模块的对外接口。
- import 命令用于引用其他模块。
比如:
1 | /** 定义模块 test.js **/ |
除此之外也引入了 export default
命令,这里就不展开了。