• 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的垃圾回收机制:

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

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

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

  • 相关阅读:
    (十六)字段表集合
    (十五)类索引
    (十四)访问标志 Access_flags
    (一)单例模式
    (二十三)IDEA 构建一个springboot工程,以及可能遇到的问题
    (十三)class文件结构:常量池(转)
    Hive优化
    标签整理
    一些学习资料
    jstree树形菜单
  • 原文地址:https://www.cnblogs.com/ahguSH/p/6087666.html
Copyright © 2020-2023  润新知