深入研究 JavaScript 闭包、高阶函数和柯里化
闭包
闭包是 JavaScript 最强大的特性之一,但一开始可能会让人有点望而生畏。扎实的闭包理解能为理解高阶函数和柯里化等主题奠定基础。
我们将讨论一些有助于说明闭包、高阶函数和柯里化的原理的概念。
JavaScript 中的函数是一等公民,这意味着:
- 函数可以分配给变量
- 函数可以作为参数传递给其他函数
- 函数可以返回其他函数
// functions can be assigned to variables
const morningGreetings = (name) => {
console.log(`Good morning ${name}`);
}
const eveningGreeting = function (name) {
console.log(`Good evening ${name}`);
}
// functions can be passed as arguments to other functions
const todaysGreeting = (morningGreetings, eveningGreeting) => {
morningGreetings('Barack')
console.log(`Thanks for all you have done during the day`);
eveningGreeting('Barack');
}
// functions can return other functions
function myCounter () {
let count = 0
return function () {
return ++count;
}
}
const noOfTimes = myCounter();
console.log(noOfTimes()); // 1
我们将要深入研究的功能是允许函数返回函数。该功能的闭包依赖于 JavaScript 的独特特性。
在 JavaScript 中,函数可以引用未在函数中定义但在封闭函数或全局范围内可用的变量。
请考虑以下示例:
const iamglobal = 'available throughout the programme';
function funky() {
const iamlocal = 'local to the function scope funky';
}
console.log(iamglobal);// available throughout the programme
console.log(iamlocal); // iamlocal is not defined
如你所见,我们无法访问iamlocal
作用域之外的变量function funky
。这是因为该变量仅在 funky 处于活动状态时才保持“活动”。
一旦调用该函数,对其范围内声明的任何变量的引用都会被删除,并且内存将交还给计算机使用。
但是,即使在函数被调用之后,我们仍然可以访问函数内声明的变量。
这就是闭包发挥作用的地方。
闭包是对另一个函数范围内声明的变量的引用,该变量通过从现有函数的调用中返回一个新函数来保持活动状态。
我们来看一个例子:
function outerScope() {
const outside = 'i am outside';
function innerScope() {
const inside = 'i am inside';
console.log('innerScope ➡️', outside);
console.log('innerScope ➡️',inside);
}
console.log('outerScope ➡️', outside);
innerScope();
}
outerScope();
// outerScope ➡️ i am outside
// innerScope ➡️ i am outside
// innerScope ➡️ i am inside
可以outside
从函数访问变量的值innerScope
。闭包的概念就是基于这种能力。
从上面的例子中,我们可以返回函数innerScope
而不是在内部调用它outerScope
,因为这接近现实世界的情况。
让我们修改上面的例子来反映这个变化:
function outerScope() {
const outside = 'i am outside';
function innerScope() {
const inside = 'i am inside';
console.log('innerScope ➡', outside);
console.log('innerScope ➡',inside);
}
return innerScope
}
const inner = outerScope();
inner();
// outerScope ➡️ i am outside
// innerScope ➡️ i am outside
这与上面的例子类似,说明了函数如何具有返回函数的能力。
让我们更进一步,看一个更真实的例子:
function closure(a) {
return function trapB (b) {
return function trapC(c) {
return c * a + b;
}
}
}
const oneEight = closure(1.8);
const thirtyTwo = oneEight(32);
const degreeToFahrenheit = thirtyTwo(30);
console.log(degreeToFahrenheit); // 86
将每个函数声明视为一个圆圈很有用,其中每个封闭的圆圈都可以访问前一个圆圈中声明的变量:
在这种情况下,trapC 可以访问变量a, b and c
,而trapB 可以访问变量a and b
,最后闭包只能访问a
。
高阶函数
高阶函数是接受另一个函数作为参数、返回另一个函数作为结果或两者兼而有之的函数。
到目前为止,我们一直在使用高阶函数,如我们的、、closure
和示例中所见。outerScope
todaysGreeting
myCounter
闭包对于高阶函数来说是不可或缺的。
高阶函数的核心优点之一是它们允许我们自定义调用函数的方式。
请考虑下面的插图:
const multiply = (a , b) => {
return a * b;
}
console.log(multiply(2,3)) // 6
如果我们只对在整个程序中获取 2 的所有倍数感兴趣,则可以在整个程序中重复 2 作为参数之一:
multiply(2,1) // 2
multiply(2,2) // 4
multiply(2,3) // 6
虽然这种方法有效,但它会在我们的代码中引入大量重复,并且违反了 DRY(不要重复自己)原则。
你可能也会说,我们可以把 2 的值硬编码到函数定义中。没错,但这会降低函数的可复用性。
让我们重新定义该函数以使用高阶函数,以便我们可以看到它在调用该函数时提供的好处和灵活性:
const multiply = (a) => {
return (b) => {
return a * b;
}
}
通过这种方式定义上述函数,我们可以创建自定义函数调用,如下所示:
const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(3)) // 6
const multiplyByThree = multiply(3);
console.log(multiplyByThree(6)); // 18
我们可以创建具有实际用途的自定义功能,同时还可以省去重复的麻烦。
柯里化
柯里化是一个涉及函数部分应用的过程。
如果函数调用所需的所有参数尚未提供,则称该函数被柯里化了。在这种情况下,它将返回另一个函数,该函数保留已提供的参数,并期望在调用该函数之前提供剩余的省略参数。
仅当所有参数都已提供时,才会调用该函数。否则,将返回一个新函数,该函数保留现有参数并接受新参数。
当你对一个函数进行柯里化时,你将其称为 ,f(a)(b)(c)(d)
而不是f(a, b, c , d)
。 更进一步来说,所有柯里化的函数都是高阶函数,但并非所有高阶函数都是柯里化的。
这里的底线是,柯里化允许我们将单个函数转变为一系列函数。
让我们考虑以下示例:
function sum (a, b) {
return a + b;
}
console.log(sum(4,5)) // 9
我们可以继续对该函数进行柯里化,这样当未提供所有参数时,我们就可以灵活地部分调用它。
function curriedSum (x,y) {
if (y === undefined) {
return function(z) {
return x + z
}
} else {
return x + y;
}
}
console.log(curriedSum(4, 5)) // 9
console.log(curriedSum(4)(5)) // 9
我们不必每次需要部分调用函数时都重新编写一个柯里化实现。相反,我们可以使用通用的柯里化函数,并将原始函数作为参数传递给它。
方法如下:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
让我们用一个例子来说明这是如何工作的。
function mean (a , b, c) {
return (a + b + c) / 3
}
const curriedMean = curry(mean);
console.log(curriedMean(1,2,3))
console.log(curriedMean(1,2)(3))
console.log(curriedMean(1)(2)(3))
结论
如您所见,这些概念是相互建立的,因为闭包在高阶函数中被广泛使用,并且高阶函数类似于柯里化函数。
对上述概念有深入的理解可以让我们深入了解流行的 JavaScript 库如何实现一些功能,例如 React-Redux 使用的 connect 函数。
connect(mapState)(MyComponent)
参考
柯里化
柯里化是一种使用函数的高级技术。它不仅用于 JavaScript,也用于其他语言。柯里化是一种函数转换,它将函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c)。柯里化并不调用函数,它只是对其进行了转换。
JavaScript:从新手到忍者,第二版
尽善尽美,包罗万象。600 多页内容,助您从 JavaScript 新手晋升为 JavaScript 大师。内容涵盖数组、逻辑和循环、函数、对象、DOM、事件、测试和调试、Ajax 等方方面面。它涵盖了您使用 JavaScript 构建所需的一切。
实现新的 JS 功能?了解 JavaScript 错误如何影响您的用户。
追踪生产环境中 JavaScript 异常或错误的原因既耗时又费力。如果您有兴趣监控 JavaScript 错误并了解它们如何影响用户,不妨尝试 LogRocket。https : //logrocket.com/signup/
LogRocket就像 Web 应用的 DVR,可以记录您网站上发生的所有事件。LogRocket 可让您汇总并报告错误,以了解错误发生的频率以及受影响的用户群数量。您可以轻松回放发生错误的特定用户会话,以了解用户的操作导致了错误。
LogRocket 会记录您的应用的请求/响应(包含标头和正文),并结合用户的上下文信息,从而全面了解问题。它还能记录页面上的 HTML 和 CSS,即使是最复杂的单页应用,也能重现像素级完美的视频。
增强您的 JavaScript 错误监控能力——开始免费监控。
文章《深入研究 JavaScript 闭包、高阶函数和柯里化》最先出现在LogRocket 博客上。
链接链接 https://dev.to/bnevilleoneill/a-closer-look-at-javascript-closures-higher-order-functions-and-currying-4ng9