详解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 相关文章推荐
JS正则表达式获取分组内容的方法详解
Nov 15 Javascript
javascript实现的多个层切换效果通用函数实例
Jul 06 Javascript
jQuery动态添加及删除表单上传元素的方法(附demo源码下载)
Jan 15 Javascript
Javascript中arguments对象的详解与使用方法
Oct 04 Javascript
jQuery通过改变input的type属性实现密码显示隐藏切换功能
Feb 08 Javascript
vue mint-ui tabbar变组件使用
May 04 Javascript
vue中子组件的methods中获取到props中的值方法
Aug 27 Javascript
jQuery实现当拉动滚动条到底部加载数据的方法分析
Jan 24 jQuery
js canvas实现星空连线背景特效
Nov 01 Javascript
ZK中使用JS读取客户端txt文件内容问题
Nov 07 Javascript
vue不操作dom实现图片轮播的示例代码
Dec 18 Javascript
微信小程序实现拍照和相册选取图片
May 09 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
基于OpenCV的PHP图像人脸识别技术
2009/10/11 PHP
PHP读取XML值的代码(推荐)
2011/01/01 PHP
php判断访问IP的方法
2015/06/19 PHP
javascript 页面只自动刷新一次
2009/07/10 Javascript
ajax异步刷新实现更新数据库
2012/12/03 Javascript
JQuery DataTable删除行后的页面更新利用Ajax解决
2013/05/17 Javascript
使用js写的一个简易的投票
2013/11/27 Javascript
js实现的类似于asp数据字典的数据类型代码实例
2014/09/03 Javascript
JavaScript对表格或元素按文本,数字或日期排序的方法
2015/05/26 Javascript
体验jQuery和AngularJS的不同点及AngularJS的迷人之处
2016/02/02 Javascript
带有定位当前位置的百度地图前端web api实例代码
2016/06/21 Javascript
jQuery 生成svg矢量二维码
2016/08/09 Javascript
最实用的jQuery分页插件
2016/10/09 Javascript
JS中注入eval, Function等系统函数截获动态代码
2019/04/03 Javascript
vue使用keep-alive保持滚动条位置的实现方法
2019/04/09 Javascript
开源一个微信小程序仪表盘组件过程解析
2019/07/30 Javascript
layui.tree组件的使用以及搜索节点功能的实现
2019/09/26 Javascript
解决removeEventListener 无法清除监听的问题
2020/10/30 Javascript
Python实现SSH远程登陆,并执行命令的方法(分享)
2017/05/08 Python
Python获取指定文件夹下的文件名的方法
2018/02/06 Python
python数据分析:关键字提取方式
2020/02/24 Python
Python自动重新加载模块详解(autoreload module)
2020/04/01 Python
Flask缓存静态文件的具体方法
2020/08/02 Python
python反编译教程之2048小游戏实例
2021/03/03 Python
CSS3实现多背景模拟动态边框的效果
2016/11/08 HTML / CSS
Skyscanner台湾:全球知名的旅行比价引擎
2018/07/01 全球购物
一套C++笔试题面试题
2012/06/06 面试题
心理学专业毕业生推荐信范文
2013/11/21 职场文书
高中毕业的自我鉴定
2013/12/09 职场文书
新年抽奖获奖感言
2014/03/02 职场文书
办公室主任个人总结
2015/02/28 职场文书
舞出我人生观后感
2015/06/16 职场文书
PHP判断是否是json字符串
2021/04/01 PHP
浅谈Python基础之列表那些事儿
2021/05/11 Python
eval(cmd)与eval($cmd)的区别与联系
2021/07/07 PHP
鸿蒙3.0体验感怎么样? 鸿蒙3.0系统评测向
2022/08/14 数码科技