一、es6
export
命令
let firstName = '牛哄哄的柯南';
let lastName = 'Keafmd';
let year = 1958;
export {
firstName as f, //可以使用as关键字,重命名对外接口
lastName,
year
};
上面代码是export
命令的另外一种写法。 在export
命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在let
语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量
export
命令除了输出变量,还可以输出函数、类(class)
export function multiply(x, y) {
return x * y;
};
export class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return this.x + ',' + this.y;
}
};
import
命令具有提升效果,会提升到整个模块的头部,首先执行:
foo();
import { foo } from './my_module.js';
上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前
意味着在预编译阶段,import
命令会执行,相应的my_module.js
会执行一次(该文件也就在这执行仅有的一次):
// /src/index.js
console.log('index.js在编译阶段执行了')
export let a = 123
setTimeout(() => {
a = 456
}, 3000)
// /index.html
<script type="module">
import {a} from './src/index.js'
console.log(a)
setTimeout(() => {
console.log(a)
}, 3000)
</script>
// 结果
index.js在编译阶段执行了
123
456
import
是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构
// 报错
import { 'f' + 'oo' } from './my_module.js';
// 报错
let module = './my_module.js';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from './module1.js';
} else {
import { foo } from './module2.js';
}
- 如果多次重复执行同一句
import
语句,那么只会执行一次,而不会执行多次
import './lodash.js';
import './lodash.js';
// 上面代码加载了两次lodash,但是只会执行一次
export default
命令用在非匿名函数前,也是可以的:
// export-default.js
export default function foo () {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
上面代码中,foo
函数的函数名foo
,在模块外部是无效的。加载的时候,视同匿名函数加载
参考:https://blog.csdn.net/weixin_43883917/article/details/110352384?utm_source=app&app_version=4.5.7
二、commonjs与es6模块区别
Commonjs
- 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值
- 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
- 当使用
require
命令加载某个模块时,就会运行整个模块的代码。(执行时运行) - 当使用
require
命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS
模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。 - 循环加载时,属于加载时执行。即脚本代码在
require
的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
es6模块
- ES6模块中的值属于
动态只读引用
- 对于只读来说,即不允许修改引入变量的值,
import
的变量是只读的,不论是基本数据类型还是复杂数据类型。 - 当模块遇到
import
命令时(在预编译阶段遇到的import
命令,接着执行import
的文件,最后生成一个只读引用),就会生成一个只读引用。等到脚本(运行阶段)真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。 - 对于动态来说,原始值发生变化,
import
加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。 - 循环加载时,
ES6
模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
参考:https://blog.csdn.net/qq_36772866/article/details/88757140
三、循环加载引用
commonjs
// b.js
exports.done = false
let a = require('./a.js')
console.log('b.js-1', a.done)
exports.done = true
console.log('b.js-2', '执行完毕')
// a.js
exports.done = false
let b = require('./b.js')
console.log('a.js-1', b.done)
exports.done = true
console.log('a.js-2', '执行完毕')
// c.js
let a = require('./a.js')
let b = require('./b.js')
console.log('c.js-1', '执行完毕', a.done, b.done)
node c.js
b.js-1 false
b.js-2 执行完毕
a.js-1 true
a.js-2 执行完毕
c.js-1 执行完毕 true true
说明一下整个过程:
- 在
Node.js
中执行c
模块。此时遇到require
关键字,执行a.js
中所有代码。 - 在
a
模块中exports
之后,通过require
引入了b
模块,执行b
模块的代码。 - 在
b
模块中exports
之后,又require
引入了a
模块,此时执行a
模块的代码。 - 因为已经
require
过一次a
模块了,因此直接取缓存中的a
模块,此时缓存中的a
模块只有执行exports.done = false
这条语句后的内容。 - 回到
b
模块,打印b.js-1, exports, b.js-2
。b
模块执行完毕。 - 回到
a
模块,接着打印a.js-1, exports, b.js-2
。a
模块执行完毕 - 回到
c
模块,接着执行require
,需要引入b
模块。由于在a
模块中已经引入过了,所以直接就可以输出值了。 - 结束。
- 从以上结果和分析过程可以看出,当遇到
require
命令时,会执行对应的模块代码。当循环引用时,有可能只输出某模块代码的一部分。当引用同一个模块时,不会再次加载,而是获取缓存。
es6模块
// b.js
import {foo} from './a.js';
export function bar() {
console.log('bar');
if (Math.random() > 0.5) {
foo();
}
}
// a.js
import {bar} from './b.js';
export function foo() {
console.log('foo');
bar();
console.log('执行完毕');
}
foo();
babel-node a.js
foo
bar
执行完毕
// 执行结果也有可能是
foo
bar
foo
bar
执行完毕
执行完毕
说明一下流程:
a.js
编译,执行import
命令,执行b.js
b.js
编译,执行import
命令,执行a.js
a.js
已经import
过了,因此不执行,b.js
中的foo
是对a.js
中的foo
的动态引用- 继续执行
b.js
,导出bar
- 回到
a.js
继续执行,导出foo
,执行foo
补充一点:如果在b.js
中提前使用a.js
导出的内容会报错:
// b.js
import {foo} from './a.js'; // 只是添加引用,还得看它是否已经export foo
console.log(foo) // 此时a.js还未export foo,因此会报错
export function bar() {
console.log('bar');
if (Math.random() > 0.5) {
foo();
}
}
可见,在循环引用中,b.js
模块中使用导入a.js
的变量只能是放在导出export
的函数中,因为a.js
的import
是在编译阶段执行的,始终是比执行阶段的代码先执行,而此时又要执行完b.js
,意味着在执行完b.js
整个过程中,foo
都未被export
,因此b.js
直接使用foo
是获取不到的
四、一些问题
commonjs
中的顶层this
指向所在模块es6
中的顶层this
指向undefined
es6
的export/export default
是在执行阶段执行的,而且不会提升,按照顺序执行,就是普通的执行代码