js中的import和require

December 12, 2019

因为经常需要使用 nodejs 和 es6 来写 JavaScript 代码,所以有必要总结一下 js 中 import 和 require 的用法和问题。

require - exports

require 是早期 js 还有没有 module 的年代的产物,是模块解决方案 commonJs 的一部分。也是 node JS 官方使用的,使用最多的模块方案。直到现在,nodejs 的稳定版本仍然在使用 commonJS,如果需要 import 则需要打开实验设置。

在 nodeJS 中,如果要引入某个模块,则有如下格式:

const a = require("a")
const { a } = require("a")
const a = require("a").a

而他们对应的 exports 形式则可以如下:

module.exports = a
----module.exports = { a }
----module.exports = { a }

exports & module.exports

值得关注的是 commonJS 常用 exports 和 module.exports 两个变量,但是只有 module.exports 是真正映射出去的值,他们之间的关系形如:

let exports = module.exports

exports 只是一个指向 module.exports 的变量。

形如

exports = a // 使用module.exports = a;
exports = {} // 使用module.exports = {};

则不会被映射到包外。

cjs 加载方式

模块采用运行时加载,优先读取缓存的模式。也就是说模块只会在第一次 require 进行加载,并对返回结果进行缓存。所以形如下列代码这样做都是可以的:

const a = require('a');
...
const a2 = getA(require('a'));  // 在任何地方都可以进行require

exports.a = new A(); //模块返回一个new A(),但是多处require这个模块后,调用的是同一个对象。

import - export

与 require,import 是 es6 的模块化方案,也就是正规军了,大部分浏览器原生支持 import 了。在大多数情况下推荐优先使用 import。

import 形式如下:

import 'a';
import a1 as a from 'a';
import { a } from 'a';
import a from 'a';

而因为有 default 关键字的存在,他的 export 形式也更加多变

1. any // 对于第一条可以export noting 或 everything
2.1 export default { a };
2.2 export a;
3.1 export default { a };
3.2 export a;
4 export default <anyname>; // 只需使用export default关键词, 其他没所谓

其中值得注意的是 3.1 的情况。

一般来说按照规范,export 一个对象,然后在 import 时解构赋值是完全 make sence 的,规范也允许你这样做。但是,由于目前大部分前端项目都使用 babel 和 webpack,在这种情况下将会导入失败,详细情况在这篇文章有提及到。

esm 加载方式

和 commonJS 不同的是 import 使用的是静态编译。这样做有很多好处,例如可以运行前判断得出模块之间的依赖关系,进行代码检查。但是由于是静态编译,一些 require 动加载的奇技淫巧就无法使用了,

比如说

if (xx) {
  require("a")
} else {
  require("b")
}

详细可以看一下深入理解 ES6 模块机制这篇文章。另外里面也谈到两个方案在处理循环依赖时的处理。

webpack+babel 中具体表现

首先可以说的是,目前其实只有 commonJS,具体是 es6 的模块方案会被编译成 commonJS 的形式,所以其实没差。在项目中你可以看到 import 懒加载,也导致了有 export default {} 这样的 bug。所以目前来说不用太纠结这件事。但是在前端使用标准的模块语法是一个好习惯。


Written by 梁伯豪