2

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

shots
56 声望2 粉丝

前端开发前进者