Javascript
的模板规范共有两种:CommonJS
和 AMD
commonjs
nodejs的模块系统,是参照commonjs
规范实现的javascript
commonjs
即为服务器端模块的规范。 commonjs
的规范: 根据commonjs
规范,一个单独的文件就是一个模块。加载模块使用require
方法,该方法读取一个文件并执行,最后返回文件内部的exports对象java
commonjs
模块的加载原理commonjs
模块不管加载多少次,都只会在第一次加载时运行一次,之后再加载,就返回第一次运行的结果,除非手动清除系统缓存。node
AMD
commonjs
规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操做。AMD
规范则是非同步加载模块,容许指定回调函数。因为Node.js主要用于服务器编程,模块文件通常都已经存在于本地硬盘,因此加载起来比较快,不用考虑非同步加载的方式,因此commonjs
规范比较适用。可是,若是是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,所以浏览器端通常采用AMD规范。es6
能够理解为AMD
即为能在客户端环境,而且能兼容服务器端模块的一种模块规范web
AMD
的模块定义:
AMD规范使用define
方法定义模块编程
Define第一个参数表达依赖的模块数组,第二个为加载完依赖的模块数组后,模块执行的函数
AMD
的模块加载定义:跟commonjs
同样,AMD
也采用require()
语句来加载模块,可是与commonjs
不一样的是,它要求有两个参数:
第一个参数[module]
,是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功以后的回调函数数组
AMD
和CMD
对比对于依赖的模块,AMD
是提早执行,CMD
是延迟执行。不过 RequireJS 从 2.0 开始,也改为能够延迟执行(根据写法不一样,处理方式不一样)。CMD
推崇 as lazy as possible.浏览器
CMD
推崇依赖就近,AMD
推崇依赖前置缓存
AMD
的API
默认是一个当多个用,CMD
的API 严格区分,推崇职责单一。好比AMD
里,require
分全局require
和局部require
,都叫require
。CMD
里,没有全局 require
,而是根据模块系统的完备性,提供seajs.use
来实现模块系统的加载启动。CMD
里,每一个API都简单纯粹。服务器
ES6 Modules
ES6
模块的设计思想,是尽可能的静态化,使得编译时就能肯定模块的依赖关系,以及输入和输出的变量。commonjs
和AMD
模块,都只能在运行时肯定这些东西。好比,commonjs
模块就是对象,输入时必须查找对象属性。
// CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
上面代码的实质是总体加载fs模块(即加载fs的全部方法),生成一个对象(_fs),而后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,由于只有运行时才能获得这个对象,致使彻底没办法在编译时作“静态优化”。
ES6模块不是对象,而是经过export
命令显式指定输出的代码,输入时也采用静态命令的形式。
// ES6模块 import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载3个方法,其余方法不加载。这种加载称为“编译时加载”,即ES6
能够在编译时就完成模块加载,效率要比CommonJS
模块的加载方式高。固然,这也致使了无法引用ES6
模块自己,由于它不是对象。
因为ES6
模块是编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽JavaScript
的语法,好比引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
除了静态加载带来的各类好处,ES6模块还有如下好处。
再也不须要UMD模块格式了,未来服务器和浏览器都会支持ES6
模块格式。目前,经过各类工具库,其实已经作到了这一点。
未来浏览器的新API就能用模块格式提供,再也不必要作成全局变量或者navigator对象的属性。
再也不须要对象做为命名空间(好比Math对象),将来这些功能能够经过模块提供。
浏览器使用ES6
模块的语法以下。
<script type="module" src="foo.js"></script>
上面代码在网页中插入一个模块foo.js,因为type属性设为module,因此浏览器知道这是一个ES6模块。
Node
的默认模块格式是CommonJS,目前还没决定怎么支持ES6
模块。因此,只能经过Babel这样的转码器,在Node里面使用ES6
模块。
export
优先考虑这种写法而不是一个一个的export
// profile.js var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year};
提供对外接口必须在接口名与模块内部变量之间保持一一对应关系
// 报错 function f() {} export f; // 正确 export function f() {}; // 正确 function f() {} export {f};
最后,export
命令能够出如今模块的任何位置,只要处于模块顶层就能够。若是处于块级做用域内,就会报错,下一节的import
命令也是如此。这是由于处于条件代码块之中,就无法作静态优化了,违背了ES6
模块的设计初衷。
import
使用export
命令定义了模块的对外接口之后,其余JS文件就能够经过import
命令加载这个模块(文件)。
// main.js import {firstName, lastName, year} from './profile'; function setName(element) { element.textContent = firstName + ' ' + lastName; }
上面代码的import命令,就用于加载profile.js文件,并从中输入变量。import
命令接受一个对象(用大括号表示),里面指定要从其余模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
若是想为输入的变量从新取一个名字,import
命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './profile';
注意,import
命令具备提高效果,会提高到整个模块的头部,首先执行。
ES6
模块加载的实质ES6
模块加载的机制,与CommonJS
模块彻底不一样。CommonJS
模块输出的是一个值的拷贝,而ES6
模块输出的是值的引用。ES6
的输入有点像Unix
系统的“符号链接”,原始值变了,import
输入的值也会跟着变。所以,ES6
模块是动态引用,而且不会缓存值,模块里面的变量绑定其所在的模块。