javascript--7函数表达式

函数表达式
1、JavaScript中定义函数有2钟方法:
1-1.函数声明:
functionfuncName(arg1,arg2,arg3){ //函数体
}
①name属性:可读取函数名。非标准,浏览器支持:FF、Chrome、safari、Opera。
②函数声明提升:指执行代码之前会先读取函数声明。即函数调用可置于函数声明之前。
1-2.函数表达式:
varfuncName = function(arg1,arg2,arg3){ //函数体
};
①匿名函数(anonymous function,或拉姆达函数):function关键字后无标识符,name属性值为空字符串。在把函数当成值使用时,都可用匿名函数。
②类似其他表达式,函数表达式使用前需先赋值,故不存在"函数声明提升"那样的作用。
③ECMAScript中的无效函数语法:
if判断中的重复函数声明
浏览器JavaScript引擎修正错误差异:大多浏览器会返回第二个声明,忽略condition;FF则会在condition为true时返回第一个声明。
使用函数表达式可解决并实现:
if判断 函数表达式
2、递归
递归函数,是在一个函数中通过名字调用自身的情况下构成的。
functionfactorial(num){ //一个经典的递归阶乘函数
if(num <= 1){ return1; } else{ returnnum * factorial(num-1); } }
     ①若使用下列代码调用该函数,会出错:
varanotherFactorial =factorial; factorial = null; alert(anotherFactorial(4));
将factorial()函数保存到变量anotherFactorial中后,将factorial变量设为null后不再引用函数,而anotherFactorial(4)中要执行factorial()函数,故出错。
使用argument.callee(指向正在执行的函数的指针)可解决:
解决方案
在非严格模式,使用递归函数时,用argument.callee代替函数名更保险
在严格模式下,使用argument.callee会出错,可用函数表达式 代替 函数声明:
函数表达式代替函数声明
 4、闭包
指有权访问另一个函数作用域中的变量的函数。(常见形式为函数嵌套)
functionwai(pro){ returnfunction(obj1,obj2){ varval1 =obj1[pro]; varval2 =obj2[pro]; if(val1<val2){ return-1; }elseif(val1>val2){ return1; }else{ return0; }; } }
return匿名函数时,匿名函数的作用域链初始化为包含函数的活动对象和全局变量对象。即匿名函数包含wai()函数的作用域。
每个函数被调用时,会创建一个执行环境、一个变量对象 及 相应的作用域链。
4-1.执行环境 及 作用域
执行环境execution context简称环境,定义了变量和函数有权访问的其他数据,并决定他们的各自行为。
①每个执行环境都有一个变量对象variable object,保存环境定义的所有变量和函数。该对象无法编码访问,但解析器在处理数据时会在后台使用它。
    全局变量对象是最外围的一个执行环境。在Web浏览器中被认为是window对象,故所有全局对象和函数都是window对象的属性和方法创建的。
    执行环境中的代码执行完后,该环境就被销毁,保存其中的变量和函数定义也随之销毁。
②代码在环境中执行时,会创建变量对象的一个作用域链scope chain,用于保证对执行环境有权访问的所有变量和函数的有序访问。
    作用域链前端,始终是当前执行的代码所在环境的变量对象。当该环境为函数时,会将活动对象作为变量对象。
    活动对象最开始只包含一个变量,即argumnt对象。
    作用域链中的下一个变量对象来自包含环境,而下一个变量对象来自下一个包含环境,直至延续到全局执行环境。
③标识符解析:从前段开始,沿着作用域链一级一级地搜索标识符的过程。【找不到通常会导致错误发生】
4-2.函数创建、执行时:
functioncompare(val1,val2){ if(val1<val2){ return-1; }elseif(val1>val2){ return1; }else{ return0; }; }
varresult = compare(5 , 10);
①创建函数compare()时,会创建一个预先包含全局变量对象的作用域链,并保存在内部[[scope]]属性中。
②局部函数compare()的变量对象,只在函数执行的过程中存在。
当调函数时,会创建一个执行环境,再通过复制函数的[[scope]]属性中的对象 构建起执行环境的作用域链。
③第一次调用函数时,如compare(),会创建一个包含this、argument、val1 和 val2的活动对象。
④全局执行环境的变量对象(包括this、result、compare)在compare()执行环境的作用域链中处于第二位。
⑤作用域链 本质是一个指向变量对象的指针列表,只引用但不实际包含变量对象。
⑥无论什么时候在函数中访问一个变量,都会行作用域链中搜索具有相应名字的变量。
4-3.闭包的作用域链
在另外一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中。
①将函数对象赋值null,等于通知垃圾回收例程将其清除,随着函数作用域链被销毁,其作用域链(不除了全局作用域)也会被安全销毁。
②由于闭包会携带包含函数的作用域,所以会比其他函数占用更多内存。
4-4.闭包与变量
作用域链的一个副作用:闭包只能取得包含函数中任何变量的最后一个值。
functioncreateFunctions(){ varresult = newArray(); for(vari=0; i < 10; i++){ result[i] = function(){ returni; }; } returnresult; }
①createFunctions()函数,将10个闭包赋值给数组result,再返回result数组。每个闭包都返回自己的索引,但实际上都返回10。
因为每个函数(闭包)的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的是同一个变量i,当createFunctions函数执行完后i的值10,故闭包中的i也都为10。
②解决办法,不使用闭包,创建一个匿名函数,将i值赋值给其参数:
functioncreateFunctions(){ varresult = newArray(); for(vari=0; i < 10; i++){ result[i] = function(num){ returnfunction(){ returnnum; }; }(i); } returnresult; }
创建一个每次循环都会执行一次的匿名函数:将每次循环时包围函数的i值作为参数,存入匿名函数中。因为函数参数是按值传递的,而非引用,所以每个匿名函数中的num值 都为每此循环时i值的一个副本。
4-5.this对象
this对象是在运行时基于函数的执行环境绑定的。
在全局函数中,this等于window;当函数被某对象调用时,this为该对象。
匿名函数的执行环境有全局性,其this对象通常指window。通过call()或spply()改变函数执行环境时,this指向其对象。
①每个函数在被调用时,都会自动取得两个特殊变量:this和argument。内部函数在搜索这两个变量时,只会搜索到期活动对象为止,永远不可能访问外部函数的这两个变量。
不过将外部作用域的this对象保存在一个闭包能访问的变量里,就可让闭包访问该对象。
闭包 访问外部函数的this对象
包围函数的argument对象 也可通过此方法被闭包访问。
5、函数声明 转换为 函数表达式
JavaScript将function关键字作为函数声明的开始,但函数声明后面不能跟圆括号,所以function(){......}();会出错。
要将函数声明转换为函数表达式,需为函数声明加一对圆括号:
(function(){ //块级作用域
})();

