zoukankan      html  css  js  c++  java
  • JS闭包

    函数表达式

    定义函数的方式有两种,1、函数声明(即正常 function a(){}方式) 2、函数表达式(即匿名函数 var a=function(){})

    两者的区别在于函数的提升,也就是说函数声明的方式ECMAScript会优先读取函数声明,因此无论在函数声明的上方还是下方调用函数,都不会出错。

    而函数表达式则不行,调用的语句必须放在函数表达式的下方才可。

    还有一个使用的不同,如:

     if (1 > 0) {
    
                function sayHi() {
    
                    alert(1);
    
                }
    
            }
    
            else {
    
                function sayHi() {
    
                    alert(2);
    
                }
    
            }
    
            sayHi(); //2

    按照正常逻辑函数应该返回为1才对,然后却返回2,这是因为ECMAScript会优先读取函数声明,又因为重复定义了两个相同的函数,第二个函数会自动覆盖第一个函数,所以无论条件如何都会返回2。

    用函数表达式则可以正确的得到效果。如:

     if (1 > 0) {
    
                var sayHi = function () {
    
                    alert(1);
    
                }
    
            }
    
            else {
    
                var sayHi = function () {
    
                    alert(2);
    
                }
    
            }
    
            sayHi(); //1           

    闭包       

    闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后

    闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。理解闭包,先从理解正常的函数调用过程开始。如:

    在函数的执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。

     function compare(value1, value2) {
    
                if (value1 > value2) {
    
                    return -1;
    
                }
    
                else {
    
                    return 1;
    
                }
    
            } 
    
    Var result=compart(5,10);

    上面的代码定义了compare函数,然后在全局作用域中进行调用。当调用时,会创建一个包含arguments,value1和value2的活动对象,全局执行环境的变量对象(包含result和compare)注意全局变量对象在compare的作用链中处于第二位。

     

     全局变量对象始终都会存在,当创建compare函数时,会创建一个预先包含全局变量对象的作用域链,指向全局变量对象。这个作用域链会被保存到当前函数的内部的[scope]中

    当调用compare函数时,会为函数创建一个执行环境,然后复制当前函数[scope]中的对象,构建起执行环境的作用域链,然后在将活动对象放到作用域链的最前端。

    这样当操作某个变量时,就会从作用域链从前向后的按地址进行搜索变量。当函数执行完成之后,局部活动对象就会被销毁,内存中仅存在全局变量对象。

    在一个函数内部定义了另一个匿名函数,(这里按父函数和子函数区分),在子函数中,其作用域链会包含父函数活动对象。(按照作用域链的概念)

    这样子函数就可以访问父函数中的所有变量,更为重要的是,父函数执行完毕之后,其活动对象也不会被销毁,因为在子函数的作用域链中还进行引用这个活动对象,所以只有父函数中的作用域链会被销毁。 

    Function  createComparisonFunction (propertyName) {
    
                return function (object1, object2) {
    
                    var value1 = object1[propertyName];
    
                    var value2 = object2[propertyName];
    
                    if (value1 > value2) {
    
                        return -1;
    
                    }
    
                    else {
    
                        return 1;
    
                    }
    
                }
    
            }
    
            //返回匿名方法给comparNames
    
            var comparNames = createComparisonFunction ("name");
    
            //实际上调用匿名方法
    
            var result = comparNames({ name: "Yu" }, { name: "Kai" });
    
            //解除匿名函数的引用 销毁闭包的活动对象
    
            comparNames = null;               

    上述代码,因为comparNames 始终引用createComparisonFunction方法内部返回的匿名方法,所以导致匿名方法的执行环境始终存在,所以也就无法回收其作用域链和活动对象,又因为匿名方法的作用域链会引用其父类的活动对象,导致其父类的活动对象也无法回收,这就导致了闭包。如果解除则将闭包对象的引用设置为null即可。

                                       

    闭包与变量的问题

    闭包只能取得包含函数中的最后一个值,即下面例子中i的最后一个值10。

     

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

    上面的代码按照逻辑应该会返回一个数组,这个数组的值就是元素的索引,但实际内部的值都会是10.这是因为每个函数的作用域链中都存放着createFunctions函数的活动对象,所以他们都是同一个变量i,当createFunctions函数返回后,i的值就是10,所以数组内部的值也都会为10。

    也就是说上面的代码其实是赋值了10次,把方法的地址给到了数组中的每个元素,而此时不会执行方法,也并不会返回i的值,而当调用时,方法会返回i的值,又因为闭包的原因createFunctions活动对象并没有回收,所以只会读到i循环后的结果,即为10,。

    解决上面的问题可以修改如下:

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

    上述代码可以理解为:我们没有直接把闭包赋值给数组,而是又定义了一个匿名函数。并将立即执行该匿名方法的值赋值给数组,在调用每个匿名函数时,我们传入了i,由于函数参数是按值传递的,所以就会将变量i的当前值赋值给参数num。而在这个匿名函数的内部又创建了一个返回num的闭包,这样一来,数组中每个函数都会有自己num副本了。

  • 相关阅读:
    对之前IoT项目的完善
    利用 esp8266 搭建简单物联网项目
    IOT(esp8266)
    ---分割线---
    百度云下载工具--雷鸟下载
    Win10安装Ubuntu子系统
    安装Ubuntu虚拟机
    搭建微信公众号后台(二)
    手把手教你基于CentOS8搭建微信订阅号后台服务(一)
    如何在PHP5中通过PDO连接SQLite3数据库
  • 原文地址:https://www.cnblogs.com/y8932809/p/5395515.html
Copyright © 2011-2022 走看看