闭包

闭包Closure是词法闭包Lexical Closure的简称
闭包定义

闭包的定义:闭包是在词法上下文中引入了自由变量(除局部变量以外的变量)的函数。
还有一种说法是闭包是由函数和与其相关的引用环境组合而成的实体。(在实现深约束(绑定),需要创建一个能显式表示引用环境的东西,并将它与相关的子程序捆绑在一起,这样的捆绑的整天被称之为闭包)。
在上述的两种说法一个认为闭包是函数,一个认为闭包是函数与引用环境组成的整体。第二种说法更为贴切。闭包是形式和表现上像函数,但实际不是函数,函数是一些可执行的代码,这些代码在函数被定义后就确定了,并不会在执行时发生变化,所以函数一个函数只有一个实例,而闭包在运行时可以有多个实例,不用的引用环境和相同的函数组合可以产生不同的实例。
引用环境

所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。其中的约束是指一个变量的名字和其所代表的对象之间的联系。
将引用环境与函数组合起来的原因是在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境,这样的语言一般具有这样的特性:

  • 函数是一阶值(First-class value),即函数可以作为另外一个函数的返回值或参数,还可以作为变量的值。
  • 函数可以嵌套定义,即在一个函数内部可以定义另外一个函数。
    如果按照作用域规则在执行时确定一个函数的引用环境,那么这个引用环境可能和函数定义时不同,如果想要两个程序正常执行,一个简单的办法是在函数定义时捕获当时的引用环境,并与函数代码组合成为一个整体,当把这个整体当作函数调用时,先把其中的引用环境覆盖到当前的引用环境上,然后执行具体代码,并在调用借宿后恢复原来的引用环境,这样就保证了函数应以和执行时的引用环境是相同的,这样的引用环境与函数代码组成的实体就是闭包,如果编译器或解释器能够在确定一个函数在定义和运行时的环境是相同的,那么久没有必要把引用环境和代码组合起来,这时候只需要将传递的普通函数就可以了。这样的结论是:闭包不是函数,值是行为和函数相似,不是所有被传递的函数都需要转化为闭包,只有引用环境可能发生变化的函数才需要这样做。
    对闭包支持完善的条件

  • 函数是一阶值
  • 函数可以嵌套定义
  • 可以捕获引用环境,并且把引用环境和函数代码组成一个可被调用的实体
  • 允许定义匿名函数
    用一句话来描述对象是附有行为的数据,而闭包是附有数据的行为
    闭包的应用

大多时候利用闭包以很优雅的方式处理一些棘手的问题,通过函数式编程来学习闭包是一个很有效的途径。

  • 加强模块化
  • 抽象
  • 简化代码
    加强模块化

