当我们要完成一个应用的时候,会根据对应的功能划分为许多不同的模块,就像一个论坛,有发帖的模块,评论的模块,js 中的模块也正是如此,一个具体功能的代码抽成一个文件,当你做一个东西的时候需要用到这个功能的时,可以直接使用这个文件,实现功能的分离,并能在多个需要的地方使用。就像是螺丝钉、螺丝帽、垫片一样的,通过组合使用实现出你的产品。
通过直白的描述,我们可以知道,模块化的好处就是,抽离代码,重复使用,如现在很直观的代表 npm 包。
那么模块化到底是怎么实现的呢?
先来了解一下历史,以前的 html 不知道大家还记不记的, 一个html 页面引入了多个 js 文件.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>So UI - A Component Library for .</title>
</head>
<body>
<div ></div>
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
</body>
</html>
如上,引入了 a/b/c/d/e 五个文件,这五个文件如果相互之间有依赖,还要注意引入的顺序,并且还需要注意它们里面的变量名,若是重复利用到其他的项目,其他项目也需要注意到以上两点问题。为了解决这一问题,就有了模块化的规范。
模块化的规范,有 CMD 和 AMD
CMD (Common Module Definition), 是在推广过程中对模块定义的规范化产出,主要用于浏览器端。它主要特点是:对于依赖的模块是延迟执行,依赖可以就近书写,等到需要用这个依赖的时候再引入这个依赖,应用有.
AMD规范(Asynchronous Module Definition):是 RequireJS 在推广过程中对模块定义的规范化产出,也是主要用于浏览器端。其特点是:依赖前置,需要在定义时就写好需要的依赖,提前执行依赖,应用有
尽情的猜测, 是怎么弄的呢? 它需要依次的加载模块然后去进行相应的操作,加载模块就是要引入这个文件,那么这里也还是通过动态加载 script 的方法,并通过 onload 去执行后面的回调了。
我们知道现如今 es6 已经支持模块化了,它分为 export 和 import 两个命令。 export 导出你定义的模块变量, import 引入一个模块变量。
export {
one,
two
}
export default three;
对应的引入代码
import { one, two } three from ''
可以看到 export 可以导出一个默认的变量,也可以导出变量对象,这里引入的时候名字不要写错了。 那么 es6 的模块化通过babel 转码其实就是 umd 模块规范, 它是一个兼容 cmd 和 amd 的模块化规范, 同时还支持老式的“全局”变量规范
(function (root, factory) {
if (typeof define === 'function' && ) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
= factory(require('jquery'));
} else {
// 浏览器全局变量(root 即 window)
= factory();
}
}(this, function ($) {
// 方法
function myFunc(){};
// 暴露公共方法
return myFunc;
}));
那么浏览器是如何支持这种规范的呢?
其实是实现了根据这种规范定制出来的功能。这里我们就按照 实现了 AMD 规范的 来讲一下实现代码。
AMD 定义一个模块的方法是 define(id?, dependencies?, factory)。
参考define 的方法代码
define = function (name, deps, callback) {
var node, context;
//Allow for anonymous modules
if (typeof name !== 'string') {
//Adjust args appropriately
callback = deps;
deps = name;
name = null;
}
//This module may not have dependencies
if (!isArray(deps)) {
callback = deps;
deps = null;
}
//If no name, and callback is a function, then figure out if it a
//CommonJS thing with dependencies.
if (!deps && isFunction(callback)) {
deps = [];
//移除注释
//查找 require 语句,收集依赖到 deps 里面
// but only if there are function args.
if () {
callback
.toString()
.replace(commentRegExp, commentReplace)
.replace(cjsRequireRegExp, function (match, dep) {
(dep);
});
//May be a CommonJS thing even without require calls, but still
//could use exports, and module. Avoid doing exports and module
//work though if it just needs require.
//REQUIRES the function to expect the CommonJS variables in the
//order listed below.
deps = ( === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
}
}
//If in IE 6-8 and hit an anonymous define() call, do the interactive
//work.
if (useInteractive) {
node = currentlyAddingScript || getInteractiveScript();
if (node) {
if (!name) {
name = ('data-requiremodule');
}
context = contexts[('data-requirecontext')];
}
}
//Always save off evaluating the def call until the script onload handler.
//This allows multiple modules to be in a file without prematurely
//tracing dependencies, and allows for anonymous module support,
//where the module name is not known until the script onload event
//occurs. If no context, use the global queue, and get it processed
//in the onscript load callback.
if (context) {
([name, deps, callback]);
[name] = true;
} else {
([name, deps, callback]);
}
};
= {
jQuery: true
};
= function (text) {
/*jslint evil: true */
return eval(text);
};
//Set up with config info.
req(cfg);
可以知道,这一段代码是解析定义是模块所需的依赖放置 context 的模块定义队列中。然后我们就要通过 req 去执行加载依赖,我们来看看 req 的定义。
req = requirejs = function (deps, callback, errback, optional) {
//Find the right context, use default
var context, config,
contextName = defContextName;
// Determine if have config object in the call.
if (!isArray(deps) && typeof deps !== 'string') {
// deps is a config object
config = deps;
if (isArray(callback)) {
// Adjust args if there are dependencies
deps = callback;
callback = errback;
errback = optional;
} else {
deps = [];
}
}
if (config && ) {
contextName = ;
}
if (config) {
(config); // 完善配置
}
return (deps, callback, errback);
这里的代码把 依赖,回调, 错误处理和配置项都传进来了,进行了配置上的处理之后,我们可以看到最后再去根据配置加载。
我们再来看 方法
makeRequire: function (relMap, options) {
options = options || {};
function localRequire(deps, callback, errback) {
.... 当前 require 的转换
return localRequire;
}
completeLoad: function (moduleName) {
判断 context 的依赖队列,是继续加载还是执行回调
}
nameToUrl: function (moduleName, ext, skipExt) {
根据模块名和配置得到加载的路径
}
load: function (id, url) {
(context, id, url);
},
execCb: function (name, callback, args, exports) {
return (exports, args);
},
onScriptLoad: function (evt) {
脚本加载完成后得到数据,执行 ();
}
onScriptError: function (evt) {
加载错误执行错误处理
}
};
= ();
那我们知道其实就是围着这语法的解析,进行一系列的脚本加载,然后执行回调。