JavaScript没有块级作用域的概念。
functionoutputNumbers(count){ for(vari=0; i < count; i++){ alert(i); } alert(i); //计数
}
①块语句中定义的变量,实际是在包含函数中而非语句中创建的。即变量i的作用域是它的包含函数outputNumber(),而不是for语句。
在java、C++等语言中,变量i只会在for语句块中定义,循环结束后就被销毁。
②甚至在outputNumber()函数中可以重新声明同一个变量,JavaScript会忽略后续的声明但会执行声明中的初始化。
在一个函数内重复声明一个变量
1、私有作用域
使用匿名函数模拟块级作用域(私有作用域) 语法:
(function(){ //这里就是块级作用域
})();
①调用并立即调用一个匿名函数。由于函数声明后面是不能加圆括号,故需要用圆括号将函数声明包围起来,使其转换为函数表达式
②临时需要一些变量时,可以使用私有作用域。
私有作用域 例子
③在匿名函数中的任何变量在执行结束后会被销毁,故变量i只能在for循环语句中被使用,使用后即被销毁。匿名函数是闭包,可访问count参数。
④优点:可避免过多的全局变量而导致的命名冲突。
同时,也有利于减少闭包占用内存问题,因为没有指向匿名函数的引用,匿名函数的作用域链在代码执行后销毁。
2、私有变量
严格来讲,JavaScript没有私有成员的概念,所有对象属性都是公有的。但有一个私有变量的概念。
任何在函数中定义的变量,都可以认为是私有变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
2-1.特权方法(privileged method)
指有权访问私有变量和私有函数的公有方法。有2种创建方法:在构造函数中定义特权方法、在私有作用域中定义私有变量或函数。
2-1-1.实例变量(在构造函数中定义特权方法)
方法1
①外界无法访问MyObj()函数内的私有变量和私有函数,但由于特权方法是闭包 而可以通过它访问包含函数中的私有变量和私有函数。
②在创建MyObj实例后,除了通过publicMethod()这一个途径,没有其他方法能直接访问privateVar和privateFunc()。
③利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
缺点:使用构造函数模式后,每个实例都会创建同样一组新方法。
即每次都要重新创建特权方法,故之前的数据无法重用。使用第二种创建方法可解决。
2-1-2.静态私有变量(在私有作用域中定义私有变量或函数+原型模式)
方法2
     ①此模式,将所有私有变量、私有函数和特权方法等 都定义在一个私有作用域中。
②构建函数中,没使用函数声明,因为函数声明只能创建局部函数。
      变量MyObj没有用var关键字声明,因为初始化为经声明的变量,都会创建一个全局变量。(在严格模式给为声明的变量赋值会出错)
③特权方法是在原型(prototype)上定义的,原型模式定义函数时不用函数声明,而是使用函数表达式,才能创建全局函数。
区别:使用此模式,私有变量和函数都是由实例共享的,由于特权方法是在原型创建的,所以实例都用同一个特权方法。
因此,当在一个实例中调用该特权方法修改私有变量时,所有实例都会被影响,所有实例中引用的私有变量对应着改变。
优点:创建静态私有变量因为使用了原型而增进代码复用。
缺点:所有实例都没有自己的私有变量和函数(都引用同一个)。
静态私有变量
arguments.callee: 指向正在执行的函数的指针。求阶乘的函数可以如下实现:


二、闭包
1、闭包作用域链
作用域链本质上是指向变量对象的指针列表。全局环境的变量对象始终存在。
如果在全局作用域中创建了一个函数,那么也会为这个函数创建一个包含全局变量对象的作用域链。被保存在函数的scope属性中。当调用此函数时,会为函数创建一个执行环境,然后通过复制函数的scope属性构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用),被创建并被推入执行环境作用域链的顶端。
在另一个函数内部定义的函数,初始化时会将包含它的函数(外部函数)的活动对象添加到他们作用域中。外部函数执行完毕后,活动对象不会被销毁,因为匿名函数或者说闭包的作用域仍然在引用这个活动对象。所以对于外部函数,仅仅是销毁了它的作用域链。直到闭包被销毁,外部函数的活动对象才会被销毁。

上面这段代码,result是一个函数数组,每个result[i]指向一个函数副本。但是所有的匿名函数function() 指向的是同一个i(外部函数的变量)。所以调用每一个result[i]返回相同值。

上面这段代码,result同样是一个函数数组,每个result[i]指向一个函数副本。但是因为函数参数是按值传递,所以每个匿名函数都会得到当前的i值。
 2、this对象
this对象指向的是包含它的最内层函数的调用者。this对象是在运行时基于函数的执行环境动态绑定的。函数在全局被调用时,this等于window对象;函数作为某对象的成员方法被调用时,this等于那个对象。
每个函数在被调用时,其活动对象都会自动取得两个特殊变量,this和arguments。内部函数在搜索这两个变量时,只会搜索到它们自己的活动对象中的this,因而不可能访问到外部函数的this。但是如果把外部作用域中的this保存为另一个变量,由于内部函数不具备这个变量,因为访问时就会搜索到外部函数的活动对象,所以可以让闭包访问该对象了。如下所示:


在闭包使用中记得避免内存泄露,使用null赋值的方法减少对dom元素的引用。
三、模仿块级作用域
私有作用域(或者叫做块级作用域)的语法如下:
(function() {
})();
这段代码定义的了一个匿名函数并立刻调用了它。function() {}外又加一层括号是为了使它变成函数表达式,如果不加的话就只是函数声明。函数声明后面不可以跟圆括号,函数表达式可以。
这种技术经常在全局作用域中被用在函数外面,以免向全局作用域中添加过多的变量。
四、私有变量
函数的参数、局部变量、内部定义的函数都算作私有变量。私有变量是不能在函数外被访问的。但是我们可以利用闭包创建可以访问私有变量的公有方法。这种方法被称为特权方法。
没用使用var关键字定义的变量都是全局变量。
1、静态私有变量
在私有作用域中定义公有方法。

如上图代码,变量name成为所有实例共享的静态私有变量。

