JS 模块化
1、模块化发展历程
IIFE自执行函数AMD使用requireJS来编写模块化(依赖必须提前声明好。)CMD使用seaJS来编写模块化(支持动态引入依赖文件。)CommonJSnodeJs中自带的模块化UMD兼容AMD、CommonJS语法webpack(require.ensure):webpack 2.x版本中的代码分割ES Modules:ES6引入的模块化,支持import来引入另一个jsscript标签type="module"

2、AMD 和 CMD 的区别
AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同,二者皆为异步加载模块
AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块CMD推崇就近依赖,只有在用到某个模块的时候再去require
3、CommonJS 规范的特点
- 所以代码都是运行在模块作用域中,不会污染全局作用域
- 模块是同步加载的,只有引入的模块加载完成,才会执行后面的操作
- 模块在首次执行后就会缓存,再次加载只返回缓存的结果
CommonJS输出的是值的拷贝,模块内部再次改变也不会影响这个值(引用类型和基本类型有区别)
4、ES6 modules 规范有什么特点
- 输出使用
export - 引入使用
import - 可以使用
export ... from ...来达到一个中转的效果 - 输入的模块变量是不可重新赋值的。只是个可读引用,但是可以改写属性
export和import命令处于模块顶层,不能位于作用域内,处于代码块中,没法做静态优化,违背了ES6模块的设计初衷import有提升效果,会提升到整个模块的头部,首先执行Babel会把export/import转化为exports/require的形式,所以可以使用exports和import
5、CommonJS 和 ES6 Modules 规范的区别
CommonJS模块是运行时加载,ES6Modules是编译时加载CommonJS输出值的拷贝,ES6Modules输出值的引用(模块内部改变会影响引用)CommonJS导入模块可以是一个表达式(是使用require()引入),ES6Modules导入只能是字符串CommonJS中 this 指向当前模块,ES6Modules中this指向undefinedES6Modules中没有arguments、require、module、exports、__filename、__dirname这些顶层变量
6、如何异步进行模块的加载
AMD 和 CMD 支持异步加载模块
7、开发一个模块需要考虑哪些问题?
- 安全性
- 封闭性
- 避免变量冲突
- 隔离作用域
- 公共代码的抽离
8、node require(X) 引入的处理顺序是什么样的?
- 如果
X是内置模块,返回该模块,不再继续执行; - 如果
X以'./'、'/'、'../'开头,将根据X所在的父模块,确定X的绝对路径:
a. 将X当成文件,依次查找,存在,返回该文件,不再继续执行;
b. 将X当成目录,依次查找目录下的文件,存在,返回该文件,不再继续执行; - 如果
X不带有路径:
a. 根据X所在的父模块,确定X可能的安装目录
b. 依次在每个目录中,将X当成文件名或者目录名加载 - 抛出
not found错误
9、node 中相互引用
有个 a.js 和 b.js 两个文件,互相引用
1. CommonJS
{
id: '...',
exports: { ... },
loaded: true, parent: null, filename: '', children: [], paths: []
}
复制代码
CommonJS 的一个模块,就是一个脚本文件。require 命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。以后需要用到这个模块的时候,就会到 exports 属性上面取值。即使再次执行 require 命令,也不会再次执行该模块,而是到缓存之中取值。
CommonJS 重要特性是加载时执行,脚本代码在 require 时,全部执行。
CommonJS 的做法是,一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
// a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
复制代码
a.js脚本先输出一个done变量,然后加载另一个脚本文件b.js。注意,此时a.js代码就停在这里,等待b.js执行完毕,再往下执行。b.js执行到第二行,就会去加载a.js,这时,就发生了”循环加载”。系统会去a.js模块对应对象的exports属性取值,可是因为a.js还没有执行完,从exports属性只能取回已经执行的部分,而不是最后的值。a.js已经执行的部分,只有一行。
exports.done = false;
复制代码
- 因此,对于
b.js来说,它从a.js只输入一个变量done,值为false。 b.js接着往下执行,等到全部执行完毕,再把执行权交还给a.js。于是,a.js接着往下执行,直到执行完毕。我们写一个脚本main.js,并运行,验证这个过程。
// main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
// 运行
// 在 b.js 之中,a.done = false
// b.js 执行完毕
// 在 a.js 之中,b.done = true
// a.js 执行完毕
// 在 main.js 之中, a.done=true, b.done=true
复制代码
- 上面的代码证明了两件事。一是,在
b.js之中,a.js没有执行完毕,只执行了第一行。二是,main.js执行到第二行时,不会再次执行b.js,而是输出缓存的b.js的执行结果,即它的第四行。
2. ES6
ES6 模块的运行机制与 CommonJS 不一样,它遇到模块加载命令 import 时,不会去执行模块,而是只生成一个引用。等到真的需要用到时,再到模块里面去取值。
ES6 模块是动态引用,不存在缓存值的问题,而且模块里面的变量,绑定其所在的模块。ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,以及变量总是绑定其所在的模块。
ES6 根本不会关心是否发生了”循环加载”,只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
counter++;
return n == 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
return n != 0 && even(n - 1);
}
复制代码
按照 CommonJS 规范,是没法加载的,是会报错的,但是 ES6 就可以执行。
之所以能够执行,原因就在于 ES6 加载的变量,都是动态引用其所在的模块。只要引用是存在的,代码就能执行。
$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17
复制代码
上面代码中,参数 n 从 10 变为 0 的过程中,foo() 一共会执行 6 次,所以变量 counter 等于 6。第二次调用 even() 时,参数 n 从 20 变为 0,foo() 一共会执行 11 次,加上前面的 6 次,所以变量 counter 等于17。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)