目前在各种前端打包构建工具(如webpack)的支持下,我们在前端可以使用cmd,amd,commonjs,esModule各种模块化规范去写我们的项目(注: 注意是规范,不是具体实现哦),最终模块之间的依赖关系的处理都交由构建工具去做了。但实际上我发现很多纯js库的作者依然有用直接引入requirejs这样的依赖去支持模块化的,比如echartjs。并且一般这些纯js库会配合rollup这样的模块打包器去实现资源整合。
尽管以后前端工程化的发展是朝着esmodule统一,但我觉得还是有必要把这些规范搞清楚。
下面来看下这些模块导入导出的写法区别:
一、写法
1.cmd,对应的具体实现seajs写法
首先直接从github上面拉下seajs的代码。
然后再index.html引入
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
.show{
display: none;
}
</style>
<body>
<button id="beautiful-sea">hello</button>
<div id="hello">hello</div>
</body>
<script src="dist/sea.js"></script>
</html>
// hello.js
define(function(require, exports, module) {
var $ = require('jquery');
exports.sayHello = function() {
$('#hello').toggle('show');
};
});
// main.js
seajs.config({
alias: { // 为引入的模块设置别名,指定路径
'jquery': './dist/jquery.js'
}
});
seajs.use(['./hello', 'jquery'], function(hello, $) {
$('#beautiful-sea').click(hello.sayHello);
});
以上有个坑。可能是seajs好久没更新了。
jquery的库文件要手动导出jQuery对象:
// jquery.js
...
define(function(require, exports, module){
module.exports = jQuery
});
2.amd,对应的具体实现requirejs写法
// m1.js
define({
method1: function() {console.log("method1")},
})
// m2.js
define({
method2: function() {console.log("method3")},
})
// m3.js
define(['m1', 'm2'], function(m1, m2) {
return {
method: function() {
m1.method1();
m2.method2();
}
}
})
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script src="./dist/require.js"></script>
<script>
require(["jquery"],function($){
require(["m3"],function(m3){
console.log($,m3)
m3.method()
})
},function(e){console.log(e)})
</script>
</html>
3.commonjs ,对应的具体实现nodejs写法,nodejs并不完全遵循commonjs规范。
// m1.js 导出
// 可以存在多个exports,导出合并为一个对象
//exports.fn=function (){
// console.log("m1")
// }
// exports.i=1
// 存在多个module.exports或者exports,以最后 module.exports的为准
module.exports =function (){
console.log("m1")
}
// index.js 导入
var fn=require("./m1")
console.log(fn)
fn()
4.esmodule ,对应的具体实现es6写法
// m1.js
export default m1;
export var i = 0;
export var x = 1;
// index.js
import m1 from "./m1"
import * as m1 from ".m1"
import { i,x } from "./m1"
二、对比
cmd | amd | commonjs | esmodule |
---|---|---|---|
依赖就近,同步模块加载,延迟执行。即将require以参数的形式提供,在哪里要用到再require同步执行引入(依赖已被预先加载)。 | 依赖前置,异步模块加载,提前执行。即提前执行模块的引入,将模块导出的数据以参数的形式传递。 | 资源存储在磁盘,同步加载模块,客户端很多异步加载,故在客户端受限制 。在执行时产生模块导出对象,导入的是模块输出值的拷贝。 | 前端模块开发统一的趋势。 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。这或许也是为什么es6模块化存在tree-shaking (当代码真正被输出前可以分析得出没有用到模块资源) |
总结: 可以看到前端再模块化的发展中,越发简易统一。esmodule是方向。虽然各式各样的框架脚手架大都是开箱即用了。但是搞懂前面的模块化使用以及实现原理对于我们的思维发散还是有一定的好处的哈。