2、模块模式
模块模式是为单例创建私有变量和特权方法。单例是指只有一个实例的对象,javascript以对象字面量的方式创建单例对象。如下。

使用方式如下:

这种方式用于需要对单例进行某些初始化并且维护其私有变量时使用的(不太了解其应用场景)。
个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作
用域中调用函数,实际上等于设置函数体内this 对象的值。首先,apply()方法接收两个参数:一个
是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是Array 的实例,也可以是
arguments 对象。例如:
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments); // 传入arguments 对象
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]); // 传入数组
}
alert(callSum1(10,10)); //20
alert(callSum2(10,10)); //20
在上面这个例子中,callSum1()在执行sum()函数时传入了this 作为this 值(因为是在全局
作用域中调用的,所以传入的就是window 对象)和arguments 对象。而callSum2 同样也调用了
sum()函数,但它传入的则是this 和一个参数数组。这两个函数都会正常执行并返回正确的结果。
call()方法与apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()
方法而言,第一个参数是this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用
call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
return sum.call(this, num1, num2);
}
alert(callSum(10,10)); //20

在使用call()方法的情况下,callSum()必须明确地传入每一个参数。结果与使用apply()没有
什么不同。至于是使用apply()还是call(),完全取决于你采取哪种给函数传递参数的方式最方便。
如果你打算直接传入arguments 对象,或者包含函数中先接收到的也是一个数组,那么使用apply()
肯定更方便;否则,选择call()可能更合适。(在不给函数传递参数的情况下,使用哪个方法都无所
谓。)
事实上,传递参数并非apply()和call()真正的用武之地;它们真正强大的地方是能够扩充函数
赖以运行的作用域。下面来看一个例子。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
这个例子是在前面说明this 对象的示例基础上修改而成的。这一次,sayColor()也是作为全局
函数定义的,而且当在全局作用域中调用它时,它确实会显示"red"——因为对this.color 的求值会
转换成对window.color 的求值。而sayColor.call(this)和sayColor.call(window),则是两
种显式地在全局作用域中调用函数的方式,结果当然都会显示"red"。但是,当运行sayColor.call(o)
时,函数的执行环境就不一样了,因为此时函数体内的this 对象指向了o,于是结果显示的是"blue"。
使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。

变量对象与作用域链)的使用进行了详细的分析介绍。需要的朋友参考下
函数表达式
1、JavaScript中定义函数有2钟方法:
  1-1.函数声明:

function funcName(arg1,arg2,arg3){
  //函数体
}

    ①name属性:可读取函数名。非标准,浏览器支持:FF、Chrome、safari、Opera。
    ②函数声明提升:指执行代码之前会先读取函数声明。即函数调用可置于函数声明之前。
  1-2.函数表达式:

var funcName = function(arg1,arg2,arg3){
  //函数体
};

    ①匿名函数(anonymous function,或拉姆达函数):function关键字后无标识符,name属性值为空字符串。在把函数当成值使用时,都可用匿名函数。
    ②类似其他表达式,函数表达式使用前需先赋值,故不存在"函数声明提升"那样的作用。
    ③ECMAScript中的无效函数语法:

if判断中的重复函数声明
if(condition){
    function sayHi(){
        alert("Hi!");
    }
} else {
    function sayHi(){
        alert("Yo!");
    }
}

      浏览器JavaScript引擎修正错误差异:大多浏览器会返回第二个声明,忽略condition;FF则会在condition为true时返回第一个声明。
      使用函数表达式可解决并实现:

if判断 函数表达式
var sayHi;
if(condition){
    sayHi = function(){
        alert("Hi!");
    }
} else {
    sayHi = function(){
        alert("Yo!");
    }
}

2、递归
  递归函数,是在一个函数中通过名字调用自身的情况下构成的。

function factorial(num){   //一个经典的递归阶乘函数
    if (num <= 1){
        return 1;
    } else {
        return num * factorial(num-1);
    }
}

     ①若使用下列代码调用该函数,会出错:

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));

      将factorial()函数保存到变量anotherFactorial中后,将factorial变量设为null后不再引用函数,而anotherFactorial(4)中要执行factorial()函数,故出错。
      使用argument.callee(指向正在执行的函数的指针)可解决:

解决方案
function factorial(num){
    if (num <= 1){
        return 1;
    } else {
        return num * arguments.callee(num-1);
    }
}
var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //24

    在非严格模式,使用递归函数时,用argument.callee代替函数名更保险
    在严格模式下,使用argument.callee会出错,可用函数表达式 代替 函数声明:

函数表达式代替函数声明
var factorial = function f(num){
    if (num <= 1){
        return 1;
    } else {
        return num * f(num-1);
    }
}

4、闭包
  指有权访问另一个函数作用域中的变量的函数。(常见形式为函数嵌套)

function wai(pro){
    return function(obj1,obj2){
        var val1 = obj1[pro];
        var val2 = obj2[pro];
        if(val1<val2){
            return -1;
        }else if(val1>val2){
            return 1;
        }else{
            return 0;
        };
    }
}

    return匿名函数时,匿名函数的作用域链初始化为包含函数的活动对象和全局变量对象。即匿名函数包含wai()函数的作用域。
  每个函数被调用时,会创建一个执行环境、一个变量对象 及 相应的作用域链。
4-1.执行环境 及 作用域
  执行环境execution context简称环境,定义了变量和函数有权访问的其他数据,并决定他们的各自行为。
  ①每个执行环境都有一个变量对象variable object,保存环境定义的所有变量和函数。该对象无法编码访问,但解析器在处理数据时会在后台使用它。
    全局变量对象是最外围的一个执行环境。在Web浏览器中被认为是window对象,故所有全局对象和函数都是window对象的属性和方法创建的。
    执行环境中的代码执行完后,该环境就被销毁,保存其中的变量和函数定义也随之销毁。
  ②代码在环境中执行时,会创建变量对象的一个作用域链scope chain,用于保证对执行环境有权访问的所有变量和函数的有序访问。
    作用域链前端,始终是当前执行的代码所在环境的变量对象。当该环境为函数时,会将活动对象作为变量对象。
    活动对象最开始只包含一个变量,即argumnt对象。
    作用域链中的下一个变量对象来自包含环境,而下一个变量对象来自下一个包含环境,直至延续到全局执行环境。
  ③标识符解析:从前段开始,沿着作用域链一级一级地搜索标识符的过程。【找不到通常会导致错误发生】
4-2.函数创建、执行时:

