ES6与CommonJS的循环加载问题

栏目: JavaScript · 发布时间: 6年前

内容简介:在很多大的项目上面,模块使用和方法很多的情况下,特别是一些依赖关系比较复杂的大项目, 很容易会出现a依赖b,b依赖c,c又依赖a这样子的情况; 这样就会出现循环加载的情况. 这个时候就在模块(模块方法)的加载机制上面就必须要考虑"循环加载"循环加载的情况了.在这篇文章里面主要说ES6和CommonJS的模块加载CommonJS的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。然后后面,再遇到这个模块的时候就不会去重新加载而是会去内存中读取.

在很多大的项目上面,模块使用和方法很多的情况下,特别是一些依赖关系比较复杂的大项目, 很容易会出现a依赖b,b依赖c,c又依赖a这样子的情况; 这样就会出现循环加载的情况. 这个时候就在模块(模块方法)的加载机制上面就必须要考虑"循环加载"循环加载的情况了.

在这篇文章里面主要说ES6和CommonJS的模块加载

CommonJS模块的加载

CommonJS的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。然后后面,再遇到这个模块的时候就不会去重新加载而是会去内存中读取.

{
  id: '...',
  exports: { ... },
  loaded: true,
  ...
}
复制代码

在require 源码中的解读就是, 在CommonJS require进来一段脚本并且加载这个脚本的时候,就会记录一些关于这个脚本的属性, 上面的id属性是模块名,exports是模块输出的各个接口,loaded属性是布尔类型的值, 表示这个模块是否已经加载完毕.

CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行. 如果出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。 官方文档的例子

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

//上面的代码会先执行a.js脚本,先输出一个done变量,然后加载另一个脚本文件b.js。注意,此时a.js代码就停在这里,等待b.js执行完毕,再往下执行。

//b.js
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);
复制代码

执行 b.js, 对于现在的b.js来说, a.js只是输入了一个变量done, 值为false; 然后,b.js接着往下执行,等到全部执行完毕,再把执行权交还给a.js, 此时b.js输出的.done的变量值为true。于是,a.js接着往下执行,打印出来b.done的值为true,直到执行完毕, a输出的变量done的值为true.

我们写一个脚本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);
复制代码

在当前的根目录下执行main.js结果如下

ES6与CommonJS的循环加载问题

上面的打印结果证明了, 首先,在执行b.js的时候, a.js还没有执行完毕,只是执行了第一行;其次,main.js执行到第二行的时候,不会重新执行b.js,(因为这里没有出现重复打印),而是直接从缓存里面取出之前b.js的执行结果. 上面的代码证明了两件事。一是,在b.js之中,a.js没有执行完毕,只执行了第一行。二是,main.js执行到第二行时,不会再次执行b.js,而是只是输出缓存的b.js的执行结果(不会再次执行b.js,但是b.js中的异步代码的执行,依然会导致b.js输出接口值得变化),即它的第四行----exports.done = true;。

ES6 循环加载

ES模块的运行机制和CommonJS类似, 在第一次遇到一个模块的加载命令色时候, 是会立即执行的,第一次加载完成之后, 会形成一个应用, 当第二次再去加载这个模块的时候,会去读取模块里面的缓存值,不会再次重复执行这个模块里面的内容(但是由于模块内部的异步代码执行,引起缓存值的变化,读取缓存的缓存里面的值也会变化)

请看下面的例子。

//a.js
console.log('a.js执行完毕')

//b.js
import * as a from './a.js'
console.log('b.js执行完毕,a.done=%j',a.done)

//main.js
import * as a from './a.js'
import * as b from './b.js'

复制代码

执行main.js文件,结果如下

ES6与CommonJS的循环加载问题

**- [注意!!! ] node 目前默认不支持es6 的模块 import解决方法有很多 我这里只是一种 **

我用的是在main.js的外面包了一层,增加了一个start.js的文件, 安装了包,做了一下babel的编译,这里就简单说一下这个问题.还有其他的方法这里不细说.

require('babel-register') ({
    presets: [ 'env' ]
})
module.exports = require('./main.js') 
复制代码

将上面CommonJS的例子改为ES的语法执行

//a.js

exports.done=false
import * as b from './b.js'
console.log('在 a.js 之中,b.done = %j',b.done);
console.log('a.js 执行完毕');

//b.js
exports.done = false;
import * as a from './a.js'
console.log('在 b.js 之中,a.done = %j',a.done);
exports.done = true;
console.log('b.js执行完毕')

