引例
第一次在公众号上看到一道面试题,最近学习javascript又看到廖大大的类似的例子,觉得有必要记录一下。
下面这道python题,大家看看会输出什么:
def testFun():
temp = [lambda x: i*x for i in range(4)]
return temp
for everyLambda in testFun():
print(everyLambda(2))
心里默默想了一下,嗯,我以为是: 0 2 4 6
正确答案是: 不妨打开编辑器调敲哈代码
6 6 6 6
原因是:闭包变量的迟绑定,在调用时才真正计算闭包中所引用的外部变量,也就是i的值。。。将上面由列表生成式与匿名函数构成的闭包改写下,就比较好看明白了。。。
def testFun():
r = []
for i in range(4):
def f(x):
return i*x
r.append(f)
return r
看了阮一峰大神的blog后, 我的理解:testFun()调用时,列表中所有元素生成,r=[f1, f2, f3,f4],每个函数对象均为闭包,只是其引用的外部函数的i值现在未确定,而testFun()调用结束,此时i的值为3,且存在了内存中。。。在for in 后取出f1 且everyLambda(2) 调用时,将i=3 x=2代入,最后得到结果6, 后面函数同理得结果
如何得到 0 2 4 6
第一种: 还是闭包思想,通过使用默认参数立即绑定它的参数
def testFun():
temp = [lambda x, i=i: i*x for i in range(4)]
return temp
for everyLambda in testFun():
print(everyLambda(2))
第二种:利用偏函数,将函数的某些参数固定住,对这个题来说,刚好可以把乘法的一个乘数固定,我觉得有点取巧,但是偏函数这个思想,非常好
from functools import partial
from operator import mul
def testFun():
temp = [partial(mul, i) for i in range(4)]
return temp
for everyLambda in testFun():
print(everyLambda(2))
第三种:优雅的写法,直接用生成器(生成式表达式)
def testFun():
return (lambda x: i*x for i in range(4))
for everyLambda in testFun():
print(everyLambda(2))
第四种:还是生成器思想,只是用函数生成器,个人最喜欢这个,觉得结构思路最清晰
def testFun():
for i in range(4):
yield lambda x: i*x
for everyLambda in testFun():
print(everyLambda(2))
Javascript引例
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
f1(); // 16
f2(); // 16
f3(); // 16
全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如何得到 1 4 9
第一种:如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
注意这里用了一个“创建一个匿名函数并立刻执行”的语法:
(function (x) {
return x * x;
})(3); // 9
这部分摘自廖大大的闭包
第二种:喜欢上了python中生成器的写法,而实际javascript生成器的思想也来自于python,那用生成器思想改写下, js中箭头函数是与python中匿名函数类似的
function* count() {
for (var i=1; i<=3; i++) {
yield (()=>(i*i));
}
}
for (var item of count()){
console.log(item());
}
最后: 关于闭包的相关知识,下面有阮老师的链接,有写得错误的地方,欢迎指出,一起学习,才不孤单
参考文档:
学习Javascript闭包(Closure)