闭包和高阶函数
闭包和高阶函数是JavaScript中的两个重要概念, 还有一些是密切相关的. Let’s find out how.
Closures
当我们在JavaScript中创建一个函数到另一个函数中时, 内部函数既可以访问外部函数的作用域,也可以访问自己的作用域.
在下面的代码示例中, count
function is able to access total
,它在外部作用域中定义 tallyCounter
.
function tallyCounter() {
let total = 0;
function count() {
total++;
return total;
}
}
Here, the count
函数是闭包,并且可以继续访问 total
variable even after tallyCounter
has finished executing. 这是由于JavaScript中的词法作用域, 允许函数访问变量,即使它们是从最初创建它们的作用域之外调用的.
上面的代码不是很有用,因为我们不能调用 count
from the outside. It was defined in the tallyCounter
作用域,外部不可用;
function tallyCounter() {
let total = 0;
function count() {
total++;
return total;
}
}
console.log(count()); // Error: count is not defined
为了使闭包变得有用,我们需要返回它. 这样,我们就可以把它赋值给外部的一个局部变量 tallyCounter
, and start using it:
function tallyCounter() {
let total = 0;
return function count() {
total++;
return total;
}
}
let count = tallyCounter();
console.log(count()); // log: 1
console.log(count()); // log: 2
console.log(count()); // log: 3
在上面的例子中,我们无法访问 total
variable from outside of the tallyCounter
function. We’re able to call count
and increment total
但是我们不能直接访问这个值. As such, 闭包在JavaScript中经常用于保护变量免受外部操作, 或者隐藏实现细节.
闭包的另一个常见用例是延迟代码执行. JavaScript中的回调就是一个典型的例子:
logTheDateLater(延迟){
let date = new Date();
function logDate() {
console.log(date);
}
setTimeout(logDate, delay);
}
Here, we’re calling setTimeout
使用一个回调函数,该函数将输出当前日期和时间(调用外部函数时),但要经过一段延迟. Internally, setTimeout
延迟之后会调用我们的回调函数吗, 不知道它是做什么的,也不知道它是怎么工作的. 它也不能访问 date
variable. 因为回调函数是闭包,所以它可以访问 date
在其词法作用域中,并且它可以将其记录到我们的控制台.
Higher-order Functions
Both tallyCounter
and setTimeout
are what we call higher-order functions in JavaScript. 这些函数要么返回另一个函数, 或者接受一个或多个函数作为输入参数.
They are called higher因为他们更关心高层概念而不是低层细节. 正如我们之前提到的,回调就是一个很好的例子:
function helloToptal() {
console.log('Hello Totpal');
}
setTimeout (helloToptal, 1000);
Here, setTimeout
高阶函数是因为它接受回调函数吗, 并在给定的延迟后执行它. 它不需要知道JavaScript函数的作用.
现在,假设我们要确保不进行log运算 "Hello Toptal"
more than once per second. 这里有一个高阶函数可以帮助我们实现这个目标:
Function throttle(fn, milliseconds) {
let lastCallTime;
return function() {
let nowTime = Date.now();
if(!lastCallTime || lastCallTime < nowTime - milliseconds) {
lastCallTime = nowTime;
fn();
}
};
}
The throttle
函数接受一个函数参数,并向调用者返回一个新的经过throttated的函数. 我们现在可以使用它来创建控件的节流版本 helloToptal
function:
let hellotoptalthrottated = throttle(helloToptal, 1000);
setInterval (helloToptalThrottled 10);
您会注意到,尽管间隔为10ms,但调用 helloToptal
are throttled and "Hello Totpal"
每秒只记录一次吗.
Higher-order functions like throttle
允许我们从更小的组件组合JavaScript应用程序, 更容易测试的单职责代码单元, reusable, 当然也更容易维护.
Contributors
Merott Movahedi
自由JavaScript顾问
Merott是一名对前端领域有浓厚兴趣的全栈开发人员. 他精通JavaScript,并且很好地掌握了JavaScript的核心概念和最佳实践. 他还擅长JavaScript框架,尤其是Angular. 除了web开发, 他对移动应用很感兴趣, 他在上面花了大量的时间. Merott非常喜欢使用他的编程技能来自动化和简化日常任务.
Show More