AMD、CommonJs、ES6 module的简单总结

发表于 2016-12-28
更新于 2024-05-23
分类于 技术专栏
阅读量 6654
字数统计 3721

前言

在公司的项目中频繁地混用AMD、CommonJs、ES6的引入文件的各种写法,最后最后自己都被绕晕了,所以有必要好好总结一下,理理这些引入文件的写法,免得下次又得绕圈子到网上搜,浪费时间不说,还影响编码心情。

1、AMD(Asynchronous Module Definition)

首先介绍的第一种引入模块的方式,这种方式只在客户端中使用,其基本语法是:

//Calling define with a dependency array and a factory function
define(['dep1', 'dep2'], function (dep1, dep2) {

    //Define the module value by returning a value.
    return function () {};
});

// Or:
define(function (require) {
    var dep1 = require('dep1'),
        dep2 = require('dep2');

    return function () {};
});

异步加载的实现得益于Js的闭包语法:当请求的模块结束加载的时候其函数才会被调用。其代表的实现方案有:require.jsDojo

豆米的博客网站前端就是用了require.js,具体使用方法可以参考:config.js

2、CommonJs

CommonJs项目旨在帮助那些在服务端开发JS应用的人员定义一系列规范,其中之一便有模块的加载规范。刚开始Nodejs的开发者都尝试遵循CommonJS规范,不过后来就决定抵制这个规范(不知道什么原因,没有去了解),不过后来Nodejs的module系统到处都有CommonJS的影子。

其基本使用方法:

// In circle.js
const PI = Math.PI;

exports.area = (r) => PI * r * r;

exports.circumference = (r) => 2 * PI * r;

// In some file
const circle = require('./circle.js');
console.log( `The area of a circle of radius 4 is ${circle.area(4)}`);

Nodejs的模块系统基本和CommonJS一样,除了Nodejs还有一个module.exports的对象,根据Nodejs的官方文档解释:

如果你想要让你的模块作为一个函数(比如作为一个构造函数)直接导出或者如果你想要导出一个完整的对象而不是对象的一个属性,那么你应该使用module.exports而不是exports。

文档中给了一个例子:

const square = require('./square.js');
var mySquare = square(2);  // 直接调用
console.log(`The area of my square is ${mySquare.area()}`);

square.js
// assigning to exports will not modify module, must use module.exports
module.exports = (width) => {
  return {
    area: () => width * width
  };
}

看着二者的区别应该是有所理解,但是内在的本质呢,这里我们简单地提一下:

2.1、module.exports vs exports

先抛出结论:

Module.exports才是真正的接口,exports只不过是它的一个引用。 最终返回给调用的是Module.exports而不是exports。给module.exports添加属性类似于给exports添加属性,注意仅仅是类似于哈!!

每一个node.js执行文件,都自动创建一个module对象,同时,module对象会创建一个叫exports的属性,初始化的值是 {}

module.exports = {};

当你使用exports.something = someObj的时候这些属性和对应的方法是直接挂靠到module.exports上去的。大致的模型应该是这样:

通过这个模型我们可以解释下面的一些现象:

2.1.1、现象1


// foo.js

exports.a = function(){
  console.log('a')
 }

module.exports = {a: 2}
exports.a = 1 

// test.js

var x = require('./foo');

console.log(x.a) // 2

为什么是2呢?

因为module.exports重新指向了一个新的对象{a: 2},而exports仍然指向之前的旧的空对象,修改的东西都是以前的旧的对象,但是我们最后使用的module.exports指向的方法,所以结果为2

2.1.2、现象2

exports = module.exports = someObject;

这样的写法是为了确保exports不会引用之前到处的对象,保证模块初始化的环境是干净的。同时保证exports指向的module.exports是我们想要导出的对象,从而不会出现现象1中exports与module.exports的关联断开。

2.1.3、总结

最后再提醒一句:module.exports直接导出的对象是可以直接调用的,而不需要使用属性的方法去调用。

2.2、结论

最后回到CommonJS上,CommonJS模块是被设计成在服务端使用。本质上,API都是同步的,也就是说模块是立即被加载的并且是按照它们require的顺序被加载。

ES2016 Module

如今ES6定义了自己的模块加载规范,支持同步和异步加载并且语法上变得更加简洁。其大致语法如下:

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
    return x * x;
}
export function diag(x, y) {
    return sqrt(square(x) + square(y));
}

//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

如果你直接使用export default someObj = xxx,那么import的时候就不再需要import {} from xxx,而是直接使用import someObj from xxx即可。

更多用法参考:import && export

参考

  1. https://auth0.com/blog/javascript-module-systems-showdown/
  2. https://nodejs.org/api/modules.html
  3. http://zihua.li/2012/03/use-module-exports-or-exports-in-node/

公众号关注一波~

微信公众号

关于评论和留言

如果对本文 AMD、CommonJs、ES6 module的简单总结 的内容有疑问,请在下面的评论系统中留言,谢谢。

网站源码:linxiaowu66 · 豆米的博客

Follow:linxiaowu66 · Github