同步和异步因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务,但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验。
因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务。
同步任务
异步任务
模块化:将复杂的系统分解成高内聚、低耦合的模块,使系统开发变得可控、可维护、可拓展,提高模块的复用率
请求发出后,无论模块是否存在,立即执行后续的逻辑,实现模块开发中对模块的立即引用

<style>
#nav {
list-style: none;
margin: 0;
padding: 0;
}
#nav li {
float: left;
width: 80px;
height: 32px;
line-height: 32px;
text-align: center;
background-color: #eee;
cursor: pointer;
color: #0051ff;
margin: 0 2px;
}
#nav li span {
margin-left: 3px;
color: red;
font-size: 12px;
}
#panel {
display: none;
position: absolute;
top: 40px;
width: 200px;
height: 150px;
line-height: 150px;
text-align: center;
background-color: #ccc;
}
style>
<body>
<ul id="nav">ul>
<div id="panel">面板div>
body>
/**
* 创建导航逻辑(由A工程师创建)
* @param {HTMLElement} dom 上下文DOM
* @param {Array} data 数据
*/
function createNav(dom, data) {
/* ============== A工程师加入 创建了导航模块并添加了导航的内容 ============== */
{
for (let i = 0; i < data.length; i++) {
dom.append(`${data[i].label}`)
}
}
/* ============== B工程师加入 在A工程师的代码里边为导航添加引导 ============== */
{
let li = $('li', dom);
for (let i = 0; i < data.length; i++) {
if (data[i].isGuide) {
$(li[i]).append('[hot]');
}
}
}
/* ============== C工程师加入 在A工程师的代码里边为导航添加事件 ============== */
{
let li = $('li', dom);
li.on('mouseover', function () {
$('#panel').css({ left: this.offsetLeft + 'px', display: 'block' });
}).on('mouseout', function () {
$('#panel').css({ display: 'none' });
});
}
};
// 模拟请求,获取导航引导数据
setTimeout(function () {
let data = [
{ label: '首页', isGuide: false },
{ label: '推荐', isGuide: true },
{ label: '我的', isGuide: false }
];
// 创建的导航模块
createNav($('#nav'), data);
}, 2000);
缺点:
解决方案:
使用模块化开发(包括模块管理器和模块调用器)// 模块管理器
const F = {};
/**
* 定义模块管理器
* @param {string} str 模块路由
* @param {Function} fn 模块方法
* @returns F模块管理器对象
*/
F.define = function (str, fn) {
// 过滤掉第一个元素为F的字符串数组
let keys = str.replace(/^F\./, '').split('.');
// 屏蔽对define与module模块方法的重写
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.define('F.string.fn', function () {
return {
// 定义一个去除字符串空格的方法
trim(str) {
return str.replace(/\s+/g, '');
}
};
});
F.string.fn.trim(' P r o s p e r L e e '); // 'ProsperLee'
F.define('create.dom', function () {
let dom = null;
return function (el) {
dom = document.createElement(el);
dom.innerHTML = `创建的DOM元素-${el}`;
document.body.appendChild(dom);
return dom;
}
});
F.create.dom('li'); // - 创建的DOM元素-li
F.define('a', function () { return function () { } });
F.define('a.b', function () { return function () { } });
F.define('a.b.c', function () { return function () { } });
F.define('a.b.c', function () { return { a: {}, b: {}, c: {}, d: {}, e: {} } });
/**
* 模块调用方法
* @param {...any} args 需要最后的参数为function
*/
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) {
// 判断参数是否是字符串(通过define创建的)
if (typeof arr[i] === 'string') {
let keys = arr[i].replace(/^F\./, '').split('.');
// 获取当前F对象
let mod = this;
for (let j = 0; j < keys.length; j++) {
// 替换当前的mod,查询到最里层对象
mod = mod[keys[j]];
}
modules.push(mod);
} else {
modules.push(arr[i])
}
i++;
}
fn && fn.apply(this, modules);
};
F.module(['create.dom', 'F.string.fn.trim', document], function (dom, trim, doc) {
console.log(this); // F
dom('li').innerText = trim(' P r o s p e r L e e '); // - ProsperLee
console.log(doc); // #document
});

