ES6

ES6和CommomJs 的模块

阅读《ES6标准入门》一书时,简单记录一下ES6和CommomJs 的模块,方便使用的时候查找

Posted by ddxg on October 19, 2019

ES6和CommomJs 的模块

在ES6之前,社区中有一些模块加载方案,主要有 CommonJSAMD 两种,前者用于服务器,后者用于浏览器。 ES6在语言层面上实现了模块功能,并且更加简单,可以成为服务端和浏览器端的通用解决方案。

ES6模块设计是尽量静态化的,在js代码编译时就能确定模块的依赖关系, 而 CommonJSAMD 模块都是在代码运行时确定模块关系的。

看一个 CommonJS 的引入:

let {stat, exists, readFile} = require('fs')

上面的代码实质上是加载fs模块的所有方法,最终只用到三个方法,被称为“运行时加载”

ES6的模块加载就不一样,通过 export 命令显示指定输出代码,输入时也采用静命令的形式

import {stat, exists, readFile} = require('fs')

上面的代码实质是从fs模块中只加载 3 个方法,称为“编译时加载”

ES6可以在编译时就完成模块的编译,效率要比 CommonJS 方式高。

ES6模块的加载机制以 CommonJS 模块完全不同, CommonJS输出的是一个值的浅拷贝,而ES6模块输出的是值的只读引用,是活的值。具体区别可以查看《ES6标准入门》P277。

ES6 模块

export 和 import 命令

可以使用 export 命令具名到处变量和方法,使用 import {xxx,xxx} from '...' 方式导入。

导出语句必须要写在文件按的顶层文字,就是不能被像 if(...) {export let name = 'mmm'} 代码块包含。

export 语句导出的值是动态的,绑定在其所在的模块。

// export.js
export let name = "ddxg" // 导出变量
export let age = 25
// 导出函数
export function showName(name) {
    console.log(name)
}
// 导出类
export class Car {...}

// ---------------------
// 上面的导出可以改写成一次性导出的方式
let name = "ddxg" // 导出变量
let age = 25

export {name, age} // 代码更简洁

// ---------------------
// 导出时还可以使用 as 关键字重命名,还可一个面两重复导出
export {
    name as lastName,
    name as firstName, // name导出两次
    age as myAge
}

导入,import 后面需要花括号,变量名需要跟导出时定义的变量名一致。

import 命令有提升效果,编译时会提升到整个模块的头部。

// index.js 引入
import {name, showName, Car} from './export' // 按需引入
// 可以使用 as关键字 重命名
import {name as lastName, showName, Car as MyCar} from './export'


// ---------------------
// 使用 * 关键字整体加载模块
import * as circle from './circle'
console.log('面积: ', circle.area(4))
console.log('周长: ', circle.circumference(4))


// ----------------------
// 使用module 命令也可以实现整体导入的效果
module circle from './circle'
console.log('面积: ', circle.area(4))
console.log('周长: ', circle.circumference(4))


// ---------------------
// 导入之后又马上导出
export {es6 as default} from 'module'
// 等价于:
import {es6} from 'module'
export default es6


// ----------------------
export {es6} from 'module'
// 等价于:
import {es6} from 'module'
export es6


// ---------------------
// 仅仅只是执行postErrot.js的代码
import 'postErrot'

export default 和 import 命令

export default 命令可以为模块指定默认输出,引入者自行定义命名,一个模块只能执行一次这个命令。

// test.js
// 默认输出的是一个匿名函数,导出非匿名函数也可,只是加载的时候视为匿名函数,没区别
export default function() {
    console.log('ddd')
}
// ------------------------
// 也可写成
function foo () {
    console.log('ddd')
}
export default foo


// -------------------------
// 导出class,portError.js
export default class {...}

导入的时候 import 后面不需要使用花括号,

import foo2 from './test.js'
foo2() // ddd

// 导入class
import PostError from './portError.js'
let postError = new PostError(...)

一个模块文件同时使用 默认导出 和 具名导出

// export.js
let name = 'ddd'
let age = 25
function showName(){...}
function showAge(){...}

export name
export age
export default {
    showName,
    showAge
}


// 引入,index.js
import fns, {name, age} from './export.js'
console.log('name', name)
console.log('age', age)

fns.showName()
fns.showAge()

模块的继承

// criclePlus.js
// 输出了cricle模块的所有属性和方法,export * 会忽略cricle模块的default导出。
export * from 'cricle'
// 又输出了新的e变量
export let e = 2.71828
// 输出默认导出
export default function(x) {
    return Math.exp(x)
}


// 引入
// 引入cricle模块所有和新增e变量
module criclePlus from './criclePlus.js'
import exp from './criclePlus.js'
console.log(exp(criclePlue.e)) // exp(2.71828)


// 等价:
import * as criclePlus from './criclePlus.js'

CommonJS 模块

Node.js 使用 CommonJS 作为模块规范,每个文件就是一个模块,有自己的作用域。

CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。

CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。

module.exports

module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。

简单的例子:

// test.js,导出
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

// --------------------------
// 导入
// 这个 example 其实就是 导出模块的 module.exports
var example = require('./test.js');
console.log(example.x); // 5
console.log(example.addX(1)); // 6

console.log(example)
// {
//   x: 5,
//   addX: [Function]
// }

直接给module.exports赋值导出:

// 导出一个对象,有add和show方法
function add() {...}
function show() {...}
module.exports = {
    add,
    show
}

// --------------------------------
// 导入
var example = require('./test.js');
example.add()
example.show()

exports变量

为了方便,Node为每个模块提供一个 exports 变量,指向 module.exports。这等同在每个模块头部,有一行这样的命令 var exports = module.exports

exports.area = function (r) {
  return Math.PI * r * r;
};

exports.circumference = function (r) {
  return 2 * Math.PI * r;
};

// 导入逻辑是一样的

有个重要的问题是不能将 exports 重新赋值,因为这样等于切断了exports与module.exports的联系。就是可以使用 module.exports = xxx,但是不能使用 exports = xxx

参考