详解CommonJS和ES6模块循环加载处理的区别


Posted in Javascript onDecember 26, 2018

CommonJS模块规范使用require语句导入模块,module.exports导出模块,输出的是值的拷贝,模块导入的也是输出值的拷贝,也就是说,一旦输出这个值,这个值在模块内部的变化是监听不到的。

ES6模块的规范是使用import语句导入模块,export语句导出模块,输出的是对值的引用。ES6模块的运行机制和CommonJS不一样,遇到模块加载命令import时不去执行这个模块,只会生成一个动态的只读引用,等真的需要用到这个值时,再到模块中取值,也就是说原始值变了,那输入值也会发生变化。

那CommonJS和ES6模块规范针对模块的循环加载处理机制有什么不同呢?

循环加载指的是a脚本的执行依赖b脚本,b脚本的执行依赖a脚本。

1. CommonJS模块的加载原理

CommonJS模块就是一个脚本文件,require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成该模块的一个说明对象。

{
  id: '', //模块名,唯一
  exports: { //模块输出的各个接口
    ...
  },
  loaded: true, //模块的脚本是否执行完毕
  ...
}

以后用到这个模块时,就会到对象的exports属性中取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。

CommonJS模块是加载时执行,即脚本代码在require时就全部执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,没有执行的部分不会输出。

案例说明:

案例来源于Node官方说明: https://nodejs.org/api/modules.html#modules_cycles

//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执行完毕!')
//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);

输出结果如下:

//node环境下运行main.js
node main.js

在b.js中,a.done = false
b.js执行完毕!
在a.js中,b.done = true
a.js执行完毕!
在main.js中,a.done = true, b.done = true

JS代码执行顺序如下:
1)main.js中先加载a.js,a脚本先输出done变量,值为false,然后加载b脚本,a的代码停止执行,等待b脚本执行完成后,才会继续往下执行。

2)b.js执行到第二行会去加载a.js,这时发生循环加载,系统会去a.js模块对应对象的exports属性取值,因为a.js没执行完,从exports属性只能取回已经执行的部分,未执行的部分不返回,所以取回的值并不是最后的值。

3)a.js已执行的代码只有一行,exports.done = false;所以对于b.js来说,require a.js只输出了一个变量done,值为false。往下执行console.log('在b.js中,a.done = %j', a.done);控制台打印出:

在b.js中,a.done = false

4)b.js继续往下执行,done变量设置为true,console.log('b.js执行完毕!'),等到全部执行完毕,将执行权交还给a.js。此时控制台输出:

b.js执行完毕!

5)执行权交给a.js后,a.js接着往下执行,执行console.log('在a.js中,b.done = %j', b.done);控制台打印出:

在a.js中,b.done = true

6)a.js继续执行,变量done设置为true,直到a.js执行完毕。

a.js执行完毕!

7)main.js中第二行不会再次执行b.js,直接输出缓存结果。最后控制台输出:

在main.js中,a.done = true, b.done = true

总结:

1)在b.js中,a.js没有执行完毕,只执行了第一行,所以循环加载中,只输出已执行的部分。

2)main.js第二行不会再次执行,而是输出缓存b.js的执行结果。exports.done = true;

2. ES6模块的循环加载

ES6模块与CommonJS有本质区别,ES6模块是动态引用,遇到模块加载命令import时不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者保证真正取值时能够取到值,只要引用是存在的,代码就能执行。

案例说明:

//even.js
import {odd} from './odd';

var counter = 0;
export function even(n){
  counter ++;
  console.log(counter);
  
  return n == 0 || odd(n-1);
}
//odd.js
import {even} from './even.js';

export function odd(n){
  return n != 0 && even(n-1);
}

//index.js
import * as m from './even.js';

var x = m.even(5);
console.log(x);

var y = m.even(4);
console.log(y);

执行index.js,输出结果如下:

babel-node index.js

1
2
3
false
4
5
6
true

可以看出counter的值是累加的,ES6是动态引用。如果上面的引用改为CommonJS代码,会报错,因为在odd.js里,even.js代码并没有执行。

//改用CommonJS规范加载文件,执行会报错
var x = m.even(5);
     ^

TypeError: m.even is not a function
  at Object.<anonymous> (/Users/zourong/Projects/node/ES6/mainx.1.js:3:11)
  at Module._compile (internal/modules/cjs/loader.js:689:30)

3. 总结

1)CommonJS模块是加载时执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,没有执行的部分不会输出。

2)ES6模块是动态引用,遇到模块加载命令import时不会去执行模块,只是生成一个指向被加载模块的引用。

CommonJS模块规范主要适用于后端Node.js,后端Node.js是同步模块加载,所以在模块循环引入时模块已经执行完毕。推荐前端工程中使用ES6的模块规范,通过安装Babel转码插件支持ES6模块引入的语法。

