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结果如下
上面的打印结果证明了, 首先,在执行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文件,结果如下
**- [注意!!! ] 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) 打印结果如下:
执行顺序:
首先执行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 得到结果如下:
###注意点
函数提升之后,是不会挂载在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 模块的循环加载
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- CommonJS和ES6模块循环加载处理的区别
- 008.Python循环for循环
- 006.Python循环语句while循环
- 007.Python循环语句while循环嵌套
- 观点 | 激励循环——加密算法如何实际修复现有激励循环
- 数组常见的遍历循环方法、数组的循环遍历的效率对比
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。