媒介
跟着 Web 手艺的蓬勃发展和依靠的基本设施日趋完善,前端范畴逐步从浏览器扩大至效劳端(Node.js),桌面端(PC、Android、iOS),乃至于物联网装备(IoT),个中 JavaScript 承载着这些应用程序的中心部份,跟着其规模化和庞杂度的成倍增长,其软件工程体系也随之建立起来(协同开辟、单元测试、需乞降缺点治理等),模块化编程的需求日趋急切。
JavaScript 对模块化编程的支撑还没有构成范例,难以堪此重任;一时候,江湖侠士自告奋勇,一起披荆棘,从刀耕火种过渡到面向未来的模块化计划;
<!– more –>
观点
模块化编程就是经由历程组合一些__相对自力可复用的模块__来举行功用的完成,其最中心的两部份是__定义模块__和__引入模块__;
- 定义模块时,每一个模块内部的实行逻辑是不被外部感知的,只是导出(暴露)出部份要领和数据;
- 引入模块时,同步 / 异步去加载待引入的代码,实行并猎取到其暴露的要领和数据;
刀耕火种
只管 JavaScript 言语层面并未供应模块化的处置惩罚计划,但应用其可__面向对象__的言语特征,外加__设想形式__加持,能够完成一些简朴的模块化的架构;典范的一个案例是应用单例形式形式去完成模块化,能够对模块举行较好的封装,只暴露部份信息给须要运用模块的处所;
// Define a module
var moduleA = (function ($, doc) {
var methodA = function() {};
var dataA = {};
return {
methodA: methodA,
dataA: dataA
};
})(jQuery, document);
// Use a module
var result = moduleA.mehodA();
直观来看,经由历程马上实行函数(IIFE)来声明依靠以及导出数据,这与当下的模块化计划并没有庞大的差别,可本质上却有千差万别,没法满足的一些重要的特征;
- 定义模块时,声明的依靠不是强迫自动引入的,即在定义该模块之前,必需手动引入依靠的模块代码;
- 定义模块时,其代码就已完成实行历程,没法完成按需加载;
- 跨文件运用模块时,须要将模块挂载到全局变量(window)上;
AMD & CMD 二分世界
题外话:因为年代久远,这两种模块化计划逐步淡出历史舞台,详细特征不再细聊;
为了处置惩罚”刀耕火种”时期存留的需求,AMD 和 CMD 模块化范例问世,处置惩罚了在浏览器端的异步模块化编程的需求,__其最中心的道理是经由历程动态加载 script 和事宜监听的体式格局来异步加载模块;__
AMD 和 CMD 最具代表的两个作品离别对应 require.js 和 sea.js;其重要区分在于依靠声明和依靠加载的机遇,个中 require.js 默许在声明时实行, sea.js 推重懒加载和按需运用;别的值得一提的是,CMD 范例的写法和 CommonJS 极为邻近,只需稍作修正,就能在 CommonJS 中运用。参考下面的 Case 更有助于明白;
// AMD
define(['./a','./b'], function (moduleA, moduleB) {
// 依靠前置
moduleA.mehodA();
console.log(moduleB.dataB);
// 导出数据
return {};
});
// CMD
define(function (requie, exports, module) {
// 依靠就近
var moduleA = require('./a');
moduleA.mehodA();
// 按需加载
if (needModuleB) {
var moduleB = requie('./b');
moduleB.methodB();
}
// 导出数据
exports = {};
});
CommonJS
2009 年 ry 宣布 Node.js 的第一个版本,CommonJS 作为个中最中心的特征之一,适用于效劳端下的场景;历年来的考核和时候的浸礼,以及前端工程化对其的充足支撑,CommonJS 被普遍运用于 Node.js 和浏览器;
// Core Module
const cp = require('child_process');
// Npm Module
const axios = require('axios');
// Custom Module
const foo = require('./foo');
module.exports = { axios };
exports.foo = foo;
范例
- module (Object): 模块自身
- exports (*): 模块的导出部份,即暴露出来的内容
- require (Function): 加载模块的函数,取得目的模块的导出值(基本范例为复制,援用范例为浅拷贝),能够加载内置模块、npm 模块和自定义模块
完成
1、模块定义
默许恣意 .node .js .json 文件都是相符范例的模块;
2、引入模块
起首从缓存(require.cache)优先读取模块,假如未掷中缓存,则举行路径剖析,然后根据差别范例的模块处置惩罚:
- 内置模块,直接从内存加载;
- 外部模块,起首举行文件寻址定位,然后举行编译和实行,终究获得对应的导出值;
个中在编译的历程当中,Node对猎取的JavaScript文件内容举行了头尾包装,效果以下:
(function (exports, require, module, __filename, __dirname) {
var circle = require('./circle.js');
console.log('The area of a circle of radius 4 is ' + circle.area(4));
});
特征总结
- 同步实行模块声明和引入逻辑,剖析一些庞杂的依靠援用(如轮回依靠)时需注重;
- 缓存机制,机能更优,同时限定了内存占用;
- Module 模块可供革新的天真度高,能够完成一些定制需求(如热更新、恣意文件范例模块支撑);
ES Module(引荐运用)
ES Module 是言语层面的模块化计划,由 ES 2015 提出,其范例与 CommonJS 比之 ,导出的值<span data-type=”color” style=”color:rgb(26, 26, 26)”><span data-type=”background” style=”background-color:rgb(255, 255, 255)”>都能够看成是一个具有多个属性或许要领的对象</span></span>,能够完成相互兼容;但写法上 ES Module 更简约,与 Python 靠近;
import fs from 'fs';
import color from 'color';
import service, { getArticles } from '../service';
export default service;
export const getArticles = getArticles;
重要差别在于:
- ES Module 会对<span data-type=”color” style=”color:rgb(26, 26, 26)”><span data-type=”background” style=”background-color:rgb(255, 255, 255)”>静态代码剖析,即在代码编译时举行模块的加载,在运转时之前就已肯定了依靠关联(可处置惩罚轮回援用的题目);</span></span>
- ES Module 关键字:
import
export
以及独占的default
关键字,肯定默许的导出值; - ES Module 中导出的值是一个
只读的值的援用
,不管基本范例和庞杂范例,而在 CommonJS 中 require 的是值的拷贝,个中庞杂范例是值的浅拷贝;
// a.js
export let a = 1;
export function caculate() {
a++;
};
// b.js
import { a, caculate } from 'a.js';
console.log(a); // 1
caculate();
console.log(a); // 2
a = 2; // Syntax Error: "a" is read-only
UMD
经由历程一层自实行函数来兼容种种模块化范例的写法,兼容 AMD / CMD / CommonJS 等模块化范例,贴上代码赛过千言万语,须要特别注重的是 ES Module 因为会对静态代码举行剖析,故这类运转时的计划没法运用,此时经由历程 CommonJS 举行兼容;
(function (global, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
this.eventUtil = factory();
}
})(this, function (exports) {
// Define Module
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = 42;
});
构建东西中的完成
为了在浏览器环境中运转模块化的代码,须要借助一些模块化打包的东西举行打包( 以 webpack 为例),定义了项目进口以后,会先疾速地举行依靠的剖析,然后将一切依靠的模块转换成浏览器兼容的对应模块化范例的完成;
模块化的基本
从上面的引见中,我们已对其范例和完成有了肯定的相识;在浏览器中,要完成 CommonJS 范例,只须要完成 module / exports / require / global 这几个属性,因为浏览器中是没法访问文件体系的,因而 require 历程当中的文件定位须要革新为加载对应的 JS 片断(webpack 采纳的体式格局为经由历程函数传参完成依靠的引入)。详细完成能够参考:tiny-browser-require。
webpack 打包出来的代码快照以下,注重看解释中的时序;
(function (modules) {
// The module cache
var installedModules = {};
// The require function
function __webpack_require__(moduleId) {}
return __webpack_require__(0); // ---> 0
})
({
0: function (module, exports, __webpack_require__) {
// Define module A
var moduleB = __webpack_require__(1); // ---> 1
},
1: function (module, exports, __webpack_require__) {
// Define module B
exports = {}; // ---> 2
}
});
实际上,ES Module 的处置惩罚同 CommonJS 相差无几,只是在定义模块和引入模块时会去处置惩罚 __esModule 标识,从而兼容其在语法上的差别。
异步和扩大
1、浏览器环境下,网络资源遭到较大的限定,因而打包出来的文件假如体积庞大,对页面机能的消耗极大,因而须要对构建的目的文件举行拆分,同时模块也须要支撑动态加载;
webpack 供应了两个要领 require.ensure() 和 import() (引荐运用)举行模块的动态加载,至于个中的道理,跟上面说起的 AMD & CMD 所见略同,import() 实行后返回一个 Promise 对象,个中所做的事情不过也是动态新增 script 标签,然后经由历程 onload / onerror 事宜进一步处置惩罚。
2、因为 require 函数是完整自定义的,我们能够在模块化中完成更多的特征,比方经由历程修正 require.resolve 或 Module._extensions 扩大支撑的文件范例,使得 css / .jsx / .vue / 图片等文件也能为模块化所运用;
附录1:特征一览表
模块化范例 | 加载体式格局 | 加载机遇 | 运转环境 | 备注 |
---|---|---|---|---|
AMD | 异步 | 运转时 | 浏览器 | |
CMD | 异步 | 运转时 | 浏览器 | 依靠基于静态剖析,require 时已 module ready |
CommonJS | 同步/异步 | 运转时 | 浏览器 / Node | |
ES Module | 同步/异步 | 编译阶段 | 浏览器 / Node | 经由历程 import() 完成异步加载 |
附录2:参考
- AMD 模块化范例: https://github.com/amdjs/amdjs-api/wiki/AMD
- CMD 模块定义范例:https://github.com/seajs/seajs/issues/242
- webpack 模块相干文档: https://webpack.js.org/concepts/modules/
- 浏览器加载 CommonJS 模块的道理与完成:http://www.ruanyifeng.com/blog/2015/05/commonjs-in-browser.html