js 闭包及其相关知识点理解

本文结合个人学习及实践,对闭包及相关知识点进行总结记录,欢迎读者提出任何不足之处

一、js变量

二、作用域(scope)

三、[[scope]] 和 scope chain

四、作用域(scope)和关键字(this)

五、闭包实例理解 及 垃圾回收

一、js变量

  在 ECMAScript 中,变量可以存在两种类型的值,即原始值和引用值。

  原始值:存储在栈区(stack)的简单数据段,变量所在的位置直接存储变量的值。

      原始值包括:Undefined、Null、Boolean、Number 和 String 型

  引用值:存储在堆区(heap)中的对象,变量所在的位置存的是地址指针(pointer),指向存储对象在内存中的地址

  

二、作用域(scope)

  1.作用域

    作用域指代码当前的上下文环境,即代码可以访问和可以被访问的区域。

  2.全局作用域(一个页面一般只有一个全局作用域)

    <script>  

    //这里是全局作用域

    var myname1="yaoming";

    </script>

  3.本地作用域/局部作用域

    <script>  

    //这里是全局作用域

    var myname1="yaoming";

    var myfun=function(){

      console.log(myname1);  //yaoming

      var myname2="liuxiang";

      //这里是本地作用域

    }

    console.log(myname2);

    //myname2 is not defined

    </script>

    myname1打印出yaoming,是因为局部域可以访问全局域中的变量(反之则不行)

    myname2未定义,是因为你全局域不能访问局部域内的变量

  4.函数域

    所有域都只能由函数域所创建

    <script>  

    // 全局作用域

    var myFunction = function () {   

      // 局部作用域1 

      var myOtherFunction = function () {     

                // 局部作用域2 

                };

     };

    注:for循环、while等循环都不能创建局部作用域

    //这里是全局作用域

    for(var i=0;i<10;i++){        

      //code

    }

    console.log(i);  //打印出10

    </script>  

    i 值为10,i 依然属于全局域,是全局变量,所以循环不能创建局部作用域

  5.作用域链(scope chain,Scope Chain是一个链表,js中的闭包就是通过作用域链实现的

    <script>  

    // 全局作用域

    var myFunction1 = function () {   

      // 局部作用域1 (scope1)

      var myOtherFunction1 = function () {     

                // 局部作用域2(scope2 

                };

     };

    var myFunction2 = function () {   

 

      // 局部作用域3  (scope3)

 

      var myOtherFunction2 = function () {     

 

                // 局部作用域4  (scope4)

 

                };

 

     };

    </script>  

    

    图解:    

    上图栈区中的var a 和 var myFun1 在全局区域

    fun对应myFunction1,在scope1中

    fun2对应myOtherFunction1,在scope2中

    

    myOtherFunction1中形成的作用域链:scope1--》scope2--》全局作用域

    说明:a.scope1scope1scope1 中可以访问scope2和全局作用域中的任何变量

       b.如果 scope2中使用了 i 变量但是没有定义 i 变量,那么它会往其上级作用域寻找 i 变量直到找到为止,找不到为null。

       上图:fun2 中使用了变量 i 

       第一步:在scope2 中寻找 i 变量,找到则停止,否则进入下一步

       第二步:在上一级域 scope1中继续寻找 i 变量,同理找到停止,否则下一步

       第三步:在scope1的上级作用域 全局作用域中 寻找 i 变量 ,找到停止,否则变量未定义        

    

    myOtherFunction2中形成的作用域链:scope4--》scope3--》全局作用域 (此处同上)

  6.闭包(closure)/词法作用域/静态作用域

    当B函数嵌套在A函数内,B中引用了A作用域中的变量,

    并且B在A的外部调用了B函数,B函数是闭包函数。

    <script>  

    //scope global

    function a(){

      var myname="liuxiang";

      return function b(){

        console.log(myname);

        //此作用域访问上级作用域的 myname变量

      }

    }

    var c=a();  //c就是函数b ,在b的外层函数a之外被使用,那么此时就形成了闭包

    c();       //此处打印出 liuxiang

    console.log(this);

    </script>  

    闭包函数会拥有许多变量和绑定了这些变量的环境的表达式   

    console.log(this);打印出window对象,从全局对象window中找到 变量c并展开如下:

    

    由上图可以看到,全局变量 c 指向了内部函数 b

    函数b的作用域scope=A0+[[scopes]],其中[[scopes]]为函数b的属性,此属性包含了一个与其形成闭包的函数a的Closure(a)作用域,和一个Global全局作用域

    Closure(a)中包含所有 b函数中使用到的变量

    Gloal 包含所有的全局变量

三、  VO/AO, scope chain , [[scope]] 和 scope

  JS 代码的执行

    在js代码执行之前,js引擎会在全局域创建一个 VO (变量对象) ,在每一个函数中的局部域创建一个 AO (活动对象)

    VO 指向属于全局域的所有对象,进入js代码块的时候即被创建

    AO 活动对象是进入函数上下文时被创建的,指向局部作用域的所有对象

  作用域链

    作用域链正是内部上下文 所有变量对象 和 所有父变量对象 的列表。

    仍然以  二、作用域 中的第 5 点 中的代码为例

    myOtherFunction1 上下文的作用域链 为 AO(myOtherFunction1) AO(myFunction1) 和 VO(global)    

  [[scope]]

    [[scope]]属性是在当前函数被定义时确定,[[scope]]属性是所有父变量的层级链,位于当前的 AO 上下文 之上。

    函数之所有能访问 上级作用域中的对象,就是[[scope]]属性来实现的。

  scope的定义:    

    scope=AO+[[scope]];  当前的 AO 是作用域 数组的第一个对象,即从当前活动对象 往上级查找 ,则局部变量 比 父级变量 有更高的优先级。

    以 二、作用域 中的第 5 点 中的代码为例 

    myOtherFunction1Context.Scope=  myOtherFunction1Context.AO + myOtherFunction1.[[Scope]]

                    =  myOtherFunction1Context.AO +  myFunction1Context.AO + myFunction1.[[Scope]]

                    =  myOtherFunction1Context.AO +  myFunction1Context.AO + globalContext.VO

    myOtherFunction1的scope数组是 myOtherFunction1Context.Scope= [ myOtherFunction1Context.AO, myFunction1Context.AO, globalContext.VO ];

四、作用域(scope)和关键字(this)

五、闭包实例理解 及 垃圾回收

  现有数组b,b中包含三个人的姓名和年龄信息。现在通过调用sayHello 方法,分别为每一个对象添加一个说出自己名字的方法。

  代码实现如下:

<script>
var b=[
    {"name":"yaoming",age:40},
    {"name":"liuxiang",age:38},
    {"name":"lining",age:50}
      ];

function addSayHello(){
    for(var j=0;j<b.length;j++){
        b[j].sayHello=function(){
            return "hello,i am "+b[j].name;
        };
    }
    --j;
}


addSayHello();
console.log(b[0].name+"说:"+b[0].sayHello());
console.log(b[1].name+"说:"+b[1].sayHello());
console.log(b[2].name+"说:"+b[2].sayHello());
    
</script>

控制台输入如下:

  

  发现 每个人的sayHello 都会打印出 我是 lining,这并不是我们想要的结果。

  分析:

    addSayHello 执行完毕之后,全局作用域 里 b数组中的每个成员都有了自己的sayHello方法,

    该方法的表达式:function(){return "hello,i am "+b[j].name;};

    当调用b[0].sayHello()时,sayHello.AO中无 j 变量的定义,那么下一步,会到 addSayHello.AO 中寻找 j 变量,此时找到了  j 变量,此时 j 变量的值为2

    因此 b[0].sayHello()会返回 "hello,i am "+b[2].name;.同理所有人调用sayHello方法都会返回"hello,i am "+b[2].name;

  正确的方法:

<script>
var b=[
    {"name":"yaoming",age:40},
    {"name":"liuxiang",age:38},
    {"name":"lining",age:50}
      ];

function addSayHello(){
    for(var j=0;j<b.length;j++){
        b[j].sayHello=(function(index){
            return function(){
                return "hello,i am "+b[index].name;
            }
        })(j);
    }
    --j;
}


addSayHello();

console.log(b[0].name+"说:"+b[0].sayHello());
console.log(b[1].name+"说:"+b[1].sayHello());
console.log(b[2].name+"说:"+b[2].sayHello());
    
</script>

正确的控制台运行结果:

思路:错误的方法中,sayHello 方法中找不到变量j,上级addSayHello.AO 中只保存其作用域中的变量 j(保存为循环执行后j的最终值),造成所有方法访问同一变量。

   如果我们能让每个 sayHello 方法在 sayHello.AO 中保存自己所需的变量,就解决了刚刚的问题。在上面正确的方法中,每次循环都把当前 j 对应的变量以参数的形式传给内层函数,在内层函数的作用域中保存当前的值即可。

  

  总结:

  在错误的方法中,内层sayhello() 函数和 外层函数 addSayHello 形成了闭包,内层变量 j 永远都指向 外层函数 addSayHello 中的 j变量。

  如下图,每一个对象的函数作用域中 都指向 j=2

  

  改进的方法中,sayhello() 函数 为一个自执行函数返回的一个匿名函数。 sayHello对应的匿名函数 和 其外层的自执行函数 形成闭包,这里 sayHello对应的匿名函数中的   index变量 会指向 其自身的 外层自执行函数,其中每个自执行函数的AO里都分别保存了运行时 j 变量的副本 index。

  如下图,每一个对象的函数作用域中 都指向 其自身对应的下标:index:index

  

  JS的垃圾回收机制:

  找到那些不被使用的变量,然后释放其所占用的内存

  ?问:为什么闭包中用到的变量会保存在内存中?

  因为 全局变量 保存了对内部函数的引用。所以, 内部函数,及其所绑定的上下文环境均被使用,因此变量不会被释放,而是保存在内存中。

原文地址:https://www.cnblogs.com/ahguSH/p/6087666.html