总结一下CommonJS 规范和ES规范的模块暴露和引用

662 阅读4分钟

一、前言:

写这个,单纯是因为对别人写的不太满意,反正自己看,写着玩

二、两中规范

JavaScript模块化的出现可以避免全局变量污染、数据被破坏的问题,并且支持模块与模块之间相互依赖。

CommonJS 规范:

Node.js 应用由模块组成,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

每个模块内部有两个变量可以使用,requiremodule

  • require 用来加载某个模块
  • module 代表当前模块,是一个对象,保存了当前模块的信息。exportsmodule 上的一个属性,保存了当前模块要导出的接口或者变量
module.exports
// tempData.js
const obj = {
  name: 'Libai',
  age: '20'
};
module.exports = {
  obj
};
引用
let tempData = require('./tempData.js');
console.log(tempData);
// {
//   obj: {
//     name: 'Libai',
//     age: '20'
//   }
// }

注意:

1、Node.js 在实现时,按照 CommonJS 规范,为每个模块提供一个 exports的私有变量,指向 module.exports,即:

var exports = module.exports

因此,上面暴露示例代码可以写成

// tempData.js
const obj = {
  name: 'Libai',
  age: 20
};
exports.obj = obj;

2、CommonJS 模块就是对象,在引入的时候必须查找对象属性。

let { stat, exists, readfile } = require('fs');
​
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。

ES6规范

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令引入。

这句话怎么理解呢?后面再说

export命令用于规定模块的对外接口或者变量,import命令用于引入其他模块提供的功能。ES6的模块暴露又有两个命令:

export和export default,下面说一下他们的区别

export
// tempData.js
// 写法一:声明的同时暴露
export const name = "Libai";
export const age = "20";
export function sayHi() {
  console.log("Hello world");
}
​
// 写法二:先声明后暴露
// 推荐这种写法
const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
}
export {
  name,
  age,
  sayHi
};
// 写法三:先声明后暴露
// 使用别名
const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
}
export {
  name as a,
  age as b,
  sayHi as c
};

注意以下暴露写法是错误的

const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
}
export name;
export age;
export sayHi;

按照阮一峰《ECMAScript 6 入门》给出的解释:

export命令规定的是对外的接口,必须与模块内部的变量或者函数建立一一对应关系,而上面的写法仅仅是直接暴露的是变量或者函数,并没有建立对应关系。

那为什么这种写法是可以建立对应关系的呢?

const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
}
export {
  name,
  age,
  sayHi
};

一开始我认为,基于要建立对应关系这个原则,实际上是这样要求的:

const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
}
export {
  name: name,
  age: age,
  sayHi: sayHi
};

只是在es6语法的支持下可以缩写成:

const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
}
export {
  name,
  age,
  sayHi
};

但事实上并不是这样。

export { }并不是暴露一个对象,{}只是个语法糖,仅仅是个格式,想要暴露哪个变量或者函数,就放在{}里面。

也就是说如果要使用export来暴露模块内部的东西,需要遵循它本身的语法要求。以上结论暂时保留。

引用
// 解构引入
import { name, age, sayHi } from './tempData.js';
​
// 通配符引入
import * as tempData from './tempData.js';
console.log(tempData);
// {
//   age: "20"
//   name: "Libai"
//   sayHi: ƒ sayHi()
// }// 使用别名
// 注意一旦使用了别名,后续只能使用别名
import {
  name as n,
  age as a,
  sayHi
} from './tempData.js';
console.log(n);
console.log(a);

注意:如果在export时就已经使用了别名,那么import时使用解构只能使用别名

// export
const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
};
export {
  name as a,
  age as b,
  sayHi
};
// import
// 注意引入是同样也可以使用别名
import {
  a,
  b,
  sayHi
} from './tempData.js';
export default

上面的示例可以发现一个问题:

如果我想引入某个模块里面的东西,前提是得知道模块对外暴露了什么?这样的设计就要求使用者需要事先阅读模块的对外暴露。

为了解决这个问题,ES6规范提供了export default用来指定模块默认暴露。

一个模块只能有一个默认暴露,因此export default命令只能使用一次,export default后面可以跟任意的变量或者函数,相当你把这个变量或者函数赋值给default,这一点很重要

// 合法
export default 42;
// 合法
const name = "Libai";
export default name;
// 合法
function sayHi() {
  console.log("Hello world");
};
export default sayHi;
​
// 或者
export default function sayHi() {
  console.log("Hello world");
};
​
// 支持匿名函数
export default function () {
  console.log("Hello world");
};
// 合法
const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
};
export default {
 name,
 age,
 sayHi
};
引用

在import时不允许使用解构来获取,因为export defualt暴露的是一个整体

通常也不使用通配符,因为export default后面可以跟任意的变量或者函数,相当你把这个变量或者函数赋值给default,你会发现

// export default
const name = "Libai";
const age = "20";
function sayHi() {
  console.log("Hello world");
};
export default {
 name,
 age,
 sayHi
};
// import
import * as data from './tempData.js';
console.log(data);
// {
//  default: {
//    age: "20"
//    name: "Libai"
//    sayHi: ƒ sayHi()
//  }
// }

正确的引入写法是

import tempData from './tempData.js';
console.log(tempData);
// {
//   age: "20"
//   name: "Libai"
//   sayHi: ƒ sayHi()
// }