闭包的概念
换言之,闭包是由函数以及声明该函数的词法环境组合而成的。词法环境包含了这个闭包创建时作用域内的任何局部变量。
上下文与作用域
变量或函数的上下文决定了它们可以访问哪些数据(变量),以及它们的行为。每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。每个函数调用都有自己的上下文。上下文中的代码在执行的时候会创建变量对象(variable object)的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。
每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能到下一级上下文中搜索。换言之,内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文的任何东西。位于最顶端或最外层的上下文称为全局上下文(global context),全局上下文取决于执行环境,如 Node 中的global
和 Browser 中的window
:
var color = "blue";
function changeColor() {
let anotherColor = "red";
function swapColors() {
let tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问 color、anotherColor 和 tempColor
}
// 这里可以访问 color 和 anotherColor,但访问不到 tempColor
swapColors();
}
// 这里只能访问 color
changeColor();
以上代码涉及 3 个上下文:全局上下文、 changeColor()
的局部上下文和 swapColors()
的局部
上下文。全局上下文中有一个变量 color
和一个函数 changeColor()
。 changeColor()
的局部上下文中有一个变量 anotherColor
和一个函数 swapColors()
,但在这里可以访问全局上下文中的变量 color
。
swapColors()
的局部上下文中有一个变量 tempColor
,只能在这个上下文中访问到。全局上下文和 changeColor()
的局部上下文都无法访问到 tempColor
。而在 swapColors()
中则可以访问另外两个上下文中的变量,因为它们都是父上下文。
再举个栗子
var a = 1
function out(){
var a = 2
inner()
}
function inner(){
console.log(a)
}
out() //====> 1
闭包的原因
**
function func() {
let name = "Closure";
function alertName() {
alert(name);
}
return alertName;
}
const myFunc = func();
myFunc();
在上面的例子中,myFunc
是执行 func
时创建的 alertName
函数实例的引用。 alertName
的实例维持了一个对它的词法环境(变量 name
存在于其中)的引用。因此,当 myFunc
被调用时,变量 name
仍然可用,其值 Closure
就被 alert
。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
在上面的示例中, makeAdder(x)
接受一个参数 x
,并返回一个新的函数。返回的函数接受一个参数 y
,并返回 x+y
。创建两个新函数:一个将其参数和 5 求和,另一个和 10 求和。
add5
和 add10
都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5
的环境中,x
为 5。而在 add10
中,x
则为 10。
闭包的作用
- 保护函数的私有变量不受外部的干扰,把一些函数内的值保存下来。
- 使用闭包来实现方法和属性的私有化。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。
const Counter = (function() {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
在上面的示例中,每个闭包都有它自己的词法环境;而这次只创建了一个词法环境,为三个函数所共享 Counter.increment
Counter.decrement
Counter.value
。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter
的变量和名为 changeBy
的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
常见错误:循环中使用闭包
for(var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
上面的这段代码,预期是每隔一秒,分别输出 0, 1, 2, 3, 4
, 但实际上依次输出的都是 5
。 setTimeout
是个闭包,这五个闭包在循环中被创建,这里的 i
使用 var
进行声明,由于变量提升,所以具有全局作用域,共享同一个词法作用域,在这个作用域中存在一个变量i
,当函数执行完毕 i = 5
,导致输出的结果都是 5。
解决办法一:新增匿名闭包
for(var i = 0; i < 5; i++) {
(function(item) {
setTimeout(() => {
console.log(item);
}, i * 1000);
})(i);
}
for(var i = 0; i < 5; i++) {
(function() {
var item = i
setTimeout(() => {
console.log(item);
}, i * 1000);
})();
}
解决办法二:使用 let 关键字
for(let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
使用let
而不是var
,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。
解决办法三:使用 forEach
[0, 1, 2, 3, 4].forEach((i) => {
setTimeout(() => {
console.log(i)
}, i * 1000)
})
本质上与解法一是一样的,分别产生五个闭包函数,入参分别在对应的上下文中。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!