ES6与CommonJS的循环加载问题

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

内容简介:在很多大的项目上面,模块使用和方法很多的情况下,特别是一些依赖关系比较复杂的大项目, 很容易会出现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 模块的循环加载


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

查看所有标签

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

Programming PHP

Programming PHP

Rasmus Lerdorf、Kevin Tatroe、Peter MacIntyre / O'Reilly Media / 2006-5-5 / USD 39.99

Programming PHP, 2nd Edition, is the authoritative guide to PHP 5 and is filled with the unique knowledge of the creator of PHP (Rasmus Lerdorf) and other PHP experts. When it comes to creating websit......一起来看看 《Programming PHP》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具