异步模块模式——AMD(Asynchronous Module Definition)
模块化
:将复杂的系统分解成高内聚、低耦合的模块,使系统开发变得可控、可维护、可拓展,提高模块的复用率
请求发出后,继续其他业务逻辑,直到模块加载完成执行后续的逻辑,实现模块开发中对模块加载完成后的引用
异步加载文件中的模块

index.js
~(function (F) {
F.define = function (str, fn) {
let keys = str.replace(/^F\./, '').split('.');
if (keys[0] === 'define' || keys[0] === 'module') return this;
let obj = this;
for (let i = 0; i < keys.length; i++) {
if (typeof obj[keys[i]] === 'undefined') {
obj[keys[i]] = {
};
if (i === keys.length - 1) {
obj[keys[i]] = fn && fn();
}
}
obj = obj[keys[i]];
}
return this;
};
F.module = function (...args) {
let modules = [];
let fn = typeof args[args.length - 1] === 'function' ? args.pop() : null;
let arr = (args.length === 1 && Array.isArray(args[0])) ? args[0] : args;
let i = 0;
while (i < arr.length) {
if (typeof arr[i] === 'string') {
let keys = arr[i].replace(/^F\./, '').split('.');
let mod = this;
for (let j = 0; j < keys.length; j++) {
mod = mod[keys[j]];
}
modules.push(mod);
} else {
modules.push(arr[i])
}
i++;
}
fn && fn.apply(this, modules);
};
}((function () {
return window.F = {
};
}())));
demo.js
F.define('demo.say', function () {
return function (name) {
console.log(`Hello ${
name}`);
}
});
index.html
<script src="./index.js"></script>
<script>
function loadScript(src) {
let _script = document.createElement('script');
_script.type = 'text/javascript';
_script.charset = 'UTF-8';
_script.async = true;
_script.src = src;
document.getElementsByTagName('head')[0].appendChild(_script);
}
loadScript('./demo.js');
F.module('demo.say', function (say) {
say('Lee');
});
setTimeout(() => {
F.module('demo.say', function (say) {
say('Lee');
});
}, 2000);
</script>
报错原因:
由于浏览器中的文件是异步加载的,虽然现在开始加载demo.js文件,不过在文件没有加载完之前你可以继续做其他的事情,并且你写的方法,对于文件什么时候加载完成,你是无法获知的。
同步模块模式会立即引用该模块,此时文件加载尚未加载完成,因此你是引用不到该模块的。
当延迟了2s后,demo.js文件已经加载完毕,所以能得到demo.say模块,所以访问无问题。
异步模块(使用require.js
)

https://requirejs.org/docs/release/2.3.6/comments/require.js
<script src="https://requirejs.org/docs/release/2.3.6/comments/require.js"></script>
<script>
require(['./lib/a', './lib/b'], function (a, b) {
console.log(a, b);
a('Lee');
b.demo.getName();
});
</script>
define([], function () {
return function (name) {
console.log(`Hello ${
name}`);
}
});
define(['./demo'], function (demo) {
return {
name: 'b',
demo
};
});
define([], function () {
return {
name: 'demo',
getName(){
console.log(this.name);
}
}
});
仿require.js
的实现
核心思想:每开始进行加载一个js脚本,计数器加1;每加载完成一个脚本,计数器减1;当全部脚本加载完成,计数器为0,此时执行回调函数,告诉控制台脚本已经加载完成


~(function (R) {
const cache = {
};
let cb = function () {
};
let depCount = 0;
function getScriptSrc(name) {
return String(name).replace(/\.js$/g, '') + '.js';
}
function loadScript(src) {
let _script = document.createElement('script');
_script.type = 'text/javascript';
_script.charset = 'UTF-8';
_script.async = true;
_script.src = src;
document.getElementsByTagName('head')[0].appendChild(_script);
}
R.define = function (name, deps, callback) {
depCount--;
deps.forEach(dep => {
depCount++;
loadScript(getScriptSrc(dep.path));
});
if (!cache[name]) {
cache[name] = {
depNames: deps.length ? deps.map(dep => dep.name) : [],
hasDep: !!deps.length,
export: deps.length ? callback : callback(),
};
}
if (depCount === 0) {
Object.values(cache).forEach(item => {
if (item.hasDep) {
let params = item.depNames.map(key => cache[key]['export']);
item.export = item.export.apply(null, params);
}
});
let obj = {
};
Object.keys(cache).forEach(key => {
obj[key] = cache[key].export;
});
cb.call(null, obj);
}
};
R.module = function (deps, callback) {
cb = callback;
deps.forEach(dep => {
depCount++;
loadScript(getScriptSrc(dep));
});
};
}((function () {
return window.R = {
};
}())));
html.html
<script src="./index.js"></script>
<script>
console.log(R);
R.module(['./lib/a', './lib/b'], function (mod) {
console.log(mod);
mod.a('Lee');
mod.b.demo1.getName();
mod.b.demo2.getName();
mod.demo1.getName();
mod.demo2.getName();
});
</script>
lib/a.js
R.define('a', [], function () {
return function (name) {
console.log(`Hello ${
name}`);
}
});
lib/b.js
R.define('b', [{
name: 'demo1', path: './lib/demo1' }, {
name: 'demo2', path: './lib/demo2' }], function (demo1, demo2) {
console.log(demo1, demo2);
return {
name: 'b',
demo1,
demo2
};
});
lib/demo1.js
R.define('demo1', [], function () {
return {
name: 'demo1',
getName(){
console.log(this.name);
}
}
});
lib/demo2.js
R.define('demo2', [], function () {
return {
name: 'demo2',
getName(){
console.log(this.name);
}
}
});
特点
模块化开发不仅解决了系统的复杂性问题,而且减少了多人开发中变量、方法名被覆盖的问题。通过其强大的命名空间管理,使模块的结构更合理。
通过对模块的引用,提高了模块代码复用率。异步模块模式在此基础上增加了模块依赖,使开发者不必担心某些方法尚未加载或未加载完全造成的无法使用问题。
异步加载部分功能也可将更多首屏不必要的功能剥离出去,减少首屏加载成本。