JavaScript模块化开发,早期会使用CommonJS或者AMD标准进行开发,但从ES6开始确定了原生支持的模块化标准。本文主要探讨CMD模块化开发和ES模块化标准的使用差异。
CommonJS模块化
CommonJS模块化运用于Node.js开发中,使用require和exports语法来输入和输出。
例如,我们输出a变量和func函数:
exports.a = 1;
exports.func = function(){
console.log("hello!")
}
那么输入语法可以这样写:
var cmdModule = require("./cmdModule.js");
cmdModule.a // 1
cmdModule.func // function
输出的变量和函数在导入时会自动被包裹上一层命名空间(JSON),需要打点去调用所暴露的变量和函数。
此外require语句无论调用几次均只执行一次,在第一次加载时模块会被缓存,之后都从缓存中直接读取结果。
使用exports可以向外暴露很多东西,但如果我们只需要向外暴露一个类,那么此时调用时候会很不方便,例如
输出:
function People(name, age){
this.name = name;
this.age = age;
}
exports.People = People;
调用:
var People = require("./People.js");
var person = new People.People("小明", 18);
可以看到,调用函数new的时候需要写People.People(),十分的麻烦。
如果js文件只需要向外输出一个函数,此时可以使用module.exports = xx的写法。
例如刚才的例子,我们可以改为
输出:
function People(name, age){
this.name = name;
this.age = age;
}
moudle.exports.People = People;
调用:
var People = require("./People.js");
var person = new People("小明", 18);
总结,如果一个js模块要向外暴露很多个变量,那么我们应该使用exports.xx = xx;如果仅仅暴露一个东西,通常是构造函数,此时我们使用module.exports = xx。
ES模块化
直接罗列需要输出的变量或者函数
export let a = 1;
export function sayHello() {
console.log("hello!")
}
顶部定义,底部向外暴露:
function calCircleArea(r){
return Math.PI * Math.sqrt(r);
}
export { calCircleArea }
引入时:
import { calCircleArea, a, sayHello } from "./circle.js";
当然我们还可以给定一个Namespace,引入该包里的所有函数和变量:
import * as Circle from "./circle.js";
如果我们只向外默认暴露一个东西,那么可以使用export default写法:
export default function People(name, age) {
this.name = name;
this.age = age;
}
People.prototype.sayHello = function() {
alert("Hello!");
}
引入时:
import People from "./People.js"
let xiaoming = new People("小明", 18);
xiaoming.sayHello();
注意,这里引入的People
是一个Alias,可以起任意的名字,我们不需要纠结到底暴露的是什么函数名。
CommonJS模块和ES模块差异
ES6的设计思想是尽量静态化,能保证在编译时就确定模块之间的依赖关系,每个模块的输入和输出变量也都是确定的,而CommonJS无法在编译时就确定这些内容,只能在运行时确定。
其次CommonJS输出的是一个值的拷贝,而ES模块输出的是值的动态映射,并且这个映射是只读的。
以CommonJS为例
// 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
index.js中的count是对calculator.js中count的一份值拷贝,因此在调用add函数时,虽然更改了原本calculator.js中count的值,但是并不会对index.js中导入时创建的副本造成影响。
另一方面,在CommonJS中允许对导入的值进行更改。我们可以在index.js更改count和add,将其赋予新值。同样,由于是值的拷贝,这些操作不会影响calculator.js本身。
下面我们使用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
上面的例子展示了ES6 Module中导入的变量其实是对原有值的动态映射。index.js中的count是对calculator.js中的count值的实时反映,当我们通过调用add函数更改了calculator.js中count值时,index.js中count的值也随之变化。