AMD的一道面试题

AMD的一道面试题

模块化现在应该已经成为了稍微复杂一点前端开发的标配了。在es6中,都已经支持了的模块化。

之前的面试中,一直感觉模块化AMD,CMD没有什么可以问的,不过昨天面试突然想到一个题目:
对于一个AMD的模式下

文件d.js如下

1
2
3
4
5
6
7
8
define(function (require) {
// ... 很多代码
require('a');
// ... 很多代码
require(['b'], function (b) {});
// ... 很多代码
require('c');
});

a.js,b.js,c.js 文件分别是什么时候加载的,如何加载的?

题目不难

答案是a.jsc.js 是在加载完d.js后就加载。
b.js是在执行到这一行时异步加载的。

具体分析:

我没有看过require.js的源码,我们使用的是esl.js(也是一个AMD的模块加载器),但是他们的实现原理应该差不多。

我从esl.js的角度解读一下:

同步加载,异步加载

首先大家需要知道AMD里面一个同步加载和异步加载的概念。

从概念上面理解,同步就是当我执行到require('a');时,我需要同步的执行a.js里面的内容,也就是需要在执行到这句话时a.js必须已经加载好了,这样才能到达同步。

而对于 require(['b'], function (b) {});,我执行到这一步时,是异步的发出请求,然后异步等待b.js的返回+执行。

同步加载的实现原理

我们从概念上面理解的同步加载的原理,现在看看esl.js的实践。
这里面需要处理两个核心步骤

  1. 执行到require('a');时,a.js必须已经加载好了;
  2. a.js文件里面的所有require('*'),也都必须加载好了,保证在执行a.js时,所有a.js依赖的同步文件都能同步执行;

对于第一步的实现,大概原理是这样的,在加载好了d.js后,会正则匹配一次文件里面的同步依赖require('*');,例如匹配出了 a.jsc.js,然后继续加载a.jsc.js

对于第二部,其实就是一个递归处理,直到没有下一步的依赖为止。

同步加载另外一种处理方法

上面有一部正则逻辑,可见如果使用这种方式,在执行代码前,js需要全部正则一次所有模块化代码的。这样性能是不是有一个无谓的耗损。

那么我们一般怎么处理了?

大家一般都了解过打包编译,例如在使用Requirejs时,线上环境的代码会经过r.js处理一次。

那么d.js文件应该会处理如下

1
2
3
4
5
6
7
8
9
10
11
define(
'path/b',
['require', 'a', 'b'],
function (require) {
// ... 很多代码
require('a');
// ... 很多代码
require(['b'], function (b) {});
// ... 很多代码
require('c');
});

define方法会增加第一个和第二个参数

第一个参数是按照路径生成一个具名id
第二个参数是此文件所依赖的同步文件

这时当模块在解析这个b,js文件时,发现如果存在第二个参数,就会直接解析所需依赖部分,而省去了正则这一步。

我们正则这一步转换到了打包编译中去分析,这样就省掉了浏览器加载时去正则所有AMD文件这一步。

那么为什么我们不在开发环境中直接使用['require', 'a', 'b']方式,我理解目的是为了提高开发便捷性,我们不需要再增加一个require('*')都在中括号内配置一次,同样删除时也不用去删掉配置。

因为这一步完全可以在编译时处理。

打包编译的延生

不知道大家有没有看过编译后的代码和开发环境代码的区别,对于这个b.js文件,编译后应该是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
define(
'path/a',
['require'],
function (require) {
// ... 很多代码
});

define(
'path/c',
['require'],
function (require) {
// ... 很多代码
});

define(
'path/b',
['require', 'a', 'b'],
function (require) {
// ... 很多代码
require('a');
// ... 很多代码
require(['b'], function (b) {});
// ... 很多代码
require('c');
});

上面可见,a.jsc.js这两个文件被合并到了d.js中,所有文件都加上了具名id。而且这个id的生成规则是更具路径生成的。

而我们异步加载的b.js文件就没有被打包进来。这是因为我们期望b.js是懒加载的,当使用时在加载,这样也能达到按需加载的目的。

微信公众号

前端修炼