- 简述
- 无副作用(No Side Effects)
- 高阶函数(High-Order Function)
- 柯里化(Currying)
- 闭包(Closure)
-- JavaScript 作用域
-- 面向对象关系
-- this调用规则
-- 配置多样化的构造重载
-- 更多对象关系维护——??榛?/a>
-- 流行的模块化方案- 不可变(Immutable)
- 惰性计算(Lazy Evaluation)
- Monad
前言
? ? ? ?在一个封闭的执行空间中(如函数),调用执行时会在内存中创建其执行上下文。而在上下文执行完毕时,本应当销毁的执行结果并没有销毁,保留了下来由调用者继续引用。这就是闭包(Closure)。
? ? ? ?闭包的表现形式可以是多样的,最稀松常见的便是返回一个对象,由调用主函数获取该对象这种方式。当然,也可以是非常复杂的过程构建,比如构建一个??榛慕换セ肪场?/p>
JavaScript ??榛硐?/h2>
? ? ? ?早些年间,JavaScript
并不是很受人待见。所有的执行环境都在一个页面中混为一体,每一个 jser
都需要考虑自己写的外部 .js
文件如何能够不被其他的插件所干扰。很多相似的代码片段重复出现,大量的造轮子,搞的一个页面环境十分笨重。让人觉得 JavaScript
只能用来玩一玩,根本无谈工程化,上不了台面。
? ? ? ?终于,前辈们不堪其扰,决心杀出一条血路,为构建更优质的 js 环境而不懈努力。他们要解决的问题是:
- 命名冲突:相似意义的变量或函数的命名冲突,可能会导致全局其他地方的引用产生歧义;
- 功能解耦:一个功能强大的 js 插件往往包含诸多内容,致使插件设计者在维护时不堪重负。需要将一个复杂的问题解耦成若干简单的问题,并且互相协作引用。
- 功能依赖:简单的诸多小的功能点可以被多处重复引用依赖,最终形成复杂的应用。因此需要配备成熟的依赖方式。
- 工程维护:在设计上解构了一个复杂的应用,在人员配备方面便可以做到分工明确。由独立的人或团队负责某一个或一部分??榈纳杓?,在接口引用的过程中只要符合标准,则开发效率也会大大提升,利于进一步维护。
AMD 规范
? ? ? ?AMD 即 Asynchronous Module Definition,中文名是“异步模块定义”的意思。它是一个在浏览器端??榛⒌墓娣?。??榻灰觳郊釉?,??榧釉夭挥跋旌竺嬗锞涞脑诵?。所有依赖某些??榈挠锞渚胖迷诨氐骱?。典型的实现是 requirejs。
requirejs 中通过 define(id?, dependencies?, factory) 函数定义??椋问硎镜暮迨牵?br> id [可选]:定义中??榈拿帧H绻挥刑峁└貌问?,模块的名字应该默认为??榧釉仄髑肭蟮闹付ń疟镜拿帧H绻峁┝烁貌问?,??槊谟τ没肪持胁辉市碇馗础?br> dependencies [可选]:当前??橐览档钠渌?楸晔???槊?所组成的数组字面量。如果忽略此参数,则默认为["require", "exports", "module"]。
factory : 模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为??榈氖涑鲋?。
require.js 示例
? ? ? ?点击下载 require.js
1. 目录结构
// 当前示例的目录结构
├─ js
│ ├─ require.js
│ ├─ a.js
│ ├─ b.js
│ └─ c.js
├─ index.html
2. 页面引用
? ? ? ?在引用 require.js
时,我们标注了其他模块的加载主入口是 a.js
,而无需把所有的??槎家胍趁?。
<body>
<script src="js/require.js" type="text/javascript" charset="utf-8" data-main="js/a.js"></script>
<script type="text/javascript">
console.log('page loaded..');
</script>
</body>
3. ??槎ㄒ?/h4>
// 文件: a.js
// 假设模块 a 依赖于???c 和 b
define('a', ['c', 'b'], function(c, b){
// ???a 的执行环境
console.log('a被加载...');
c.printC('a');
b.printB('a');
console.log('a加载完毕...');
});
// 文件: a.js
// 假设模块 a 依赖于???c 和 b
define('a', ['c', 'b'], function(c, b){
// ???a 的执行环境
console.log('a被加载...');
c.printC('a');
b.printB('a');
console.log('a加载完毕...');
});
之后是模块 c 和 b 的定义
// 文件: c.js
define('c', function(){
console.log('c被加载...');
// 定义函数 printC
const printC = function(who){
console.log(who + ' print C!!!');
}
// 导出调用对象
return {
printC
};
});
// 文件: b.js
define('b', function(){
console.log('b被加载...');
// 定义函数 printC
const printB = function(who){
console.log(who + ' print B!!!');
}
// 导出调用对象
return {
printB
};
});
4. 执行结果
? ? ? ?requirejs
以异步的方式进行??槎ㄒ澹⑽醋枞绦蛑飨叱?。因此其他的 js
活动可以正常运行。而在加载模块依赖的过程中,则会根据声明的依赖标识逐一加载。此时,并无关联性的三个 js
文件就可以互相引用,完成了功能解构。
CMD 规范
? ? ? ?CMD 即 Common Module Definition,通用??槎ㄒ澹枪诜⒄蛊鹄吹囊惶?js
??榛娣?,所解决的问题与 AMD
规范相同,只不过对于??榈亩ㄒ宸绞胶图釉厥被杂胁煌4聿酚?Alibaba 的玉伯所设计的 Sea.js
。与 AMD
规范不同的是 AMD
规范推崇依赖加载前置,而 CMD
规范推崇依赖就近。从依赖调用过程中我们就能看出他们之间的差别。
sea.js 示例
? ? ? ?点击下载 sea.js
1. 目录结构
// 当前示例的目录结构
├─ js
│ ├─ sea.js
│ ├─ aa.js
│ ├─ bb.js
│ └─ cc.js
├─ index.html
2. 页面引用
? ? ? ?使用 sea.js
的过程中,我们可以并不指定任何其他??榈南允降既?,而是在需要使用时再手动调用:
<script src="js/sea.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
// 通过 seajs 对象调用 use() 方法获取其他??榈囊? seajs.use('./js/aa.js', function (aa) {
aa.printBB();
aa.printCC();
});
// 未阻塞的页面其他 js 语句
console.log('page loaded...');
</script>
3. 模块定义
// aa.js
define(function (require, exports, module) {
console.log('aa被加载...');
// 在需要时引用的其他???,而非定义时描述模块
const cc = require('./cc');
const bb = require('./bb');
// 通过 module.exports 导出??? module.exports = {
printCC: cc.print,
printBB: bb.print
};
});
其他的依赖???bb.js
和 cc.js
:
// bb.js
define(function (require, exports, module) {
console.log('bb被加载...');
// 导出模块内容
module.exports = {
print: function(){
console.log('bb 执行 print()!');
}
};
});
// cc.js
define(function (require, exports, module) {
console.log('cc被加载...');
// 导出??槟谌? module.exports = {
print: function(){
console.log('cc 执行 print()!');
}
};
});
4. 执行结果
? ? ? ?与 AMD
规范最大的不同,便是对于??榈募釉厥被?code>aa 首先被加载,在 aa
??橹葱械墓讨邪葱杓釉亓?cc
和 bb
???。
? ? ? ?不过值得一提的是,随着 ES6
中对??榛亩ㄒ逡约捌占埃窦涞挠判愎娣兑步ソケ黄?。Sea.js
上一次的更新时间是 2014 年,已停止维护。但他们都是非常优秀的闭包案例,值得学习。
CommonJS 规范
? ? ? ?CommonJS 规范 是 node.js
中的??榛娣?,其应用方式与 CMD
规范十分相似,但要简化一些。CommonJS 规范
的实现方式是:
(function(exports, require, module, __filename, __dirname){
return module.exports;
});
熟悉 node.js
环境的同学能看到在??椴问?,node为我们传递了全局变量 __filename
和 __dirname
以便于更方便的引用。而我们自己在写基于 node.js
环境的 js
源码时,便可以很方便的直接这样定义:
let fs = require('fs');
// 定义一个读取文件返回 Promise 对象的异步函数
let readFile = (txtOrig) => new Promise((resolve, reject) => {
fs.readFile(txtOrig, {encoding: 'utf8'}, (err, data) => {
if(err) reject(err);
resolve(data);
});
});
module.exports = {
readFile
}
模块定义的语法被隐去了,留给开发者的是可以更加关注业务流程,减少了代码冗余。
ES6 模块规范
? ? ? ?ES6 ??楣娣?/strong> 是官方拟定的一套??榧釉毓娣叮淠?橐梅绞胶陀锓ǘ加肭罢呗杂胁煌?/p>
? ? ? ?ES6 通过 export default:导出的值作为模块对象的 示例: 接收默认导入: 接收声明导入: 全部导入: 通配符导入: 总结:闭包是一个?;ぶ葱谢肪场⒒捍嬷葱薪峁纳杓品绞?。在各种支持函数式编程的语言中我们都能看到闭包的身影,它的概念很模糊,却无处不在。??榈汲?/h4>
export default
和 export
两种语法进行??榈汲觯汲龅慕峁且桓?strong>??槎韵?/strong>,而两种语法会分别将导出结果作为这个模块对象的属性。他们之间的差别是:
default
属性,可由导入方进行重命名。一个??橹兄辉市沓鱿忠桓?export default
。
export:导出的语法必须是一个声明语句,声明的标识符(变量名或函数名)会作为导出的??槎韵?/strong>的属性进行动态绑定。一个??橹锌梢猿鱿侄喔?export
语法,并且可以与 export default
语法共存。// 模块 foo
export const num1 = 10;
export const num2 = 19;
export default function(a, b){
return a + b;
}
??榈既?/h4>
// 模块 bar
// 默认导入时可以为导入的结果重新命名
import calc from './foo';
calc(2, 3); // 结果: 5
// ???bar
// 通过 export 导出的声明,必须以原命名进行导入
import {num1, num2} from './foo';
num1; // 结果: 10
num2; // 结果: 19
// ???bar
import calc, {num1, num2} from './foo';
calc(num1, num2); // 结果: 29
// 模块 bar
import calc, * as Num from './foo';
calc(Num.num1, Num.num2); // 结果: 29