谈一谈闭包

【JavaSE】之流程控制与方法 Java基础之Java流程控制与方法相关知识介绍~ 阅读详情

什么是闭包

好, 下面开始进入主题, 首先一个问题, 什么是闭包? 对于我们这些习惯了命令式编程, 尤其是Java这种完全面向对象的语言的人, 闭包可能是一个很陌生的概念, 闭包时常在函数式语言中被提及, 那闭包到底是一个什么概念? 下面对闭包的解释来自维基百科.

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

闭包更多强调的是引用环境, 函数在定义时因为使用到了自由变量, 函数的调用和定义时的引用环境不同, 所以它必须要清楚的了解到自己定义时的引用环境, 在调用的时候需要切换到它定义的引用环境中执行, 这样就形成了闭包.

更通俗的讲, 因为函数引用了自由变量, 当它执行的环境和定义的环境不同时, 在执行它的地方就不仅仅是拿到这个函数这么简单了, 因为它必须要清楚它引用的自由变量, 所以最好的方式是把此时的引用环境和函数本身一并返回, 这样就是闭包.

所以, 闭包 = 函数 + 引用环境

分解一下就是闭包形成的必要条件.

  1. 函数引用自由变量
  2. 函数的执行环境和自由变量的声明环境不同

只有符合了以上两点, 函数的执行点才不仅仅需要函数本身, 而是函数+引用环境.

两个重点是自由变量, 引用环境, 但就这几个字可能对于一些对闭包一知半解的人来说确是很难理解, 因为在大部分人印象里闭包应该是这样的.

// kotlin
list.map { println it}
 
  • 1
  • 2
  • 1
  • 2

这样的一个实例中没有任何地方提及到自由变量. 其实这压根和闭包没有任何关系, 这仅仅是一种语法-lambda表达式, 很多人将lambda表达式想当然的认为是闭包. 再来看看下面语法.

// groovy
android {
  compileSdkVersion 24
}
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

对于搞Android的人来说这行代码并不陌生, 很多人把这个也理解成闭包, 和上面kotlin的代码一样, 其实这和闭包也没有任何关系.

那什么样的才算闭包? 上面提到了, 引用了自由变量的函数, 这句话里有一个名词-自由变量, 那什么又是自由变量呢? 理解了这个名词后才能继续理解这句话.

在某个作用域内使用了其他作用域声明的变量, 那该变量就是自由变量

上面的概念是是我自己写的, 可能有点难理解, 来看看代码

