JavaScript高级程序设计:第七章

函数表达式

1.函数表达式的特征:

定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的:

function  functionName(arg0,arg1,arg2){

         //函数体

}

首先是函数function关键字,然后是函数的名字,这就是指定函数名的方式。关于函数声明,它的一个重要特征就是函数声明提升,意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

         sayHi();

         function  sayHi(){

                  alert(“Hi!”);

         }

         这个例子不会抛出错误,因为在代码执行之前会先读取函数声明。

         第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面就是一种最常见的形式:

         var  functionName = function(arg0,arg1,arg2){

                  //函数体

         };

         这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。这种情况下创建的函数叫做匿名函数,因为function关键字后面没有标识符。匿名函数的name属性是空字符串。

         函数表达式与其他表达式一样,在使用前必须先赋值。以下代码会导致错误:

         sayHi();    //错误:函数还不存在

         var  sayHi = function(){

                  alert(“Hi!”);

         };

         理解函数提升的关键,就是理解函数声明与函数表达式之间的区别。

2.递归

递归函数是在一个函数通过名字调用自身的情况下构成的。如下所示:

         function  factorial(num){

                  if(num<=1){

                          return  1;

                  } else {

                          return num * factorial(num-1);

                  }

         }

         这是一个经典的递归阶乘函数。

         arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用,例如:

         function  factorial(num){

                  if(num<=1){

                          return 1;

                  } else {

                          return num*arguments.callee(num-1);

                  }

         }

3.闭包

         闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。以前面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()的作用域。

(1)闭包与变量

         作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说明这个问题:

function  createFunctions(){

         var  result = new Array();

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

                  result[i] = function(){

                          return i;

                  };

         }

         return result;

}

         这个函数会返回一个数组。表面上看,似乎每个函数都应该返回自己的索引值,即位置0的函数都返回0,位置1的函数都返回1。

(2)关于this对象

         在闭包中使用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()()就会立刻调用它返回的函数,结果就是返回一个字符串。

(3)内存泄露

         闭包在IE版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。来看下面的例子:

         function  assignHandler(){

                  var  element = document.getElementById(“someElement”);

                  element.onclick = function(){

                          alert(element.id);

                  };

         }

以上代码创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对assignHandler()活动对象的引用,因此就会导致无法减少element的引用数。只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收。不过,这个问题可以稍微改写一下代码来解决:

function  assignHandler(){

         var  element = document.getElementById(“someElement”);

         var  id = element.id;

         element.onclick = function(){

                  alert(id);

         };

         element = null;

}

在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。要记住,闭包会引用包含函数的整个活动对象,而其中包含着element。

4.模仿块级作用域

(1)如前所述,javascript没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的,来看下面的例子:

         function  outputNumbers(count){

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

                          alert(i);

                  }

                  alert(i);     //计数

         }

         这个函数中定义了一个for循环,而变量i的初始值被设置为0。在java、c++等语言中,变量只会在for循环的语句块中没有定义,循环一旦结束,变量i就会被销毁。可是javascript中,变量是定义字ouputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。

5.私有变量

         严格来讲,javascript中没有私有成员的概念;所有对象属性都是共有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。来看下面的例子:

function  add(num1 , num2){

         var  sum = num1+num2 ;

         return  sum ;

}

         在这个函数的内部,有3个私有变量:num1,num2和sum。在函数内部可以访问这几个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。

         我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式。第一种是在构造函数中定义特权的方法,基本模式如下:

         function  MyObject(){

                  //私有变量和私有函数

                  var  privateVariable = 10;

                  function  privateFunction(){

                          return false;

                  }

                  //特权方法

                  this.publicMethod = function(){

                          privateVariable++;

                          return  privateFunction();

                  };

         }

这个模式在构造函数内部定义了所有私有变量和函数。然后又继续创建了能够访问这些私有成员的特权的方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。对这个例子而言,变量privateVariable和函数privateFunction()只能通过特权方法publicMethod()来访问。在创建Object的实例后,除了使用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”

posted @ 2016-01-05 08:57  -cyber  阅读(251)  评论(0编辑  收藏  举报