JavaScript中的闭包和柯里化简介
介绍
如果您使用 JavaScript 编写代码,您很可能遇到过 closure
这个术语,这是一个有用但经常令人困惑的概念。 但究竟什么是闭包?
闭包可以描述为函数和声明它的词法环境的组合。
但这究竟意味着什么? 词法环境 由创建函数时函数范围内的任何局部变量组成。 闭包使人们能够在找到它们的状态下引用函数的所有局部变量。
这本质上是通过在另一个函数内部定义一个函数来实现的,一个函数内部的这个函数在技术上是闭包。 每次调用父函数时,都会创建一个新的执行上下文,其中包含所有局部变量的新副本。 这些局部变量可以通过将它们链接到全局声明的变量或从父函数返回闭包来在全局范围内引用。
一个基本示例将采用与此类似的格式:
function closuredFunc (){ function closure(){ // some logic } }
也可以有一个返回多个方法的闭包,如下所示:
function closure(){ function first() { console.log('I was declared first')} function second() { console.log('I was declared second')} function third() { console.log('I was declared third')} return [first, second, third] }
为了引用这些方法中的每一个,我们将我们的闭包分配给一个全局变量,然后该变量将指向一个公开方法的数组。 然后可以将每个方法分配给唯一的变量名称,以将它们带入全局范围,如下所示。 此时,它们现在可以被调用。
let f = closure() let one = f[0] let two = f[1] let three = f[2] one() // logs I was declared first two() // logs I was declared second three() // logs I was declared third
为什么使用闭包?
您可能想知道为什么要经历关闭的麻烦。 好吧,闭包有许多用途和优点。
在 ES6 中引入类之前,闭包提供了一种创建类隐私的方法,类似于面向对象编程中使用的方法,允许我们模拟私有方法。 这被称为 module pattern
,它允许我们编写易于维护的代码,同时减少命名空间污染和提高可重用性。
让我们看一个这样做的案例:
var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } }; var counter1 = makeCounter(); var counter2 = makeCounter(); counter1.value(); // returns 0 counter1.increment(); // adds 1 counter1.increment(); // adds 1 counter1.value(); // returns 2 counter1.decrement(); //subtracts 1 counter1.value(); // returns 1 counter2.value(); // returns 0
在上面的示例中,我们声明了一个函数 makeCounter
,它是一个公共函数,可以访问其中的一些私有变量,例如 privateCounter
和操作它的函数。 这模仿了将 makeCounter 创建为具有自己内置功能和变量的类的行为。 这可以在我们创建两个不同的计数器 counter1
和 counter2
时看到。 每个计数器相互独立,并引用不同版本的变量。
闭包还允许我们使用函数来创建其他函数,从而为其参数添加特定值。 在这种情况下,允许这种行为的父函数称为 function factory
,因为它本质上创建了其他函数。
使用函数工厂,我们能够实现称为 Currying
的行为,我们将在下一节中介绍。
什么是柯里化
Currying
是立即计算并返回其他函数的函数模式。 这是因为 Javascript 函数是可以返回其他函数的表达式。
柯里化函数是通过同时定义并立即返回它们的内部函数来链接闭包来构造的。
这是一个柯里化的例子:
let greeting = function (a) { return function (b) { return a + ' ' + b } } let hello = greeting('Hello') let morning = greeting('Good morning') hello('Austin') // returns Hello Austin hello('Roy') // returns Hello Roy morning('Austin') // returns Good morning Austin morning('Roy') //returns Good Morning Roy
从 greeting
创建的两个函数(hello
和 morning
)每个都返回处理提供的输入以生成问候语句的函数。 他们还接受一个参数,即要打招呼的人的名字。
在上面的例子中,greeting 也被用作一个函数工厂,它生成了两个函数 hello 和 morning。
内部函数也可以在第一次调用之后调用,如下所示:
greeting('Hello There')('General Kenobi') //returns Hello There General Kenobi
柯里化被认为是函数式编程的一部分,因此柯里化函数可以很容易地使用 ES6 和更新版本的 Javascript 中的箭头函数语法编写,以获得更简洁、更优雅的代码:
let greeting = (a) => (b) => a + ' ' + b greeting('Hello There')('General Kenobi') //returns Hello There General Kenobi
结论
虽然自从 Javascript 在 ES6 中合并类以来,闭包可能不那么常用,但在编写干净的可重用代码时,它们仍然占有一席之地。 闭包和currying也是理解函数式编程的重要概念,它们在本质上与面向对象编程中的私有方法具有相似的目的。