// 模块管理器
const F = {};
/**
* 定义模块管理器
* @param {string} str 模块路由
* @param {Function} fn 模块方法
* @returns F模块管理器对象
*/
F.define = function (str, fn) {
// 过滤掉第一个元素为F的字符串数组
let keys = str.replace(/^F\./, '').split('.');
// 屏蔽对define与module模块方法的重写
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;
};
/**
* 模块调用方法
* @param {...any} args 需要最后的参数为function
*/
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) {
// 判断参数是否是字符串(通过define创建的)
if (typeof arr[i] === 'string') {
let keys = arr[i].replace(/^F\./, '').split('.');
// 获取当前F对象
let mod = this;
for (let j = 0; j < keys.length; j++) {
// 替换当前的mod,查询到最里层对象
mod = mod[keys[j]];
}
modules.push(mod);
} else {
modules.push(arr[i])
}
i++;
}
fn && fn.apply(this, modules);
};
// 创建导航模块
F.define('nav.create', function () {
return function (dom, data) {
for (let i = 0; i < data.length; i++) {
dom.append(`${data[i].label}`)
}
}
});
// 添加导航引导模块
F.define('nav.create.addGuide', function () {
return function (dom, data) {
let li = $('li', dom);
for (let i = 0; i < data.length; i++) {
if (data[i].isGuide) {
$(li[i]).append('[hot]');
}
}
}
});
// 添加导航事件模块
F.define('nav.create.hover', function () {
return function (dom) {
let li = $('li', dom);
li.on('mouseover', function () {
$('#panel').css({ left: this.offsetLeft + 'px', display: 'block' });
}).on('mouseout', function () {
$('#panel').css({ display: 'none' });
});
}
});
// 导航数据请求模块
F.define('nav.request', function () {
return function (cb) {
setTimeout(function () {
let data = [
{ label: '首页', isGuide: false },
{ label: '推荐', isGuide: true },
{ label: '我的', isGuide: false }
];
cb($('#nav'), data);
}, 2000);
}
});
console.log(F);
F.module(['nav.request', 'nav.create', 'nav.create.addGuide', 'nav.create.hover'], function (request, create, addGuide, hover) {
request(function (dom, data) {
create(dom, data);
addGuide(dom, data);
hover(dom);
});
});
模块化开发即是以分而治之的思想,实现对复杂系统的分解,使系统随着其功能的增加而变得可控、可拓展、可维护。
模块化开发是一种对系统的分解,但使用时又像是以组合模式对模块的组合。
因此这也使得系统中的问题一般出现在局部,使得开发人员处理相应模块即可,而不用顾虑整个系统。
因此相对于整个复杂的系统,对于局部模块的改造、优化甚至替换所需成本要小得多。
组合的灵活性也使得我们可以实现更复杂、多样化的功能。
模块化:将复杂的系统分解成高内聚、低耦合的模块,使系统开发变得可控、可维护、可拓展,提高模块的复用率
请求发出后,继续其他业务逻辑,直到模块加载完成执行后续的逻辑,实现模块开发中对模块加载完成后的引用