function compare(val1,val2){
     if(val1<val2){
        return -1;
    }else if(val1>val2){
        return 1;
    }else{
        return 0;
    };
}
var result = compare(5 , 10);

  ①创建函数compare()时,会创建一个预先包含全局变量对象的作用域链,并保存在内部[[scope]]属性中。
  ②局部函数compare()的变量对象,只在函数执行的过程中存在。
   当调函数时,会创建一个执行环境,再通过复制函数的[[scope]]属性中的对象 构建起执行环境的作用域链。
  ③第一次调用函数时,如compare(),会创建一个包含this、argument、val1 和 val2的活动对象。
  ④全局执行环境的变量对象(包括this、result、compare)在compare()执行环境的作用域链中处于第二位。
  ⑤作用域链 本质是一个指向变量对象的指针列表,只引用但不实际包含变量对象。
  ⑥无论什么时候在函数中访问一个变量,都会行作用域链中搜索具有相应名字的变量。
4-3.闭包的作用域链
  在另外一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中。
  ①将函数对象赋值null,等于通知垃圾回收例程将其清除,随着函数作用域链被销毁,其作用域链(不除了全局作用域)也会被安全销毁。
  ②由于闭包会携带包含函数的作用域,所以会比其他函数占用更多内存。
4-4.闭包与变量
  作用域链的一个副作用:闭包只能取得包含函数中任何变量的最后一个值。

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

  ①createFunctions()函数,将10个闭包赋值给数组result,再返回result数组。每个闭包都返回自己的索引,但实际上都返回10。
   因为每个函数(闭包)的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的是同一个变量i,当createFunctions函数执行完后i的值10,故闭包中的i也都为10。
  ②解决办法,不使用闭包,创建一个匿名函数,将i值赋值给其参数:

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

  创建一个每次循环都会执行一次的匿名函数:将每次循环时包围函数的i值作为参数,存入匿名函数中。因为函数参数是按值传递的,而非引用,所以每个匿名函数中的num值 都为每此循环时i值的一个副本。
4-5.this对象
  this对象是在运行时基于函数的执行环境绑定的。
    在全局函数中,this等于window;当函数被某对象调用时,this为该对象。
    匿名函数的执行环境有全局性,其this对象通常指window。通过call()或spply()改变函数执行环境时,this指向其对象。
  ①每个函数在被调用时,都会自动取得两个特殊变量:this和argument。内部函数在搜索这两个变量时,只会搜索到期活动对象为止,永远不可能访问外部函数的这两个变量。
    不过将外部作用域的this对象保存在一个闭包能访问的变量里,就可让闭包访问该对象。

闭包 访问外部函数的this对象
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());  //"MyObject"

   包围函数的argument对象 也可通过此方法被闭包访问。
5、函数声明 转换为 函数表达式
  JavaScript将function关键字昨晚函数声明的开始,但函数声明后面不能跟圆括号,所以function(){......}();会出错。
  要将函数声明转换为函数表达式,需为函数声明加一对圆括号:

(function(){
   //块级作用域
})();


函数是一组可以随时随地运行的语句,它们是ECMAScript的核心。
函数是由关键字function、函数名加一组参数以及置于括号中的要执行的代码声明的。
基本语法:
function functionname([arg1 [, arg2 [,...[, argN]]]])
{
   statements
}
函数可以通过其名字加置于括号中的参数(如果有多个参数,中间用逗号分隔)调用。
调用函数基本语法:
functionname([arg1 [, arg2 [,...[, argN]]]])
函数有没有返回值,都不必明确地声明它。即使有,只需要使用return运算符后跟要返回的值即可。
函数在执行过return语句后停止执行代码。因此,return语句后的代码都不会被执行。
如果函数无返回值,那么可以调用没有参数的return运算符,随时退出函数。
如果函数无明确的返回值,或调用了没有参数的return语句,那么它真正返回的值是undefined。
 
无重载
ECMAScript中的函数不能重载。
可用相同的名字在同一个作用域中定义两个函数,而不会引发错误,但真正使用的是后一个函数。
例子:
function doAdd(n){
 return n+100;
}
function doAdd(n){
 return n+10;
}
alert(doAdd(10));
结果显示"20",因为第二个doAdd()函数的定义覆盖了第一个定义
 
arguments对象
在函数代码中,使用特殊对象arguments,开发者无需明确指出参数名,就能访问它们。
第一个参数位于位置0,第二个参数位于位置1,依此类推
例子:
function doAdd(){
 return arguments[0]+arguments[1];
}
alert(doAdd(10,20));
用arguments[0]表示第一个参数"10",arguments[1]表示第二个参数"20",结果是"30"
可用arguments对象检测传递给函数的参数个数,
引用属性arguments.length,可以检测传递给函数的参数个数
function doAdd(){
 return arguments.length;
}
alert(doAdd(10,20));
结果是"2"
ECMAScript不会验证传递给函数的参数个数是否等于函数定义的参数个数。
开发者定义的函数都可以接受任意个数的参数(根据Netscape的文档,最多能接受25个),而不会引发任何错误。任何遗漏的参数都会以undefined传递给函数,多余的参数将忽略。
用arguments对象判断传递给函数的参数个数,还可以模拟函数重载
 
Function类
Function类可以表示开发者定义的任何函数。
用Function类直接创建函数的语法:
var function_name = new Function([arg1 [, arg2 [,...[, argN]]]],function_body);
每个arg都是一个参数,最后一个参数是函数主体(要执行的代码),这些参数必须是字符串
例子:
var doAdd = new Function("n","return n+100;");
这里说明函数只是一种引用类型,这也说明了为什么第二个函数的定义覆盖了第一个的定义
例子:
var doAdd = new Function("n","return n+100;");
var doAdd = new Function("n","return n+10;");
显然第二个doAdd函数的定义会覆盖第一个的定义
函数名只是指向函数对象的引用值,行为就像其他指针一样,甚至可以使两个变量指向同一个函数
例子:
var doAdd = new Function("n","return n+10;");
var num = doAdd;
alert(doAdd(10));
alert(num(10));
都是显示"20"
可以把函数作为参数传递给另一个函数
例子:
function nAdd(n){
 return n+10;
}
function doAdd(fun_name,n){
 return fun_name(n);
}
alert(doAdd(nAdd,10));
将显示"20"
注意:最好不要使用Function构造函数创建函数,因为用它定义函数比用传统方式要慢得多。不过,所有函数都应看作是Function类的实例。

