zl程序教程

您现在的位置是:首页 >  其它

当前栏目

闭包终结解惑

闭包 终结 解惑
2023-06-13 09:15:12 时间

本文是对闭包(closure)的思考总结,其中夹杂着笔者对其的理解, 如若理解不到位,还望各位读者指出。

闭包是函数和声明该函数的词法环境的组合。

以上是MDN对闭包的最新解释, 这个概念高度抽象, 待笔者从代码角度进行阐述 首先从JavaScript代码引入, 现在考虑如下例子

var show = function() {
    // str是创建在show函数中的局部变量, 其作用域在show函数内
    var str = 'hello world'
    // 返回了一个匿名函数, 且该匿名函数引用了show函数中的str局部变量
    return function() {
        console.log(str)
    }
}

// 调用show方法, 返回一个函数类型的引用, 由fun变量接受
var fun = show()
// 调用fun指向的函数
fun()

从直观上看,该例子是在一个函数中返回了另一个函数。 show函数的返回值为函数类型,当调用show函数时返回的是另一个函数,而再调用这个返回的函数,则调用了在show函数中定义的匿名函数。该程序执行结果为在控制台输出了’hello world’,为了更直观的讲述这个例子,笔者将其改写

var show = function() {
    // str是创建在show函数中的局部变量, 其作用域在show函数内
    var str = 'hello world'
    // 定义closure函数, 且该函数引用了show函数中的str局部变量
    var closure = function() {
        console.log(str)
    }
    // 将定义的函数返回
    return closure
}

// 调用show方法, 返回一个函数类型的引用, 由fun变量接受
var fun = show()
// 调用fun指向的函数
fun()

该例子只是将匿名函数写成了实名的函数,程序执行效果不变。注意,当13行调用了show函数时, show函数内部的closure函数并没有调用,只是做了定义并返回给了fun变量, 在15行执行时才进行了调用。我们都知道在函数内部定义的局部变量, 当函数执行完后就应该释放了, 而15行执行时正确输出了str变量中的值, 说明局部变量中的str变量并未释放。这就是闭包带来的效果,如果把show函数看成是外部函数, closure函数看成是内部函数, 则这个内部函数即为闭包函数。所以闭包可以理解成定义在一个函数内部的函数

而文章开头给出的闭包定义又作何解释呢?这是将闭包理解为了一种现象。从表现形式上看,闭包就是在一个作用域中访问了另一个作用域中的变量,即在全局处对局部的函数内的局部变量进行了访问。所以说闭包是由函数以及创建该函数的词法环境组合而成, 而这个环境包含了这个闭包创建时所能访问的所有局部变量


  以下是python中闭包 (注意下面例子中的str变量不符合命名规范,与str类型重名,不过以下例子只是为了演示,无伤大雅)

def show():
    str = "hello world"
    def closure():
        print(str)
    return closure

if __name__ == '__main__':
    fun = show()
    fun()

举了个与上面JavaScript例子相同逻辑的python程序代码, 其执行结果与相关解释都与JavaScript类似。 但需要注意的是在python3中引入了nonlocal关键字, 先考虑如下代码

def show():
    str = "hello world"
    print("show's id = %d" % id(str))
    def closure():
        print("closure's id = %d" % id(str))
        print(str)
    return closure

if __name__ == '__main__':
    fun = show()
    fun()

运行上面程序后, 发现两个id是一样的,这符合我们对闭包的理解。

再考虑如下情况

def show():
    str = "hello world"
    print("show's id = %d" % id(str))
    def closure():
        str = 'closure'
        print("closure's id = %d" % id(str))
        print(str)
    return closure

if __name__ == '__main__':
    fun = show()
    fun()

这段代码运行后,输出的两个id不同,且控制台输出的是’closure’,这说明了closure函数中定义的str变量对show函数中的str变量进行了覆盖。

最后考虑如下代码

def show():
    str = "hello world"
    print("show's id = " + id(str))
    def closure():
        print("closure's id = " + id(str))
        print(str)
        str = 'closure'
    return closure

if __name__ == '__main__':
    fun = show()
    fun()

这段代码运行报错, 这是因为python解释器感知到了closure函数中局部变量的存在,则不使用show函数中的str变量, 但是str变量又定义在了使用之后,所以报错了。

那如果仍要使用外部函数中的变量呢? 解决办法是用nonlocal关键字声明变量。

def show():
    str = "hello world"
    print("show's id = %d" % id(str))
    def closure():
        # 声明不使用该函数内的局部变量
        nonlocal str
        print("closure's id = %d" % id(str))
        print(str)
        str = 'closure'
    return closure

if __name__ == '__main__':
    fun = show()
    fun()

这一段代码就会正确运行。

本文作者: Ifan Tsai  (菜菜) 本文链接: https://www.caiyifan.cn/p/28440.html 版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!