代码改变世界

Javascript学习---1、函数

2010-12-09 22:43  名刘天下  阅读(271)  评论(0编辑  收藏  举报

    在很多语言中,函数(Java里面成为方法)和对象时截然不同的两种东西。函数被定义为对象的动作。但是在JavaScript中,函数实际上也是对象,与其他的引用类型一样,可以具有属性和方法。

1、函数的声明

函数的声明比较简单,可以通过2种方式进行声明:函数声明式和函数表达式(其实还有第三种Function构造函数方式,但是我们基本不用,所以不提也罢)

      1.1  函数声明式。最常用的方式了,如下所示的例子:

   1: function sum(num1,num2)
   2: {
   3:   return num1 + num2;
   4: }

注意:函数名实际上也是一个指向函数对象的指针,而不会与某个函数绑定。

      1.2  函数表达式。和函数声明式基本相同。如下所示:

   1: var sum = function(num1,num2)    {
   2:    return num1 + num2;
   3: ;

   上述代码定义了变量sum,并将其初始化为1个函数。当然,此处函数的定义采用了匿名函数的方式。另外注意语句的最后还有个分号,就像声明其他变量时一样;

    由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其他变量没有什么不同。如下代码所示:

   1: function sum(num1,num2)
   2: {
   3:     return num1+num2;
   4: }
   5: alert(sum(10,10));   //20
   6: var anotherSum=sum;  //anotherSum和sum指向了同一个函数
   7: alert(another(10,10)); //20
   8: sum =null;  //sum设为null,并不会影响anotherSum
   9: alert(another(10,10)); //20

注意:在第6行中,使用不带括号的函数名是访问函数指针,而带括号则是调用函数!

 

      1.3 函数声明与函数表达式的区别

解析器在向执行环境加载数据时,对函数声明和函数表达式并非一视同仁。其主要差别在于:
解析器会率先读取函数声明,并使其在执行代码前可用,至于函数表达式则必须等到解析器执行到它所在的代码行,才会被真正执行。
   1: alert(sum(10,10));
   2: function sum(num1,num2){
   3:   return num1+num2;
   4: }
 
以上代码,完全可以正常运行。因为在代码开始执行alert之前,解析器已经已经完全读取了函数声明,并将其添加到执行环境里去了。而如果如下,把函数声明改为函数表达式方式,就会出现错误。
 
   1: alert(sum(10,10));
   2: var sum=function(num1,num2)
   3: {
   4:     return num1+num2;
   5: };
上述代码之所以会出现问题,原因就在于使用了函数表达式声明了函数。在代码执行到第2行函数表达式之前,变量sum不会保存对函数的引用。而且由于第一行就运行出错,实际上也不会运行到第2行了。2、理解函数的一些要点
2.1  函数没有重载
函数没有重载的主要原因,就如上曾经提到的函数名其实是一个指向函数对象的指针。如下所示的例子:
   1: function add(num){
   2:     return num+100;
   3: }
   4: function add(num){
   5:     return num+200;
   6: }
   7: var result=add(100);
   8: alert(result);
显然,例子中定义了2个同名函数,最后起作用的将是最后一个,后面的函数覆盖了前面的函数。(Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次“预编译”)
 
2.2 函数的内部属性
在函数内部,定义了2个特殊的对象arguments和this.
1、arguments:数组类型的对象,存放着传入函数中的所有参数;同时,该函数还有1个属性callee,该属性为一个指针,指向拥有该arguments对象的函数;
如下所示的一个经典的阶乘函数:
   1: function factorial(num){
   2:     if(num<=1)
   3:        return 1;
   4:     else
   5:        return num*factorial(num-1);
   6: }

      该阶乘函数执行起来是没有问题的,但是它也存在一个问题:函数的执行和函数名factorial(第5行所用)紧紧耦合在一起。此时,可以使用arguments.callee属性进行改善,如下所示:

   1: function factorial(num)
   2: {
   3:     if(num<=1)
   4:        return 1;
   5:     else
   6:        return num*arguments.callee(num-1);
   7: }

       改善的代码提高了代码的可复用性了。

      2、this:this是Javascript中非常有用的一个特殊对象,其作用大致和java、c#中的this类似。this引用的是函数据以执行操作的对象-或者可以说,this是函数在执行时所处的作用域。(当在网页的全局作用域中调用时,this对象引用的window)

   1: window.color="red";
   2: var o ={color:"blue";}
   3: function sayColor(){
   4:     alert(this.color);
   5: }
   6: sayColor(); //“red”,在全局作用域中调用,this指向window
   7: o.sayColor=sayColor;
   8: o.sayColor(); //“blue”   此时this指的当然就是o了

      注意:

函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的执行环境中,全局的sayColor函数与o.sayColor指向的仍然是同一个函数!
 
2.3 函数属性和方法
 
之前提到过,函数也是对象,因此函数也具有属性和方法。
每个函数都有2个属性:length和prototype属性。
1、length:表示函数希望接收的命名参数的个数;如下代码所示:
   1: function sayName(name){
   2:   alert(name)
   3: }
   4: function sum(n1,n2){
   5:   return n1+n2;
   6: }
   7: alert(sayName.length);//output :1
   8: alert(sum.length); //output:2
 2、prototype:prototype属性是ECMAScript核心所定义的全部属性中可以说最耐人寻味的属性了。它是实现javascript面向对象编程的核心和基础,用于创建自定义引用类型和实现继承。后续单独介绍。
每个函数也包含2个方法:apply和call方法。
这2个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内的this对象的值。只是二者的调用方法稍有不同。语法格式如下所示。
call方法:

call([thisObj[,arg1[, arg2[,   [,.argN]]]]])
参数
thisObj:可选项。将被用作当前对象的对象。
arg1, arg2,  , argN:可选项。将被传递方法参数序列。

apply方法

apply([thisObj[,argArray]])
参数
thisObj:可选项。将被用作当前对象的对象。
argArray:可选项。将被传递给该函数的参数数组。

 
对于这2个方法,《javascript高级程序设计II》上列举如下例子:
   1: function sum(n1,n2){
   2:     return  n1+ n2;
   3: }
   4: function callsum(n1,n2){
   5:     return sum.call(this,n1,n2);
   6: }
   7: alert(callsum(10,20)); //output 30
感觉这个例子举得不是很好,在第5行sum.call时,由于该行位于callsum内,因此此时的this指的是callsum,所以如果sum函数体内有this的应用的话,相应的this即被替换成callsum。然而在代码中,sum函数内并没有使用this,因此也就没有了应用call方法的意义了。
代码更改一下,以体验call方法的作用(即设置函数体内的this的值或者说更改函数赖以运行的作用域)。
 
   1: var number =10;
   2: function sum(n1,n2){
   3:    return  this.number + n1+ n2;
   4:  
   5: function callsum(n1,n2){
   6:    this.number =20;
   7:    return sum.call(this,n1,n2);
   8:  
   9: alert(sum(10,20));  //output 40
  10: alert(callsum(10,20)); //output 50

  上述代码中,为什么sum(10,20)会输出40呢?由于sum函数处于全局作用域中,因为在执行sum时,this指向的对象即为window,而var number=10,其实等价于var window.number=10,因此输出40。而在调用callsum时,在callsum函数内部先定义了一个它的属性this.number=20,而后,在执行sum.call语句时,那个this所指的自然是目前所在的函数callsum,因此在执行sum函数时,sum函数内的this所指的就是callsum,而callsum.number=20,因此最终结果为50了。

     不好意思,,《javascript高级程序设计II》中也列举了相应的示例用来说明call和apply的主要用途的例子。代码如下:

   1: window.color="red" ;
   2: var o={color:"blue"};
   3: function sayColor(){
   4:     alert(this.color);
   5: }
   6: sayColor(); //outpu:  red
   7: sayColor.call(this);//output:red
   8: sayColor.call(window);//output:red
   9: sayColor.call(o);//output:blue

这个虽然比较简单,但是对于call的用法其实展示的比较清楚了。

第6行直接调用sayColor()时,因为此时是在全局作用域中进行调用的,因此函数体内的this指向的就是window这个全局对象,所以输出的其实是window.color;

第7,8行其实是等同的。因为在全局作用域中的this就是window;

第9行则是最具代表性的。在执行sayColor.call(o)时,sayColor函数体内的this对象指向了o,因此输出blue.

 

 

主要参考:《javascript高级程序设计第二版》