因为函数是引用类型,所以它们也有属性和方法。
length 属性 返回函数定义的参数数目
functionName.length
例子:
function nAdd(n,m){}
alert(nAdd.length);
显示"2"
注意:无论定义了几个参数,ECMAScript函数可以接受任意多个参数(最多25个)。属性length只是为查看默认情况下预期的参数个数提供了一种简便的方式。
Function对象也有与所有对象共享的标准valueOf()方法和toString()方法。
这两个方法返回的都是函数的源代码,在调试时尤其有用。
例子:
function nAdd(n){
 return n+10;
}
alert(nAdd.valueOf());
alert(nAdd.toString());
这两个方法都输出了nAdd()函数的文本内容
还有两个Function类的方法与对象的讨论相关,下一章将讨论它们。
 
闭包
ECMAScript最容易让人误解的一点是它支持闭包(closure)。
闭包是指词法表示包括不必计算的变量的函数,也就是说,该函数能使用函数外定义的变量。
在ECMAScript中使用全局变量是一个简单的闭包实例。
例子:
var iBaseNum = 10;
function addNumbers(iNum1, iNum2) {
     function doAddition() {
          return iNum1 + iNum2 + iBaseNum;
     }
     return doAddition();
}
alert(addNumbers(3, 6));
将显示"19",这里,函数addNumbers()包括函数doAddition()(闭包)。内部函数是个闭包,因为它将获取外部函数的参数iNum1和iNum2以及全局变量iBaseNum的值。addNumbers()的最后一步调用了内部函数,把两个参数和全局变量相加,并返回它们的和。这里要掌握的重要概念是doAddition()函数根本不接受参数,它使用的值是从执行环境中获取的。
可以看到,闭包是ECMAScript中非常强大多用的一部分,可以用于执行复杂的计算。就像使用任何高级函数一样,在使用闭包时要当心,因为它们可能会变得非常复杂。

有不少开发人员总是搞不清匿名函数和闭包这两个概念,因此经常混用。闭包是指有权访问另一个
函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数,仍以前面的
createComparisonFunction() 函数为例,注意加粗的代码。
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() 的作用域。要彻底搞清楚其中的细节,必须从理解函数被调用的时候
都会发生什么入手。
第 4 章介绍了作用域链的概念。而有关如何创建作用域链以及作用域链有什么作用的细节,对彻底
理解闭包至关重要。当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。
然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object) 。但在作用域
链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作
为作用域链终点的全局执行环境。
在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。来看下面的例子。
function compare(value1, value2){
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
}
var result = compare(5, 10);

以上代码先定义了 compare() 函数,然后又在全局作用域中调用了它。当调用 compare() 时,会
创建一个包含 arguments 、 value1 和 value2 的活动对象。全局执行环境的变量对象(包含 result
和 compare )在 compare() 执行环境的作用域链中则处于第二位。图 7-1 展示了包含上述关系的
compare() 函数执行时的作用域链。

后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像
compare() 函数这样的局部环境的变量对象,则只在函数执行的过程中存在。在创建 compare() 函数
时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的 [[Scope]] 属性中。
当调用 compare() 函数时,会为函数创建一个执行环境,然后通过复制函数的 [[Scope]] 属性中的对
象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执
行环境作用域链的前端。对于这个例子中 compare() 函数的执行环境而言,其作用域链中包含两个变
量对象:本地活动对象和全局变量对象。显然,作用域链本质上是一个指向变量对象的指针列表,它只
引用但不实际包含变量对象。
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,
当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象) 。
但是,闭包的情况又有所不同。
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因
此,在 createComparisonFunction() 函数内部定义的匿名函数的作用域链中,实际上将会包含外部
函数 createComparisonFunction() 的活动对象。图 7-2 展示了当下列代码执行时,包含函数与内部
匿名函数的作用域链。
var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });
在匿名函数从 createComparisonFunction() 中被返回后,它的作用域链被初始化为包含
createComparisonFunction() 函数的活动对象和全局变量对象。这样,匿名函数就可以访问在
createComparisonFunction() 中定义的所有变量。 更为重要的是, createComparisonFunction()
函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换
句话说,当 createComparisonFunction() 函数返回后,其执行环境的作用域链会被销毁,但它的活
动对象仍然会留在内存中;直到匿名函数被销毁后, createComparisonFunction() 的活动对象才会
被销毁,例如:
//创建函数
var compareNames = createComparisonFunction("name");
//调用函数
var result = compareNames({ name: "Nicholas" }, { name: "Greg" });
//解除对匿名函数的引用(以便释放内存)
compareNames = null;

首先, 创建的比较函数被保存在变量 compareNames 中。 而通过将 compareNames 设置为等于 null
解除该函数的引用,就等于通知垃圾回收例程将其清除。随着匿名函数的作用域链被销毁,其他作用域
(除了全局作用域)也都可以安全地销毁了。图 7-2 展示了调用 compareNames() 的过程中产生的作用
域链之间的关系。


由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过
度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭
包。虽然像 V8 等优化后的 JavaScript 引擎会尝试回收被闭包占用的内存,但请大家
还是要慎重使用闭包。

严格来讲,JavaScript 中没有私有成员的概念;所有对象属性都是公有的。不过,倒是有一个私有
变量的概念。 任何在函数中定义的变量, 都可以认为是私有变量, 因为不能在函数的外部访问这些变量。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。来看下面的例子:
function add(num1, num2){
var sum = num1 + num2;
return sum;
}

在这个函数内部,有 3 个私有变量: num1 、 num2 和 sum 。在函数内部可以访问这几个变量,但在
函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访
问这些变量。而利用这一点,就可以创建用于访问私有变量的公有方法。
我们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method) 。有两种在对象
上创建特权方法的方式。第一种是在构造函数中定义特权方法,基本模式如下。
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}

这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员
的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的
所有变量和函数。对这个例子而言,变量 privateVariable 和函数 privateFunction() 只能通过特
权方法 publicMethod() 来访问。在创建 MyObject 的实例后,除了使用 publicMethod() 这一个途
径外,没有任何办法可以直接访问 privateVariable 和 privateFunction() 。
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据,例如:
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"

