3

node和es6中的模块使用对比

目前使用js变成离不开模块,而现在最为常见的模块也就是node采用的COMMONjs的方式和es6规范,这里对两种的使用进行对比,并没有深入源码尽情扣,

node--commonjs规范的模块化

node的模块是比较常见的,是全局变量global中的一个属性,文件和模块是一一对应的(每个文件被视为一个独立的模块)。

使用

目前比较规范的是一个文件就是一个模块,主要是exports和require进行处理,

exports

exports 变量是在模块的文件级别作用域内有效的,它在模块被执行前被赋于 module.exports 的值。它有一个快捷方式,以便 module.exports.f = ... 可以被更简洁地写成 exports.f = ...。 注意,就像任何变量,如果一个新的值被赋值给 exports,它就不再绑定到 module.exports。可以具体看看代码:

function add(x, y){
    return x+y;
}
function multiply(x, y){
    return x*y;
}
exports.add = (x, y) => x+y; //exports作为module.exports的快捷方式
exports.multiply = (x, y) => x*y;
module.exports = add; //此时exports module.exports从新被赋为新对象或者函数(函数也是对象)
exports = {add: add} //这是exports已经和module.exports没有关系了,成了独立的模块作用域内对象,并不会被导出
module.exports = {  //最后的导出部分
        add: add,
        multiply: multiply
    };

关于exports和module.exports的关系看下面代码的类似实现,好像实参和形参一样,不同的是最后如果module.exports的没有直接复制操作,会被赋值为exports。

function require(/* ... */) {
  const module = { exports: {} };
  ((module, exports) => {
    // 模块代码在这。在这个例子中,定义了一个函数。
    function someFunc() {}
    exports = someFunc;
    // 此时,exports 不再是一个 module.exports 的快捷方式,
    // 且这个模块依然导出一个空的默认对象。
    module.exports = someFunc;
    // 此时,该模块导出 someFunc,而不是默认对象。
  })(module, module.exports);
  return module.exports;
}

require

定义好了一个模块就应该使用它,

var test = require('./moduleTest.js');

console.log(test(1, 2));
console.log(mutiply(1, 2));

使用是比较简单的,使用变量获取导出的对象exports,就可以使用对象里面的方法了,我们有的时候可能会遇到这样的情况,无需使用变量接受模块的exports,既可以直接使用,这种情况一般是在模块代码导出的时候做了处理:

module.exports.add = global.add = (x, y) => x+y;

把模块的方法直接提升到全局作用域,这其实并不是好的选择。

module对象的一些解读

Node 使用两个核心模块来管理模块依赖:

  1. require 模块,是个看起来像在全局作用域有效的模块——不需要 require('require')。

  2. module 模块,看起来也像是在全局作用域内有效——不需要 require('module')。

由 require 模块导出的主要对象是一个函数(如上例所用)。 当 Node 使用本地文件路径作为函数的唯一参数调用该 require() 函数时,Node 将执行以下步骤:

  • 解析:找到文件的绝对路径。

  • 加载:确定文件内容的类型.

  • 封装:给文件其私有作用域。 这使得 require 和 module 对象两者都可以下载我们需要的每个文件。

  • 评估:这是 VM 对加载的代码最后需要做的。

  • 缓存:当我们再次需要这个文件时,不再重复所有的步骤。

模块在第一次加载后会被缓存。 这也意味着(类似其他缓存机制)如果每次调用 require('foo') 都解析到同一文件,则返回相同的对象。

多次调用 require(foo) 不会导致模块的代码被执行多次。 这是一个重要的特性。 借助它, 可以返回“部分完成”的对象,从而允许加载传递的依赖, 即使它们会导致循环。

模块是基于其解析的文件名进行缓存的。 由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如从 node_modules 目录加载),这样就不能保证 require('foo') 总能返回完全相同的对象。

此外,在不区分大小写的文件系统或操作系统中,被解析成不同的文件名可以指向同一文件,但缓存仍然会将它们视为不同的模块,并多次重新加载。 例如,require('./foo') 和 require('./FOO') 返回两个不同的对象,而不会管 ./foo 和 ./FOO 是否是相同的文件。

es6的模块

在ES6之前,要使用一个模块,必须使用require函数将一个模块引入,但ES6并没有采用这种模块化方案,在ES6中使用import指令引入一个模块或模块中的部分接口,并没有将require写入标准,这也就是说require对于ES6代码而言,只是一个普通函数。

同理,在ES6标准中,导出模块的接口也只能使用export指令,而非exports对象,这也同样意味着module.exports只是node,requirejs等模块化库的自定义变量,而非ES标准接口。

当然,ES6的模块化也很复杂,不只模块接口的导入导出这点东西,只不过本文无耻的只讨论这个相对而言比较常见的问题。

export使用

先列举一些常用的使用方法:

export function fun() {};
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const

export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;

导出的不是变量是绝对错误的,包括导出表达式,也是绝对错误的,export允许多次export {}这种形式,看上去很奇怪,其实和react的setState一样,是一个增量的添加模式。另外,ES6更厉害之处在于,可以在export变量之后,继续修改变量。

import使用

import的使用其实比require更加的舒服

import {a,obj} from './module-file';

和node之前的require不一样,require只能把模块放到一个变量中,而在ES6中,拥有对象解构赋值的能力,所以直接就把引入的模块的接口赋值给变量了。内在机理也有不同,require需要去执行整个模块,将整个模块放到内存中(也就是我们说的运行时),如果只是使用到其中一个方法,性能上就差很多,而import...from则是只加载需要的接口方法,其他方法在程序启动之后根本触及不到,所以这种又被称为“编译时”,性能上好很多。

AS DEFAULT * 关键字

编程的同学对as都容易理解,简单的说就是取一个别名。上面export中可以用,import中其实也可以用:

// a.js
var a = function() {};
export {a as fun};

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

而default则是一种语法糖,实践类似:

// d.js
export default function() {}

// 等效于:
function a() {};
export {a as default};

//在import的时候,可以这样用:
import a from './d';

// 等效于,或者说就是下面这种写法的简写,是同一个意思
import {default as a} from './d';

则是代表把所有的export赋值给 as newExport,这个后面跟这个的别名对象,从而获取模块所有方法。

参考:http://www.tangshuang.net/288...
https://juejin.im/entry/58d4e...
http://nodejs.cn/api/modules....


caoweiju
1.5k 声望53 粉丝

class Myself {