这是我参与更文挑战的第9
天,活动详情查看:更文挑战
前言
我们都知道代码模块化带来的好处有很多很多,但是在ES6以前的JavaScript中时没有代码import
的概念的,那么他们又是怎么组合起来的呢?
立即执行函数
看过JQuery
源码的小伙伴都知道,JQuery
是用一个立即执行函数包裹住代码,对外暴露部分变量供其他模块调用的,举个例子:
(function(window) {
const Jq = function (target) {
this.element = document.querySelector(target);
}
Jq.prototype.setBackground = function(color) {
this.element.style.background = color;
return this;
}
// 对外抛出 $ 方法
window.$ = (target) => new Jq(target)
})(window)
$('body').setBackground('red');
上面代码的意思是声明了一个函数并立即调用,通过传入的window
对象向外部暴露我们想要暴露的变量或方法。这就是一个简单的模块化的体现。
这样的好处就是可以将一些变量和方法私有化。但它的坏处也很明显:不提供依赖管理机制;对外暴露方法只能通过全局对象实现。
这样的简单模块化工具显然不能满足我们复杂的系统设计的需求。
显式模块声明
从上面立即执行函数对外暴露的方法可以看出,对外暴露方法只能通过传入的全局对象才能向外部暴露方法或变量。如果我们可以将想要暴露的数据集合起来统一返回,那就最好了。而且这样的实现貌似也不难,继续来写一个简单的方法:
const module = function() {
function sum(a, b) { return a + b };
function multiply(a, b) { return a * b };
return {
sum,
multiply,
}
}()
module.sum(1, 2); // 3
module.sum(2, 3); // 6
我们用一个变量来装函数的返回值。这样我们就可以访问这个声明的变量去调用返回的方法。达到代码复用和代码封装的功能。
但是与立即执行函数一样,它也不提供模块管理机制。
什么是模块管理机制
上面有两次都提到了模块管理机制,现在来简单了解一下。
在以前写JavaScript的时候,如果需要引用其他JS文件,是需要在HTML文件中添加<script src="..."></script>
标签引入的,在JS文件没有找到很好的办法去引入。当我们需要用到很多的模块文件时,那么管理模块时也必然会乱。
所以需要引入一种约定,在JS文件中也能实现模块的引用,就比如说import
和export
提供引入和输出,require
和module.export
也是一样,这就是模块管理机制
。
异步模块定义(AMD)
了解完模块管理机制的概念,下面来看一种引入了模块管理机制的模块化方案。
异步模块定义(Asynchronous Module Definition)
,从名字可以看到,它是用异步去加载模块的。而且他是基于RequireJS的,来看下面的代码:
// moduleA.js
define(function() {
return {
TEST: 'test moduleA'
}
})
// main.js
require.config({
baseUrl: 'js',
paths: {
"testModule": "./moduleA",
}
})
require(['testModule'], function(moduleA) {
const test = moduleA();
console.log(test.TEST); // test moduleA
})
上面代码声明了两个js文件,moduleA.js
是定义的模块文件,使用define
方法定义模块。
main.js中使用require
方法引入定义的模块。require.config
定义引入的配置。
require
方法接收两个参数:模块数组
和引入成功后的回调函数
,当定义的模块加载完成后,调用回调函数。
从上面例子可以看到,它提供一种模块管理机制
,允许我们在js文件中引入其他的js文件,而不再是从HTML的<script>
标签引入。除此之外,AMD还有如下优点:
- 采用异步方式加载模块,模块的加载不影响它后面语句的运行。
- 所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
AMD虽然可以做到异步加载,但是它也是会有缺点的:
- 它不能做到按需加载,而是必须提前加载所有的依赖。
共同模块定义(CMD)
共同模块定义(Common Module Definition)
也是一种异步模块定义规范。
CMD定义模块的方法是define(factory)
,如果 factory 是一个函数,回调函数中会指定三个参数 require,exports,module
require
是一个函数,这个函数接收一个模块标识符(模块 id),返回的是导出模块的API。
exports
提供在模块执行时添加模块 API 的对象。
module
是一个对象,提供exports
,dependencies
,uri
方法,具体了解可以点击这里。
下面是使用cmd定义模块的伪代码:
define(function(require, export, module) {
const md = require('modulePath');
const result = md[ moduleFunction ].get();
module.exports = {
result
}
})
CMD是SeaJS在推广过程中对模块定义的规范化产出,具体例子可以看到SeaJs的使用文档。
与AMD的区别
AMD与CMD都是异步模块定义规范,但是他们也会存在一些区别:
- 对于模块的依赖,AMD是提前执行,CMD是延时执行。
- AMD推崇依赖前置,CMD推崇就近依赖。
依赖前置:在定义模块的时候要先声明其依赖的模块,就像这样:
require(['module'], () => {...})
就近依赖:可以在在使用时引入依赖的模块
define(function(require, export, module) {
...
const md = require('modulePath');
...
})
CommonJs
CommonJs也是一种模块定义规范,node的模块系统就是基于CommonJs
的。
- 在
CommonJs
中每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。 CommonJS
规范规定,每个模块内部,module
变量代表当前模块。这个变量是一个对象,它的module.exports
属性是对外的接口。
// module.js
module.exports = {
...
...
}
// main.js
const md = require('./module.js');
...
- 加载某个模块,其实是加载该模块的
module.exports
属性。require
方法用于加载模块。
需要注意的是,CommonJs是同步加载,而上面提到的AMD
,UMD
是异步加载。
通用模块定义(UMD)
通用模块定义(Universal Module Definition)
把前端和后端的模块加载融合在一起了,他提供了一个前后端统一的解决方案。支持AMD和CommonJS模式。UMD的实现其实很简单,前面我们已经了解了AMD
还是CommonJs
,那么UMD就是提供了一个方法判断是前端加载还是后端加载,主要的判断步骤是:
- 先判断是否支持Node.js模块格式(exports是否存在),存在则使用Node.js模块格式。
- 再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
- 前两个都不存在,则将模块公开到全局(window或global)。
ES6中的模块
ES6中使用了import
,export
来实现模块的引入和导出代码,模块加载分为静态加载和动态加载。
静态加载时,ES6规定import
必须放在代码顶层,因为import命令会被 JavaScript 引擎静态分析,先于模块内的其他模块执行。
import {} from 'modulePath';
...
上面的import语法显然是不能实现动态加载的动态加载,如果在某一些场景需要用到动态加载(例如动态路由),那么应该怎么做呢?
ES6提供一个import()
函数,它可以在代码运行时动态引入模块,加载完成后会返回一个Promise
。
import('modulePath').then(module => {
...
})
小结
本文主要介绍了JavaScript是如何实现模块化的。
前端使用模块化的定义有:立即执行函数
,显式模块声明
,AMD
,CMD
,UMD
node使用CommonJs进行模块化
若文章中有不严谨或出错的地方请在评论区域指出。
参考
- 了不起的JavaScript工程师
- JavaScript高级程序设计
- RequireJS
- SeaJS
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!