每一个函数都有其所处的作用域,如果函数是在作用域之外(对于函数而言,作用域只跟声明时所处的位置有关,跟调用处无关)执行时,此时函数如果能够记住并访问得到自己所在的作用域的话,那么此时便形成了一个闭包。
看一个简单例子:
function foo(){
var a = 2
function bar(){console.log(a)}//bar函数所处的作用域即foo函数内部块中
return bar
}
var baz = foo()
baz()//2
通过上面的例子结合自己实际经验,你会发现闭包真的就是无处不在。下面分析一下:当我们通过函数变量baz调用函数的时候,此时调用的便是内存中的bar函数。需要记住的是,尽管是不同的名字,但是对于浅拷贝来说,他们实际上引用的都是同一块内存中的数据,所以说,这里的bar()和baz()是一样的结果,问题是闭包会产生这样一种效果:阻止垃圾回收机制回收foo函数内部块中的某些数据,比如说我们这里要用上的a以及bar函数本身。对于函数而言,他的作用域和调用位置无关,所以说关于闭包的核心点就是:对所需要用的资源的延迟回收。
下面来两个复习作用域的例子:
var a = 3
function foo(){
var a = 2
function bar(){console.log(a)}
return bar
}
var baz = foo()
baz()//2
再看看下面这个例子
function handle(fun){
var test = 1
var num = fun()
return num+1
}
function dowork(){
var test = 6
var nm = handle(function ques(){console.log(test);return 1})//6
console.log(nm)//2
//var can = ques//ReferenceError,ques is not defined
}
dowork()
//输出结果=>6 2
讨论一下ques函数所处的作用域,猜测一:dowork块;猜测二:handle块。根据运行结果很容易得知,肯定不是handle块。那么是不是dowork块?根据6这个输出结果来看,我们可以得知可能是dowork块,但是既让如此为什么在dowork块里面不能访问得到ques函数呢?这又使得我们怀疑是否是dowork块。经过思考发现,ques函数的作用域的确是dowork块,那么为什么不能再dowork内部访问ques呢?答案是这样的,先举个例子:
var fun = function bar(){console.log(1)}
bar()//ReferenceError
就是上面的这种情况,一样的道理,但我们将一个函数声明的代码作为右值赋值的时候,那么函数声明中的那个名字会被编译器给忽略。
1.循环与闭包
先看一个经典例子:
for(var i = 0; i < 10; i++){
setTimeout(function(){console.log(i)}, i*1000)
}
分析:运行的结果是——连续输出了10个10,每间隔1s输出一次。但是,我们期望的可不是这样的,我们希望能够输出0123456789,那么问题来了,为什么会导致这个问题呢?答案就是,setTimeout函数的特殊性,延时函数里面的回调函数会在循环结束时才执行。那么你设置的延时时间是0而不是1s,所有的回调函数仍旧是在循环结束时才会执行。而我们这里的回调函数的操作就是输出变量i,由于是循环结束才会开始执行回调函数,所以他们都输出了变量最后的那个值10。
要解决这个问题有好多办法,这里就介绍最简单的那个,利用let关键字:
for(let i = 0; i < 10; i++){
setTimeout(function(){console.log(i)}, i*1000)
}
当for与let结合时,let声明的变量在每次循环都会被再次声明。第n次声明时所赋的值由第n-1次结束时的值来决定。
网友评论