在了解es6Module与RequireJS以前,仍是须要先来简单地了解下什么是模块化,模块化开发node
模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思惟把问题进行系统性的分解以之处理。模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。es6
做为一个模块化系统所必须的能力:浏览器
历史上,JavaScript 一直没有模块(module)体系,没法将一个大程序拆分红互相依赖的小文件,再用简单的方法拼装起来。其余语言都有这项功能,好比 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,可是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目造成了巨大障碍。缓存
因此社区制定了CommonJs规范,Node 从 Commonjs 规范中借鉴了思想因而有了 Node 的 module,而 AMD 异步模块 也一样脱胎于 Commonjs 规范,以后有了运行在浏览器上的 require.js。服务器
ES6 在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。异步
Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口,用require加载模块。模块化
// 定义模块 area.js function area(radius) { return Math.PI * radius * radius; } // 在这里写上须要向外暴露的函数、变量 module.exports = { area: area } // 引用自定义的模块时,参数包含路径 var math = require('./math'); math.area(2);
可是咱们并无直接定义 module、exports、require这些模块,以及 Node 的 API 文档中提到的__filename、__dirname。那么是从何而来呢?其实在编译的过程当中,Node 对咱们定义的 JS 模块进行了一次基础的包装:函数
(function(exports, require, modules, __filename, __dirname)) { ... })
这样咱们即可以访问这些传入的arguments以及隔离了彼此的做用域。CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,而后在内存生成一个对象。工具
{ id: '...', exports: { ... }, loaded: true, ... }
之后须要用到这个模块的时候,就会到exports属性上面取值。即便再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。commonJS用同步的方式加载模块,只有在代码执行到require的时候,才回去执行加载。ui
CommonJS规范规定,每一个模块内部,module变量表明当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,实际上是加载该模块的module.exports属性。
下面是Module._load的源码
Module._load = function(request, parent, isMain) { // 计算绝对路径 var filename = Module._resolveFilename(request, parent); // 第一步:若是有缓存,取出缓存 var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; // 第二步:是否为内置模块 if (NativeModule.exists(filename)) { return NativeModule.require(filename); } // 第三步:生成模块实例,存入缓存 var module = new Module(filename, parent); Module._cache[filename] = module; // 第四步:加载模块 try { module.load(filename); hadException = false; } finally { if (hadException) { delete Module._cache[filename]; } } // 第五步:输出模块的exports属性 return module.exports; };
// exportDemo.js count = 1; module.exports.count = count; module.exports.Hello = function() { var name; this.setName = function(newName) { name = newName; } this.sayHello = function() { console.log("hello Mr." + name); } this.getId = function() { return count++ } } var { Hello, count } = require('./exportDemo') var hello = new Hello(); // 让count自增 console.log(hello.getId()); console.log(hello.getId()); // 发现获取的count仍是原值 console.log(count) // 真正的count实际上是已经改了的 var newHello = new Hello(); console.log(newHello.getId()) var { Hello: newHello, count: newCount } = require('./exportDemo') console.log(newCount, 'newCount'); // 再次require,取得的newHello和以前require的Hello指向同一个拷贝 console.log(newHello === Hello)
es6在语言标准的层面上,实现了模块功能。
ES6 模块不是对象,而是经过export命令显式指定输出的代码,再经过import命令输入。
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其余方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 能够在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。固然,这也致使了无法引用 ES6 模块自己,由于它不是对象。
因为 ES6 模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,好比引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各类好处,ES6 模块还有如下好处。
// exportDemo.mjs export let counter = 1; export function incCounter() { counter ++; } // importDemo.mjs import { counter, incCounter } from './exportDemo.mjs' incCounter(); console.log(counter) // 打印结果为2,而不是初始值的1
这是一个从静态到动态导入转换的例子
// STATIC import './a.js'; import b from './b.js'; b(); import {c} from './c.js'; c(); // DYNAMIC import('./a.js').then(()=>{ console.log('a.js is loaded dynamically'); }); import('./b.js').then((module)=>{ const b = module.default; b('isDynamic'); }); import('./c.js').then(({c})=>{ c('isDynamic'); });
动态import()给咱们提供了用异步方式使用ES模块的额外功能。可让咱们根据咱们的须要动态或有条件地加载它们。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
如下这些顶层变量在ES6模块之中都是不存在的