es6模块是在编译时加载,这里的"编译时"怎么理解?

546 阅读2分钟

这里的“编译时”,指的是js代码在运行之前的编译过程,我们熟悉的变量提升就发生在编译阶段,但是由于编译过程是引擎的行为,开发者没法在编译阶段做任何操作,所以不容易直观地理解“编译时加载”这个过程。

不过我们在循环引用这个场景,对比CommonJS模块规范,就可以轻松地理解ES6 module 的编译时加载。

先来看下面CommonJS的代码

// index.js
const {log} = require('./lib.js');
module.exports = {
    name: 'index'
};
log();

// lib.js
const {name} = require('./index.js');
module.exports = {
    log: function () {
        console.log(name);
    }
};

执行index.js:node index.js结果会打印"undefined"。

这里index模块和lib相互依赖,形成了循环引用。

esm1.png

我们分析一下代码执行过程,首先const {log} = require('./lib.js');导入lib.js模块,这时候开始加载lib模块,lib会先导入index,const {name} = require('./index.js');但这个时候index还没有定义name,所以lib中这里的name是undefined,然后lib导出log方法。

接下来index导出name,然后执行log,由于lib中的name是undefined,因此最终结果是打印undefined。

整个过程模块都是运行时加载,代码依次执行,所以很容易分析出执行结果。

而ES6 module有所不同,接下来看一个es6 module的例子。代码内容和上面一样,只是把模块规范从CommonJS换成es6 module。

// index.mjs
import {log} from './lib.mjs';
export const name = 'index';
log();

// lib.mjs
import {name} from './index.mjs';
export const log = () => {
    console.log(name);
};

esm2.png

首先import {log} from './lib.mjs';导入lib模块,注意这个时候虽然没有执行export const name = 'index';,index模块还没有导出name的值,但是index模块已经编译完成,lib已经可以获取到name的引用,只是还没有值。这非常像代码编译阶段的变量提升。

然后加载lib模块,import {name} from './index.mjs';这句导入了index模块的name,(这时候获取到的是name这个引用,但因为还没有值,因此如果马上打印name console.log(name)会报错。)接下来lib导出log方法。

然后index模块执行export const name = 'index';导出name,其值为"index"。

最后执行log方法log();因为name已经赋值,所以lib中name的引用可以正常访问到值"index",所以最终结果是打印"index"。

综上所述,es6 module虽然模块未初始化好时候就被lib导入,但因为获取的是导出的接口(接口编译阶段就已经输出了),等初始化好之后就能使用了。

另外,关于ES6 module和CommonJS的区别可以看下这篇

ES6 module与CommonJS的区别,记住这5点!

更多前端高频面试题,可以看灵题库