// javascript
var a = 10;
function add5() {
  return a + 5;
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

上面的变量a即为自由变量, a变量不是在函数add5的作用域中声明的, 却在函数add5中被使用.

好了, 知道了什么是自由变量, 那下面我们来看看什么是闭包.

// javascript
function add(a) {
  return function(b) {
    return a + b;
  };
}

var add10 = add(10);
var result = add10(5);
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的例子是最典型的闭包, add函数返回了一个函数, 这个匿名函数引用了add函数的一个a变量, 所以a变量是一个自由变量. 而且, 这个匿名函数的执行点不在变量a声明的作用域内, 根据上面的概念, 这样就形成了闭包.

再来看看下面的代码. 下面的代码来自apple官方的Swift文档中闭包章节.

// swift
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

这样的算不算闭包? 30s思考一下, 在心里有答案了再往下看… 










当然不是, 虽然是来自官方的文章, 但这里确实是对概念的错误理解. 将上面的代码普通化来看看.

// swift
func sorter(s1: String, s2: String) -> Bool {
  return s1 > s2
}

reversedNames = names.sorted(sorter)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一目了然, 上面的代码丝毫没有看到对自由变量的引用, 这里仅仅算是高阶函数罢了… 所以很多时候官方文档也是会误导人的…

相似的代码, 再来看看kotlin官方文档上闭包的实例

// kotlin
var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这是不是形成闭包了? 把它普通化来看看, 毕竟去除语法糖才更适合人理解.

// kotlin
var sum = 0
var filter: (Int) -> Bool = { a -> a > 0 }
var each: (Int) -> Int = { a -> sum + a }

ints.filter(filter).forEach(each)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

主要看这个each函数, 很明显, 这里有对自由变量a的引用, 而且each函数的定义和执行时的引用环境不同, 所以上面的实例确实形成了闭包. 不过我还是要吐槽一下kotlin的文档, 它将闭包放在了Lambda章节里, 虽然实例代码确实是形成了闭包, 但肯定会去一些人产生误导作用, 毕竟是两个毫无关系的概念.

闭包的作用

通过概念和实例代码, 很明显闭包的存在改变了变量的生命周期, 大部分情况下它可以将自由变量的生命周期延迟到闭包函数的执行, 而函数式中最重要的一个思想是尽可能多使用纯函数(纯函数是指对于相同的输入必定有相同的输入的函数), 在纯函数中如果想要保持一个变量, 那闭包肯定是最佳选择. 来看一下实例.

// javascript
function nameBy(lastName) {
  return function(firstName) {
    retrn firstName + " " + lastName;
  }
}

var group = nameBy("Jordan")
var michael = group("Michael")
var susan = group("Susan")
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

另外一个函数式中重要的概念-柯里化大部分情况下也离不开闭包的支持, 什么是柯里化?

柯里化(英语:Currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

其实上面的大部分例子中已经实现了柯里化, 柯里化的好处就是大大的提高了函数的灵活性.

// javascript
var add = (x, y) => x + y

// user
alert(add(10, 20))
alert(add(10, 30))
alert(add(10, 40))
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面对add函数的调用其实都是10+y的形式, 很多时候我们为了封装, 又对10+y这样的式子进行封装.

// javascript
var add = (x, y) => x + y
var add10 = (y) => 10 + y

// user
alert(add10(20))
alert(add10(30))
alert(add10(40))
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里有一个不好的地方就是add10这个函数的封装只能适用于10+y, 虽然实现了柯里化, 但是对于使用者来说灵活性不够, 其实这里我们可以利用闭包对add函数稍加改造, 既方便使用又不失灵活性

// javascript
var add = (x) => (y) => x + y

// user
var add10 = add(10)
alert(add10(20))
alert(add10(30))
alert(add10(40))
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

再来看, 上面实例中的函数add的定义其实不太符合我们人类的思想, 这方面, 其实很多函数式编程语言中已经实现了自动柯里化, 来看个实例.

// elm
add x y =
 x + y

add 10 20 // result is 30
add10 = add 10
add10 20 // result is 30
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这些支持自动柯里化的语言可以根据实参的个数推断出返回的类型, 所以可以很轻松的实现柯里化, 而不必在我们定义函数的时候过多考虑. 其实JavaScript也可以实现自动柯里化, 大家可以参考我的一个demo-https://github.com/qibin0506/js-curry.

好了, 虽然说了这么多关于柯里化的东西, 其实都是对闭包的一些应用, 可以帮助大家去理解闭包. 最后留几个代码片段, 大家判断一下是否实现了闭包.

// 片段 1
function f1() {
  return function() {
    return "hello world"
  }
}

f1()()
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
// 片段 2
function f1() {
  return function(arg) {
    return arg + 10
  }
}

f1()(20)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
// 片段 3
var name = "qibin";
var f = function(name) {
  return name + " HI"
}

f(name)
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
// 片段 4
function someFunction() {
    var name = "qibin"
    var f = funtion() {
      return name + " HI"
    }
    f()
}
someFunction()
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// 片段 5
var getName = function() {
  var name = "qibin"
  var f = function() {
    return name + " HI"
  }

  prntName(f)
}

function prntName(f) {
  console.log(f())
}
STM32F103VCT6+W5500 TCP 服务端通信 STM32F103VCT6 W5500的TCP连接通讯的程序 阅读详情

相关推荐

HyperLynx实战:手把手教你搞定DDR4信号完整性仿真(附ODT与终端电阻配置避坑)

本文详细介绍了使用HyperLynx进行DDR4信号完整性仿真的实战技巧,重点解析了ODT配置与终端电阻优化的黄金法则。通过模型准备、拓扑构建、ODT扫描等关键步骤,帮助硬件工程师规避常见设计陷阱,提升高速数字电路设计的效率与可靠性。

weixin_30861797的博客 640

kotlin——闭包

闭包的概念,外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象。Kotlin 的闭包可以获取上下文的局部变量,并可以修改它。实现办法是 Kotlin 编译器给引用的局部变量封装了一层引用。

wy313622821的博客 1502

adb 通信协议分析以及实现 (三) (Adb 网络通信部分解析)

这篇文章说下adb 和adb 服务进程之间通信的socket 协议。  adb 服务进程启动之后,会监视本地的5037 端口,使用的是TCP 协议,所以adb 发送的命令其实就是往本地的5037 端口发送数据,做过网络编程的都知道,本机可以用127.0.0.1 ip地址。 有了ip 地址和端口号socket 通信就没问题 1)  命令发送方式      

inforecovvery的博客 2870

kotlin中闭包的概念和原理

kotlin中闭包的概念和原理 问题背景 安卓开发,kotlin学习和使用过程中,kotlin闭包肯定算是个新概念,尤其从安卓java开发转kotlin的同学,现在一起看看kotlin的闭包到底是什么意思? 闭包的概念关键:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包...

baorant24的博客 714

BPMN-JS :修改节点“Change Type”事件类型

修改路径:node_modules\bpmn-js\lib\features\replace\ReplaceOptions.js。

TaylorY的博客 679

《活用数据——驱动业务的数分析实战》读书笔记 (实战篇)

第四章、战略分析案例解析         如思维篇所述,分析思路是从研究目的到研究内容的分解过程。因此需要考虑如下两个问题: 战略分析的研究目的 战略分析的研究内容 1、研究目的:战略选择         战略分析的研究目的是帮助企业进行战略选择,选择适合自己的目标市场。需要回答以下两个问题: 市场吸引力:这个市...

Xiaozhu的博客 3698

Kotlin原理-闭包

这种做法的原理在于,data这个引用本身的内存在栈区,方法调用完会被回收,但是它所指向的数组的内存在堆区,只有还有引用指向它就不会被回收。而Java编译器生成的这个类巧合就有个成员变量保存了data的副本,指向了这个数组。

yihanss的博客 584

【大语言模型 21】语言模型训练目标:MLM vs CLM vs GLM对比分析

本文深入对比分析三种主流语言模型训练目标:掩码语言模型(MLM)、因果语言模型(CLM)和双空白填充(GLM)。通过数学推导、代码实现和实验对比,揭示不同训练目标的工作机制、适用场景和性能差异,为选择合适的预训练策略提供科学指导。

熵数实验室 1176

Android Groovy 闭包(六)

首先申明下,本文为笔者学习《Groovy 程序设计》的笔记,并加入笔者自己的理解和归纳总结。 闭包是Groovy的特性之一,类似于Java中的匿名内部类,但功能更加强大。 1. 闭包应用 一般用法 def filter(array, block) { for (val in array) { block(val) } } iarray ...

假装你是大灰狼的专栏 664

爬虫进阶必备:深入理解requests.Session(),提升爬虫效率与稳定性

前两天,有个朋友跟我吐槽,说自己写了个爬虫爬取某个电商网站的商品信息,刚开始跑得好好的,爬了大概几百页之后,突然就开始报错了。HTTP状态码一会儿429,一会儿403,最离谱的是有些页面明明登录了却显示未登录状态。他排查了半天也没找到原因,最后把代码发给我看。 我扫了一眼代码,发现一个典型问题:他每次请求都新建一个requests.Session(),甚至有些请求直接用了requests.get()。这就好比每次进超市都要重新注册会员,每次都重新跟服务器“握手”,不翻车才怪。 其实,这就是我们今天要聊的

2201_76125261的博客 1228

【DaVinci Developer专题】-8-创建一个枚举Enum ADT(Application Data Type)

目录 1 创建一个枚举Value ADT(Application Data Type) 1.1 命名application data type 1.2 创建一个computation method计算方法属性 1.3 创建一个Contraint约束条件属性 2 创建了一个新的Type Mapping Sets 3 创建了一个S/R Port Interface 4 SWC引用Type Mapping set 5 导入CFG生成代码的总结 6 结尾 <--返回「实战篇」博文-总目录-

「汽车电子助手」的博客 2292

谈谈对闭包的理解

/** 1. 闭包是什么? a. 某一个函数可以记住并在所在的词法作用域(父函数)外部访问所在的词法作用域(父函数)时,就产生了闭包。 b. 所以在以上情况满足的条件下,对所在的词法作用域的引用,这个引用就是闭包。而这个引用是一个函数,这个函数包含了(父函数)的变量。 简单的说。所以 闭包是 一种引用。 闭包函数: 就是被引用的能记住所在的词法作用域的函数。 */ /** 2. 下面我们将闭包的概念进行图形化(那就是代码)理解,让我想起了:taik is cheap, show ..

吞吞吐吐屠屠兔兔的博客 790

ADB 安装教程:如何在 Windows、 Linux 上安装 Android Debug Bridge

本文介绍了在Windows和Linux系统上安装ADB(Android调试桥)的方法。对于Windows系统:1)从官网下载ADB工具并解压;2)通过命令提示符验证安装;3)配置环境变量以实现全局使用;4)再次验证全局ADB功能。Linux系统可通过包管理器直接安装(如Ubuntu使用apt,Fedora使用dnf),安装后同样需验证版本信息确认成功。两种系统安装完成后,都能通过"adb version"命令检查安装状态。

qq_53545309的博客 444

谈一谈对JavaScript闭包的理解

一,闭包的定义 **通俗点来说闭包就是能够读取其他函数内部变量的函数。**或者说闭包是指有权访问另一个函数作用域中的变量的这么一个函数。 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成定义在一个函数内部的函数”。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。 需要理解的是,闭包存在于嵌套的内部函数中。 二,闭包产生的条件 函数嵌套...

mazha_yuan的博客 216

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

谈一谈对JS闭包的理解

个人对前端的一些浅显的理解。

FE_HB的博客 3272

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

谈一谈JavaScript中的闭包

一个函数和对其周围(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样一个组合就是闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。结合上面的定义,我们可以得到如下公式:闭包 = 函数 + 外层作用域举个简单的例子,现在有一个test函数,在其函数内部访问一个外层作用域中的变量,这个行为就是闭包的引用。var name = '闭包'test()

qq_51976882的博客 802

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

请你谈一谈闭包?详细解释闭包的概念、形成原因、作用及与作用域、垃圾回收机制的关系

闭包是 JavaScript 中一个强大的工具,允许函数记住并访问其外部环境的变量。这种特性使得闭包在数据封装、异步编程和函数柯里化等场景中非常有用。然而,闭包的使用也需要小心,避免内存泄漏和不必要的内存占用。理解闭包的概念、形成原因以及与作用域和垃圾回收的关系,将有助于更有效地编写和优化 JavaScript 代码。

m0_73882020的博客 828

谈谈javascript闭包

谈谈js闭包 在平时的js开发中,有时候你可能不知不觉就在使用js闭包,js闭包到底是啥呢?今天就来聊一聊,闭包是js高级特性,闭包是指:可以访问另一个函数作用域的变量的函数。也就是说闭包有两个特点,1.闭包是一个函数。2.这个函数可以访问其他函数的作用域中的变量。 举个例子: function funA() { var a = '变量1'; var funB = function() { console.info(a); } return funB // f

qq_35188157的博客 277

谈谈我对js中闭包的理解

闭包是一个能够访问其他函数作用域的函数。 很显然这样的定义晦涩难懂,很多人都很难理解闭包的真正含义 那么我们就用通俗一点的语言来解析一下什么是闭包 我在知乎上看到一个比较有意思的回答: 由此我们可以通俗的理解为: 函数A的内部函数B,被函数A外部的一个变量引用时,就会形成一个闭包。 我们最常用到闭包的情况就是函数封装 展示一下我在实际项目中遇到闭包的应用场景: 我们需要调用这个getFeedbackList方法的时候,只需要formFeedbackServics.getFeedbackList()就

weixin_45766034的博客 271

浅谈js闭包

简介 闭包是啥,用一句通俗的话来说就是能够读取函数内部局部变量的函数。闭包在一个函数内部定义一个新的函数,在执行的时候,会将这个函数返回,也可能是返回一个对象。闭包为啥会出现呢,它其实是js作用域链的特性而产生的。我们在函数内部是可以直接访问函数外部的全局变量的,但是我们在函数的外部是无法直接获取到函数内部的局部变量的,如果我们想在函数外部获取函数内部的局部变量,那么就要用到闭包。 函数内部可以直接访问函数函数外部的全局变量 var num = 66 function fn1() { con

cai_niao5623的博客 219

谈谈我对闭包的理解

今天有人在问我什么是闭包,当场就做了解释和演示。为了避免后面还有人问类似的问题,写一个博文吧,方便查阅。 我们先来理解闭包的概念。 “闭包”的中文,就有“封闭,包裹,封装”的概念在里面。所以,在我看来,闭包的主要作用就是封装功能,防止变量,函数之间相互污染。 JS有“闭包”这个概念的很大一部分原因是,JS允许函数A内部再定义函数B,当然这个函数B只能在函数A内部调用。但是函数B可以使用函数A...

九天翔龙的技术博客 339

JS 谈谈你对闭包的理解?

闭包的定义是:那些引用了另一个函数作用域中的变量的函数。——《红宝书》 书中举了一个经典的例子: function compare(name) { return function(obj1,obj2) { let value1 = obj1[name]; let value2 = obj2[name]; if (value1 > value2) { return 1 }else if(value1 &

qq_39280289的博客 297

面试题:说说你对闭包的理解?

闭包:一个函数和它的词法环境的引用捆绑在一起这样的组合就形成了闭包,比如一个函数A,return出去了一个函数B,那么这个函数B可以在外部访问函数A内部的变量,此时在就形成了一个函数B的变量背包,A函数执行结束,这个变量也不会被销毁,并且这个变量背包在函数A外部只能通过函数B来访问。闭包的使用场景:模仿块级作用域,实现柯里化,在vue响应式数据observe中使用闭包,在构造函数中使用特权方法。闭包解决的问题:在函数执行结束后,函数作用域中的变量不会立即被销毁,在函数外部可以访问函数内部的局部变量。

m0_52644825的博客 337

scala闭包

到本章这里,所有函数文本的例子仅参考了传入的参数。例如,(x: Int) => x > 0里,函数体用到的唯一变量,x > 0,是x,被定义为函数参数。然而也可以参考定义在其它地方的变量: (x: Int) => x + more // more是多少?  51CTO编辑推荐:Scala编程语言专题 函数把“more”加入参考,但什么是more呢?从这个函数的视点来看

西红柿炒土豆 1001

谈谈JavaScript的闭包

前言 闭包(closure)这个概念总是那么晦涩难懂,这篇文章希望结合自己的经验来说说这个概念。 作用域 在谈闭包之前,作用域这个概念是一定绕不开的。但是这里并不准备展开来详细说明他。简单总结几点。 JavaScript中函数能形成一个作用域。 在使用某个变量时,会在当前作用域查找;如果没有找到,则向外层作用域查找,直到全局作用域。也就是说,变量的搜索是从内到外,而不是从外到内。 变量的生存周...

大笨儿钟的博客 352
上一篇: yii2.0高级框架配置时打开init.bat秒退的解决方法
下一篇: 计算经纬度求出最短的距离
php_zhaop
博客等级 码龄10年 19粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值