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

    面试必问题,闭包是啥有啥子用,以为本身以前回答的并很差,因此此次复习红皮书的时候总结一下。前端

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

      1. 做用域链,活动对象数组

      2. 关于this对象闭包

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

      4. 模仿块级做用域,私有变量函数

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

    1. 做用域链,活动对象操作系统

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

      执行环境:执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有与之相关联的变量对象,环境中定义的全部变量和函数都保存在这个对象中。某个执行环境中的代码被执行完后,该环境被销毁,保存其中的全部变量和函数也会被销毁。啊说的文绉绉的,我按本身的理解白话文一下好了。简单来说,每一个函数开始调用执行都会建立一个执行环境,这个环境就跟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();           }         })();

相关文章
相关标签/搜索