【前端面试题】ES6 module和CommonJS有什么区别?


es6 modue和Commonjs到底有什么区别

ES6 module 是编译时加载,输出的是接口,Commonjs运行时加载,加载的是一个对象。

ES6输出的是值的引用,Commonjs模块输出的是一个值的拷贝,那么‘值的引用’和‘值的拷贝’对开发者又有什么区别呢?


提示:以下是本篇文章正文内容,下面案例可供参考

一、编译时导出接口VS运行时导出对象

Commonjs模块是运行时加载,因为Commonjs 加载的是一个对象,该对象只有在脚本运行完才会生成。

ES6模块是它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

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相互依赖。

我们分析一下代码执行过程,首先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);
};

首先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导入,但因为获取的是导出的接口(接口编译阶段就已经输出了),等初始化好之后就能使用了。

引用 VS 值拷贝

ES6 module导入的模块不会缓存值,它是一个引用,这个在上面的例子中已经讨论过。
CommonJS会缓存值,这个很好理解,因为js中普通变量是值的拷贝,其实就是把模块中的值赋给一个新的变量。看下CommonJS的一个例子

// index.js
const {name} = require('./lib.js'); // 等价于const lib = require('./lib'); const {name} = lib;
setTimeout(() => {
    console.log(name); // 'Sam'
}, 1000);

// lib.js
const lib = {
    name: 'Sam'
};
module.exports = lib;
setTimeout(() => {
    lib.name = 'Bob';
}, 500);

index模块中导入lib的nameconst {name} = require('./lib.js');其实就是把lib中的name赋给index里面一个name变量。后面lib中name的变化不会影响到index中的name变量。

而ES6中类似的引用语法,导入的则是引用

// index.mjs
import {name} from './lib.mjs';
setTimeout(() => {
    console.log(name); // 
}, 1000);

// lib.mjs
export let name = 'Sam';
setTimeout(() => {
    name = 'zhangsan';
}, 500);

这里index模块中的name是lib导出的name的引用,因此lib中name变化会同步到index中。

当然这并不意味着ES6 module可以做到比CommonJS更多的事情,因为如果希望在CommonJS中获取到变化,也可以直接访问lib.name

// index.js
const lib = require('./lib.js');
setTimeout(() => {
    console.log(lib.name); // 'Bob'
}, 1000);

所以这个特性的区别只是需要我们在实现模块时候注意一下,避免预期之外的情况。

其实在上面循环引用的例子中,也能看到CommonJS拷贝值和ES6 module引用的区别,CommonJS因为是拷贝值,所以导入模块时候如果还没初始化好,就是undefined,而ES6 module是引用,所以初始化好之后就可以用了。

静态VS 动态

ES6 module静态语法和CommonJS的动态语法是很重要的区别,

CommonJS的动态性体现在两个方面

所以ES6 module静态语法支持打包tree-shaking,而CommonJS不行。

只读VS可变

CommonJS导入的模块和普通变量没有区别,ES6 module导入的模块则不同,import导入的模块是只读的。

异步VS同步

ES6 module支持异步加载,浏览器中会用到该特性,而Commonjs是不支持异步的,因为服务器端不需要异步加载。所以CommonJS不可替代ES6 module,ES6 module可以替代CommonJS。

  1. 可以根据条件判断是否加载模块
  2. require的模块参数可以是一个变量
  3. 这种动态性导致依赖关系不好分析,打包工具在静态代码分析阶段不容易知道模块是否需要被加载、模块的哪些部分需要被加载,哪些不会被用到。

    相应地,ES6 module的静态性体现在

  4. import必须在顶层
  5. import的模块参数只能是字符串,不能是变量所以打包工具能够静态分析出依赖关系,并确定知道哪些模块需要被加载、模块的哪些部分被用到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值