JavaScript的不断发展,功能不断增强,代码也就越来越复杂,伪类便于管理和维护,模块化必不可少
规范的作用就是统一一规则,让所有人都可以分享让自己制作的模块;比如USB接口,鼠标线、U盘、数据线,只要实现了接口就以插入电脑,连接上外设。
CommonJS
主要用于nodejs,服务端语言的,同步加载的模块规范
CommonJS也是一种规范,而nodejs的模块化内置API实现了这种规范
使用
main.js
1
2let moduleA = require('./moduleA')
// do...moduleA.js
1
2
3
4function f1(){}
module.exports = {
f1:f1,
}CommonJS的原理大概是,先把模块的代码加上一层函数包裹,调用函数(利用eval函数),最后得到模块导出,多次导出会有缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22function wrapper(script){
let wrapper = ['function(exports,require,module,__filename,__dirname){','}']
return wrapper[0] + script + wrapper[1]
}
function require(id){
// 已存在在缓存,直接返回
let cacheModule = Module._cache[id]
if(cacheModule){
return cacheModule
}
const module = {exports:{}}
// 缓存
Module._cache[id] = module
// 执行代码
eval(wrapper('module.exports = 1236'))(module.exports,require,module,'filename','dirname')
return module.exports
}
AMD和RequireJS
主要用于浏览器JavaScript,客户端端语言的,异步加载的模块规范
- 这是由于,同步加载会阻塞应用渲染,影响页面性能,所以客户端的模块化应该是异步加载的
- 服务端nodejs因为模块本身就存在于服务器,同步加载和异步加载无影响(只看硬盘读取时间)
AMD是一种规范,而RequireJS是一个实现AMD规范的库;当然,是RequireJS出现带动了AMD规范的制定,先有的库再有的规范…
RequireJS使用
index.html
1
2
3...
<script defer asyn="true" data-main="scripts/main" src="scripts/require.js"></script>
...data-main
: 指定入口,默认是js文件,所以scripts/main
不用写成scripts/main.js
src
: 首先要引入require.js
main.js
1
2
3reuire(['moduleA'],function($){
// do...
})require函数即指定了本文件所需要的依赖模块,第一个参数是一个数组指定所需要的依赖,第二个参数是依赖加载完的回调函数。加载的模块会被作为参数传入回调函数中;RequireJS会加载完所有的依赖模块后执行回调函数
RequireJS默认模块moduleA就和main.js在同一目录下,所以也会在
scripts/moduleA.js
找到moduleA.jsmoduleA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 不用依赖其他模块的
define({
f1:function(){},
f2:function(){}
})
// 与上面等价
define(function(){
// 必须返回一个对象
return {
f1:function(){},
f2:function(){},
}
})
// 有依赖其他模块的
define(['moduleB'],function (mB){
// do...
})RequireJS推崇的是依赖前置,提前执行,也即,当声明需要某个模块时,模块在加载完就会马上执行
CMD和seaJS
和AMD很类似,也是异步加载的规范,主要用于客户端js
CMD是一种规范,seaJS是实现了CMD规范的一个库;当然地,CMD也是seaJS在推广时指定的一种规范
使用
main.js
1
2
3
4
5
6define(function factory(require,exports,module){
var a = require('a')
console.log(a)
var b = require('b')
console.log(b)
})
a.js
1 | define(function factory(require,exports,module){ |
CMD和AMD不同在于
- AMD推崇依赖前置,提前执行,把所有依赖都在最显眼地方声明好,同时下载完也是立即执行模块代码,推崇事先把一切都准备好;
- 而CMD推崇的则是依赖就近,延迟执行,依赖的导入可以写在需要依赖的地方,下载完也会执行模块代码,但不执行define里面的代码,等到代码执行到某个依赖再执行依赖模块里面define的代码,推崇的是需要的时候再来弄。
值得一提的是,AMD规范,把所有依赖都事先声明,下载资源。这种方式对于某些可能不执行的代码,性能上就不太好(多解析了一段代码)
1
2
3if(status){
// do...
}而CMD规范,可以在条件里面声明然后使用,这样可以避免事先准备了无用的代码
UMD
Universal Module Definition; 统一模块定义(规范);
UMD是一种规范,旨在可以在服务端nodejs或者客户端js上使用统一的模块管理语法
UMD原理其实是基于客户端js使用的AMD规范(requireJS库)和服务端nodejs用的CommonJS,它在此之上加一个判断,有AMD语法则用AMD,没有AMD语法则看是否有CommonJS语法,有则用CommonJS,若也没有CommonJS语法则把暴露到全局上【因为UMD只是规范,不是库,只能靠人为加判断】
以使用jquery作为一个模块为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
}));若使用了打包工具,打包称UMD规范,则打包工具就会自己生成这样的代码(如webpack的umd模式)
ES6 Module
ES6把js的模块化纳入了标准,使用
import
,export
ES6 Module
index.html
1
<script type="module" src="./main.js"></script>
需要注意的是,
file://
等直接浏览html文件会报错;使用服务器时,返回的模块文件时,http头的content-type
的MIME类型应该为text/javascript
;ES6 Module 可以使用后缀为
.mjs
来代表js模块文件,与普通js文件,后缀为.js
区分开在前面已经提到了,在这里再重申一次:
.mjs
后缀的文件需要以 MIME-type 为javascript/esm
来加载(或者其他的JavaScript 兼容的 MIME-type ,比如application/javascript
), 否则,你会一个严格的 MIME 类型检查错误,像是这样的 “The server responded with a non-JavaScript MIME type”.main.js
1
2
3
4import { count,countAdd } from './moduleA.js'
console.log(count) // 3
countAdd()
console.log(count) // 4,这里和commonjs不同,commonjs的不会加以,因为是值复制;而ES6 Module是值的引用所以count会加一moduleA.js
1
2
3
4
5
6
7
8var count = 3
function countAdd(){
count++
}
export {
count:count,
countAdd:countAdd,
}
值得注意的是,模块文件中,顶级的this是undefined的(正常js文件是window)
- ES Module和CommonJs的区别
- 首先,ES Module是异步加载的,而CommonJS是同步加载的。客户端页面在用户机器上,加载资源要靠网络,考虑用户体验,客户端一般采用异步加载,服务端因为是在服务器上的,资源在本地磁盘读取很快,所以可以直接使用同步加载
- 另外,ES Module是值的引用,CommonJS是值的复制;CommonJS在运行时加载,然后执行代码,暴露的是一个对象,对象就是模块里面
module.exports
对象,const a = require()
其实就是这个对象赋值给a,是一个值的拷贝(也即,模块的string等非引用类型是不会被 调用模块 修改的);而ES Module要求import ,export在最顶部且不可拼接路径,因为ES Module是静态识别的,在编译时就加载好,最终得到模块里面的变量一个引用(非赋值),所以当调用模块改变被调用模块时,被调用模块本身的值就会被改变(string,number等也是) - 语法上不同
参考
Javascript模块化编程(三):require.js的用法
最后更新: 2021年08月18日 16:03
原始链接: https://idkhts.github.io/2021/08/18/js%E6%A8%A1%E5%9D%97%E5%8C%96/