闭包和自由变量详解

74 阅读4分钟

 自由变量

自由变量:能够在函数中使用但既不是函数的参数,也不是局部变量的那些变量

所有的自由变量都应该在函数定义的地方,向上级作用域寻找,不是在执行的地方。

闭包和自由变量

闭包:能够访问自由变量的函数

var scope = "global scope";
function checkscope(){
  var scope = "local scope";
  function f(){
    return scope
  }
  return f//返回函数的声明
}
var foo = checkscope();
foo();//静态local scope,动态global scope


globalContext = {
  VO:{//变量对象
    scope:undefined(声明)/gloabl scope(执行),
    checkscope:reference to function checkscope,
    foo:undefined(声明)/函数执行,
  },
  scope:[globalContext.VO]//作用域链
}
checkScopeContext = {
  VO/AO:{//声明时VO,执行后变成AO
    scope:undefined/gloabl scope,
    f:reference to function f
  },
  scope:[
    checkScopeContext.AO,
    globalContext.VO
  ]
}
fContext = {
  AO:{
    arguments:{
      length:0
    }
  },
  scope:[
    fContext.AO
    checkScopeContext.AO,
    globalContext.VO
  ]
}

执行顺序图和2.3case2图相同

(自由变量:在执行上下文栈时,已经在栈中弹出,但是可以在作用域链中找到的变量)

此时,虽然在上下文栈中,已经弹出了checkscope,但是可以通过作用域链找到,内存并没有释放

变量scope就是自由变量,函数f形成闭包

闭包:当前函数内部变量在外部使用时,内存地址没有释放,形成闭包

闭包是一种表现结果

闭包优点:能够引入自由变量(获取属性方便)

闭包缺点:不知道什么时候释放,可能导致内存泄露(导致全局污染(解决:垃圾回收机制)

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

使用回调函数就是使用闭包

将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

内部函数(作为参数或以其他方式)传递到原本词法作用域以外,在执行这个函数时,就形成闭包

你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

说明闭包,最好例子for循环

//例1:
for (var i=1; i<=5; i++) {
setTimeout(function timer() {
console.log(i);//5个6
}, i*1000 );
}
//延迟函数的回调会在循环结束时才执行
//所有的回调函数依然是在循环结束后才会被执行
//加入立即执行函数,增加作用域,也不行,如下
//例2:
for (var i=1; i<=5; i++) {
(function() {
setTimeout(function timer() {
console.log(i);//5个6
}, i*1000 );
})();
}
//需要有自己的变量,用来在每个迭代中储存i的值:
//例3:
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout(function timer() {
console.log(j);//1到5
}, j*1000 );
})();
}
//对这段代码进行一些改进:
//例4:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j*1000 );
})(i);
}
//总结:以上4个例子中,i都是全局作用域,先执行同步循环任务,
  //每次循环i得到一个新的值,循环结束后i的值为6,
  //然后执行setTimeout的回调函数timer,此时调用i,得到全局变量i的值为6。
 // 例3和例4中,在新的作用域中,新建变量,每次循环,都生成新的作用域,
  //并存储i的值,再次调用setTimeout时,取得对应作用域中j的值。
 // 所以如果用let,直接生成新的块作用域,可以达到相同的效果

在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问

for循环每次迭代时都会重新声明一次并重新赋值

块作用域和闭包联手

        for (let i=1; i<=5; i++) {
            setTimeout(function timer() {
              console.log(i);
            }, i*1000 );
        }

参考

  • 《你不知道的JavaScript》

最后

这是JavaScript系列第4篇,下一篇更新《模块》。

小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!

欢迎评论讨论交流