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副本了。

 

posted @ 2016-04-15 14:58  8932809  阅读(115)  评论(0编辑  收藏  举报