1: function functionName(arg0,arg1){
2:
3:
4:
5: }
6:
7: var functionName = function( arg0 , arg1 ){
8:
9:
10:
11: }
匿名函数时一种强大的令人难以置信的工具
这两种的区别, 前者会在代码执行以前被加载到作用域中, 而后者则是在代码执行到那一行的时候才会有定义.
另外的区别是, 函数声明( 前者) 会给函数一个名字, 而函数表达式(后者) 则是创建一个匿名函数, 然后把这个匿名函数赋给一个变量 functionName.
关于函数声明, 它的一个重要特征就是 函数声明提升, 意思是在执行代码之前会先读取函数声明, 这就意味着可以把函数声明放在调用它的语句后面.
//匿名函数, 直接写也是可以的,只不过没有什么实际意义。
把函数想成值时, 都可以使用匿名函数 例如 :
在将函数作为参数传入另一个函数,或者从一个函数中返回另一个函数时,通常都要使用以这种形式来定义匿名函数。
1: function createCompare(propertyName){
2:
3: return function( object1, object2 ){
4:
5: var value1 = object1[ propertyName] ;
6:
7: var value1 = object2[ propertyName] ;
8:
9: if ( value1 < value2 ){
10:
11: return 1 ;
12:
13: }else {
14:
15: return -1;
16:
17: }
18:
19:
20:
21: } ;
22:
23: }
递归 ( 用途 1 )
1: <SPAN style="COLOR: #000000">function factorial( num ){
2:
3: if(num <=1 ){
4:
5: return 1 ;
6:
7: } else {
8:
9: return num * factorial( num -1 ) ;
10:
11: }
12:
13: }
14:
15: var anotherFactorial = factorial ;
16:
17: factorial = null ;
18:
19: alert( anotherFactorial(4) ) ; // 出错, 因为此时 factorial = null , 已经不再可用, 所以第一次调用没问题, 因为使用 anotherFactorial 指针, 可以找到对应的函数, 但是函数内部,
20:
21: // 由于是递归函数, 所以函数内部的名字 factorial 失效 ( null ) , 所以就会出错
22:
23: return arguments.callee( num - 1 ) ; // 可以解决问题, 之前有提到过, 因此递归函数时, 使用 arguments.callee 总比使用函数名好
24: </SPAN>
使用 arguments.callee( num –1 );
1: function factorial( num ){
2:
3: if (num <= 1){
4:
5: return 1;
6:
7: } else (
8:
9: return num * arguments.callee( num -1 ) // 其中 arguments.callee 是一个指向正在执行的函数的指针
10:
11: )
12:
13: }
14:
15: var anotherFactorial = factorial ;
16:
17: factorial = null ;
18:
19: alert( anotherFactorial(4) ) ;
闭包
闭包是指 有权访问另一个函数作用域中的变量的函数. 创建闭包的常见方式, 是在一个函数内部创建另一个函数,建议非必要时, 不要使用闭包,匿名函数也是差不多。
即,闭包是一个函数,是能够访问别的函数作用域变量的函数,那么内部函数肯定能访问外部函数的变量, 所以,此时就用到了匿名函数 ( 闭包 ), 所以闭包和匿名函数是什么关系的,是包含关系,即闭包肯定是个匿名函数。例如:
1: function createComparisonFunction(propertyName){
2:
3: return function( object1, object2){
4:
5: var value1 = object1[propertyName];
6:
7: var value2 = object2[propertyName];
8:
9: if (value1 < value2){
10:
11: return -1;
12:
13: } else if ( value1 > value2){
14:
15: return 1;
16:
17: } else {
18:
19: return 0;
20:
21: }
22:
23: };
24:
25: }
内部函数访问了外部函数变量 prototyName,即使这个内部函数被返回了,而且是在其他地方被调用了,它仍然可以访问变量 prototyName,内部函数的作用域链包括外部函数,
当某个函数第一次被调用时, 会创建一个执行环境及相应的作用域链, 并把作用域链赋值给一个特殊的内部属性[Scope] , 然后, this, arguments 和其他命名参数的值来初始化函数的活动对象.但在作用域链中, 内部函数-> 外部函数-> 更外部函数->......-> 最外部函数. ( 这种包含关系 )
1: function compare(value1, value2){
2: if(value1 < value2){
3: return -1;
4: }else if(value1 > value2){
5: return 1;
6: }else{
7: return 0;
8: }
9:
10: }
11:
12: var result = compare(5,10);
对于这个例子中compare()函数的执行环境,其作用域链中包含两个变量对象,本地活动对象和全局变量对象,显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
后台的每个执行环境都有一个表示变量的对象-变量对象, 全局环境的变量对象始终存在, 而像compare()函数这样的局部环境变量对象, 则只在函数执行的过程中存在, 在创建compare()函数时, 会创建一个预先包含全局变量对象的作用域链, 这个作用域链保存在内部的[[Scope]]属性中, 当调用 compare()函数时, 会为函数创建一个执行环境, 然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用于链. 此后, 又有一个活动对象(在此作为变量对象使用) 被创建并被推入执行环境作用域链的前端, 对于这个例子 compare() 函数的执行环境而言, 其作用域链中包含两个变量对象, 本地活动对象和全局变量对象, 显然作用域链本质上是一个指向变量对象的指针列表, 它只引用但不实际包含变量对象.
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存仅保存全局作用域,但是闭包的情况不同.
在另一个函数内部定义的函数会将包含函数( 即 外部函数 )的活动对象添加到它的作用域链中。
承接以上代码例子 :
var compare = createComparisonFunction(“name”); //compare指针保存函数地址
var result = compare( {name : “Nicholas”}, {name : “Greg”});
在匿名函数从 createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局对象,这样,匿名函数就可以访问createComparisonFunction()中定义的所有变量, 更为重要的是, createComparisonFunction()函数执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。(那么匿名函数为什么没有被释放呢? 因为createComparisonFunction 这个函数本身返回的是一个函数, 所以compare=createComparisonFunction 这条语句的本质就是又做了一个指针指向了内存中的匿名函数, 所以在释放空间时, 因为匿名函数在外围函数的外围还有一个指针指向, 所以它不能被释放, 同样它指向的外围函数内部的变量也不能被释放)换句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。所以需要显示解除 : 例如:
// 创建函数
var compareNames = createComparisonFunction("name"); // 这是 compareNames 已经是指向了函数内部的匿名函数
// 调用函数, // 注意, 这个函数执行完毕后, 函数的内存空间将被释放, 但是... 匿名函数和匿名函数所指向的函数的内部变量不能被释放
var result = comareNames({name: "Nicholas"}, {name: “Greg”});
// 解除对匿名函数的引用( 以便释放内存 )
compareNames = null;
注意: ( anonymous ) Scope 中的 this 是 window, 这也就呼应了下边的 this 的问题.
关于函数参数传递(特别是匿名函数 )
1 /* 匿名函数传参办法 */ 2 3 /* 01. 普通函数 */ 4 5 var aa = createFunction( xx ); // xx为实参 6 7 /* 02. 匿名函数 */ 8 var aa = function(num){}(argu); // num为形参, argu为实参
作用域链的副作用,即闭包只能取得包含函数中的任何变量的最后一个值,而不是某个特殊变量。
1: function createFunctions(){
2: var result = new Array();
3: for(var i=0; i<10; i++){
4: result[i] = function(){
5: return i;
6: };
7: }
8: return result;
9: }
10:
11: var funcs = createFunctions();
12: //每个函数都输出10
13: for(var i=0; i<funcs.length; i++){
14: document.write(funcs[i]());
15: }
注意:
var funcs = createFunctions(); // 这个 createFunctions() 函数会执行, 并将返回结果 赋值给 funcs, 那么返回的是数组, funcs 就是数组, 返回的是函数 funcs 是函数.
var funcs = createFunctions; // 这个 createFunctions 函数不会执行, 只是又定义了一个指针指向该函数.
首先, 来说createFunction函数的返回结果, 它所返回的不是一个数字, 而是一个函数, 所以, 当我们接到返回结果时, 因为是一个函数, 而funcs[i]() 表示执行这个函数, 而此时执行这个函数, 这个函数只有1条语句 return i, 而这时因为已经退出了 createFunction函数, 所以i的值是9. 所以最后显示的结果全是9, 之前有误区 :
误区1 createFunctions()函数返回的数值, 首先 return result 是返回语句, 表示此函数返回的是一个数组, 那么数组中装的是什么的, 很直接的是函数, 而并非是 i 的值, 如果是i的值, 那么后面输出时也就不用写成funcs[i]()了, 而直接写成 funcs[i]就可以了. 注意闭包内部名没有执行, 因为开始是个赋值语句, result[i]=function(){}; 这个赋值成功了就可以了, 函数内部并没有执行, 函数必须后面跟()后才能执行, 剩下的就是单纯的赋值, 不要想的那么复杂.
误区2 以为result[i]=function(){}; 这一步会执行function里的内容, 其实直到funcs[i]() 这个语句的时候, 闭包里的内容才真正的执行, 包括 var funcs = createFunctions(); 这条语句, 闭包都没有执行, 这条语句时 createFunction 这个函数执行了, 并把结果 包含有函数的数组返回给了funcs, 这个时候 funcs就是一个数组了, 并且数组元素是一个function, 所以才可以调用 funcs[i](), 来执行数组元素函数.
上例中,每个函数都返回10,而不是1,2,3,4,5,6,7,8,9,10,因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是 同一个变量 i , 当createFunctions()函数返回后,变量 i 的值是 10 , 此时每个函数都引用这保存变量 i 的同一个变量对象,(如上, var funcs = createFunctions() 这条语句会执行 createFunctions函数, 执行后, 函数内的 i 的值就是10了, 并且将数组返回给了 funcs, 此时 funcs 就是一个数组, 而数组元素是 函数, 注意, 此处的数组元素并非数字, 所以当下边又执行 document.write(funcs[i]()))时, 实际上是在执行数组元素里的函数, 也就是匿名函数, 而此时, 匿名函数会去调用createFunctions 中的 i , 而此时, i 已经是10了, createFunction这个函数还在作用域链的原因就是因为有匿名函数还在引用它. 所以整个 createFunction函数的作用域都没有被释放. )
所以在每个函数内部 i 的值都是 10。改进如下 :
1: function createFunctions(){
2: var result = new Array();
3: for(var i=0; i<10; i++){
4: result[i] = function(num){
5: return function(){
6: return num;
7: }(i);
8:
9: };
10: }
11: return result;
12: }
13:
14: var funcs = createFunctions();
15:
16: for(var i=0; i<funcs.length; i++){
17: document.write(funcs[i]());
18: }
这里, 我们没有直接把闭包赋值给数组, 而是定义了一个匿名函数, 并立即执行该匿名函数, 将结果赋值给数组, 这里匿名函数的参数是 num, 每次执行时会将 i 传递进去, 由于函数参数是按值传递的, 所以就会将变量 i 的当前值赋值给参数 num, 而在这个匿名函数内部, 又创建了一个访问 num 的闭包, 这样一来, result 数组中每个函数都有自己的 num 变量的一个副本, 因此就有各自不同的值了. 注意这里的匿名函数, 如下:
result[i] = function(num) { // 匿名函数
return function() { // 闭包, 所有的闭包都是匿名函数
return num;
}
}(i); // (i) 表示要执行
这个匿名函数之所以能够立刻执行, 就是因为后边的(i), 函数名称后边带(), 就表示要执行函数, 正式因为要执行函数, 所以 var funcs = createFunctions(); 要执行函数 createFunctions, 而进入createFunction 后, 又要执行函数 function(num), 注意上边例子的闭包是不需要执行这个的, 这里就有一个闭包, result[i] 里边存放的也都是函数, result[i] 里的函数不需要执行, 但是此时的 num 所处的环境是在 function(num) 这里, 这里每个 num 的值已经不同, 当再执行 document.write(funcs[i]()); 调用闭包的匿名函数时, 同样此时在执行 result[i] 里的函数, 而这时的函数所返回的内容是 num, 而 num 因为每个所处的环境不同, 都在各自的 function(num) 环境中, 所以它们之间没有影响, 所以最后返回的结果是, 1,2,3,4,5,6,7,8,9,10.
function createFunction() { var result = new Array(); for (var i=0; i<=9; i++) { result[i] = function() { return i; }(); } return result; } var funs = createFunction(); for(var i=0; i<funs.length; i++) { alert(funs[i]); }
所以, 以上是更简单的得到0,1,2,3,4,5,6,7,8,9的方法, 只要在原来的基础上, result[i] = function(){return i;}(); 加上这一对小括号, 让这个闭包在函数内部执行就可以了
this对象
在闭包中使用 this 对象也可能会有点问题,this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,在函数被作为某个对象的方法调用时,this等于那个对象。不过, 匿名函数的执行环境具有全局性, 因此 this 对象通常指向 windows.
1: var name = "The window";
2:
3: var object = {
4: name : "My Object",
5: getNameFunc : function(){
6: return function(){
7: return this.name;
8: }
9: }
10: };
11:
12: alert(object.getNameFunc()); //"The window"
以上代码先创建了全局变量 name, 又创建了一个包含 name 属性的对象, 这个对象还包含一个方法-getNameFunc(), 它返回一个匿名函数, 而匿名函数又返回 this.name,
为什么上边例子执行结果是 The window ?
前面曾经提过, 每个函数在被调用时, 其活动对象都会自动取得两个特殊变量 this 和 arguments, 内部函数在搜索这两个变量时, 只会搜索到活动对象为止, 因此永远不可能访问到外部函数中的这两个变量, 不过, 把外部作用域中的 this对象保存在一个闭包能够访问的到的变量里, 就可以让闭包访问到该变量了,
个人总结, 因为闭包会将它所在的函数的作用域链全部保存起来, 所有就有类似的事情, 闭包->外部函数->再外部函数->全局, 这是闭包的作用域链, 因为闭包全部保存了, 而又由于闭包只能取得以上作用域链的最后值, 例如上边例子 i, 都是显示10 一样, 所以当你定义的变量名字 name 与环境变量同名时, 闭包要搜索到作用域链最后的这个 name, 所以就后这个例子就返回了 全局 name 属性.
匿名函数的执行环境具有全局性,所以它的 this 属性都是 window, 并且 this 是关键字。看下边例子 :
1 var name = "The window"; 2 3 var object = { 4 name : "My object", 5 getNameFunc : function(){ 6 var that = this; // 此时是可以访问this的, 因为this是 7 // 这个对象的一个属性, 8 return function() { // 闭包 9 return that.name; // that 作为外层函数的活动对象, 10 // 会被引用(注意不是this, this不会 11 // 被引用, 12 }; 13 } 14 };
之所以这个会显示我们想要的结果, 是因为在这个作用域链上没有 that 同名的属性, 所以当闭包搜索 that 属性时, 这个 Object 里的 that 就是最顶端了, 换句话说是最后了, 所以自然就要显示这个 that 的结果了.
模仿块级作用域
JavaScript中没有块级作用域的概念, 例如 for 循环中的 i , 当离开 for 循环时, 也还继续存在, 而在 C 和 Java 这些语言中就不一样了,
function outputNumbers(count) {
for (var i=0; i<count; i++) {
alert(i);
}
alert(i); // 此处的 i, 仍然可以调用, 因为没有块级别作用域
}
一般来说, 我们应该尽量少向全局作用域中添加变量和函数. 通过这种创建私有作用域, 每个开发人员既可以使用自己的变量, 又不必担心搞乱全局作用域.
1: ( //此处的 (
2:
3: function(){
4:
5: //这里是块级作用域
6:
7: }
8:
9: ) ( ) ; // 此处的 ) ( ) ;
以上代码定义并立即调用了一个匿名函数, 将函数声明包含在一对圆括号中, 表示它实际上是一个函数表达式, 而紧随其后的另一对圆括号会立即调用这个函数.
如果只是单纯的写 function(){
}();
这样会出现错误,因为 Javascript 将 function 关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号,然而,函数表达式后面可以跟圆括号(表示执行函数),要将函数声明转换成函数表达式,只要在function前后加一个圆括号就可以了, 即
( function {
}) ();
无论什么地方,只要临时需要一些变量,就可以使用似有作用域,例如 :
1 function outputNumbers(count) { 2 (function() { 3 for (var i=0; i<count; i++) { 4 alert(i); 5 } 6 })(); 7 alert( i ); //导致一个错误 8 }
1: var someFunction = function () {
2:
3: // 这里是块级别作用域
4:
5: } ;
6:
7: someFunction(); //调用函数
调用函数的方式是在函数名后边加一对圆括号 (), 即 someFunction()
临时使用一些变量时, 可以使用这种块级作用域
1: (
2:
3: function(){
4:
5: for ( var i = 0 ; i < count ; i++ ) {
6:
7: alert( i ) ;
8:
9: }
10:
11: }
12:
13: ) () ;
14:
15: alert ( i ) ; // 导致一个错误
私有变量
JavaScript中没有私有成员概念, 不过, 有一个私有变量的概念, 任何函数中定义的变量, 都可以认为是私有变量, 外部不能访问函数内部的私有变量, 但是, 可以通过在函数内部创建一个闭包( 即内部函数) , 那么闭包通过自己的作用域链也可以访问这些变量, 而利用这一点, 就可以创建用于访问私有变量的公有方法.
我们把有权访问私有变量和私有方法的公有方法称为 特权方法.
第一种,构造函数中定义的特权方法:
1: function MyObject(){
2:
3: var privateVariable = 10 ; //私有变量
4:
5: function privateFunction() {
6:
7: return false ;
8:
9: }
10:
11: this.publicMethod = function() { // 特权方法
12:
13: privateVariable++;
14:
15: return PrivateFunction();
16:
17: } ;
18:
19: }
另一种方法:
1: ( function(){
2: //似有变量和私有函数
3: var privateVariable = 10;
4: function privateFunction(){
5: return false;
6: }
7:
8: //构造函数
9: MyObject = function(){};
10:
11: //公有特权方法
12: MyObject.prototype.publicMethod = function(){
13: privateVariable++;
14: return privateFunction();
15: };
16: }
17:
18: )();
1 ( function() { 2 var name = ""; //私有变量 3 Person = function(value) { 4 name = value; 5 }; 6 Person.prototype.getName = function() { 7 return name; 8 }; 9 Person.prototype.setName = function(value) { 10 name = value; 11 }; 12 13 14 } ) (); 15 16 var person = new Person("Nicholas"); 17 alert( person.getName() ); // "Nicholas" 18 person.setName("Greg"); 19 alert( person.getName() ); // "Greg"