编程中的闭包 另一种方式理解闭包

121 阅读4分钟

闭包包含自由(未绑定到特定对象)变量,这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
引用自百度百科

关键词:代码段,包有变量不能被其他成员访问的变量,该变量通过触发某个函数更改

例1:看一段ts代码

function setupCounter(element: HTMLButtonElement) {
  let counter = 0
  const setCounter = (count: number) => {
    counter = count
    element.innerHTML = `count is ${counter}`
  }
  element.addEventListener('click', () => setCounter(++counter))
  setCounter(0)
}

这是一段闭包代码,setupCounter函数中包了counter这个变量,能使用buttononclick事件绑定的函数(setCounter)触发,每次点击,button内计数加一

例2::再看一段python代码

def myGenerator(start):
    count = start
    def gen():
        nonlocal count 
        count += 1
        return count
    return gen
  
g = myGenerator(5)
for _ in range(3):
    print(g())

每次调用g()得到的结果都会加1

让我们逐条翻译闭包的关键词:

  1. 代码段 函数调用
  2. 包有变量不能被其他成员访问的变量私有变量
  3. 该变量通过触发某个函数更改访问控制

我们最终得到了结论:闭包等价于函数调用+私有变量+访问控制 = 类 我们用Java 完全可以写成以下代码

/**
 * MyGenerator
 */
public class MyGenerator {

    private int counter = 0;

    public MyGenerator(int start){
        this.counter = start;
    }

    public int Gen(){
        this.counter++;
        return this.counter;
    }
}

public class Main {
    public static void main(String[] args) {
        var g = new MyGenerator(5)
        
    }
}

以后我们在看js或者其他语言闭包的时候,完全可以按照面向对象的方式进行理解。无非是类+成员+访问控制。一旦我们看到函数内部套函数,找函数内部对外部变量的使用,然后看有没有外部访问控制,如果上述条件都满足,可以断定这是个闭包。例如在第一个js代码中,counter在内部被进行了赋值,最后通过绑定事件 addEventListener的方式导出了访问控制函数。

按照面相对象的理解,我们可以把例2的myGenerator理解为一个微型的构造方法,创建了一个函数对象,使用了start为构造方法的参数,里面包有一个count的私有的变量,最后导出了gen方法导出访问控制函数。这样们也应该能够理解,为什么go以下的代码全部输出为10了。(类比于类里声明方法,声明时候并没有被调用,调用的时候外边的i已经完成的迭代都是10了,i为什么没有被回收可以看看go的逃逸分析,内部匿名方法+内部变量i+外部gorountie调度器调度go func)

func manyRountine(){
    i:=1
    for (;i<10;i++){
        go func(){
           fmt.println(i) 
        }()
    }
}

这种闭包的写法,多出现于以函数为第一公民的语言中,例如python,js,go.很优雅的使用闭包来保存函数内部的一个私有变量,利用函数内部和外部的作用域的隔离,轻松的把一个函数变成了一个类,使用函数生成一个对象,调用方法控制对象里面的内容。纯OOP语言例如JavaC#则用的较少,因为语言已经将私有的变量的访问控制完善的相当完美,使用闭包纯粹是有些多余了,除了少许的例外,例如内部类,lambda表达式等,需要传递实现类的地方。


由此我们可以看到,无论是面向对象编程,还是函数式编程,无非是一种思想的不同表述方式而已。没有什么高下好用之分,无非是换个说法而已。

因此,在我们学习一门技术的时候,语言,语法,实现层面的东西往往每一种语言的实现方式各有不同,我们需要找到背后解决问题的思想,为什么这个思想可以解决问题。语言没有什么高下之分,只有适合的场景不同,不同的语言解决的问题不同而已。

以下是我个人的总结语言适应的方面:

Java: 工程化,标准化
Python: 快速实现想法
Go: 体积小,编译快,快速部署
js: 浏览器端唯一指定语言,可以理解为浏览器的汇编码,正在被高级的TS取代
C/C++: 底层,快速,直接操作硬件

欢迎补充