以上代码的构造函数中定义了两个特权方法: getName() 和 setName() 。这两个方法都可以在构
造函数外部使用, 而且都有权访问私有变量 name 。 但在 Person 构造函数外部, 没有任何办法访问 name 。
由于这两个方法是在构造函数内部定义的,它们作为闭包能够通过作用域链访问 name 。私有变量 name
在 Person 的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。不过,在构造
函数中定义特权方法也有一个缺点,那就是你必须使用构造函数模式来达到这个目的。第 6 章曾经讨论
过,构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方
法就可以避免这个问题。
7.4.1 静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下所示。
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数
MyObject = function(){
};
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();


这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,
首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,
这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是
使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因,我们也没
有在声明 MyObject 时使用 var 关键字。记住:初始化未经声明的变量,总是会创建一个全局变量。
因此, MyObject 就成了一个全局变量,能够在私有作用域之外被访问到。但也要知道,在严格模式下
给未经声明的变量赋值会导致错误。
这个模式与在构造函数中定义特权方法的主要区别,就在于私有变量和函数是由实例共享的。由于
特权方法是在原型上定义的,因此所有实例都使用同一个函数。而这个特权方法,作为一个闭包,总是
保存着对包含作用域的引用。来看一看下面的代码。
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
这个例子中的 Person 构造函数与 getName() 和 setName() 方法一样, 都有权访问私有变量 name 。
在这种模式下,变量 name 就变成了一个静态的、由所有实例共享的属性。也就是说,在一个实例上调
用 setName() 会影响所有实例。而调用 setName() 或新建一个 Person 实例都会赋予 name 属性一个
新值。结果就是所有实例都会返回相同的值。
以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变
量。到底是使用实例变量,还是静态私有变量,最终还是要视你的具体需求而定。
多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用
闭包和私有变量的一个显明的不足之处。
、函数声明
1.函数的声明语法:function funcName(){}。js执行前会把函数声明载入,因此可以在调用之后声明函数,不必向c语言一样先声明,再调用,然后写实现。这个学名叫“函数声明提升”。
2.函数的表达式语法:var funcName=function(){}。这种方式其实是先创建一个匿名函数,然后把函数的地址传给变量。所以在调用funcName之前,必须先写好函数,不能像声明语法那样先用,后声明。


二、作用域链的创建过程
//小实验
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1>value2){
return 1;
}else{
return 0;
}
}
var result=compare(3,4);
1.函数的执行
(1)当函数compare第一次执行时,解析器会为它创建一个执行环境,完成其作用域链。
(2)使用this、arguments和其他命名参数的值初始化函数的活动对象。
(3)在作用域链中,第一层外部函数的活动对象处于第二位,全局执行环境里的活动对象在最后。
(4)函数执行过程中,为读取和写入变量,需要在作用域链中查找变量。
2.作用域链的生成
(1)创建compare时,预先创建一个包含全局变量的作用域链,这时作用域链中只有一个活动对象,活动对象里包含了this(指window)、compare、result。
(2)调用compare时,再创建一个活动对象,把该活动对象放在作用域链的顶端,之前包含全局变量的活动对象挪到第二。新的活动对象包含了value1和value2。
(3)compare执行完毕,(2)中创建的活动对象(包含value1和value2)会被销毁,compare的作用域链只剩下全局变量活动对象。
三、闭包
1.闭包的概念
(1)指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
(2)下面例子中return的匿名函数A就是一个闭包,因为A使用了另一个函数compare的局部变量attrName。
//小实验
//这是一个可以比较对象属性的函数,用于array.sort十分方便。
function compare(attrName){
return function(object1,object2){//匿名函数A
if(object1[attrName]<object2[attrName]){
return -1;
}else if(object1[attrName]>object2[attrName]){
return 1;
}else{
return 0;
}
}
}
var func=compare("age");
var result=func({age:14},{age:15});
alert(result);//-1
(3)虽然上面例子中的匿名函数A是返回到compare外面执行的,但是A的作用域链中包含了compare的局部变量。
2.闭包形成的原因
(1)正因为匿名函数A在compare内部,A的作用域链中才会包含compare创建的活动对象,从而可以使用compare的局部变量。如果还不清楚,请仔细阅读“作用域链的生成”,然后阅读下面的内容。
(2)匿名函数A从compare中返回后,它的作用域链内有两个活动对象:第一个是compare的活动对象(包含attrName);第二个是全局活动对象(包含compare、func、result),这里this先不说。
(3)更关键的问题(闭包能够工作的核心条件)是,compare执行完,返回匿名函数A给func后,它自己创建的活动对象(包含attrName)并没有销毁。因为func的作用域链中还在引用这个活动对象。这是js内存回收机制决定的,确切地说,compare执行完毕后,它的执行环境、作用域链会被销毁。但是作用域链其实只是一个队列,其中存放着指向活动对象的指针。活动对象被销毁的前提是所有指向该对象的指针都已经销毁了。因此,当func的作用域链包含指向compare活动对象的指针时,compare的活动对象仍要在内存中保留。
(4)当func执行完毕,func创建的活动对象(包含age14和age15)销毁,然后compare的活动变量才会销毁。
3.闭包的优缺点
(1)使用起来非常灵活。
(2)不容易理解,尤其是层次比较多的时候。
(3)闭包比普通函数占用内存多。因此不在万不得已的时候,不要使用闭包。即便使用闭包,层次也不要太多。
四、闭包与父级变量
1.最后的值
(1)闭包的作用域链中,保存着各个层次的活动对象,通过这些对象去使用外部函数的变量。这是闭包实现的基本原理。
(2)根据基本原则,外部函数执行完后,外部函数的活动对象中保存着局部变量最后的值。
//小实验
function box(){
var arr=[];
for(var n=0;n<10;n++){
arr.push(function(){
alert(n);
});
}
return arr;
}
var funcs=box();
funcs[0]();//10
funcs[9]();//10
(3)上面例子中,alert的永远是10。虽然box执行完毕后返回了一个闭包数组,而且每个闭包都能使用n,但n的值并不是想象中的0到9,而是10。原因在前面已经解释过了。为了让每个闭包alert出自己在数组中的索引,我们可以创建另一个匿名函数,并强制执行。
//小实验
function box(){
var arr=[];
for(var n=0;n<10;n++){
arr[n]=function(num){
return function(){
alert(num);
}
}(n);
}
return arr;
}
var funcs=box();
funcs[0]();//0
funcs[9]();//9
(4)函数的强制执行(补充知识):下面例子都是函数强制执行(以下是在v8引擎下实验的结果)。
  <1>如果不用变量保存强制执行的返回值,函数体必须用括号括起来(正常1)。否则如果函数是声明函数,不会强制执行(异常1);如果是匿名函数,会出现语法错误(异常2)。
  <2>如果使用变量保存强制执行的返回值,函数是不是匿名的,函数体括与不括,都没有问题(正常2-5)。
  <3>有很多人把“正常1”理解为闭包,其实是不正确的。这只是匿名函数强制执行而已,与闭包没有多大关系,只不过在闭包设计中,为了避免上面的问题,经常使用这种强制执行而已。
