一道面试题看python与javascipt殊途同归的闭包

引例

第一次在公众号上看到一道面试题,最近学习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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值