“睡服”面试官系列第十一篇之module加载实现(建议收藏学习)

本文详细探讨了JavaScript模块加载的机制,包括浏览器加载、ES6模块与CommonJS模块的区别以及Node.js加载ES6模块的特点。通过实例分析了循环加载在CommonJS和ES6模块中的处理方式,揭示了两者在处理循环加载时的本质差异。文章适合想要深入理解模块加载机制的开发者阅读。
摘要由CSDN通过智能技术生成

目录

1. 浏览器加载

1.1传统方法

1.2加载规则

2. ES6 模块与 CommonJS 模块的差异

3. Node 加载

3.1概述

3.2内部变量

4ES6 模块加载 CommonJS 模块

5CommonJS 模块加载 ES6 模块

6循环加载

6.1CommonJS 模块的加载原理

6.2CommonJS 模块的循环加载

7ES6 模块的循环加载

8ES6 模块的转码

8.1ES6 module transpiler

8.2SystemJS

总结

“睡服“面试官系列之各系列目录汇总(建议学习收藏)


1. 浏览器加载

1.1传统方法

HTML 网页中,浏览器通过 <script> 标签加载 JavaScript 脚本

<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script>
<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>

上面代码中,由于浏览器脚本的默认语言是 JavaScript,因此 type="application/javascript" 可以省略。
默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到 <script> 标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还
必须加入脚本下载的时间。
如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览
器允许脚本异步加载,下面就是两种异步加载的语法

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

上面代码中, <script> 标签打开 defer 或 async 属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,
而是直接执行后面的命令。
defer 与 async 的区别是: defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行; async 一旦下载
完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话, defer 是“渲染完再执行”, async 是“下载完就执行”。另外,如果有多个 defer
脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。

1.2加载规则

浏览器加载 ES6 模块,也使用 <script> 标签,但是要加入 type="module" 属性。

<script type="module" src="./foo.js"></script>

上面代码在网页中插入一个模块 foo.js ,由于 type 属性设为 module ,所以浏览器知道这是一个 ES6 模块。
浏览器对于带有 type="module" 的 <script> ,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了 <script>
标签的 defer 属性

<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>

如果网页有多个 <script type="module"> ,它们会按照在页面出现的顺序依次执行。
<script> 标签的 async 属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染

<script type="module" src="./foo.js" async></script>

一旦使用了 async 属性, <script type="module"> 就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。
ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致

<script type="module">
import utils from "./utils.js";
// other code
</script>

对于外部的模块脚本(上例是 foo.js ),有几点需要注意。
代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
模块脚本自动采用严格模式,不管有没有声明 use strict 。
模块之中,可以使用 import 命令加载其他模块( .js 后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用 export 命令输出对外接口。
模块之中,顶层的 this 关键字返回 undefined ,而不是指向 window 。也就是说,在模块顶层使用 this 关键字,是无意义的。
同一个模块如果加载多次,将只执行一次。
下面是一个示例模块

import utils from 'https://example.com/js/utils.js';
const x = 1;
console.log(x === window.x); //false
console.log(this === undefined); // true
delete x; // 句法错误,严格模式禁止删除变量

利用顶层的 this 等于 undefined 这个语法点,可以侦测当前代码是否在 ES6 模块之中

const isNotModuleScript = this !== undefined;

2. ES6 模块与 CommonJS 模块的差异

讨论 Node 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。
它们有两个重大差异。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接
口只是一种静态定义,在代码静态解析阶段就会生成。
下面重点解释第一个差异。
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件 lib.js 的例子。

// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};

上面代码输出内部变量 counter 和改写这个变量的内部方法 incCounter 。然后,在 main.js 里面加载这个模块。

// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3

上面代码说明, lib.js 模块加载以后,它的内部变化就影响不到输出的 mod.counter 了。这是因为 mod.counter 是一个原始类型的值,会被缓存。除非写
成一个函数,才能得到内部变动后的值。

// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};

上面代码中,输出的 counter 属性实际上是一个取值器函数。现在再执行 main.js ,就可以正确读取内部变量 counter 的变动了。

$ node main.js
3
4

ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import ,就会生成一个只读引用。等到脚本真正执行
时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的 import 有点像 Unix 系统的“符号连接”,原始值变了, import 加载的值

也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其

评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端小歌谣

放弃很容易 但是坚持一定很酷

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值