一、 import 和 require加载情况对比
- import的加载
// 1-1.js
export let counter = 3;
export function add() {
counter++;
}
// main.js
import { counter, add} from './1-1.js';
console.log(counter); // 3
//counter++;
add();
console.log(counter); //4
//babel-node main.js的执行结果:
//3
//4
结论:模块加载命令import生成的是一个只读引用。
注意: 上面运行需要babel的支持。
- require的加载
下面这个模块文件lib.js的例子。
// main.js
var mod = require('./1-2');
console.log(mod.counter); // 3
mod.add();
console.log(mod.counter); // 3
// 1-2.js
var counter = 3;
function add() {
counter++;
}
module.exports = {
counter: counter,
add: add,
};
//node main.js的执行结果:
//3
//3
结论: CommonJS模块输出的是值的浅拷贝。也就是说,一旦输出module.exports,模块内部的变化就影响不到这个值。
以取值器函数的形式获取
// main.js
var mod = require('./1-3');
console.log(mod.counter); // 3
mod.add();
console.log(mod.counter); // 4
// 1-3.js
var counter = 3;
function add() {
counter++;
}
module.exports = {
get counter() { //取值器函数
return counter
},
add: add,
};
//node main.js的执行结果:
//3
//4
- ES6模块与CommonJS模块的差异
- CommonJS模块输出的是值的浅拷贝 ( 是module.exports浅拷贝了脚本相关变量 ),ES6模块输出的是值的只读引用。
- CommonJS模块是运行时加载,ES6模块是编译时输出接口。
- CommonJS模块 require初次加载一个模块时候必然是阻塞的,初次加载之后会被缓存,所以加载之后再require同一个模块不会被执行,从缓存里面取值。
- CommonJS输出的是一个对象(即module.exports),该对象只有在脚本运行结束时才会生成。而ES6模块输出的不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
ES module 原理详情见 : www.jianshu.com/p/ba0faf79c…
二、循环加载
- 概念
“循环加载”(circular dependency)指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。
- ES6模块的循环加载
// a.js
import {bar} from './b.js';
console.log('a.js');
console.log(bar);
export let foo = 'foo';
// b.js
import {foo} from './a.js';
console.log('b.js');
console.log(foo);
export let bar = 'bar';
//babel-node a.js的运行结果:
//b.js
//undefined
//a.js
//bar
分析:
- 上面的代码中,由于a.js的第一行是加载b.js,所以先执行的是b.js。而b.js的第一行又是加载a.js,这时由于a.js已经开始执行,所以不会重复执行,而是继续执行b.js,因此第一行输出的是b.js。
- 接着,b.js要打印变量foo,这时a.js还没有执行完,取不到foo的值,因此打印出来的是undefined。b.js执行完便会开始执行a.js,这时便会一切正常。
结论: 上面的代码中,a.js之所以能够执行,原因就在于ES6加载的变量都是动态引用其所在模块的。只要引用存在,代码就能执行。
- CommonJS模块的循环加载
//a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done); //3
exports.done = true;
console.log('a.js 执行完毕'); //4
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done); //1
exports.done = true;
console.log('b.js 执行完毕'); //2
//ab.js
var a = require('./a.js');
var b = require('./b.js'); //从缓存中取
console.log('在 ab.js 之中, a.done=%j, b.done=%j', a.done, b.done); //5
//执行node ab.js的执行结果:
//在 b.js 之中,a.done = false
//b.js 执行完毕
//在 a.js 之中,b.done = true
//a.js 执行完毕
//在 ab.js 之中, a.done=true, b.done=true
执行流程产生的结果:
第一,在b.js之中,a.js没有执行完毕,只执行了第一行。
第二,ab.js执行到第二行时不会再次执行b.js,而是输出缓存的b.js的执行结果,即它的第四行。
( 原因在于 require初次加载模块时候必然是阻塞的,初次加载之后会被缓存,所以加载之后再require不会被执行,从缓存里面取值。)
三、require源码分析
下载nodejs源码,require源码见文件 \node-v14.17.5\lib\internal\modules\cjs\loader.js 。
分析过程参考该篇文章 : blog.csdn.net/a460550542/…
调试node源码: cloud.tencent.com/developer/n…
最简单的方法chrome inspect: node --inspect-brk=9229 1-2.js