掀翻闭包的遮羞布
首先,什么是闭包?你可以理解为在两个不相连接的作用域下共享变量。
举个栗子:
function foo() {
var a = 10;
function bar () {
console.log(a);
}
return bar;
}
foo()();// 10 请注意这里的10怎么来的。
注意到了么,这里在全局的作用域下面,使用了foo函数内部的变量,这就是闭包!!!
函数bar作用域覆盖了函数foo,我们把bar函数的引用作为返回值传递了回来,然后就可以在引用里使用foo的函数引用。
而闭包本身的神奇之处并不在此,一般来讲在foo函数执行完成之后foo的作用域也就被垃圾回收器回收了,但是这里并不是,因为还存在一个对foo作用域的引用被传递出来了,也就是bar,bar有一个指向a变量的指针,所以foo的作用域在foo函数使用完成之后没有被回收。bar依然持有对foo作用域的引用,这就叫做闭包。
还有多种各式各样的函数闭包。
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 妈妈快看呀,这就是闭包!
}
也许在你了解了之后觉得闭包只是一个稍微奇怪一点的玩具而已,但是我想说的是闭包它并不仅仅只是一种写法而已,日常生活中都会用到各式各样的情况:
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
timer在1000毫秒之后其内部作用域并不会消失,然后含有对message的引用。
2、循环与闭包
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
在我的另一篇对前端的小知识点里提到过这里存在变量提升,每次都会打印出6,稍微解释一下,由于在函数延迟之后再去对i的引用进行RHS查找的话只会找到i=6,我们本来是想在每次迭代的时候都捕获一个i的副本,但是每次迭代都是共享的作用域,所以找到的都是6;
加入通过作用域的原理来修改代码的话,来试试:
for (var i = 0; i < 5; i++) {
(function () {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})();
}
这样可以吗?哈哈哈哈,还是不行,因为我们的词法作用域是空的,我们需要给他建立一个自己的私有属性。
for (var i = 0; i < 5; i++) {
(function () {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}
这样就可以了,因为i指向的是每个内部的匿名函数的内部作用域j!
给这个代码美化一下:
for (var i = 0; i < 5; i++) {
(function (j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})(i);
}
当然你如果想更简洁的完成逻辑,可以用let
for (let i = 0; i < 5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
模块
在理解闭包之后再了解一下一般怎么去使用闭包。
function Car() {
var name = "五菱宏光";
var age = "2 年";
function sayName () {
console.log(name);
}
function sayAge () {
console.log(age);
}
function drive () {
console.log('drived');
}
return {
sayName: sayName,
sayAge: sayAge,
drive: drive
}
}
var car = Car();
car.sayName();
这就是模块的使用。
本博客思路来源:(NEW)[美]-Kyle-Simpson-你不知道的JavaScript(上卷)