内容简介:[原]谈一谈闭包
每次打开Atom准备写文章的时候, 都要纠结如何开头… 烦~~
今天这篇文章我们来探讨一下 闭包 , 因为我在查阅很多资料时, 发现这些文章对于闭包的理解很多都是有出入的, 所以今天我们来探讨一下什么才是闭包. 当然, 这篇文章大多数是概念性的东西, 代码演示可能会涉及到几种不同的语言实现, 不过我会在代码开头标识出是哪种语言. 另外, 本文除了探讨闭包, 还可能会出现譬如 柯里化 等概念, 因为在这些概念里应用闭包可能是对闭包的作用的很好的说明. 最后需要说明的: 本文仅仅是我对闭包的理解, 当然可能存在不合理的地方, 不对的地方希望有人可以在评论中给我指出.
什么是闭包
好, 下面开始进入主题, 首先一个问题, 什么是闭包? 对于我们这些习惯了命令式编程, 尤其是 java 这种完全面向对象的语言的人, 闭包可能是一个很陌生的概念, 闭包时常在函数式语言中被提及, 那闭包到底是一个什么概念? 下面对闭包的解释来自维基百科.
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
闭包更多强调的是引用环境, 函数在定义时因为使用到了自由变量, 而且函数的调用和定义时的引用环境不同, 所以它必须要清楚的了解到自己定义时的引用环境, 在调用的时候需要切换到它定义的引用环境中执行, 这样就形成了闭包.
两个重点是 自由变量 , 引用环境 , 但就这几个字可能对于一些对闭包一知半解的人来说确是很难理解, 因为在大部分人印象里闭包应该是这样的.
// kotlin list.map { println it}
这样的一个实例中没有任何地方提及到自由变量. 其实这压根和闭包没有任何关系, 这仅仅是一种语法-lambda表达式, 很多人将lambda表达式想当然的认为是闭包. 再来看看下面语法.
// groovy android { compileSdkVersion 24 }
对于搞android的人来说这行代码并不陌生, 很多人把这个也理解成闭包, 和上面kotlin的代码一样, 其实这和闭包也没有任何关系.
那什么样的才算闭包? 上面提到了, 引用了自由变量的函数 , 这句话里有一个名词-自由变量, 那什么又是自由变量呢? 理解了这个名词后才能继续理解这句话.
在某个作用域内使用了其他作用域声明的变量, 那该变量就是自由变量
上面的概念是是我自己写的, 可能有点难理解, 来看看代码
// javascript var a = 10; function add5() { return a + 5; }
上面的变量a即为自由变量, a变量不是在函数add5的作用域中声明的, 却在函数add5中被使用.
好了, 知道了什么是自由变量, 那下面我们来看看什么是闭包.
// javascript function add(a) { return function(b) { return a + b; }; } var add10 = add(10); var result = add10(5);
上面的例子是最典型的闭包, add函数返回了一个函数, 这个匿名函数引用了add函数的一个a变量, 所以a变量是一个自由变量. 而且, 这个匿名函数的执行点不在变量a声明的作用域内, 根据上面的概念, 这样就形成了闭包.
再来看看下面的代码. 下面的代码来自apple官方的swift文档中 闭包章节 .
// swift reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })
这样的算不算闭包? 30s思考一下, 在心里有答案了再往下看…
当然不是, 虽然是来自官方的文章, 但这里确实是对概念的错误理解. 将上面的代码普通化来看看.
// swift func sorter(s1: String, s2: String) -> Bool { return s1 > s2 } reversedNames = names.sorted(sorter)
一目了然, 上面的代码丝毫没有看到对自由变量的引用, 这里仅仅算是高阶函数罢了… 所以很多时候官方文档也是会误导人的…
相似的代码, 再来看看kotlin官方文档上闭包的实例
// kotlin var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum)
这是不是形成闭包了? 把它普通化来看看, 毕竟去除语法糖才更适合人理解.
// kotlin var sum = 0 var filter: (Int) -> Bool = { a -> a > 0 } var each: (Int) -> Int = { a -> sum + a } ints.filter(filter).forEach(each)
主要看这个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")
另外一个函数式中重要的概念-柯里化大部分情况下也离不开闭包的支持, 什么是柯里化?
柯里化(英语:Currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
其实上面的大部分例子中已经实现了柯里化, 柯里化的好处就是大大的提高了函数的灵活性.
// javascript var add = (x, y) => x + y // user alert(add(10, 20)) alert(add(10, 30)) alert(add(10, 40))
上面对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))
这里有一个不好的地方就是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))
再来看, 上面实例中的函数add的定义其实不太符合我们人类的思想, 这方面, 其实很多函数式编程语言中已经实现了自动柯里化, 来看个实例.
// elm add x y = x + y add 10 20 // result is 30 add10 = add 10 add10 20 // result is 30
这些支持自动柯里化的语言可以根据实参的个数推断出返回的类型, 所以可以很轻松的实现柯里化, 而不必在我们定义函数的时候过多考虑. 其实javascript也可以实现自动柯里化, 大家可以参考我的一个demo- https://github.com/qibin0506/js-curry .
好了, 虽然说了这么多关于柯里化的东西, 其实都是对闭包的一些应用, 可以帮助大家去理解闭包. 最后留几个代码片段, 大家判断一下是否实现了闭包.
// 片段 1 function f1() { return function() { return "hello world" } } f1()()
// 片段 2 function f1() { return function(arg) { return arg + 10 } } f1()(20)
// 片段 3 var name = "qibin"; var f = function(name) { return name + " HI" } f(name)
// 片段 4 var name = "qibin"; var f = funtion() { return name + " HI"; } f()
// 片段 5 var getName = function() { var name = "qibin" var f = function() { return name + " HI" } prntName(f) } function prntName(f) { console.log(f()) }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python Algorithms
Magnus Lie Hetland / Apress / 2010-11-24 / USD 49.99
Python Algorithms explains the Python approach to algorithm analysis and design. Written by Magnus Lie Hetland, author of Beginning Python, this book is sharply focused on classical algorithms, but it......一起来看看 《Python Algorithms》 这本书的介绍吧!