今天来扒一扒在node和ES6中的module,主要是为了区分node和ES6中的不一样意义,避免概念上的混淆,同时也分享一下,本身在这个坑里得到的心得。html
模块的概念是在ES6发布以前就出现的,我感受主要是为了适应大型应用开发的须要而引入了JavaScript世界。模块化编程已经从噱头上升为必备,因此ES6也顺应时代,把这个写进了标准。前端
CommonJS和AMD都是JavaScript模块化规范,在ES6以前,Node主要遵循CommonJS,而AMD则主要运用在浏览器端,好比requirejs。node
并且node发布的时候,就天生具有module,因此从某种意义上讲,是node促进了js世界里面的模块化编程。express
// module-file.js for node
module.exports = {
a : function() {},
b : 'xxx'
};
复制代码
把上面这个js文件放在node环境中,咱们这样去用它:编程
var myModule = require('./module-file');
var b = myModule.b;myModule.a();
复制代码
从模块化思想出发,requirejs和国内前端大牛发布的seajs(遵循CMD)也容许前端猿们经过require去加载另外一个模块。不过在模块定义的时候,须要借助一个define函数:数组
// module-file.js for requirejs
define(function(require, exports, module){
module.exports = {};
});
复制代码
requirejs和seajs都支持上面这种形式,在define的回调函数中提供了模拟的require, exports, module,这让习惯了node环境中使用方法的猿类能够顺便在浏览器端写基本相同的代码。浏览器
node,requirejs,seajs也同时支持下面这种导出模块的方式:bash
define(function(){
return {}; // return的值就是导出的模块
});
复制代码
这就出现了UMD,即一个兼容多种环境的方案。babel
node中导出模块接口就是用exports,你能够这样作:网络
module.exports.a = function() {};
module.exports.b = 'xxx';
复制代码
也能够写在一个对象中:
module.exports = {
a : function() {},
b : 'xxx'
}
复制代码
在requirejs中,还提供了一个exports变量做为module.exports的别名:
define(function(require, exports, module){
exports.a = function(){};
exports.b = 'xxx';
});
复制代码
注意“别名”的含义:exports是module.exports的地址的引用。它的本质是:
var exrpots = module.exports;
复制代码
所以,你必须注意两个点,就是导出接口的时候: 1.不能直接用exports={}来导出整个接口; 2.若是使用了module.exports={},那么exports.a等都会被覆盖无效。
在ES6以前,要使用一个模块,必须使用require函数将一个模块引入,但ES6并无采用这种模块化方案,在ES6中使用import指令引入一个模块或模块中的部分接口,并无将require写入标准,这也就是说require对于ES6代码而言,只是一个普通函数。
同理,在ES6标准中,导出模块的接口也只能使用export指令,而非exports对象,这也一样意味着module.exports只是node,requirejs等模块化库的自定义变量,而非ES标准接口。
在ES6规定中,这样导出一个模块的接口:
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 …;
复制代码
ES6里面,直接把要导出的变量、函数、对象、类等前面加一个export关键字。好比:
// module-file.js
export function a(){};
export var obj = {};
复制代码
有一个点:export必须导出具备对应关系的变量,下面的接口输出是错误的:
// 错误演示
export 1; // 这种导出的内容不是变量是绝对错误的,包括导出表达式,也是绝对错误的
var a = 1;
export a;
function b() {}
export b;
复制代码
上面的这些方法都是错误的,不能这样导出接口。导出接口仅限两种:
1.声明时导出 2.以对象的形式导出(和解构联系起来)
若是要导出某个变量,能够用花括号括起来,像这样:
var a = 1;
export {a}; // 等效于:{a:a}
function b() {}
export {b};
复制代码
我有点疑惑的是,为什么export容许屡次export {}这种形式?看上去很奇怪。另外,ES6更厉害之处在于,能够在export变量以后,继续修改变量:
export var obj {};obj.a = 1;
复制代码
在import以后,obj的值仍然能够在模块内继续改变,这是CommonJS以往不可能作到的。
在另一个js文件里面这样使用这些接口:
import {a,obj} from './module-file';
a();
alert(obj.b);
复制代码
和node以前的require不同,require只能把模块放到一个变量中,而在ES6中,拥有对象解构赋值的能力,因此直接就把引入的模块的接口赋值给变量了。内在机理也有不一样,require须要去执行整个模块,将整个模块放到内存中(也就是咱们说的运行时),若是只是使用到其中一个方法,性能上就差不少,而import...from则是只加载须要的接口方法,其余方法在程序启动以后根本触及不到,因此这种又被称为“编译时”,性能上好不少。
编程的同窗对as都容易理解,简单的说就是取一个别名。上面export中能够用,import中其实也能够用:
// a.js
var a = function() {};
export {a as fun};
// b.js
import {fun as a} from './a';a();
复制代码
上面这段代码,export的时候,对外提供的接口是fun,它是a.js内部a这个函数的别名,可是在模块外面,认不到a,只能认到fun。
import中的as就很简单,就是你在使用模块里面的方法的时候,给这个方法取一个别名,好在当前的文件里面使用。之因此是这样,是由于有的时候不一样的两个模块可能经过相同的接口,好比有一个c.js也经过了fun这个接口:
// c.js
export function fun() {};
复制代码
若是在b.js中同时使用a和c这两个模块,就必须想办法解决接口重名的问题,as就解决了。
其余人写教程什么的,都把default放到export那个部分,我以为不利于理解。在export的时候,可能会用到default,说白了,它实际上是别名的语法糖:
// d.js
export default function() {}
// 等效于:function a() {}; export {a as default};
复制代码
在import的时候,能够这样用:
import a from './d';
// 等效于,或者说就是下面这种写法的简写,是同一个意思import {default as a} from './d';
复制代码
这个语法糖的好处就是import的时候,能够省去花括号{}。简单的说,若是import的时候,你发现某个变量没有花括号括起来,那么你在脑海中应该把它还原成有花括号的as语法。
因此,下面这种写法你也应该理解了吧:
import _,{each,map} from '_';
复制代码
*就是表明全部,只用在import中,咱们看下两个例子:
import * as underscore from '_';
复制代码
在乎义上和import _ from '';是不一样的,虽然实际上后面的使用方法是同样的。它表示的是把''模块中的全部接口挂载到underscore这个对象上,因此能够用underscore.each调用某个接口。
export * from '_';
// 等效于:import * as all from '_';export all;
复制代码
接下来的问题,就是咱们在实操中,还有必要用require吗?我感受ES6标准已经将以前的全部模块化规范都给碾压了,这在之前的标准发布中极少见,ES6用更加简单的方式,实现了更加有效的module,感受require能够回家养老了。
既然是标准,那么就是全部引擎应该去实现的,node和浏览器将来都会直接支持这种模块加载方式,require完成历史使命回家本身玩儿。并且做为node或浏览器,同时能够利用import提供本身的API,好比手机端提供基于网络的定位API,这都不用SDK了,直接内置在客户端内部,import一下就能够了。
不过如今import导入模块还并非所有环境都支持,使用babel可让node支持ES6,但在浏览器端,则毫无办法,可能还得暂时依赖require。可是很是很差的消息是,require不是ES6标准,这也就是说若是未来浏览器支持import后,你想用它,就必须升级代码,而不能直接被兼容。
import只能在文件开头使用,在import以前,你不能有其余的代码,这和其余语言是同样的。可是require则不一样,它至关于node的一个定义在全局的函数,你能够在任意地方使用它,甚至使用变量表达式做为它的参数,这样有一个好处,就是能够在循环中加载模块。
可是很坑的是,node的模块导出和ES6标准也不符,由于node的模块体系遵循的是CommonJS规范,这就致使你写的模块文件,不可能同时支持require和import。
要强调的就是,不要把require和import两种模块加载方案混用,好比:
// module-file.js
module.exports = {};
// a.js
import a from './moule-file';
复制代码
这种混搭感受不是很好(但能够用,下面有解释)。因此,其实我没有任何建议,我只是以为,躺在坑里,挺自在的……毕竟node中require的使用更加灵活一点,它没有必须放在哪里的限制,因此能够在任意位置使用,并且它的结果也很是形象,甚至能够把require当作一个引用类型别名,能够这样使用:
require('./a')(); // a模块是一个函数,当即执行a模块函数
var data = require('./a').data; // a模块导出的是一个对象
var a = require('./a')[0]; // a模块导出的是一个数组
复制代码
这样的写法感受像给模块取了一个别名,使用的时候很是灵活。可是须要注意的是,若是你打算使用require来导入这个模块,那么请使用module.exports导出这个模块。
有没有一种兼容方案呢?
function a() {}
class b {}
module.exports = {a,b}; // {a,b}是ES6的写法
复制代码
在实践中发现,module.exports能够兼容require和import,并且这个案例须要你的node环境配置好支持ES6语法。module.exports导出的模块,若是使用import,那么彻底就是一个对象赋值、解构的过程:
import mod,{a,b} from './a';
复制代码
之因此这是成立的,是由于咱们使用babel对ES6代码进行转码后执行,而实际上,目前为止,没有任何一个环境是支持ES6 module方案的,即便babel,也仅仅是将ES6的import,export转码为require, module.exports后交给node去执行。
导出的模块接口被赋值给mod,因此mod是一个对象,含有a,b两个方法。这里的mod并无经过default导出,因此和ES6有很是大的意义上的区别,这种非标准的写法,墙裂建议永远不要用。并且,因为require和module.exports是非标准的东西,仅在Node环境中有效,因此当将来浏览器支持模块导入时,并不会主动提供require,而是采用import,若是要使用require,仍是不得不使用requirejs等库,借助define来用。
因此,最终,若是你打算用CommonJS,就不要掺和进ES6.