闭包与私有域[第7章-函数表达式 笔记1]

闭包

看如下示例:

function createComparisonFunction(propertyName) {

return function(object1, object2){

var value1 = object1[propertyName];

var value2 = object2[propertyName];

if (value1 < value2){

return -1;

} else if (value1 > value2){

return 1;

} else {

return 0;

}

};

}

例子中,突出的那两行代码是内部函数(一个匿名函数)中的代码,这两行代码访问了外部函数中的变量 propertyName 。即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问变量 propertyName 。之所以还能够访问这个变量,因为内部函数的作用域链中包含createComparisonFunction() 的作用域。

在匿名函数从 createComparisonFunction() 中被返回后,它的作用域链被初始化为包含createComparisonFunction() 函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction() 中定义的所有变量。 更为重要的是, createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当 createComparisonFunction() 函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后, createComparisonFunction() 的活动对象才会被销毁。

例如:

//创建函数

var compareNames = createComparisonFunction("name");

//调用函数

var result = compareNames({ name: "Nicholas" }, { name: "Greg" });//1

调用 compareNames() 的过程中产生的作用域链之间的关系如下图所示:

闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用, 即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。

示例如下:

function createFunctions(){

var result = new Array();

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

result[i] = function(){

return i;

};

}

return result;

}

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,但实际上,每个函数都返回 10。因为每个函数的作用域链中都保存着 createFunctions() 函数的活动对象,所以它们引用的都是同一个变量 i 。当createFunctions() 函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量对象,所以在每个函数内部 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;

}

例子中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数 num ,也就是最终的函数要返回的值。在调用每个匿名函数时,我们传入了变量 i 。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num 。而在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来, result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。

this 对象

this对象是在运行时基于函数的执行环境绑定的:在全局函数中, this 等于 window ,而当函数被作为某个对象的方法调用时, this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。但有时候由于编写闭包的方式不同,这一点可能不会那么明显。

如下示例:

var name = "The Window";

var object = {

name : "My Object",

getNameFunc : function(){

return function(){

return this.name;

};

}

};

alert(object.getNameFunc()); //"The Window"(在非严格模式下)

以上代码先创建了一个全局变量 name ,又创建了一个包含 name 属性的对象。这个对象还包含一个方法getNameFunc() , 它返回一个匿名函数, 而匿名函数又返回 this.name 。 由于 getNameFunc()返回一个函数,因此调用 object.getNameFunc() 就会立即调用它返回的函数。而每个函数在被调用时都会自动取得两个特殊变量: this 和 arguments 。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

模仿块级作用域

JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。而且JavaScript从来不会告诉你是否多次声明了同一个变量,遇到这种情况,它只会对后续的声明视而不见,不过,它会执行后续声明中的变量初始化。

不过匿名函数可以用来模仿块级作用域并避免这个问题:

(function(){

//这里是块级作用域

})();

将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:

function outputNumbers(count){

(function () {

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

alert(i);

}

})();

alert(i); //导致一个错误!

}

在这个重写后的 outputNumbers() 函数中,我们在 for 循环外部插入了一个私有作用域。在匿名函数中定义的任何变量, 都会在执行结束时被销毁。 因此, 变量 i 只能在循环中使用, 使用后即被销毁。而在私有作用域中能够访问变量 count ,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。

时间: 2024-10-13 14:37:57

闭包与私有域[第7章-函数表达式 笔记1]的相关文章

javascript高级程序设计笔记(第7章 函数表达式)

7.5 小结 在JavaScript 编程中,函数表达式是一种非常有用的技术.使用函数表达式可以无须对函数命名,从而实现动态编程.匿名函数,也称为拉姆达函数,是一种使用JavaScript 函数的强大方式.以下总结了函数表达式的特点.? 函数表达式不同于函数声明.函数声明要求有名字,但函数表达式不需要.没有名字的函数表达式也叫做匿名函数.? 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂:? 递归函数应该始终使用arguments.callee 来递归地调用自身,不要使用函数名——函数

7章 函数表达式

定义函数的两种方式 函数声明 function functionName(arg0, arg1,arg2){     //函数体 } //非标准的name属性 //只在Firefox.Safari.Chrome和Opera有效 alert(functionName.name);    //'functionName' 特点: 函数声明提升(执行代码之前会先读取函数声明,因此可以把函数声明放在调用它的语句后面): 不能出现在判断.循环等位置. 函数表达式 //匿名函数(拉姆达函数) var fun

第七章 函数表达式

闭包 在一个函数内部创建一个子函数,子函数可以访问父函数作用域中的变量. function f (propertyName) { //创建 f()函数时,会创建一个预先包含全局变量的作用域链,这个作用域链中包含函数f(),并把这个作用域链保存再内部的[[scope]]属性中 return function(obj1, obj2) { //匿名函数(子函数)把父函数(f())的活动对象添加到它自己的作用域链中. var value1 = obj1[propertyName]; //匿名函数被返回后

第7章 函数表达式

定义函数方式有两种:函数声明,函数表达式 函数声明有一个重要特征:函数声明提升 7.1 递归 function func(num){ if (num<=1){ return 1; }else{ return num*func(num-1); } } 这是一个经典的阶乘函数 arguments.callee式一个指向正在执行的函数的指针,可以代替函数名 7.2 闭包 闭包是指有权访问另一个函数作用域中的变量的函数 闭包只能取得包含函数中任何变量的最后一个值.见p181 function fn(pr

【红宝书】第7章.函数表达式

定义函数的方法两种 函数声明 function fnName(arg0, arg1, arg2) { //函数体 } 重要特征:函数声明提升 函数表达式 let fnName = function(arg0, arg1, arg2) { //函数体 } 即创建一个匿名函数(因为function关键字后面没有标识符)赋值给变量fnName 使用前必须先赋值 7.1递归 递归函数是一个函数通过名字调用自身的情况下构成的 function fn(num) { if (num <= 1) { retur

JavaScript高级程序设计(第三版)第七章 函数表达式

一种是函数声明:另一种是函数表达式. 函数声明提升: say Hi(); function say Hi(){ alert("Hi!"); }      //这个例子不会抛出错误,因为在代码执行之前会先读取函数声明. 匿名函数:var functionName=function(arg0,arg1,arg2){函数体}; sayHi();   //错误:函数还不存在 var sayHi=function(){ alert("Hi!"); }; //不要这样做    

javaScript高级程序设计--第7章函数表达式

1.定义函数的方式有两种: a.函数声明: getName(); function getName(){ alert("123")}; 函数声明的特征:函数声明的提升,所以上面的例子才可以在函数声明前调用函数而不报错 b.函数表达式(又叫匿名函数,注意不能在表达式完成前调用,会报错) var getName = function(){alert("123")}; getName(); 2.递归 arguments.callee  指向一个正在执行的函数的指针,所以实

函数表达式笔记

1.定义函数的两种方式 函数声明:function functionName(a,b,c){}   ---一个重要特性就是 函数声明提升 在执行代码之前会先读取函数声明   比如 sayHi() ; function sayHi(){alert(11)}; 函数表达式: var functionName=function(a,b,c); 创建一个函数并将它赋值给变量 这种情况下的创建叫做匿名函数  因为function关键字后面的name是空字符串 2. 递归 argument.callee是一

读书笔记 - js高级程序设计 - 第七章 函数表达式

闭包 有权访问另一个函数作用域中的变量的函数 匿名函数 函数没有名字 少用闭包 由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存.过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包 模块模式   增强的模块模式   特权方法 有权访问私有变量的公有方法叫做特权方法 块级作用域   实现单例的特权方法