require/exports 、require 方法以及import/export使用详解

失去的东西,其实从来未曾真正地属于你,也不必惋惜,始终真心真意

Posted by yishuifengxiao on 2023-02-27

CommonJS模块规范和ES6模块规范完全是两种不同的概念

module.exportsexports是属于commonJs规范,exportexport default是ES6模块规范

require/exports 是 CommonJS/AMD 中为了解决模块化语法而引入的

import/export 是ES6引入的新规范,因为浏览器引擎兼容问题,需要在node中用babel将ES6语法编译成ES5语法

一 CommonJS模块规范

1.1 基本使用

Node应用由模块组成,采用CommonJS模块规范。

根据这个规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

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

1
2
3
4
5
6
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

上面代码通过module.exports输出变量x和函数addX。

require方法用于加载模块。

1
2
3
4
var example = require('./example.js');

console.log(example.x); // 5
console.log(example.addX(1)); // 6
  • module.exports 初始值为一个空对象 {}
  • exports 是指向的 module.exports 的引用
  • require() 返回的是 module.exports 而不是 exports

1.2 exports 与 module.exports

优先使用module.exports,不建议同时使用 exportsmodule.exports。如果先使用 exports 对外暴露属性或方法,再使用module.exports 暴露对象,会使得 exports 上暴露的属性或者方法失效。原因在于exports 仅仅是 module.exports的一个引用

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

1
var exports = module.exports;

exports 等于 module.exports,相当于在js文件头部,有一个module对象,module.exports = exports;
exports是一个对象,所以可以exports多个值

1
2
3
4
5
6
7
8
9
10
11
12
// 暴露.js
exports.fn1;
exports.fn2;
exports.fn3;
// 引用.js
var fns = require('暴露.js');
fns.fn1(); ...

// 相当于
exports = {fn1,fn2,fn3}
// 相当于
modules.exports = {fn1, fn2,fn3}

于是我们可以直接在 exports 对象上添加方法,表示对外输出的接口,如同在module.exports上添加一样。

注意,因为 Node 模块是通过 module.exports 导出的,如果直接将exports变量指向一个值,就切断了exports与module.exports的联系,导致意外发生:

1
2
3
4
5
// a.js
exports = function a() {};

// b.js
const a = require('./a.js') // a 是一个空对象

示例1

导出:

1
2
3
4
5
6
7
8
9
10
11
12
13
//output.js
const Name1 = 'hhh1';
const Name2 = 'hhh2';
module.exports = {
Name1,
Name2,
foo1: function () {
console.log("这是foo1函数!");
},
foo2:function (){
console.log("这是foo2函数!");
}
};

导入:

1
2
3
4
5
6
// input.js
const test = require('./output.js');
console.log(test.Name1); // hhh1
console.log(test.Name2); // hhh2
test.foo1();// 这是foo1函数!
test.foo2();// 这是foo2函数!

示例2

导出:

1
2
3
4
5
6
7
8
9
10
11
//output.js
const Name1 = 'hhh1';
const Name2 = 'hhh2';
exports.foo1 = function(){
console.log("这是foo1函数!");
}
exports.foo2 = function(){
console.log("这是foo1函数!");
}
exports.Name1 = Name1;
exports.Name2 = Name2;

导入:

1
2
3
4
5
6
// input.js
const test = require('./output.js');
console.log(test.Name1); // hhh1
console.log(test.Name2); // hhh2
test.foo1();// 这是foo1函数!
test.foo2();// 这是foo2函数!

示例三

exports.xxx = xxxmodule.exports = xxx;一起使用时, exports.xxx 的属性会失效

导出:

1
2
3
4
5
6
7
8
9
10
//output.js
const firstName = 'Michael';
const lastName = 'Jackson';
const year = 1958;
module.exports = {
firstName,
lastName,
year
};
exports.name = 'hhhh';

导入:

1
2
3
4
5
6
// input.js
const test = require('./output.js');
console.log(test.firstName);//Michael
console.log(test.lastName);//Jackson
console.log(test.year);//1958
console.log(test.name);//undefined

在上面的console中, exports.name = 'hhhh'的属性被 module.exports覆盖了,所以失效了.

1.3 module 对象

NodeJs 内部提供一个 Module 构建函数。所有模块都是 Module 的实例。

每个模块内部,都有一个 module 对象,代表当前模块。它有以下属性。

  • module 对象的属性

    • module.id 模块的识别符,通常是带有绝对路径的模块文件名。
    • module.filename 模块的文件名,带有绝对路径。
    • module.loaded 返回一个布尔值,表示模块是否已经完成加载。
    • module.parent 返回一个对象,表示调用该模块的模块(程序入口文件的module.parent为null)
    • module.children 返回一个数组,表示该模块要用到的其他模块。
    • module.exports 表示模块对外输出的值。
  • module.exports 属性 module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。module.exports属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。

  • exports 变量

