16.11.11
过了又一个关棍节,感觉。。。。不好
参考:阮一峰ES6
CommonJS 的模块规范
CommonJS对模块的定义非常简单,主要分为模块引用、模块定义和模块标志
模块的引用
var math = reqiure('math');
require()接受模块的标志符。以此引入一个模块的API到当前的上下文中。
模块的定义
上下文提供exports
对象用于导出当前模块的方法和变量,并且它是唯一导出的出口。module
对象,它代表模块自身。而exports是module的属性。
模块的标识
模块标识就是传递给require()方法的参数。必须符合小驼峰命名的字符串。
CommonJS的模块导出和引用机制使得用户完全不必考虑变量污染。
ES6 module
ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块,都只能在运行时确定这些东西。
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于 这种方式称为“运行时加载”
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
exports
exports
&& module.exports
直接复制给exports,会报错。
exports= function(){
//
}
因为exports对象是通过形参的方式传入的,直接赋值形参会改变形参的作用,但并不能改变作用域外的值。
如果要达到require引入一个类的效果,请赋值给module.exports对象,这个迂回的方案不改变形参的引用。
export
需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
import
使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块(文件)。
import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块s对外接口的名称相同。
模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
export default
export default 一个模块只有一个。为模块指定默认输出。
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
以下比较默认输出和正常输出:
// 输出
export default function crc32() {
// ...
}
// 输入
import crc32 from 'crc32';
// 输出
export function crc32() {
// ...
};
// 输入
import {crc32} from 'crc32';
本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。
模块的继承
ES6模块加载的实质
CommonJS模块的是一个值的拷贝,而ES6模块输出的是值的引用。
ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。
CommonJS模块输出的是被输出值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6模块的运行机制与CommonJS不一样,它遇到模块加载命令import时,不会去执行模块,而是只生成一个动态的只读引用。等到真的需要用到时,再到模块里面去取值,换句话说,ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。因此,ES6模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。
循环加载
CommonJS的加载原理
CommonJS的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
{
id: '...',
exports: { ... },
loaded: true,
...
}
该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。
以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
<mark>CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。</mark>
// a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
//main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
//执行main.js
$ node main.js
在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true
ES6处理“循环加载”与CommonJS有本质的不同。ES6模块是动态引用,如果使用import从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
// a.js如下
import {bar} from './b.js';
console.log('a.js');
console.log(bar);
export let foo = 'foo';
// b.js
import {foo} from './a.js';
console.log('b.js');
console.log(foo);
export let bar = 'bar';
$ babel-node a.js
b.js
undefined
a.js
bar
跨模块常量
上面说过,const声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),可以采用下面的写法。
// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。