目录
1. 什么是闭包?
2. 闭包的应用
2.1. 模拟私有方法
2.2. 实现函数柯里化
2.3. Thunk函数与进阶
3. 几道笔试题
1. 什么是闭包?
计算机科学中,闭包(Closure)是引用了自由变量的函数。即使自由变量原来所属的内存空间不存在了,该自由变量也依然对该函数有效。闭包是函数和其相关的“环境”组成的实体。
函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
2. 闭包的应用
2.1. 模拟私有方法
编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
var Counter = (function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
}
}
})();
console.log(Counter.value());/* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value());/* logs 2 */
Counter.decrement();
console.log(Counter.value());/* logs 1 */
2.2. 实现函数柯里化
在计算机科学中,柯里化是接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数。并且返回接受余下的参数而且返回结果的新函数的技术。柯里化其实本身是固定一个可以预期的参数,并返回一个特定的函数,处理批特定的需求。这个技术由Christopher Strachey以逻辑学家 Haskell Curry 命名的。
经典示例:打折程序
版本1:常规思路
function discount(price, discount) {
return price * discount
}
// 普通顾客消费
let normal1 = discount(500, 0.1) // $50
let normal2 = discount(1500, 0.1) // $150
let normal3 = discount(2000, 0.1) // $200
let normal4 = discount(50, 0.1) // $5
let normal5 = discount(300, 0.1) // $30
console.log(normal1, normal2, normal3, normal4, normal5);
// vip 顾客消费
let vip1 = discount(500, 0.2) // $50
let vip2 = discount(1500, 0.2) // $150
console.log(vip1, vip2);
版本2:柯里化
// 将这个函数柯里化,然后我们就不用每次都写那0.1了
function discountFactory(discount) {
return (price) => {
return price * discount
}
}
// 普通顾客消费
const normalDiscount = discountFactory(0.1);
let normal1 = normalDiscount(500) // $50
let normal2 = normalDiscount(1500) // $150
let normal3 = normalDiscount(2000) // $200
let normal4 = normalDiscount(50) // $5
let normal5 = normalDiscount(300) // $30
console.log(normal1, normal2, normal3, normal4, normal5);
// vip 顾客消费
const vipDiscount = discountFactory(0.2);
let vip1 = vipDiscount(500) // $50
let vip2 = vipDiscount(1500) // $150
console.log(vip1, vip2);
2.3. Thunk 函数与进阶
2.3.1. Thunk 的起源
最早的 Thunk 函数起源来自于“传值调用”(call by value)和“传名调用”(call by name)之争。
let x = 1;
function fn(m) {
return m * 2;
}
fn(x + 1);
广义上thunk函数就是“传名调用”的实际表现,表示
let x = 1;
function fn(thunk) {
return thunk() * 2;
}
function thunk() {
return x + 1;
}
fn(thunk);
此时x+1是在fn被执行后才进行计算的,名副其实的传名调用。传名调用的好处是使用时计算,不浪费资源。
个人理解,核心在于
延迟——必要的时候才做必要的事
2.3.2. JS 中的 Thunk
Javascript 本身是就是【传值调用】,在 JS 中 thunk 函数主要用于延迟callback 的执行。即将带有 callback 的函数拆分为两部分,第一部分只包含必要业务参数,第二部分是当需要结果时传入的 callback。
经典示例1:Thunk 函数
var Thunk = function (fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return function (callback) {
args.push(callback);
return fn.apply(this, args);
}
};
};
// 原始函数
function test(a, b, cb) {
cb(a + b);
}
// Thunk函数
let thunker = Thunk(test);
// 1. 先收集参数
let handle = thunker(1, 2);
// 2. 未来再传入回调
handle(function (value) {
console.log(value);
});
经典示例2
Thunk + Generator 模拟 async await
// ====================
// Tools
// ====================
function Thunk(fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return function (callback) {
args.push(callback);
return fn.apply(this, args);
}
};
}
function Worker(generator){
function doNext(gInst, result, cb){
var hasNext = !result.done;
var yieldRet = result.value;
if(hasNext && typeof yieldRet == "function"){
yieldRet(function(cbkret){ // thunker+callback => result
doNext(gInst, gInst.next(cbkret), cb);
});
}else{
cb(yieldRet);
}
}
this.run = function(cb){
var gInst = generator();
var result = gInst.next();
doNext(gInst, result, cb);
}
}
// ====================
// DEMO
// ====================
// function with callback
function add(a, b, cb) {
cb(a + b);
}
// make function Thunkable
var thunkAdd = Thunk(add);
// Generator: convert callback to yield
function* myComputer() {
let sum = 0;
sum += yield thunkAdd(1, 2);
console.log(sum);
sum += yield thunkAdd(3, 4);
console.log(sum);
sum += yield thunkAdd(5, 6);
console.log(sum);
return sum;
}
// run "myComputer"
new Worker(myComputer).run(function(result){
console.log("result", result);
});
3. 几道笔试题
题目01:
题目02:
题目03:
题目04:
题目05:
// 【简答题】:
//
// 要求写一个函数add(),分别实现能如下效果:
//
// 1. console.log(add(1,2)()) // 3
// 2. console.log(add(1)(2)()) // 3
// 3. console.log(add(1)(2)(3)()) // 6
// 4. console.log(add(1,2,3)(4)()) // 10
//
// 【答案】:
//
function add(...args) {
let argsArr = [];
function __calc(){
return argsArr.reduce(function (acc, cur) {
return acc + cur;
}, 0);
}
function __saveArgs(...args){
argsArr = [...argsArr, ...args];
}
function __innerAdd(...args) {
if (!args.length) {
return __calc();
}
__saveArgs(...args);
return __innerAdd;
}
return __innerAdd(...args);
}
console.log(add(1, 2)());
console.log(add(1)(2)());
console.log(add(1)(2)(3)());
console.log(add(1, 2, 3)(4)());
参考:
Closures: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures 《自制编程语言》(郑刚著)