模块设计的思想是尽可能静态化,使得编译的时候就能够肯定模块的一来关系,以及输入和输出的变量。
CommonJS和AMD都只能在运行时肯定这些东西,commonJS模块就是对象,输入时须要查找对象属性javascript
// CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile;
nodeJS 中模块化使用的就是CommonJS的规范,实质就是总体加载fs模块,生成fs_对象,在对象上读取属性和方法,
这种加载方式是“运行时加载”html
ES6 模块不是对象,而是经过export命令显式指定输出的代码,再经过import命令输入。
// ES6模块 import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其余方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 能够在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。java
一个模块就是一个独立的文件。该文件内部的全部变量,外部没法获取。若是你但愿外部可以读取模块内部的某个变量,就必须使用export关键字输出该变量。node
// profile.js export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; // profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year};
export的语法,对外导出接口,在接口名与模块内部变量之间,创建了一一对应的关系。es6
// 写法一 export var m = 1; // 写法二 var m = 1; export {m}; // 写法三 var n = 1; export {n as m};
注意,import命令具备提高效果,会提高到整个模块的头部,首先执行。json
目前阶段,经过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,能够写在同一个模块里面,可是最好不要这样作。由于import在静态解析阶段执行,因此它是一个模块之中最先执行的。下面的代码可能不会获得预期结果。数组
require('core-js/modules/es6.symbol'); require('core-js/modules/es6.promise'); import React from 'React';
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,所以export default命令只能使用一次。因此,import命令后面才不用加大括号,由于只可能惟一对应export default命令。promise
本质上,export default就是输出一个叫作default的变量或方法,而后系统容许你为它取任意名字。因此,下面的写法是有效的。缓存
// modules.js function add(x, y) { return x * y; } export {add as default}; // 等同于 // export default add; // app.js import { default as foo } from 'modules'; // 等同于 // import foo from 'modules'; // 正确 export var a = 1; // 正确 var a = 1; export default a; // 错误 export default var a = 1;
每一个文件就是一个模块,有本身的做用域。在一个文件里面定义的变量、函数、类,都是私有的,对其余文件不可见。
CommonJS规范规定,每一个模块内部,module变量表明当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,实际上是加载该模块的module.exports属性。app
var x = 5; var addX = function (value) { return value + x; }; module.exports.x = x; module.exports.addX = addX; // 使用 var example = require('./example.js'); console.log(example.x); // 5 console.log(example.addX(1)); // 6
export var foo = 'bar'; setTimeout(() => foo = 'baz', 500);
ES6 模块化上面代码输出变量foo,值为bar,500 毫秒以后变成baz。
这一点与 CommonJS 规范彻底不一样。CommonJS 模块输出的是值的缓存,不存在动态更新
Node内部提供一个Module构建函数。全部模块都是Module的实例
module.id 模块的识别符,一般是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其余模块。
module.exports 表示模块对外输出的值。
module.exports属性表示当前模块对外输出的接口,其余文件加载该模块,实际上就是读取module.exports变量。
为了方便,Node为每一个模块提供一个exports变量,指向module.exports。这等同在每一个模块头部,有一行这样的命令。
var exports = module.exports;
形成的结果是,在对外输出模块接口时,能够向exports对象添加方法。
exports.area = function (r) { return Math.PI * r * r; }; exports.circumference = function (r) { return 2 * Math.PI * r; };
注意,不能直接将exports变量指向一个值,由于这样等于切断了exports与module.exports的联系。
exports.hello = function() { return 'hello'; }; module.exports = 'Hello world';
面代码中,hello函数是没法对外输出的,由于module.exports被从新赋值了。
这意味着,若是一个模块的对外接口,就是一个单一的值,不能使用exports输出,只能使用module.exports输出。
和node中模块加载规则一致
一般,咱们会把相关的文件会放在一个目录里面,便于组织。这时,最好为该目录设置一个入口文件,让require方法能够经过这个入口文件,加载整个目录。
在目录中放置一个package.json文件,而且将入口文件写入main字段。下面是一个例子。
// package.json { "name" : "some-library", "main" : "./lib/some-library.js" }
require发现参数字符串指向一个目录之后,会自动查看该目录的package.json文件,而后加载main字段指定的入口文件。若是package.json文件没有main字段,或者根本就没有package.json文件,则会加载该目录下的index.js文件或index.node文件。