码迷,mamicode.com
首页 > Web开发 > 详细

js 作用域,闭包及其相关知识的总结

时间:2015-07-13 20:30:51      阅读:125      评论:0      收藏:0      [点我收藏+]

标签:

    面试必问题,闭包是啥有啥子用,觉得自己之前回答的并不好,所以这次复习红皮书的时候总结一下。

    提到闭包,相关的知识点比较多,所以先罗列一下要讲的内容。

      1. 作用域链,活动对象

      2. 关于this对象

      3. 垃圾回收机制,内存泄漏

      4. 模仿块级作用域,私有变量

   涉及的内容这么多,也难怪面试官喜欢问这个问题啊,就像niko大神说的,应该是根据回答的深浅了解你的思维模式吧。废话不多说,开始步入正题。

    1. 作用域链,活动对象

      活动对象:活动对象就是在函数第一次调用时,创建一个对象,在函数运行期是可变的,它包含了this,arguments,以及局部变量和命名参数。

      执行环境:执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有与之相关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。某个执行环境中的代码被执行完后,该环境被销毁,保存其中的所有变量和函数也会被销毁。啊说的文绉绉的,我按自己的理解白话文一下好了。简单来讲,每个函数开始调用执行都会创建一个执行环境,这个环境就跟c里面的作用域一个道理,就是你在函数执行期间可以访问的变量啊数据啊那些都会放到一个环境栈中,里面有的数据你可以访问,没有的就当然不能访问啦,当你执行完之后,就把你从环境栈中弹出来,执行环境就被销毁了,就访问不到啦~~~

      作用域链:就是一个保存对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象,全局变量在最后。这个也很好理解的嘛,在函数执行时访问一个变量,一开始会搜索在当前环境里有没有该变量,没有的话在寻找这个函数 外部的变量有没有,就这样层层往上找,最后再回访问全局变量。所以原型链如果很长,查到原型的属性值就显得灰常漫长了~~

      闭包:有权访问另一个函数作用域中变量的函数。创建闭包常见方式是即是在一个函数内部创建另一个函数。

      前面介绍了这么多名词,那我们串一下当函数第一次调用的时候究竟会发生啥子?

      在函数第一次调用时,创建一个执行环境及相应的作用域链,并吧主作用域链值付给一个特殊的内部属性([[scope]])。然后,利用this,arguments和其他名命名参数 的值来初始化函数的活动对象。在作用域链中,当前函数的活动对象处于第一位,外部函数的活动对象处于第二位,外部的外部的活动对象处于第三位......作用域链重点为全局执行环境。

      创建一个函数时i,创建一个包含全局变量对象的作用域链,这个作用域链被保存在内部的[[scope]]属性中。当调用该函数时,会为该函数创建一个执行环境,通过复制函数的[[scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(变量对象)被创建并推入执行环境作用域链的前端。作用域链包含两个变量对象:本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针列表,它只引用但不包含实际变量对象。

      闭包跟普通函数相比又有哪里不同呢?

      普通函数执行完之后,局部活动被销毁,内存中仅保存全局作用域。但是闭包有所不同,闭包的外部函数在执行完毕后,其活动对象不会被销毁,因为其匿名函数的作用域链扔在引用这个活动对象。所以即使外部函数执行环境的作用域链会被销毁,但它的活动对象仍在内存中,直至匿名函数被销毁。

      闭包与变量:由于作用域链本质上是一个指向变量对象的指针列表,它只引用但不包含实际变量对象,所以闭包只能取得包含函数中最后一个值。闭包所保存的是整个变量对象,而不是某个特殊变量的值。下面贴个小例子。

      

    function createFunctions(){
      var result = new Array();
      for (var i = 0; i < 10; i++) {
        result[i] = function(){
          return i;
         };
      }
      return result;
    }

    这个函数返回一个函数数组。期望是这个函数每个函数执行之后返回[0~9],而现实总是那么的残酷,返回的是[10......10]。因为数组中每个函数的作用域链中保存着createFunctions()的活动对象,它们引用同一个变量i。当createFunctions()函数返回后,变量i的值为10,每个函数引用的同一个变量i = 10,所以函数数组的每个函数返回10。

改良之后的2.0版如下:

      function createFuncs(){
        var result = new Array();
        for(var i=0;i<10;i++){
          result[i] = function(num){
            return function(){
              return num;
            }
          }(i);
        }
        return result;
      }
      改动之后的函数,定义了一个自执行的匿名函数,吧这个匿名函数赋值给result数组。这个匿名函数传了一个参数num,就是函数的正确索引值i。在调用函数数组中每个函数时,传入了变量i,因为函数是按值传递的,所以把当前值复制给参数num。在这个匿名函数内部,创建一个返回num的闭包。这样就能返回正确的索引值了。 

  呼哧呼哧的喘着气吧第一个部分讲完了,现在研究研究this对象。

  2. this对象

    this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,当函数被作为某个对象的方法调用时,this等于那个对象。匿名函数的执行环境居有全局性,因此this指向window。那么在闭包中想访问外部作用域的活动对象,不能直接用this,在外部作用域中的this对象保存在一个闭包能访问的变量里,然后访问那个变量就好。

    

      var name = ‘window‘;
      var object = {
        name :‘object‘,
        getNameFunc:function(){
          var that = this;
          return function(){
            return that.name;
          }
        }
      }
      console.log(obejct.getNameFunc());//‘object‘

      忙完这阵赶紧把博客搭起来===不然每次贴代码都想捅自己一刀好心累=.=

  3. 垃圾回收机制,内存泄漏

    js有自动垃圾回收的机制,不用手动管理,不过了解一下回收机制,谨防内存泄漏也是一个蛮好的习惯啊。

    红皮书上提到的回收方式有两种,及标记清除和引用计数。标记清除是现在最常用的方式,IE8及其更早的版本还用着引用计数的方式,下面就稍微讲一下概念。

    标记清除:这种方式就是当变量进入环境时,将变量标记为”进入环境“,不能释放进入环境的变量占用的内存。当变量离开环境时,将其标记为“离开环境”。垃圾收集器在运行的时候会给内存中的所有变量加上标记,它去掉环境中的变量坏人被环境中的变量引用的变量的标记。做完这个工作,还有标记的变量被视为准备删除的变量 ,就可以清除内存啦~

    引用计数:跟踪每个引用类型值被引用次数。声明一个变量并对其赋引用类型值时,计数为1.同一个值被赋给别的变量,计数加1,。包含对这个值引用的变量去了另一个值,则计数减1。引用计数等于0时,可以回收。这样有个棘手的问题那就是循环引用。如果a引用b,b引用a。则两个引用计数都为2,并不能被清除。

    内存泄漏:回收机制用的引用计数方式时,如果出现了循环引用,那么有些内存中的垃圾无法回收,导致内存泄漏。解决循环引用的办法就是,在不使用的时候,手动断开js对象与dom元素间的链接,解除引用。闭包在引用计数的方法下也会有问题,比如在闭包的外部函数中的某个引用型值,闭包引用了这个变量那么这个引用计数至少为1,无法释放内存。如下面的例子。

    

      function assignHandler(){
        var element = document.getElementById(‘someElement‘);
        element.onclick = function(){
          console.log(element.id);
        }
      }

  解除循环引用,代码修改为:

    

      function assignHandler(){
        var element = document.getElementById(‘someElement‘);

        var id = element.id;
        element.onclick = function(){
          console.log(id);
        }

        element = null;
      }

    吧element.id 赋给一个变量,可以解除循环引用。并把 element = null解除对dom对象的引用。

  4. 模仿块级作用域,私有变量

    模仿块级作用域

    function outputNumbers(){
      for(var i = 0; i <10; i++){
        console.log(i);
      }
        console.log(i);
    }

    Java等语言是有块级作用域的,及上代码中的i 在for语句块内可以访问 ,出了for语句块是访问不了的。Js没有块级作用域的概念。i是定义在函数outputNumbers函数的活动对象中,因此在这个函数内都可以访问i。 所以第二个打印语句访问得到i的值 。除此之外,js语句不会提示时候多次声明同一个变量。它只会忽略后续的声明。但是它会执行后续的初始化。

    用闭包可以模仿块级作用域(私有作用域)。

        语法如下(function(){

            //块级作用域

          })();

      由于在匿名函数中定一点的任何变量都在执行结束时被销毁。根据这个原理,可以吧语句块的代码放进自执行的匿名函数里,模仿块级作用域。

      这个原理更棒的用法就是在全局作用域中被用在函数外部,限制在全局作用域中添加过多的变量和函数。多个开发人员共同开发,也可以用私有作用域,防止命名冲突。如果在多个闭包间相互通信,可定义一个全局变量或者直接在this上定义相应的方法。

    私有变量

      任何在函数内定义的变量都可以认为是私有变量。吧有权访问私有变量和函数的公有方法称为特权方法。

      在函数内部创建一个闭包,闭包通过自己的作用域链可以访问私有变量。特权方法有构造函数方法,静态私有变量。

       构造函数方法:每个实例都会创建同样的方法。

      function MyObject(){
        var privateVariable = 10;

        function privateFunc(){
          return false;
        }
        this.publicMethod = function(){
          privateVariable++;
          return privateFunc();
        }
      }

        静态私有变量:运用的选型模式,复用同一个共有函数。缺点是多个实例对象共用一个私有变量。都共享上了,怎么还算是私有呢。。。yy到了操作系统的临界资源.... 

        

        (function(){
          var privateVariable = 10;
          function privateFunc(){
            return false;
          }
          MyObject = function(){

          };
          MyObject.prototype.publicMethod = function(){
            privateVariable++;
            return privateVariable();
          }
        })();

        

        

      

    

    

 

 

      

js 作用域,闭包及其相关知识的总结

标签:

原文地址:http://www.cnblogs.com/echo-yaonie/p/4643732.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!