//小实验
//正常1
(function(i,j){
alert(i+j);//3
})(1,2);
//异常1
function abc(i,j){
alert(i+j);//没有alert,也就是没有执行。
}(1,2);
//异常2
function(i,j){
alert(i+j);
}(1,2);//Unexpected token (


//正常2
var b=(function(i,j){
alert(i-j);//-1
return i-j;
})(1,2);
alert(b);//-1
//正常3
var a=function(i,j){
alert(i+j);//3
}(1,2);
alert(a);//undefined


//正常4
var c=(function func(i,j){
alert(i-j);//-1
return i-j;
})(1,2);
alert(c);//-1
正常5
var d=function func(i,j){
alert(i-j);//-1
return i-j;
}(1,2);
alert(d);//-1
五、闭包与this指针
1.this指针是函数运行时基于函数的执行环境绑定的。换句话说,this跟作用域链没啥直接关系,看的是函数在哪儿。
2.闭包在哪儿执行谁也不好说,this指的到底是啥,也要看运行期。所以在闭包中使用this要格外小心。(这种题目在面试中很多,我在A公司2015校招web前端线试题中就遇到一个。)
//小实验
var name="window";
var obj={
name:"obj",
getName:function(){
return function(){
return this.name;
}
},
getName1:function(){
var _this=this;
return function(){
return _this.name;
}
}
}
alert(obj.getName()());//window
alert(obj.getName1()());//obj




//函数表达式:
//使用函数声明创建函数:

function functionName(arg0,arg1,arg2){
console.log();
}
console.log(functionName.name); //函数名.name:返回函数名。functionName

sayHi(); //函数声明提升:在执行代码之前会先读取函数声明。所以可以把函数声明放在调用它的语句后面。
function sayHi() {
console.log("hi!");
}


//使用函数表达式创建函数:
//函数表达式不同于声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫匿名函数(本章重点)。
sayHi(); //Error:函数还不存在。因为用了匿名函数,没有进行函数声明提升。
sayHi = function() {
console.log("hi!");
};

if(true) { //不可以使用这样的条件表达式。
function sayHi() {
console.log("hi!");
}
} else {
function sayHi() {
console.log("no!");
}
}
sayHi(); //"no!"本质上是错误的,每种浏览器处理方式不一样。


if(true) { //但可以使用这样的条件表达式。
sayHi = function() {
console.log("hi!");
}
} else {
sayHi = function() {
console.log("no!")
};
}
sayHi(); //"hi!"
//这样不会发生意外,不同的函数根据条件被赋给sayHi

//匿名函数实例:创建比较器的函数。
function createComparisonFunction(propertyName) {
return function(object1,object2) {
var val1 = object1[propertyName];
var val2 = object2[propertyName];

if(val1<val2) {
return 1;
} else if(val1>val2) {
return -1;
} else {
return 0;
}
};
}

function functionName () {
return arguments.callee.name;
}
console.log(functionName()); //functionName:使用函数的.name属性获取了函数名。

//以前写过的例子:递归函数应该始终使用arguments.callee来递归地调用自身,降低耦合性。
function factorial(num) {
if(num==1)
return 1;
else
return num*arguments.callee(num-1); //降低耦合性。
}
console.log(factorial(6));

//注意:严格模式下不能使用arguments.callee函数,会发生错误。
//解决方法:使用命名函数表达式(给匿名函数命名)

var factorial1 = function f(num) { //用f表示匿名函数,即使将函数名改变,函数也不会出错。
if(num==1)
return 1;
else
return num*f(num-1);
}
var factorial2 = factorial1;
console.log(factorial1(6)); //720
console.log(factorial2(6)); //720,改了名仍然有效。

//闭包:有权访问另一个函数作用域中变量的函数(通过外部函数中的变量创建内部函数)。
//例:
function createComparisonFunction(propertyName) {
return function(object1,object2) {
var val1 = object1[propertyName];
var val2 = object2[propertyName]; //这两行代码访问了外部函数中的变量。

if(val1<val2) {
return 1;
} else if(val1>val2) {
return -1;
} else {
return 0;
}
};
}
var fun = createComparisonFunction("name");
console.log(fun.propertyName);
*/

/*
* 对闭包的理解:
* 一般函数在被调用时,会创建一个执行环境,执行环境指向了一组指针:作用域链
* 作用域链中优先级为 函数活动对象→全局对象。
* 而闭包被调用时,有闭包的作用域链为 闭包活动对象→外部函数活动对象→全局变量;
* 外部函数的作用域链为 外部函数活动对象→全局对象。
* 由于外部函数的活动对象一直存在于闭包的作用域链中。
* 所以即使外部函数执行完毕以后,其活动对象也不会被销毁(因为一直在被闭包的作用域链引用)。
* 而是一直被匿名函数的作用域链所指向从而留在内存中,直到匿名对象被销毁。
* 解决:少用闭包,在绝对必要时才用。用完以后手动解除其和外部函数活动对象之间的关联(理解:《JavaScript高级程序设计》 P180 图7-2)
*/
//手动解除关联:

var compareNames = createComparisonFunction("name"); //将函数创建的闭包函数记录以调用
var result = compareNames({ name: 'Steve'},{ name: 'Erison'}); //调用函数
//使用完毕后:
compareNames = null; //解除对匿名函数的引用,释放内存
//以便垃圾收集器释放闭包活动对象
//然后其作用域链中的其他活动对象(除了全局作用域)也被安全地销毁。

//闭包的副作用:闭包只能取的外部函数中任何变量的最后一个值
function createFunctions() { //目的:通过闭包创建十个函数,十个函数的返回值为从0到9。
var result = new Array();
for(var i=0;i<10;i++) {
result[i] = function() {
return i;
}
}
return result;
}

var result = createFunctions();

for(var i=0;i<result.length;i++) {
console.log(result[i]()); //返回了10个10!
}
//原因:闭包的作用域链中包含外部函数,所以闭包中的i是通过作用域链在外部函数中搜索来的。
//在最后return result时,i是10,故10个函数的返回值都是10.

