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

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

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

(0)

相关推荐

  • 浅析AMD CMD CommonJS规范--javascript模块化加载学习心得总结

    这是一篇关于javascript模块化AMD,CMD,CommonJS的学习总结,作为记录也给同样对三种方式有疑问的童鞋们,有不对或者偏差之处,望各位大神指出,不胜感激. 本篇默认读者大概知道require,seajs的用法(AMD,CMD用法),所以没有加入使用语法. 1.为何而生: 这三个规范都是为javascript模块化加载而生的,都是在用到或者预计要用到某些模块时候加载该模块,使得大量的系统巨大的庞杂的代码得以很好的组织和管理.模块化使得我们在使用和管理代码的时候不那么混乱,而且也方便

  • 使用Browserify来实现CommonJS的浏览器加载方法

    Nodejs的模块是基于CommonJS规范实现的,可不可以应用在浏览器环境中呢? var math = require('math'); math.add(2, 3); 第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成.也就是说,如果加载时间很长,整个应用就会停在那里等.这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间.但是,对于浏览器,这却是一个大问题,因为模块都放在服务

  • 读懂CommonJS的模块加载

    叨叨一会CommonJS Common这个英文单词的意思,相信大家都认识,我记得有一个词组common knowledge是常识的意思,那么CommonJS是不是也是类似于常识性的,大家都理解的意思呢?很明显不是,这个常识一点都不常识.我最初认为commonJS是一个开源的JS库,就是那种非常方便用的库,里面都是一些常用的前端方法,然而我错得离谱,CommonJS不仅不是一个库,还是一个看不见摸不着的东西,他只是一个规范!就像校纪校规一样,用来规范JS编程,束缚住前端们.就和Promise一样是

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

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

  • 详解tomcat热部署和热加载的方法

    详解tomcat热部署和热加载的方法 我在项目开发过程中,经常要改动Java/JSP 文件,但是又不想从新启动服务器(服务器从新启动花时间),想直接获得(debug)结果.有两种方式热部署 和热加载: 1.热加载:在server.xml -> context 属性中 设置 reloadable="true" <Context docBase="xxx" path="/xxx" reloadable="true"/&

  • 详解springboot启动时是如何加载配置文件application.yml文件

    今天启动springboot时,明明在resources目录下面配置了application.yml的文件,但是却读不出来,无奈看了下源码,总结一下springboot查找配置文件路径的过程,能力有限,欢迎各位大牛指导!!! spring加载配置文件是通过listener监视器实现的,在springboot启动时: 在容器启动完成后会广播一个SpringApplicationEvent事件,而SpringApplicationEvent事件是继承自ApplicationEvent时间的,代码如下

  • 详解react-router如何实现按需加载

    注:本文使用的 react-router 版本为 2.8.1 React Router 是一个非常出色的路由解决方案,同时也非常容易上手.但是当网站规模越来越大的时候,首先出现的问题是 Javascript 文件变得巨大,这导致首页渲染的时间让人难以忍受.实际上程序应当只加载当前渲染页所需的 JavaScript,也就是大家说的"代码分拆" - 将所有的代码分拆成多个小包,在用户浏览过程中按需加载. 所得到的效果是: 以前是这样(23333,我真不是故意的..) 现在是这样: 实际上就

  • 详解bootstrap的modal-remote两种加载方式【强化】

    方法一: 使用链接 <a href="demo.jsp" data-toggle="modal" data-target="#mymodal">打开</a> 当点击该连接时,demo.jsp的内容就可以动态的加载到<div class="modal-content"></div>中.当然这里的连接也可以是controller 方法二: 使用脚本 $("#myModal&

  • 详解vue 模拟后台数据(加载本地json文件)调试

    本文介绍了vue 模拟后台数据(加载本地json文件)调试,分享给大家,也给自己留个笔记 首先创建一个本地json文件,放在项目中如下 { "runRedLight":{ "CurrentPage": 1, "TotalPages": 0, "TotalItems": 0, "ItemsPerPage": 100, "Items":[ {"DEVICEID":&quo

  • 详解Jquery EasyUI tree 的异步加载(遍历指定文件夹,根据文件夹内的文件生成tree)

    Jquery EasyUI tree 的异步加载(遍历指定文件夹,根据文件夹内的文件生成tree)具体代码如下: private void SMT(HttpContext context) { string SqlConnection82 = System.Configuration.ConfigurationManager.AppSettings["LocalConnectionString"]; string path = context.Server.MapPath(@"

  • 详解Android_性能优化之ViewPager加载成百上千高清大图oom解决方案

    一.背景 最近做项目需要用到选择图片上传,类似于微信.微博那样的图片选择器,ContentResolver读取本地图片资源并用RecyclerView+Glide加载图片显示就搞定列表的显示,这个没什么大问题,重点是,点击图片进入大图浏览,比如你相册有几百张图片,也就意味着在ViewPager中需要加载几百个view,况且手机拍出来的图片都是1-2千万左右像素的高清大图(笔者手机2千万像素 也就是拍照出来的照片3888*5152),大小也有5-7个兆,ViewPager滑动不了十几张就oom了,

  • 详解Android中Glide与CircleImageView加载圆形图片的问题

    最近在项目中遇到了一个奇怪的问题,Glide和CircleImageView一起使用加载圆形头像,发现第一次死活都加载出来,出来的是一张占位图,当你刷新的时候或者第二次进入的时候才能加载出来.究其原因,CircleImageView 把位置占了.这时候我们有如下4种解决方案,不管是哪一种都是可以解决的(亲测可行). 1. 不使用占位符 注释掉这两句代码即可. .placeholder(R.drawable.normal_photo) .error(R.drawable.normal_photo)

  • 详解关于webpack多入口热加载很慢的原因

    之前为公司搭建了套webpack多页面应用脚手架,起初用得挺爽,资源整合打包,组件化开发,各种组件复用,需要什么就拿什么,后来入口有50个页面了,慢慢的发现了每次按保存后的热加载等待时间越来越长,需要等待10几秒,实在太影响开发效率,于是我查找原因. 观察发现,每次到热加载时会在emitting HtmlWebpackPlugin这停留很久. 于是我想知道它到底停留了多长时间呢? 在package.json内加入--profile,它会告诉你编译过程中哪些步骤耗时最长. 停止npm,重新运行we

随机推荐