index.js/**
* 同步模块模式-SMD
*/
// 向闭包中传入模块管理器对象F(~屏蔽压缩文件时,前面漏写;报错)
~(function (F) {
/**
* 定义模块管理器
* @param {string} str 模块路由
* @param {Function} fn 模块方法
* @returns F模块管理器对象
*/
F.define = function (str, fn) {
// 过滤掉第一个元素为F的字符串数组
let keys = str.replace(/^F\./, '').split('.');
// 屏蔽对define与module模块方法的重写
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;
};
/**
* 模块调用方法
* @param {...any} args 需要最后的参数为function
*/
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) {
// 判断参数是否是字符串(通过define创建的)
if (typeof arr[i] === 'string') {
let keys = arr[i].replace(/^F\./, '').split('.');
// 获取当前F对象
let mod = this;
for (let j = 0; j < keys.length; j++) {
// 替换当前的mod,查询到最里层对象
mod = mod[keys[j]];
}
modules.push(mod);
} else {
modules.push(arr[i])
}
i++;
}
fn && fn.apply(this, modules);
};
}((function () {
return window.F = {};
}())));
demo.jsF.define('demo.say', function () {
return function (name) {
console.log(`Hello ${name}`);
}
});
index.html<script src="./index.js">script>
<script>
/**
* 加载脚本文件
* @param {string} src 脚本路径
*/
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); // 将元素插入到页面中
}
// 加载demo.js脚本
loadScript('./demo.js');
// 调用demo.say模块方法 (Error ---> Uncaught TypeError: Cannot read properties of undefined (reading 'say'))
F.module('demo.say', function (say) {
say('Lee');
});
setTimeout(() => {
// 调用demo.say模块方法 (Success ---> Hello Lee)
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.js)
*/
require(['./lib/a', './lib/b'], function (a, b) {
console.log(a, b);
a('Lee'); // Hello Lee
b.demo.getName(); // demo
});
script>
// a.js
define([], function () {
return function (name) {
console.log(`Hello ${name}`);
}
});
// b.js 依赖于demo.js
define(['./demo'], function (demo) {
return {
name: 'b',
demo
};
});
// demo.js
define([], function () {
return {
name: 'demo',
getName(){
console.log(this.name);
}
}
});
require.js的实现核心思想:
每开始进行加载一个js脚本,计数器加1;每加载完成一个脚本,计数器减1;当全部脚本加载完成,计数器为0,此时执行回调函数,告诉控制台脚本已经加载完成


// index.js
/**
* 异步模块 - 创建与调度模块
*/
// 向闭包中传入模块管理器对象R(~屏蔽压缩文件时,前面漏写;报错)
~(function (R) {
// 缓存所有加载的模块
const cache = {};
// 最终的回调
let cb = function () { };
// 脚本加载数量(每添加一个js脚本累加1;加载完成累减1;全部完成变为0)
let depCount = 0;
/**
* 获取脚本路径
* @param {string} name 路径
* @returns 路径
*/
function getScriptSrc(name) {
// 拼接完整的文件路径字符串,如'lib/ajax' => 'lib/ajax.js'
return String(name).replace(/\.js$/g, '') + '.js';
}
/**
* 加载脚本文件
* @param {string} src 路径
*/
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); // 插入页面中
}
/**
* 定义模块(当引入的js加载完成,就会自动执行此方法)
* @param {string} name 模块名称
* @param {Array} deps 依赖项 ([{name: 模块名称, path: 模块路径}, ...])
* @param {Function} callback 回调
*/
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) : [], // 脚本的依赖脚本模块名称(作为缓存对象的key,用于最终整理)
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);
}
};
/**
* 执行各个脚本js模块
* @param {Array} deps 当前页面的依赖项,js脚本路径集合
* @param {Function} callback 回调
*/
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) {
/**
* {
* a: ƒ (name),
* b: {
* name: 'b',
* demo1: {name: 'demo1', getName: ƒ},
* demo2: {name: 'demo2', getName: ƒ},
* },
* demo1: {name: 'demo1', getName: ƒ},
* demo2: {name: 'demo2', getName: ƒ},
* }
*/
console.log(mod);
mod.a('Lee'); // Hello Lee
mod.b.demo1.getName(); // demo1
mod.b.demo2.getName(); // demo2
mod.demo1.getName(); // demo1
mod.demo2.getName(); // demo2
});
script>
lib/a.js// a.js
R.define('a', [], function () {
return function (name) {
console.log(`Hello ${name}`);
}
});
lib/b.js// b.js 依赖于 demo1.js demo2.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// demo1.js
R.define('demo1', [], function () {
return {
name: 'demo1',
getName(){
console.log(this.name);
}
}
});
lib/demo2.js// demo2.js
R.define('demo2', [], function () {
return {
name: 'demo2',
getName(){
console.log(this.name);
}
}
});
模块化开发不仅解决了系统的复杂性问题,而且减少了多人开发中变量、方法名被覆盖的问题。通过其强大的命名空间管理,使模块的结构更合理。
通过对模块的引用,提高了模块代码复用率。异步模块模式在此基础上增加了模块依赖,使开发者不必担心某些方法尚未加载或未加载完全造成的无法使用问题。
异步加载部分功能也可将更多首屏不必要的功能剥离出去,减少首屏加载成本。