闭包有益于模块化编程,它能以简单的方式开发较小的模块,从而提高了开发的速度和程序的可复用性,用闭包可以将模块化划分更小。(在ruby中一切都是对象,数字也是对象

nums = [10, 22, 42, 31]
sum  = 0;
nums.each{|n| sum += n}
print sum

这样的处理方式有点类似JS中的回调函数,不过比回调函数写法更简单,功能更加强大,因为在闭包里引用环境是函数定义的环境,所以在闭包里改变引用环境中的变量的值,直接可以反映到它定义的上下文中。
抽象

闭包是数据和行为的组合,这使得闭包具有较好的抽象能力。

function make_stack()

    local data = {};
    local last = -1;

    local function push(e)
        last = last + 1;
        data[last] = e;
    end

    local function pop()
        if last == -1 then
            return nil
        end
        last = last - 1
        return data[last+1]
    end

    return function (index)
        local tb = {push=push, pop=pop}
        return tb[index]
    end
end

s = make_stack()

s("push")("test0")
s("push")("test1")
s("push")("test2")
s("push")("test3")

print(s("pop")())
print(s("pop")())
print(s("pop")())

这段代码通过闭包来模拟面向对象编程,函数make_stack用来生成stack对象,它的返回值是一个闭包,这个闭包作为一个Dispatcher,当以push和pop为参数调用时,返回一个与函数push或pop相关联的闭包,进而可以操作data中的数据。在lua中很常见,但对于Scheme 等没有内建面向对象支持也没有内建复杂数据抽象机制的语言,使用闭包来进行抽象是非常重要的手段。

简化代码

在一个窗口上有一个按钮控件,当点击按钮时会产生事件,如果选择在按钮中处理这个事件,那就必须在按钮控件中保存处理这个事件时需要的各个对象的引用。另一种选择是把这个事件转发给父窗口,由父窗口来处理这个事件,或是使用监听者模式。无论哪种方式,编写代码都不太方便,甚至要借助一些工具来帮助生成事件处理的代码框架。用闭包来处理这个问题则比较方便,可以在生成按钮控件的同时就写下事件处理代码

song = Song.new
start_button = MyButton.new("Start") { song.play }
stop_button = MyButton.new("Stop") { song.stop }

上下文

上下文(context)是一段程序运行所需要的最小数据集合。我们可以从上下文交换(context switch)来理解上下文,在多进程或多线程环境中,任务切换时首先要中断当前的任务,将计算资源交给下一个任务。因为稍后还要恢复之前的任务,所以中断的时候要保存现场,即当前任务的上下文,也可以叫做环境。即上下文就是恢复现场所需的最小数据集合。容易把人弄晕的一点是,我们这里说的上下文、环境有时候也称作作用域(scope),即这两个概念有时候是混用的。不过,它们有不同的侧重点
JavaScript 中常见的情形是一个方法/函数的执行。从一段程序的角度看,这段程序运行所需的所有变量,就是它的上下文。
作用域

作用域(scope)是标识符(变量)在程序中的可见性范围。作用域规则是按照具体规则维护标识符的可见性,以确定当前执行的代码对这些标识符的访问权限。作用域(scope)是在具体的作用域规则之下确定的。
前面说过,有时候上下文、环境、作用域是同义词;不过,上下文(context)指代的是整体环境,作用域关注的是标识符(变量)的可访问性(可见性)。上下文确定了,根据具体编程语言的作用域规则,作用域也就确定了。这就是上下文与作用域的关系。
写 JavaScript 代码时,如果 Function 作为参数,可以指定它在具体对象上调用时,这个对象常常叫做 context:

function callWithContext(fn, context) {
  return fn.call(context);
}

const apple = {
  name: "Apple"
};

const orange = {
  name: "Orange"
};

function echo() {
  console.log(this.name);
}

callWithContext(echo, apple);  // Apple
callWithContext(echo, orange); // Orange

上述参数之所以叫做 context,因为它关系到调用环境,指定了它,就指定了函数的调用上下文。再加上具体的作用域规则,作用域也确定了。
在 JavaScript 中,这个具体的作用域规则就是词法作用域(lexical scope),也就是 JavaScript 中的作用域链的规则。词法作用域是的变量在编译时(词法阶段)就是确定的,所以词法作用域又叫静态作用域(static scope),与之相对的是动态作用域(dynamic scope)。

function foo() {
  console.log(a);
}

function bar() {
  let a = 3;
  foo();
}

//如果在动态作用域规则应该是输出3,即为a的引用不再是编译时确定的,而是调试的确定的。
let a = 2;

bar(); // 2 无论函数在哪里被调用,也无论它如果被调用,它的词法作用域只能由函数声明所处的的位置决定,这问题让我疑惑了很久啊。
之所以输出为2,是因为foo是一个闭包函数,词法作用域也称之为静态作用域,变量在此法阶段确定,就是定义时就确定,虽然在bar内是调用,但由于foo是闭包函数,即为它在自己定义的词法作用域之外的地方执行,它也一直保持着的自己的作用域,所谓闭包函数,就是这个函数封闭了自己的定义时的环境,形成了一个闭包,所以foo并不会从bar中去寻找变量,这就是静态作用域的特点。
掌握一门函数语言是理解闭包的最佳途径,而且通过学习函数语言可以了解不同的编程思想,有益于写出更好的程序。

function fn() {
  let a = 0;
  function func() {
    console.log(a);
  }
  return func;
}

let a = 1;
let sub = fn();

sub(); // 0;

sub 就是 func 这一返回值,func 定义在 fn 内部并且被传递出来了,所以fn 执行之后垃圾回收器依然没有回收它的内部作用域,因为 func/sub 在使用。sub 依然持有 func 定义时的作用域的引用,而这个引用就叫作闭包。调用 sub 时,它可以访问 func 定义时的词法作用域,因此找到的 a 是 fn 内部的变量 a,它的值是 0。

转载于:https://www.cnblogs.com/yiquan/p/10046797.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值