当函数在当前的词法作用域之外执行,函数可以记住并访问该函数所在的词法作用域时,就产生了闭包。当函数跳出当前的词法作用域之外,但是该函数的作用域链有对它所在的词法作用域的引用,这样采用标记--清除算法的Javascript垃圾回收机制,就能够从根部出发找到该词法作用域,因此,该词法作用域不会被垃圾回收。这样该块词法作用域可以和全局变量一样存储在内存中,直到没有引用指向该块词法作用域(也就是从根出发无法找到该块词法作用域)时,该词法作用域才会被js的垃圾回收机制回收。
function Test(){
for (var i =1 ; i<=5; i++)
{
setTimeout(function Timer(){
console.log(i)}, i*1000);
}
}
上面的例子也是闭包的一种,即函数在当前的词法作用域之外被调用。该函数最终会以每秒一次输出的频率输出5个6,因为没有块级作用域,Timer 作用域链中的 i 都是引用函数外部的i,当当前js主线程执行完,Timer()函数从事件队列返回到js线程执行时,此时 i=6。
如何能够让该函数以每秒一次的频率输出1~5呢?
这时就要为每次调用的setTimeout()函数创建一个函数作用域:
for(var i =1 ;i <=5 ;i++)
{
(function (j){
setTimeout(function Timer(){
console.log(i)}, i*1000);})(i);
}
Js 在创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]] 属性中。当调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。在函数内部定义的函数会将外部函数的活动对象(即外部函数定义的所用变量的对象)添加到该函数的作用域链中。在下面的例子中,函数state和setState都会将由变量_val,noargs组成的活动对象添加到自己的作用域链中,所以调用这个两个函数能够对这两个变量进行读写操作。而在useState外部创建_val时,会在当前的作用域中添加一个变量,值为initiValue。当setVar({aa:1000})执行后,再次访问该变量时,只能在当前的作用域中找到之前赋初始值的变量,无法访问复制到setSate和state函数作用域链中的变量。也就是一个函数在执行完,函数内部的局部活动对象会被销毁,只是当有闭包存在时,函数的内部活动对象的引用会被复制到闭包函数的作用域链中,相当于外部函数的活动对象被销毁了,只是在销毁之前被复制了一份到内部函数的作用域链中。也就是只用形成闭包的函数的内部才能访问到外部函数的活动对象。
1 function useState(initialValue) {
2 var _val = initialValue 3 var noargs = initialValue 4 5 function state() { 6 return "_val.aa="+_val.aa +" noargs.aa="+ noargs.aa 7 } 8 function setState(newVal) { 9 noargs = newVal 10 _val = newVal 11 } 12 return [noargs, state, setState] 13 } 14 15 const [_var,getVal,setVar] = useState({aa:10}) 16 17 setVar({aa:1000}) 18 19 console.log(_var,getVal())
// 输出: {aa:10}
// _val.aa=1000 noargs.aa=1000
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用会导致内存占用过多的问题。