//main.js
import * as a from './a.js'
import * as b from './b.js'
console.log('在main.js中,a.done=%j,b.done=%j',a.done,b.done)
复制代码

执行main.js(因为在编译外面包了一层,所以执行命令是node start.js) 打印结果如下:

ES6与CommonJS的循环加载问题

执行顺序:

首先执行a.js, 然后执行到第二行,把执行权交给了b.js, 和CommonJS的语法不同点在于, a.js没有编译完成的情况下export导出的对象都是空对象{}, 所以在这里执行到b.js的时候,打印a.done,由于a.js还没有完成编译,得到的是undefined的(CommonJS就是执行多少就暴露多少的接口,而ES6要这个文件全部编译完成才可以).

b.js执行完毕,将执行权交给a.js,然后直到a.js执行完毕,将执行权交回给main.js.

main.js此时打印a.done的时候,a.js已经导出完成,所以得到的是true, b.js导出完成所以得到的也是true了

那么问题来了? ----如何解决这个ES6循环导入的问题呢, 然a.js没有编译完成也可以在另一个js文件里面导入,并使用里面的变量或者方法

函数的提升目前只是发现函数可以提升,如果有其他的方法,欢迎评论啊

改写之后

//a.js

export function done(){  // 使用这个方式就不会打印出undefined 
    return false
}
exports.done=false
import * as b from './b.js'
console.log('在 a.js 之中,b.done = %j',b.done);
console.log('a.js 执行完毕');

//b.js
exports.done = false;
import * as a from './a.js'
console.log('在 b.js 之中,a.done = %j',a.done());
exports.done = true;
console.log('b.js执行完毕')

//main.js
import * as a from './a.js'
import * as b from './b.js'
console.log('在main.js中,a.done=%j,b.done=%j',a.done(),b.done)
复制代码

执行main.js 得到结果如下:

ES6与CommonJS的循环加载问题

###注意点

函数提升之后,是不会挂载在a.js export导出的对象里面,但是是属于a.js里面的方法可以调用, 所以在这里去import * as a from './a.js' 的时候打印a会发现是空对象,但是调用a.done()的方法可以调用得到false这个值

导入导出的方法有多种, import * as a from './a.js' 和 exports.done=false 写法只是其中之一,原理相同.

##例子 下面还有其他的几个例子大家可以打印一下

请看下面的例子(摘自 Dr. Axel Rauschmayer 的《Exploring ES6》)

// a.js
import {bar} from './b.js';
export function foo() {
  bar();  
  console.log('执行完毕');
}
foo();

// b.js
import {foo} from './a.js';
export function bar() {  
  if (Math.random() > 0.5) {
    foo();
  }
}

命令行中执行
$ babel-node a.js

执行完毕
复制代码

将上面例子转为CommonJS执行

var b=require('./b.js');
export function foo() {
  b.bar();  
  console.log('执行完毕');
}
foo();

// b.js
var a=require('./a.js')
export function bar() {  
  if (Math.random() > 0.5) {
    a.foo();
  }
}

命令行中执行
//start.js
require('babel-register') ({
    presets: [ 'env' ]
})

module.exports = require('./a.js')   

$ node start.js
执行完毕

复制代码

//注意点 这里用到了export function... ES6的导出方式 使得变量提升, 所以这里需要配置babel转译 否则在执行到b.js的时候,会找不到foo这个方法会报出 ReferenceError: foo is not defined 这样的提示,这也是ES6 和 CommonJS循环加载中的我认为的最大的区别

结语

感谢您的来访! 如果你还希望深入的学习可以参考这里,部分参考 JavaScript 模块的循环加载


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

司法的过程

司法的过程

(美)亨利·J.亚伯拉罕 / 泮伟江 宦盛奎 韩阳 / 北京大学出版社 / 2009-07-28 / 58.00元

本书是以比较研究的方法来分析司法哲学的经典文本之一。作者以敏锐的眼光透视了司法过程背后的理论、实践和参与其中的人。比较了美国、英国、法国的具体法院运作,审视了“司法能动主义”和“司法克制主义”之间的争辩。本书第七版的介绍吸收了美国、英国、法国和欧洲法院体系运作中的最新和重要的发展。 目前国内非常关注司法的运作过程、法官的裁判过程,此书的翻译对于这方面的研究很有助益,对于英国和法国法院的介绍填补了国......一起来看看 《司法的过程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试