前言
起因是因为一次来回引入引发的问题。例如A引入了B的方法,B引入了A的方法,然后在调用时发现遇到了问题,于是决定做一篇关于??橐氲恼?。
Module语法
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。
在 ES6 之前,社区制定了一些??榧釉胤桨?,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了??楣δ?,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的??榻饩龇桨浮?/p>
ES6 ??榈纳杓扑枷胧蔷×康木蔡?,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS ??榫褪嵌韵螅淙胧北匦氩檎叶韵笫粜?。
require和import本质上就是commonjs和ES6module的区别。一个是JavaScript社区指定的野生规范,一个是后续指定的官方规范。
// 常规用法
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// ES6???import { stat, exists, readFile } from 'fs';
import的一些注意事项
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载??榈慕疟纠锩?,改写接口。
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
因此也不存在着页面根据条件判断去import资源这种说法。
import会执行所加载模块。例如:import 'lodash'
,仅仅执行lodash???,但是不输入任何值。
require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';
目前阶段,通过 Babel 转码,CommonJS 模块的require命令和 ES6 ??榈膇mport命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个??橹凶钤缰葱械摹?/strong>下面的代码可能不会得到预期结果。
关于import()函数,其诞生的目的是为了让import也有动态加载的能力,实现require先前拥有的基本功能。
关于require和import本质区别
寸志:形式上看上去五花八门,本质可以涵盖为以下三点。
- CommonJS 还是 ES6 Module 输出都可以看成是一个具备多个属性或者方法的对象;
- default 是 ES6 Module 所独有的关键字,export default fs 输出默认的接口对象,import fs from 'fs' 可直接导入这个对象;
- ES6 Module 中导入模块的属性或者方法是强绑定的,包括基础类型;而 CommonJS 则是普通的值传递或者引用传递。
第3点具体该如何理解?
// counter.js
exports.count = 0
setTimeout(function () { ++exports.count }, 500)
// commonjs.js
const {count} = require('./counter')
setTimeout(function () {
console.log('read count after 1000ms in commonjs is', count)
}, 1000)
// 输出结果:read count after 1000ms in commonjs is 0
//es6.js
import {count} from './counter'
setTimeout(function () {
console.log('read count after 1000ms in es6 is', count)
}, 1000)
// 输出结果:read count after 1000ms in es6 is 1
count为number类型,因此commonjs中是值传递,传递0之后后续原来的参数发生变化并不会影响到引用方的count值。而es6中为强绑定,因此后续也跟着发生了改变。
关于循环引用
解答这个问题需要先了解commonjs和es6底层的加载原理
commonjs
CommonJS 的一个??椋褪且桓鼋疟疚募?。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
{
id: '...', // 模块名
exports: { ... }, // ??槭涑龅母鞲鼋涌? loaded: true, // 表示是否执行完毕
...
}
执行一次后,再次执行只会去exports上取值,返回第一次运行的结果。
CommonJS ??橛龅窖芳釉厥?,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。
es6
ES6 ??槭嵌?,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载??榈囊?/strong>,需要开发者自己保证,真正取值的时候能够取到值。
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';
直接抛出这个典型的例子。执行a.mjs后,引用b,已知它从a输入了foo接口,这时不会去执行a,而是认为这个接口已经存在了。因此执行到第三行时发现foo并没有定义ReferenceError。
可以通过将bar,foo写成函数的形式,这是因为函数具有提升作用,在执行import {bar} from './b'
时,函数foo就已经有定义了,所以b.mjs加载的时候不会报错。这也意味着,如果把函数foo改写成函数表达式,也会报错。
心得体会
关于import的讨论,先前分析过它预加载、动态加载的行为。本次熟悉了语法用法和加载的机制,尽量通过函数的形式去返回可能存在循环调用的??椋诜⑸返饔檬币惨任璞苊獠豢稍ぜ腷ug。
参考
Module 的语法 - 阮老师的es6教程
Module 的加载实现 - 阮老师的es6教程
require,import区别?-知乎