一文读懂JavaScript模块化开发(一)
JavaScript
2021-05-21
380
0

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的值也随之变化。

Reference

ES6Javascript