本文讲述闭包及柯里化知识点。
一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 闭包 |
三 闭包好处和坏处 |
四 柯里化 |
4.1 柯里化举例 |
4.2 柯里化好处 |
4.3 题目:实现 add(1)(2)(3) |
4.4 题目:实现 compose(foo, bar, baz)('start') |
五 题目列表 |
5.1 求打印结果 1 |
5.2 求打印结果 2 |
六 参考文献 |
二 闭包
返回目录
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量。
当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。
简单来说:
- 在函数 A 中还有函数 B,函数 B 调用了函数 A 中的变量,那么函数 B 就称为函数 A 的闭包。
举个例子:
function foo() {
let num = 0;
return function() {
num++;
console.log(num);
};
}
const f = foo();
f(); // 1
f(); // 2
这个 num
在返回的函数中存在了,所以每次调用都会 + 1。
三 闭包好处和坏处
返回目录
好处:
- 缓存。将变量隐藏起来不被 GC 回收。
- 实现柯里化。利用闭包特性完成柯里化。
坏处:
- 内存消耗。闭包产生的变量无法被销毁。
- 性能问题。由于闭包内部变量优先级高于外部变量,所以需要多查找作用域链的一个层次,一定程度影响查找速度。
四 柯里化
返回目录
柯里化(Currying)是把接受多个参数的函数转变为单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
简单来说:
- 通过闭包管理
- 支持链式调用
- 每次运行返回一个
function
即:通过将多个参数换成一个参数,每次运行返回新函数的技术
4.1 柯里化举例
返回目录普通的 add 函数
function add (a, b) {
return a + b;
}
add(1, 2);
柯里化函数
function curryingAdd (x) {
return function(y) {
return x + y;
}
}
curryingAdd(x)(y);
哎,这样不是浪费程序员时间么?
不急,我们继续往下面看。
4.2 柯里化好处
返回目录
- 参数复用
- 提前确认
- 延迟运行
参数复用
// 正则表达式
// 校验数字
let numberReg = /[0-9]+/g;
// 校验小写字母
let stringReg = /[a-z]+/g;
// currying 后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt);
}
}
// 校验数字
let checkNumber = curryingCheck(numberReg);
let checkString = curryingCheck(stringReg);
// 使用
console.log(checkNumber('13888888888')); // true
console.log(checkString('jsliang')); // true
利用闭包实现柯里化。
4.3 题目:实现 add(1)(2)(3)
返回目录
// 实现一个 add 方法,使计算结果能够满足以下预期
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
答:
function add () {
const numberList = Array.from(arguments);
// 进一步收集剩余参数
const calculate = function() {
numberList.push(...arguments);
return calculate;
}
// 利用 toString 隐式转换,最后执行时进行转换
calculate.toString = function() {
return numberList.reduce((a, b) => a + b, 0);
}
return calculate;
}
// 实现一个 add 方法,使计算结果能够满足以下预期
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2, 3)(4)); // 10;
console.log(add(1)(2)(3)(4)(5)); // 15;
4.4 题目:实现 compose(foo, bar, baz)('start')
返回目录
function foo(...args) {
console.log(args[0]);
return 'foo';
}
function bar(...args) {
console.log(args[0]);
return 'bar';
}
function baz(...args) {
console.log(args[0]);
return 'baz';
}
function compose() {
// 闭包元素 - 函数列表
const list = Array.from(arguments);
// 闭包元素 - 函数列表执行位置
let index = -1;
// 闭包元素 - 上一个函数的返回
let prev = '';
// 返回闭包函数
const doNext = function() {
index++; // 索引值累加
// 一开始没有上一个元素时,获取第二个括号的值
if (!prev) {
prev = arguments[0];
}
// 设置前一个结果为当前函数返回
prev = list[index](prev);
// 递归调用
if (index < list.length - 1) {
doNext(index + 1);
}
};
// 第一次返回闭包函数
return doNext;
}
compose(foo, bar, baz)('start');
五 题目列表
返回目录
5.1 求打印结果 1
返回目录
function test() {
var n = 4399;
function add() {
n++;
console.log(n);
}
return {
n,
add
};
};
var result = test();
var result2 = test();
result.add(); // 输出啥
result.add(); // 输出啥
console.log(result.n); // 输出啥
result2.add(); // 输出啥
选择:
- A:4400 4401 4399 4400
- B:4400 4401 4401 4402
- C:4400 4400 4399 4400
- D:4400 4401 4399 4402
- E:4400 4401 4401 4400
答案:A
5.2 求打印结果 2
返回目录
function Foo() {
var i = 0;
return function() {
console.log(i++);
}
}
var f1 = Foo();
var f2 = Foo();
f1();
f1();
f2();
请选择:
- A:0 1 0
- B:0 1 2
- C:0 0 0
- D:0 0 2
答案:A
原因:f1
每次执行会产生闭包,然后 i++
是先赋值后展示。
六 参考文献
返回目录
- [x] 详解JS函数柯里化【阅读建议:20min】
- [x] 编写add函数 然后 add(1)(2)(3)(4) 输出10 再考虑拓展性【阅读建议:10min】
- [x] 发现 JavaScript 中闭包的强大威力【阅读建议:20min】
- [x] JavaScript闭包的底层运行机制【阅读建议:20min】
- [x] 我从来不理解JavaScript闭包,直到有人这样向我解释它【阅读建议:10min】
- [x] 发现 JavaScript 中闭包的强大威力【阅读建议:10min】
- [x] JavaScript闭包的底层运行机制【阅读建议:20min】
- [x] 我从来不理解JavaScript闭包,直到有人这样向我解释它...【阅读建议:10min】
- [x] 破解前端面试(80% 应聘者不及格系列):从闭包说起【阅读建议:10min】
<br/>jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。<br/>基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/>本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。