//解决:
function createFunctions() {
var result = new Array();
for(var i=0;i<10;i++) {
result[i] = function(num) {
return function() {
return num;
}
}(i); //用定义匿名函数的方式给数组赋予函数。最后传入一个参数i。由于函数参数是按值传递的,所以i会被赋值给参数num。
}
return result;
}
var result = createFunctions();
for(var i=0;i<result.length;i++) {
console.log(result[i]()) //返回了0到9
}

//闭包与this对象

var name = "The Window";

var object = {
name: "My Object",
get: function() { //get:返回一个匿名函数
return function() {
return this.name;
};
}
};

console.log(object.get()()); //The Window:返回的匿名函数中的this指向外部作用域。
//!*这里我没搞懂。书上的解释是:内部函数在搜索this和argument变量时,
//只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。

//让闭包访问外部函数对象:
var name = "The Window"
var object = {
name: "My Object",
get: function() {
var that = this;
return function() {
return that.name;
}
}
};

console.log(object.get()()); //My Object:在函数内定义了that指向object,起到过渡作用,使内部函数访问到object。

//细微区别导致this改变:
var name = "The Window";

var object = {
name: "My Object",
getName: function() {
return this.name;
}
}

console.log(object.getName()); //My Object:因为this.name就是object.name。
console.log((object.getName)()); //My Object:将object中的函数当作一个独立的函数运行,但this的值得到了维持。
//因为object.getName()和(object.getName)()的定义是相同的。
console.log((object.getName = object.getName)()); //The Window:
//!*这里也没太看懂。书上的解释:因为这个赋值表达式的值是函数本身。 //所以this的值不能得到维持。
//(因为将object中的函数重新赋予,this没有维持,所以改变了作用域?)

//IE中的BUG:如果闭包的作用域链中保存着一个HTML元素,该元素无法被销毁。
//因为IE中队JS对象和COM对象使用的不同的垃圾收集例程(JS:标记清除/COM:引用计数)
function assignHandler() {
var element = document.getElementById("someElement");

element.onclick = function() {
alert(element.id);
};
}
//由于匿名函数一直保存着对assignHandler活动对象的引用,导致活动对象一直存在,element的引用数至少是1,所占用的内存永远不会被回收。
//解决:不让闭包直接访问HTML属性,而是创建一个副本让闭包访问。

function assignHandler() {
var element = document.getElementById("someElement");

var id = element.id; //创建闭包要访问的属性的副本。

element.onclick = function() {
alert(id); //将内部函数解除对element的引用(变为引用所需要的element对象属性的副本)。
};

element = null; //解除对element的引用。
}
//由于被闭包访问的整个活动对象会一直存在,所以即使闭包引用的是id,包含函数中还是会存在一个对element的引用。所以要手动解除对element的引用。

//模仿块级作用域:js中没有块级作用域,语句块中的变量实际上在函数中,所以可以用匿名函数来模仿块级作用域。
function output() {
for(var i=0;i<10;i++) {

}
var i; //js中,后续声明一个已存在的变量会被无视,i还是10。
console.log(i); //后面的输出结果说明了,这里可以访问到i。
}

output(); //10:由于没有块级作用域,导致所有变量都在函数中。所以在函数内的for语句之外也可以访问到i。
(function(){
//块级作用域:创建一个匿名函数并立即执行。
for(var i=0;i<10;i++) {

}
})();

console.log(i); //错误:因为i在匿名函数模仿的块级作用域中,所以外部访问不到。
//用途:经常会用在全局作用域中的函数外部,为了不向全局作用域中添加过多的数据,增强封装型,满足程序员们的强迫症。


//私有变量

//构造函数方法
function Person(name) { //通过在构造函数内部定义两个特权方法,定义了私有变量和函数
this.getName = function() { //由于没有显式地定义name属性,除了通过这两个方法以外,没有其他方法可以访问name属性。
return name;
};

this.setName = function(value) {
name = value;
};
}

var person = new Person("Steve");
console.log(person.getName());

//通过构造函数定义特权变量缺点:像创建对象的构造函数模式一样,无法实现方法的复用。

//静态私有变量
//通过在私有作用域(通过匿名函数实现)中定义私有变量或函数,创建特权方法。

(function {
//私有变量和私有函数
var privateVariable = 10;

function privateFunction() {
return false;
}

//构造函数
MyObject = function() {};

//公有/特权方法
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction;
};
})();
//与构造函数中定义特权方法的区别:将方法定义在原型上,原型上的方法是共享的。因为不管是哪个实例中的此方法都包含着对作用域的引用。

(function() {
var name = "";

Person = function(value) { //不使用var关键字:确保Person为全局函数
name = value;
}

Person.prototype.getName = function(){
return name;
}

Person.prototype.setName = function(value) {
name = value;
}
})();

var p1 = new Person("Steve");
console.log(p1.getName());

var p2 = new Person("Erison");
console.log(p1.getName()); //Erison
console.log(p2.getName()); //Erison 由于变量的共享,都被改成了Erison

//模块模式:为单例添加私有变量和特权方法。
var application = function(){ //创建了一个含有指定方法的实例:在私有域中定义方法和属性,并添加方法,然后立刻返回一个单例。
//私有变量和函数。
var components = new Array();

//初始化
component.push(new BaseComponent());

//公共方法
return { //用字面量定义法返回一个单例
getComponentCount: function(){
return component.length;
},

registerComponent: function(component){
if(typeof conponent == "object") {
components.push(conponent);
}
}
}(); //这种单例以Object形式存在(因为由对象字面量方法定义)。

//增强的模块模式。
//在返回对象之前加入对其增强的代码。适合单例必须是某种类型的实例。
//使用构造函数而不是字面量方法来返回对象
//修改后的上述代码:

var application = function(){
//私有变量函数
var components = new Array();

//初始化
components.push(new BaseComponent());

//创建局部副本:相比模块模式加强的地方,因为返回的对象必须是BaseComponent的实例!
var app = new BaseComponent();

//再为局部副本添加能够访问私有变量的公共方法
app.getComponentCount = function() {
return components.length;
};

app.registerComponent = function(component) {
if(typeof component == "object"){
components.push(component);
}
}

//返回局部副本,此时返回以后便是指定类型的单例了。
return app;
}();

猜你喜欢

转载自zhyp29.iteye.com/blog/2304203