对于初学者可能对CommonJS和ESmodules两种规范的导入导出的使用有点儿混淆,另外微信小程序官方文档写得只支持Common JS,一来是想理清两种导入导出方式,二来是想实际验证下微信小程序是否两种规范都支持。于是探索的过程便形成了此文。
CommonJS是一种JavaScript语言的模块化规范,主要用于服务端的Node.js环境。每个文件在CommonJS中都被视为一个模块,具有独立的作用域、变量和方法,对其他模块不可见。模块通过module.exports
暴露其公共接口,其他文件则通过require()
方法来加载这个模块。CommonJS的主要目的是让各种模块能够在各个服务器环境中得到支持,并实现类似于本地文件模块系统一样的功能。CommonJS从原始设计上就是考虑到服务器端JavaScript,不适合客户端(这就是引入ES模块的原因)。
ES Modules(ESM)是ECMAScript的官方模块系统,也被称为ECMAScript Modules或简称ES Modules。它是JavaScript中用于模块化开发的新规范,也是ECMAScript 6(ES6)标准的一部分。
ES Modules引入了一种新的、官方的模块系统,以解决以前在JavaScript中模块化开发中存在的一些问题。它允许动态地导入和导出模块,提供了一种官方的模块化解决方案,促进了代码的模块化和复用。
与CommonJS等其他模块系统相比,ES Modules具有一些优势,例如静态导入和导出、静态模块结构、更好的异步支持、更强的类型检查等。主流的浏览器和Node.js环境都已支持ES Modules,开发者可以使用export和import语句来导入和导出模块,并在script标签中设置type="module"来使用模块内容。
在创建 JavaScript 模块时,export 语句用于从模块中导出实时绑定的函数、对象或原始值,以便其他程序可以通过 import
语句使用它们。被导出的绑定值依然可以在本地进行修改。在使用 import 进行导入时,这些绑定值只能被导入模块所读取,但在 export 导出模块中对这些绑定值进行修改,所修改的值也会实时地更新。
存在两种 exports 导出方式:
// 导出单个特性
export let name1, name2, …, nameN; // also var, const
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName(){...}
export class ClassName {...}
// 导出列表
export { name1, name2, …, nameN };
// 重命名导出
export { variable1 as name1, variable2 as name2, …, nameN };
// 解构导出并重命名
export const { name1, name2: bar } = o;
// 默认导出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
// 导出模块合集
export * from …; // does not set the default export
export * as name1 from …; // Draft ECMAScript? 2O21
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
因此,对于ESModules 模块导出,是通过export语句来进行模块导出。
静态 import 语句用于导入由另一个模块导出的绑定。
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
The import() syntax, commonly called dynamic import, is a function-like expression that allows loading an ECMAScript module asynchronously and dynamically into a potentially non-module environment.
语法:
import(moduleName)
The import()
call is a syntax that closely resembles a function call, but import
itself is a keyword, not a function. You cannot alias it like const myImport = import
, which will throw a SyntaxError
.
标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。
下面的是可能会需要动态导入的场景:
import("/modules/my-module.js").then((module) => {
// Do something with the module.
});
学习完了理论,接下来,看看在微信小程序中,基于ESModules,进行导入导出的实验。
本示例实现一个简单的加法运算,加法函数放在common.js中导出,然后在mod.js中导入并使用。
<!--mod/mod.wxml-->
<view class="container">
<view>数字1:</view>
<input type="number" placeholder="输入数字" bindinput="onInput1" />
<view>+</view>
<view>数字2:</view>
<input type="number" placeholder="输入数字" bindinput="onInput2" />
<button bindtap="onTapButton">计算</button>
<view>结果: {{result}}</view>
</view>
/* mod/mod.wxss */
.container {
display: flex;
flex-direction: column;
align-items: center;
}
input {
width: 80%;
margin-bottom: 10px;
}
button {
width: 80%;
}
view {
margin-bottom: 10px;
}
// common.js
function add(a,b){
return (a + b);
}
function sub(a,b){
return (a - b);
}
export {add, sub};
// mod/mod.js
import {add,sub} from './common'
Page({
data: {
num1: 0,
num2: 0,
result: 0,
},
onInput1: function(e) {
this.setData({ num1: e.detail.value });
},
onInput2: function(e) {
this.setData({ num2: e.detail.value });
},
onTapButton: function() {
let result = add(Number(this.data.num1), Number(this.data.num2));
this.setData({ result: result });
},
})
从上图的运行结果来看,导入导出是奏效的。接下来,再看看import()导入是否work。
Congratulations!动态导入也是奏效的。
CommonJS模块规范是Node.js中用于处理模块的标准。
require(id)
id
:<string> module name or pathUsed to import modules, JSON
, and local files. Modules can be imported from node_modules
. Local modules and JSON files can be imported using a relative path (e.g. ./
, ./foo
, ./bar/baz
, ../foo
) that will be resolved against the directory named by __dirname
(if defined) or the current working directory.
Modules.exports 是一个对象 。
The module.exports
object is created by the Module
system. Sometimes this is not acceptable; many want their module to be an instance of some class. To do this, assign the desired export object to module.exports
. Assigning the desired object to exports
will simply rebind the local exports
variable, which is probably not what is desired.
关于exports,它是一个变量,只是为了更简洁地书写而存在。module.exports.f = ...
更简洁的书写成exports.f = ...
,但是,请注意,与任何变量一样,如果将新值分配给 exports
,则它不再绑定到 module.exports
。一般不建议用这种。
The exports
variable is available within a module’s file-level scope, and is assigned the value of module.exports
before the module is evaluated.
下面一段伪代码可以很好地解释不建议使用exports
的原因:
function require(/* ... */) {
const module = { exports: {} }; // default object
((module, exports) => {
// Module code here. In this example, define a function.
function someFunc() {};
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
})(module, module.exports);
return module.exports;
}
这是示例的逻辑和上一节相同,只是将导入和导出用法改成了CommonJS的规范。
通过本文的学习,掌握了在微信小程序中的模块的导入导出 既可以使用ES modules标准,也可以使用CommonJS标准。我个人更倾向于ES标准,模块的可移植性更好些。