ES6与CommonJS的循环加载问题

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

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


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

查看所有标签

猜你喜欢:

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

C专家编程

C专家编程

Peter Van Der Linden / 徐波 / 人民邮电出版社 / 2008-2 / 45.00元

《C专家编程》展示了最优秀的C程序员所使用的编码技巧,并专门开辟了一章对C++的基础知识进行了介绍。 书中C的历史、语言特性、声明、数组、指针、链接、运行时、内存以及如何进一步学习C++等问题进行了细致的讲解和深入的分析。全书撷取几十个实例进行讲解,对C程序员具有非常高的实用价值。 本书可以帮助有一定经验的C程序员成为C编程方面的专家,对于具备相当的C语言基础的程序员,本书可以帮助他们......一起来看看 《C专家编程》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具