页面内容主要来源于《ES6标准入门》Module 这一章的介绍。如果有描述不清楚或错误的地方,欢迎留言指证。

参考资料:

《ES6标准入门》之Module

Node.js Cycle

ES-Module-Loader

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jquery 插件开发 extjs中的extend用法小结
Jan 04 Javascript
js判断FCKeditor内容是否为空的两种形式
May 14 Javascript
通过遮罩层实现浮层DIV登录的js代码
Feb 07 Javascript
js实现图片拖动改变顺序附图
May 13 Javascript
Flash图片上传组件 swfupload使用指南
Mar 14 Javascript
HTML页面定时跳转方法解析(2种任选)
Dec 22 Javascript
canvas实现图片根据滑块放大缩小效果
Feb 24 Javascript
从对象列表中获取一个对象的方法,依据关键字和值
Sep 20 Javascript
关于vue项目中搜索节流的实现代码
Sep 17 Javascript
在vue中阻止浏览器后退的实例
Nov 06 Javascript
Jquery使用each函数实现遍历及数组处理
Jul 14 jQuery
Axios取消重复请求的方法实例详解
Jun 15 Javascript
vue-router beforeEach跳转路由验证用户登录状态
Dec 26 #Javascript
Vuerouter的beforeEach与afterEach钩子函数的区别
Dec 26 #Javascript
使用Sonarqube扫描Javascript代码的示例
Dec 26 #Javascript
angular6的table组件开发的实现示例
Dec 26 #Javascript
详解VUE里子组件如何获取父组件动态变化的值
Dec 26 #Javascript
JavaScript基础之静态方法和实例方法分析
Dec 26 #Javascript
微信小程序实现文字跑马灯
May 26 #Javascript
You might like
绿山咖啡和蓝山咖啡
2021/03/04 新手入门
PHP 二维数组根据某个字段排序的具体实现
2014/06/03 PHP
tp5框架使用cookie加密算法实现登录功能示例
2020/02/10 PHP
让你的CSS像Jquery一样做筛选的实现方法
2011/07/10 Javascript
js字符串转换成xml对象并使用技巧解读
2013/04/18 Javascript
Node.js开发之访问Redis数据库教程
2015/01/14 Javascript
jQuery插件制作之全局函数用法实例
2015/06/01 Javascript
JS实现点击上移下移LI行数据的方法
2015/08/05 Javascript
纯CSS3代码实现滑动开关效果
2015/08/19 Javascript
AngularJS数据源的多种获取方式汇总
2016/02/02 Javascript
jQuery与JavaScript节点创建方法的对比
2016/11/18 Javascript
AngularJS中使用ngModal模态框实例
2017/05/27 Javascript
基于ES6作用域和解构赋值详解
2017/11/03 Javascript
详解JavaScript的BUG和错误
2018/05/07 Javascript
JS对象与json字符串相互转换实现方法示例
2018/06/14 Javascript
基于vue如何发布一个npm包的方法步骤
2019/05/15 Javascript
微信小程序移动拖拽视图-movable-view实例详解
2019/08/17 Javascript
js点击事件的执行过程实例分析【冒泡与捕获】
2020/04/11 Javascript
js抽奖转盘实现方法分析
2020/05/16 Javascript
js实现简单五子棋游戏
2020/05/28 Javascript
Vue如何实现变量表达式选择器
2021/02/18 Vue.js
[01:20:47]DOTA2-DPC中国联赛 正赛 Ehome vs Magma BO3 第一场 1月19日
2021/03/11 DOTA
Python实现简单HTML表格解析的方法
2015/06/15 Python
详解Python字符串对象的实现
2015/12/24 Python
Python中%是什么意思?python中百分号如何使用?
2018/03/20 Python
对numpy.append()里的axis的用法详解
2018/06/28 Python
python:删除离群值操作(每一行为一类数据)
2020/06/08 Python
Python 实现劳拉游戏的实例代码(四连环、重力四子棋)
2021/03/03 Python
Farfetch中文官网:奢侈品牌时尚购物平台
2020/03/15 全球购物
用Java语言将一个键盘输入的数字转化成中文输出
2013/01/25 面试题
先进工作者获奖感言
2014/02/08 职场文书
幼儿园六一主持词
2015/06/30 职场文书
大学生就业指导课心得体会
2016/01/15 职场文书
导游词之宿迁乾隆行宫
2019/10/15 职场文书
MySQL表锁、行锁、排它锁及共享锁的使用详解
2022/04/02 MySQL
win10忘记pin密码登录不了怎么办?win10忘记pin密码登不进去的解决方法
2022/07/07 数码科技