我们有时候会这么写:

1
2
3
4
5
6
7
8
// test.js
function test(){
console.log(test);
}
export.test = test;

// result.js
const test = require("./test")

这样也可以拿到正确的结果,这是因为:exports 变量指向 module.exports。这等同在每个模块头部,有一行这样的命令。

1
var exports = module.exports;

注意:不能直接给 exports 变量赋值,这样会改变 exports 的指向,不再指向 module.exports。在其他模块使用 require 方法是拿不到赋给 exports 的值的,因为 require 方法获取的是其他模块的 module.exports 的值

建议:尽可能的使用 module.exports 来导出结果。

1.4 require

NodeJs 遵循 CommonJS 规范,该规范的核心是通过 require来加载其他依赖的模块。

require 是 node 用来加载并执行其它文件导出的模块的方法。

  • 通过require引入基础数据类型时,属于复制该变量
  • 通过require引入复杂数据类型时, 属于浅拷贝该对象

在导出的文件中使用module.exports对模块中的数据导出,内容类型可以是字符串,变量,对象,方法等不予限定。使用require()引入到需要的文件中即可

  在模块中,将所要导出的数据存放在moduleexport属性中,在经过CommonJs/AMD规范的处理,在需要的页面中使用require指定到该模块,即可导出模块中的export属性并执行赋值操作(值拷贝)

1
2
3
4
5
6
// module.js
module.exports = {
a: function() {
console.log('exports from module');
}
}
1
2
3
// sample.js
var obj = require('./module.js');
obj.a() // exports from module

当我们不需要导出模块中的全部数据时,使用大括号包含所需要的模块内容。

1
2
3
4
5
6
7
// module.js
function test(str) {
console.log(str);
}
module.exports = {
test
}
1
2
3
// sample.js
let { test } = require('./module.js');
test ('this is a test');

二 ES6模块规范

  • 每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

  • 每一个模块内声明的变量都是局部变量, 不会污染全局作用域;

  • 模块内部的变量或者函数可以通过export导出;

  • 一个模块可以导入别的模块

2.1 基本使用

2.1.1 import

import是在编译过程中加载,也就是说是在代码执行前执行,比如说,import后面的路径写错了,在运行代码前就会抛错,所以在编写代码时,必须放在模块顶部(import是静态执行的).

不同于CommonJS,ES6使用 export 和 import 来导出、导入模块。

1
2
3
4
5
6
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

需要特别注意的是,export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

1
2
3
4
5
6
7
8
9
10
// 写法一
export var m = 1;

// 写法二
var m = 1;
export {m};

// 写法三
var n = 1;
export {n as m};

2.1.2 import 的导入方式

1
2
3
4
5
6
import foo from './output'
import {b as B} from './output'
import * as OBj from './output'
import {a} from './output'
import {b as BB} from './output'
import c, {d} from './output'

2.2 es6导出、导入

2.2.1 基本使用

export { xx, xx}import {xx, xx} from '../../xxx.js'

1
2
3
4
5
6
7
// 暴露.js
let name = 'xiaoming';
let age = 18;
export {name, age}

// 引用.js
import {name, age} from '暴露.js'

2.2.2 别名

在export接口的时候, 我们可以使用 XX as YY修改导出的接口名字

1
2
3
4
5
6
// 暴露.js
let fn1 = function() {console.log('sayHi')};
export { fn1 as sayHi };

// 引用.js
import { sayHi } from '暴露.js'

2.2.3 特定引用导出

直接在export的地方定义导出的函数

1
2
// 举个vue组件懒加载的例子
export const Index = () => import('@modules/index/index');

2.2.4 export default导出

1
2
3
4
5
6
7
8
9
// 如果一个js模块文件就只有一个功能, 那么就可以使用export default导出
// 暴露.js
export default {
name: 'xiaoming'
age: 18
};

// 引用.js
import person from '暴露.js'

2.2.5 通配符导入

1
2
3
4
5
6
7
// 暴露.js
export fn1;
export fn2;
export fn3;

// 引用.js
import * as fns from '暴露.js';

import的时候可以使用通配符导入外部的模块:

  • import * as xxx from ‘xxx’: 会将若干export导出的内容组合成一个对象返回;

  • import xxx from ‘xxx’:(export default Din)只会导出这个默认的对象作为一个对象

2.3 export default 命令

export default导出,这种导出的方式不需要知道变量的名字, 相当于是匿名的;

1
2
3
4
// export-default.js
export default function () {
console.log('foo');
}