以前在CommonJS中,我们用module.exports和require来导出和导入模块,而到了ES6却变成了export和import了,这两者到底有什么区别呢?
一句话总结:CommonJS模块是运行输出(加载)一个值(或对象)的拷贝,而ES6模块则是编译时输出(加载)一个值的引用(或者叫做连接).
这样的差异在平常使用是不易被察觉的,可是一旦出现循环引用,两者的差异就很明显了。直接的循环引用(a引用b,b又引用a)一般不会有,但在依赖关系复杂的大项目中,很容易出现a引用b,b引用其它模块,在若干次引用后,模块n又引用回a这样的情况。为了讲解的方便我们直接构造出一个a,b相互引用的项目。
首先,我们来看看CommonJS模块中的现象:
// APage.js 关键代码
let BPage = require('./BPage');
class APage extends Component {
render() {
return (
<View style={styles.containerAll} >
<TouchableOpacity style={styles.btn} onPress={this.onPress.bind(this)}>
<Text>PushToB</Text>
</TouchableOpacity>
</View>
);
}
onPress() {
this.props.navigator.push(BPage);
}
}
var route = {
key: 'APage',
component: APage,
};
module.exports = route;
// BPage.js 关键代码
let APage = require('./APage');
class BPage extends Component {
constructor(props) {
super(props);
console.log('BPage alloc');
}
render() {
return (
<View style={styles.containerAll} >
<TouchableOpacity style={styles.btn} onPress={this.onPress.bind(this)}>
<Text>resetToA</Text>
</TouchableOpacity>
</View>
);
}
onPress() {
this.props.navigator.resetTo(APage);
}
}
var route = {
key: 'BPage',
component: BPage,
};
module.exports = route;
可以看到,APage正常显示,并且点击PushToB可以正常显示出BPage,可从BPage再Reset到APage就成了空白了。这是为什么呢?
我们来仔细分析一下整个过程:CommonJS的一个模块,就是一个脚本文件,require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。本例在index.js中先require了APage.js,那就开始执行该脚本,可是在执行过程中先遇到了
let BPage = require('./BPage');
这时候就会先去执行BPage.js。在BPage.js中又会遇到let APage = require('./APage');
但这时候APage.js
已经开始执行了,不会重复执行,所以系统会去模块对应的exports
属性取值,可是因为APage.js
还没执行完,从exports
属性中只能取回已经执行的部分,所以APage
还是空的,也就是说resetTo(APage)
其实是reset到一个空。接着BPage.js
会继续往下执行,等到全部执行完毕,再把执行权还给APage.js
。从上面的例子可以看出,在复杂项目中加载CommonJS模块需要非常小心处理各模块之间的引用关系。接下来我们来看看同样的场景在ES6中会是怎样:
// APage.js 关键代码
import BPage from './BPage';
...//中间部分与上文相同,故不重复贴代码
export default route;
// BPage.js 关键代码
import APage from './APage';
...//中间部分与上文相同,故不重复贴代码
export default route;
只是把导入和导出改为import和export,这一次就可以顺利走完整个流程,得到我们想要的。这是因为import只是指向被加载模块,我们只需要保证真正取值的时候能够取到值即可。与require时相同,在
APage.js
中遇到import BPage from './BPage';
也是会先去执行BPage.js
,也就是说BPage.js
在遇到import APage from './APage';
时,APage.js
同样是没执行完,这时候APage是undefined。不同的是使用import
从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用。所以等到BPage.js
执行完,把控制权交回给APage.js
,这时就一切正常了。