函数表达式
定义函数的方式有两种,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副本了。