前端之十万个为什么

第一次解决前端项目的时候还在使用 bootstrap 和 jQuery,后续慢慢地开始接触 react 和 vue ,一晃这么多年过去,整个前端圈已经发生了天翻地覆的变化。

有一天自己心血来潮想要开发小的前端项目,结果发现很多概念作为一个后端程序员的自己已经不怎么了解了,真是前端一天,后端一年。因此在这里对自己有疑问的地方做一个简单的梳理。因为本人对前端了解比较有限,因此内容比较杂,而且先就不求甚解了,如果理解有偏差,欢迎看到的同学可以随时指出。

frontend frameworks

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 & JavaScript

不过浏览器并没有原生支持 TypeScript ,因此需要将 TypeScript 编译生成 JavaScript 。从下图中我们也可以看到实际上 TypeScript 也是 ES6 的超集,TypeScript ES6 和 JS 之外还增加了强类型,编译器也会进行类型检查。

相比较于 JS 新增的一些语法糖包括并不限于:

  • 类型签名/编译期静态检查
  • 类型推断与预测
  • 接口
  • 泛型
  • async/await

JavaScript 模块化

我们最原始的在前端引入 JavaScript 依赖的方式就是通过 script 标签的方式。这样带来的缺点是显而易见的:

  1. 多个 JavaScript 文件的引入污染了全局作用域,进而开发人员就要自己解决这些问题。
  2. 依赖问题,默认情况下,script 标签是顺序往下加载的,如果各个 JavaScript 文件存在相互依赖的关系,就需要考虑 JavaScript 文件的顺序问题。

以上问题在大型项目中会非常突出。

特别地,浏览器是同步加载 JavaScript 脚本的,渲染引擎遇到 <script> 标签就会先停下来,等到脚本执行完再继续往下渲染,如果是外部脚本,而且外部脚本体积也比较大的话,就可能会导致下载和执行的时间很长。因此造成浏览器阻塞,用户的体感就会很不好,因此浏览器也是允许脚本异步加载的。

1
2
<script src="path/to/module_a.js" defer></script>
<script src="path/to/module_b.js" async></script>

script 标签添加 defer 和 async 属性,脚本就会异步加载。

  • defer: 等到整个页面在内存中正常渲染完才会执行,当有多个脚本时则按照顺序执行。
  • async: 一旦脚本下载完成,渲染引擎就会中断,执行完这个脚本再继续渲染,因此有多个脚本时不能保证其都按顺序执行。

回到我们模块化上面。模块化通常方式一个代码文件都是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前比较流行的 JavaScript 模块化规范有:CommonJS/AMD/CMD/UMD/ESM 。

CommonJS

Node.js 是 CommonJS 规范的主要实践者,CommonJS 也主要应用在服务端,当然因为后续 CommonJS 在服务端表现出色,后续也被移植到了浏览器端。

CommonJS 使用同步地方式加载模块。在服务端编程时候这样处理是合适的,因为这些模块文件都是在问题,本地磁盘 IO 的速度还是很快而且稳定可控的,但是对于前端领域,就不那么合适了,客户的网络环境往往是不稳定的,使用异步加载的方式显然会更合适一些。Browserif 就是浏览器端的实现,也被称为 CommonJS 浏览器端的打包工具。

CommonJS 中定义的几个特别的变量:moduleexportesrequireglobal 以及 __filename__dirname 。这些变量在 CommonJS 中是可以直接打印的。

1
2
3
4
5
6
console.log(module);
console.log(exports);
console.log(require);
console.log(global);
console.log(__filename);
console.log(__dirname);

在 CommonJS 中,可以使用 module.exports (最好不直接使用 exports)定义当前模块对外暴露的接口与变量,然后使用 require 来加载模块。

一个典型的定义和使用 CommonJS 模块的样例:

test.js
1
2
3
4
5
6
7
8
9
// 定义模块 test.js
var foo = 0;
function add(a, b) {
return a + b;
}
module.exports = {
add: add,
foo: foo
}
main.js
1
2
3
// 引用自定义的模块时,参数包含路径,可省略后续的.js
var test = require('./test');
test.add(1, 2);

Node.js 并非完全按照 CommonJS 的规范进行实现。

AMD(Asynchronous Module Definition)

AMD 即 Asynchronous Module Definition,即异步加载模块定义,在这个规范之下,一个单独的文件就是一个模块,模块本身的加载不会影响它后续语句的运行,而所有依赖这个模块的语句都定义在一个回调函数之中,等待模块加载完成了,这个回调函数才会运行。

AMD 也使用 require() 语句来加载模块,但是不同于 CommonJS,AMD 在使用 require 语句的时候是下面这样的:

1
require([module], callback);

示例如下:

1
2
3
require(['math'], function (math) {
math.add(2, 3);
});

那么因为是异步的,所以 AMD 是比较适合浏览器环境的。现在主要有两个 JavaScript 库实现了 AMD 规范,分别是 RequireJScurl.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 官方推出的模块管理方式,既然是官方推出的,那么就是出来一统江湖的了。主要有 importexport 这两个关键词,这两个关键词是语法层面的关键词,因此不能像 CommonJS 一样直接打印了。其中:

  • export 用于规定模块的对外接口。
  • import 命令用于引用其他模块。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
/** 定义模块 test.js **/
var foo = 0;
var add = function (a, b) {
return a + b;
};
export { foo, add };

/** 引用模块 **/
import { foo, add } from './test';
function test(ele) {
result = add(1 + foo);
}

除此之外也引入了 export default 命令,这里就不展开了。

参考链接

作者

Aaron Ai

发布于

2017-01-28

更新于

2022-10-15

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×