CommonJS 与 ES6 Modul

2020-04-24  本文已影响0人  弹指一挥间_e5a3

前言

CommonJS 是由 JavaScript 社区于 2009 年提出的包含模块、文件、IO、控制台在内的一系列标准。在Node.js 的实现中采用了 CommonJS 标准的一部分,并在其基础上进行了一些调整。我们所说的CommonJS 模块和 Node.js 中的实现并不完全一样,现在一般谈到 CommonJS 其实是 Node.js 中的版本,而非它的原始定义。

CommonJS 最初只为服务端而设计,直到有了 Browserify ——一个运行在 Node.js 环境下的模块打包工具,它可以将 CommonJS 模块打包为浏览器可以运行的单个文件。这意味着客户端的代码也可以遵循CommonJS 标准来编写了。不仅如此,借助 Node.js 的包管理器,npm 开发者还可以获取他人的代码库,或者把自己的代码发布上去供他人使用。这种可共享的传播方式使 CommonJS 在前端开发中逐渐流行了起来。

CommonJS 中规定每个文件是一个模块。将一个 JavaScript 文件直接通过 script 标签插入页面中与封装成CommonJS 模块最大的不同在于,前者的顶层作用域是全局作用域,在进行变量及函数声明时会污染全局环境;而后者会形成一个属于模块自身的作用域,所有的变量及函数只有自己能访问,对外是不可见的。请看下面的例子:

// calculator.js
var name = 'calculator.js';

// index.js
var name = 'index.js';
require('./calculator.js');
console.log(name); // index.js

这里有两个文件,在 index.js 中我们通过 CommonJSrequire 函数加载 calculator.js 。运行之后控制台结果是 “index.js” ,这说明 calculator.js 中的变量声明并不会影响 index.js ,可见每个模块是拥有各自的作用域的。

CommonJs 与 ES6 Module 区别

一、动态与静态

CommonJSES6 Module 最本质的区别在于前者对模块依赖的解决是“动态的”,而后者是“静态的”。在这里“动态”的含义是,模块依赖关系的建立发生在代码运行阶段;而“静态”则是模块依赖关系的建立发生在代码编译阶段。

ES6 代码的编译阶段就可以分析出模块的依赖关系。它相比于 CommonJS 来说具备以下几点优势:

二、拷贝与映射

CommonJS

index.js 中的 count 是对 calculator.jscount 的一份值拷贝,因此在调用 add 函数时,虽然更改了原本 calculator.jscount 的值,但是并不会对 index.js 中导入时创建的副本造成影响。另一方面,在CommonJS 中允许对导入的值进行更改。我们可以在 index.js 更改 countadd ,将其赋予新值。同样,由于是值的拷贝,这些操作不会影响 calculator.js 本身。

// calculator.js
var count = 0;
module.exports = {
    count: count,
    add: function(a, b) {
        count += 1;
        return a + b;
    }
};

// index.js
var count = require('./calculator.js').count;
var add = require('./calculator.js').add;

console.log(count); // 0(这里的count是对 calculator.js 中 count 值的拷贝)
add(2, 3);
console.log(count); // 0(calculator.js中变量值的改变不会对这里的拷贝值造成影响)

count += 1;
console.log(count); // 1(拷贝的值可以更改)
ES6 Module

ES6 Module 中导入的变量其实是对原有值的动态映射index.js 中的 count 是对 calculator.js 中的count 值的实时反映,当我们通过调用 add 函数更改了 calculator.jscount 值时,index.jscount 的值也随之变化。我们不可以对 ES6 Module 导入的变量进行更改,可以将这种映射关系理解为一面镜子,从镜子里我们可以实时观察到原有的事物,但是并不可以操纵镜子中的影像。

// calculator.js
let count = 0;
const add = function(a, b) {
    count += 1;
    return a + b;
};
export { count, add };

// index.js
import { count, add } from './calculator.js';

console.log(count); // 0(对 calculator.js 中 count 值的映射)
add(2, 3);
console.log(count); // 1(实时反映calculator.js 中 count值的变化)

// count += 1; // 不可更改,会抛出SyntaxError: "count" is read-only

三、循环依赖

循环依赖是指模块A依赖于模块B,同时模块B依赖于模块A。比如下面这个例子:

// a.js
import { foo } from './b.js';
foo();

// b.js
import { bar } from './a.js';
bar();
CommonJS
// foo.js
const bar = require('./bar.js');
console.log('value of bar:', bar);
module.exports = 'This is foo.js';

// bar.js
const foo = require('./foo.js');
console.log('value of foo:', foo);
module.exports = 'This is bar.js';

// index.js
require('./foo.js');

在这里,index.js 是执行入口,它加载了 foo.jsfoo.jsbar.js 之间存在循环依赖。让我们观察foo.jsbar.js 中的代码,理想状态下我们希望二者都能导入正确的值,并在控制台上输出。

value of foo: This is foo.js
value of bar: This is bar.js

而当我们运行上面的代码时,实际输出却是:

value of foo: {}
value of bar: This is bar.js

为什么 foo 的值会是一个空对象呢?让我们从头梳理一下代码的实际执行顺序。

ES6 Module
// foo.js
import bar from './bar.js';
console.log('value of bar:', bar);
export default 'This is foo.js';

// bar.js
import foo from './foo.js';
console.log('value of foo:', foo);
export default 'This is bar.js';

// index.js
import foo from './foo.js';

执行结果如下:

// value of foo: undefined
// foo.js:3 value of bar: This is bar.js

很遗憾,在 bar.js 中同样无法得到 foo.js 正确的导出值,只不过和 CommonJS 默认导出一个空对象不同,这里获取到的是 undefined

上面我们谈到,在导入一个模块时,CommonJS 获取到的是值的拷贝,ES6 Module 则是动态映射,那么我们能否利用 ES6 Module 的特性使其支持循环依赖呢?请看下面这个例子:

//index.js
import foo from './foo.js';
foo('index.js');

// foo.js
import bar from './bar.js';
function foo(invoker) {
    console.log(invoker + ' invokes foo.js');
    bar('foo.js');
}
export default foo;

// bar.js
import foo from './foo.js';
let invoked = false;
function bar(invoker) {
    if(!invoked) {
        invoked = true;
        console.log(invoker + ' invokes bar.js');
        foo('bar.js');
    }
}
export default bar;

执行结果如下

// index.js invokes foo.js
// foo.js invokes bar.js
// bar.js invokes foo.js

可以看到,foo.jsbar.js 这一对循环依赖的模块均获取到了正确的导出值。下面让我们分析一下代码的执行过程。

上一篇 下一篇

猜你喜欢

热点阅读