闭包详解

基础了解

首先理解闭包得先理解作用域和作用域链,要想理解作用域和作用域链,得先理解编译和引擎

了解浏览器内核推荐一篇不错文章:https://www.cnblogs.com/Leo_wl/p/5119719.html#_label1

浏览器的核心是两部分:渲染引擎和JavaScript解释器(又称JavaScript引擎)

1.什么是作用域和作用域链

答:我们可以将‘作用域’定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套作用域的自作用于中根据标识符名称进行变量查找,

通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域:全局作用域、局部作用域(函数作用域和快级作用域),局部作用域一般会有嵌套作用域,

嵌套作用域一般又会形成作用域连,引擎执行的代码的时候,是通过作用域链去查找所需要的标识符,如果在本极没有找到,就会到上一级到所嵌套的作用域继续查找,直到找到全局作用域,如果还是找不到,就会抛出异常。

作用域共有两种主要工作模式,第一种是最为普遍的,被大多数编程语言所采用的词法作用域,另外一种叫做动态作用域

再者,说到作用域就离不开js执行环境

每个函数运行时都会产生一个执行环境,而这个执行环境怎么表示呢?js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。 
全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。 
js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。 
举个例子:

   <script>
      var scope = "global"; 
      function fn1(){
         return scope; 
      }
      function fn2(){
         return scope;
      }
      fn1();
      fn2();
   </script>

上面代码执行情况演示: 
执行环境

了解了环境变量,再详细讲讲作用域链。 
当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this,arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。 
以上面的代码为例,当第一次调用fn1()时的作用域链如下图所示: 
(因为fn2()还没有被调用,所以没有fn2的执行环境) 
作用域链 
可以看到fn1活动对象里并没有scope变量,于是沿着作用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,所以就返回全局变量对象里的scope值。

标识符解析是沿着作用域链一级一级地搜索标识符地过程。搜索过程始终从作用域链地前端开始,然后逐级向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)—-《JavaScript高级程序设计》

那作用域链地作用仅仅只是为了搜索标识符吗? 
再来看一段代码:

   <script>
      function outer(){
         var scope = "outer";
         function inner(){
            return scope;
         }
         return inner;
      }
      var fn = outer();
      fn();
   </script>

outer()内部返回了一个inner函数,当调用outer时,inner函数的作用域链就已经被初始化了(复制父函数的作用域链,再在前端插入自己的活动对象),具体如下图: 
闭包作用域链 
一般来说,当某个环境中的所有代码执行完毕后,该环境被销毁(弹出环境栈),保存在其中的所有变量和函数也随之销毁(全局执行环境变量直到应用程序退出,如网页关闭才会被销毁) 
但是像上面那种有内部函数的又有所不同,当outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。 
具体如下图: 
outer执行结束,内部函数开始被调用 
outer执行环境等待被回收,outer的作用域链对全局变量对象和outer的活动对象引用都断了 
闭包作用域链

像上面这种内部函数的作用域链仍然保持着对父函数活动对象的引用,就是闭包(closure)

2.什么是闭包

闭包有两个作用: 
第一个就是可以读取自身函数外部的变量(沿着作用域链寻找) 
第二个就是让这些外部变量始终保存在内存中 
关于第二点,来看一下以下的代码:

   <script>
      function outer(){
         var result = new Array();
         for(var i = 0; i < 2; i++){//注:i是outer()的局部变量
            result[i] = function(){
               return i;
            }
         }
         return result;//返回一个函数对象数组
         //这个时候会初始化result.length个关于内部函数的作用域链
      }
      var fn = outer();
      console.log(fn[0]());//result:2
      console.log(fn[1]());//result:2
   </script>
  • 1

返回结果很出乎意料吧,你肯定以为依次返回0,1,但事实并非如此 
来看一下调用fn[0]()的作用域链图: 
闭包作用域链
可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果。 
由此也可以得出,js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的。

3.为什么使用闭包

闭包可以用在许多地方。它的最大用处有两个,

1.可以读取函数内部的变量。

2.让这些变量的值始终保持在内存中,不会在f1调用后被自动清除


3.闭包的运用场景

1.闭包可以读取函数内部变量的运用场景


2.让这些变量的值始终保持在内存中,不会在f1调用后被自动清除 。这样就可以利用这个功能做迭代器


备注:这是本人在csdn的第一篇博客文章,有什么错误请积极指出,大家共同进步,么么哒^_^@^_^

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值