开放和封闭。图片来自unsplash.com
闭包是每个 JavaScript 开发者都应该知道并理解的一个关键特性。今天这篇文章只是流于闭包的表面,但通过阅读本你可以对闭包是什么以及闭包如何动作建立一个良好的概念。我们开始...
我们先从两个教科书中的闭包定义开始。
定义 #1:
闭包是一个即使父级作用域关闭之后仍然能对其访问的函数。
定义 #2:
闭包是在函数声明中,这个函数及其词法环境的组合。
很好。但闭包到底是什么意思?
首先你得明白JavaScript 的作用域。作用域本质上是 JavaScript 变量的生命周期。要知道,变量定义在哪里对其生存时间以及程序中什么函数可以访问到,有着巨大的影响。
我们来看一个示例。
JavaScript 中创建的函数,可以访问函数内以及函数外的变量。
函数内部定义的变量是定义在局部的变量。局部变量只能在定义它的函数内部(作用域)访问到。在下面的示例中,如果我们尝试在函数外面输出words
的值,会得到一个引用错误。因为words
是一个存在于局部作用域的变量:
// Example of accessing variables INSIDE the function
// words is a LOCAL variable
function speak(){
var words = 'hi';
console.log(words);
}
speak(); // 'hi'
console.log(words); // Uncaught ReferenceError: words is not defined
与上面的例子不同,下面例子中的words
是定义在全局作用域的。也就是说,它可以被文档中的所有函数访问到。
// Example of accessing variables OUTSIDE the function
// words is a GLOBAL variable
var words = 'hi';
function speak(){
console.log(words);
}
speak(); // 'hi'
console.log(words); // 'hi'
如果我们把一个函数嵌套在另一个函数中会怎样?我希望你们能跟着看看下面的例子,因为它会很有意思!
如果你在使用 Google Chrome,可以通过 [WINDOWS]: Ctrl+Shift+J [MAC]: Cmd + Opt + J 打开开发者控制台。
酷。现在把下面的代码拷贝下来并粘贴到控制台。我们要做的是创建一个名为speak
的函数。speak
返回一个名为logIt
的函数。最后logIt
会把words
的值输出的控制台,在这个示例中会在控制台输出'hi'
。
function speak() {
return function logIt() {
var words = 'hi';
console.log(words);
}
}
把代码拷贝到控制台之后,我们会像下面那样创建一个变量,并从 speak 函数赋值给它。
var sayHello = speak();
现在我们调用sayHello
变量来看看它是什么值,注意不是执行这个内部函数:
sayHello;
// function logIt() {
// var words = 'hi';
// console.log(words);
// }
正如预期,sayHello
引用了我们返回的内部函数。这意味着如果我们在控制台运行sayHello()
,它会运行logIt()
函数:
sayHello();
// 'hi'
确实如此!但这并没有什么特别。现在我们移动一行代码看看会有什么变化。看下面的例子。我们把声明变量words
的语句移到了内部函数的外面,在speak()
函数中:
function speak() {
var words = 'hi';
return function logIt() {
console.log(words);
}
}
跟之前一样,声明一个变量并从 speak 函数赋值给它:
var sayHello = speak();
现在看看sayHello
变更引用的是什么:
sayHello
// function logIt() {
// console.log(words);
// }
呃喔。没有定义words
变量。那我们调用这个函数会发生什么事情?
sayHello();
// 'hi'
仍然没问题!那是因为你刚刚体验到了闭包的作用!
不明白?好吧,回想一下我们对闭包的定义:
闭包是一个即使父级作用域关闭之后仍然能对其访问的函数。
这个示例中speak()
函数的作用域已经闭包了。因此var words = 'hi'
应该不存在了。然而,在 JavaScript 中存在着一个称为闭包的很酷的小概念:内部函数维护着一个创建它的作用域的引用。这样即使在speak()
关闭之后,logIt()
函数仍然可以访问words
变量。
function speak() {
var words = 'hi';
return function logIt() {
console.log(words);
}
}
注意到 JavaScript 中的每个函数都存在闭包,这很重要。你不需要专门对函数做什么事来使其生效。它就是 JavaScript 的一部分。
我们再看一个例子。这个例子会稍微复杂一点。代码如下:
function name(n) {
return function(a) {
return `${n} likes ${a}`;
};
}
我们有一个函数name
,它需要一个参数,然后返回一个需要不同函数的匿名函数。内部函数最终会返回字符串。
然后调用两次name
函数。第一次传入名字 John,另一个传入 Cindy:
var j = name('John');
var c = name('Cindy');
看看j
现在实际引用的什么:
j;
// function (a) {
// return `${n} likes ${a}`;
// }
令人惊讶。我们从前面的例子知道,由于闭包,这个函数仍然可以访问父级作用域的n
变量。我们只需要在调用函数的时候传入a
值即可。
来试试:
j('dogs'); // 'John likes dogs'
c('cats'); // 'Cindy likes cats'
运行无误!因为闭包,我们可以成功执行引用了已关闭作用域中变量的函数。
往期精选文章 |
---|
ES6中一些超级好用的内置方法 |
浅谈web自适应 |
使用Three.js制作酷炫无比的无穷隧道特效 |
一个治愈JavaScript疲劳的学习计划 |
全栈工程师技能大全 |
WEB前端性能优化常见方法 |
一小时内搭建一个全栈Web应用框架 |
干货:CSS 专业技巧 |
四步实现React页面过渡动画效果 |
让你分分钟理解 JavaScript 闭包 |
小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。