yunshichen

我相信人生是值得活的,尽管人在一生中必须遭受痛苦,卑劣,残酷,不幸和死亡的折磨,我依然深信如此.但我认为人生不一定要有意义,只是对一些人而言,他们可以使人生有意义. ---J 赫胥黎

Advanced JavaScript 1: Function


一 function的定义

function(函数): 一段JavaScript代码,可以被调用(执行)0次或多次.函数可能有参数(argument)----参数是局部变量,当函数被调用时创建.

通常,可以用如下两种方法创建函数,两者等价:

<html>
    
</html>

<script>
    
function my_func(a,b){
        
return a+b;
    }
    //function literals
    
var another_func=function(a,b){
        
return a+b;
    }
    alert(my_func(
1,2));
    alert(another_func(
1,2));
    
</script>



上述例子的第二个函数通过函数字面量(function literals)来定义. function作为表达式,返回一个匿名函数,而这个匿名函数赋给变量another_func.

当用函数字面量定义函数时,可以附带一个函数名.这种情况通常用于定义递归函数,如下示例:

<html>
    
</html>

<script>
    
    
var f=function fibonacci(x){
        
if(x==0){return 0};
        
if(x==1){return 1};
        
return fibonacci(x-1)+fibonacci(x-2)
    }
    alert(
"fibonacci(1)=: "+f(1));
    alert(
"fibonacci(10)=: "+f(10));
    
</script>


二 调用与参数

由于JavaScript是弱类型语言,所以function并不对传入参数的个数作检查.这会出现如下情况:

  • 如果传入function的参数不足,那些没传入的参数值会被设置为undefined
  • 如果传入的参数过多,多余的参数会被忽略.
<html>
    
</html>

<script>
    
    
var f=function(a,b){
        alert(
"a:= "+a+" ,b:= "+b);
    }
    f(
5);
    f(
5,6);
    f(
5,6,7);
    
</script>



并且,JavaScript不会检查参数类型,如果传入的参数类型不当,JavaScript会将之转为string类型(对于object类型,则调用object类型的toString方法)并调用函数.如下所示,sum函数大概想完成两个数字的求和.但由于参数类型不正确,除了第一次调用,其他调用结果都不正确.(但不会报错)

<html>
    
</html>

<script>
    
    
function sum(a,b){
        
return a+b;
    }
    alert(sum(
5,6));
    alert(sum(
"s",6));
    alert(sum(
"a","b"));
    alert(sum({},
5));
    
</script>

既然function对参数这么宽容,那么用一些复杂类型作参数,似乎也是很正常的想法. 下面这个例子用object作为参数,并且,这种技术大量应用于ExtJS中

<html>
    
</html>

<script>
    
    
var f=function(o){
        
var s="";
        
        s
="Object.width:="+o.width
            
+", Object.height:="+o.height;
            
            alert(s);
    }
    
    f({width:
50,height:100});
    
</script>




三 参数(Argument)对象


在函数内部,arguments是一个关键字,表示对象Argument的引用.Argument的主要属性length表示实际传入的参数个数,还允许通过下标方式取得参数的值.

<html>
    
</html>

<script>
    
    
var f=function(x,y,z){
        alert(
"x:="+x+", y:="+y+", z:="+z);
        alert(
"arguments.length:="+arguments.length
            
+", arguments[0]:="+arguments[0]
            
+", arguments[1]:="+arguments[1]
            
+", arguments[2]:="+arguments[2]
        );        
    }
    
    f(
4,5,6);
    f(
1,2);
    
</script>

如上述例子,arguments.length 能准确反映传入的参数数目,所以可以用它来对参数进行检测,以确保程序得到正确结果:

<html>
    
</html>

<script>
    
    
var sum=function(a,b){
        
if(!|| !|| arguments.length!=2){
            
throw new Error("The number of arguments must be corret.");
        }
        
        
if(typeof(a)!="number" || typeof(b)!="number"){
            
throw new Error("The datatype of arguments must be number.");
        }
        
        
return a+b;        
    }
    
    alert(sum(
5,6));
    alert(sum(
5,6,7));
    alert(sum(
"5",7));    
    
</script>

应用arguments还可以实现不定参数的函数.

<html>
    
</html>

<script>
    
    
var max=function(){
        
//Omit datatype checking here.
        
        
var size=arguments.length;
        
var i=0;
        
var total=0;
        
for(i;i<size;i++){
            total
+=arguments[i];
        }
        
return total;
    }
    
    alert(max(
1,2));
    alert(max(
1,2,3,4,5,6,7,8,9,10));
    
</script>

callee,是Argument对象的一个属性,表示正在执行的function.这个属性很少用到,但在某些场合会很有用.第一节例子介绍用函数字面量定义函数时怎么写递归函数.应用callee,递归函数可以改写如下:

<html>
    
</html>

<script>
    
    
var f=function(x){
        
if(x==0){return 0};
        
if(x==1){return 1};
        
return arguments.callee(x-1)+arguments.callee(x-2)
    }
    
    alert(
"fibonacci(1)=: "+f(1));
    alert(
"fibonacci(10)=: "+f(10));
    
</script>



四 function预定义的属性和方法


每个function本身都有如下预定义的属性和方法:length,prototype,call,apply

属性length: 表示function定义的参数数目.例如function sum(a,b),那么length就是2.
属性prototype: 方便定义类的对象,在介绍JS面向对象特性时会深入讨论.
方法call: 允许某个对象调用函数,即使该对象实际上没有这个方法.这是个很酷的功能,通常用于面向对象程序的设计.
方法apply: 同上,只是调用时参数必须是数组类型.

下面例子介绍length的用法:

<html>
    
</html>

<script>
    
    
var sum=function(a,b){
        
if(arguments.length!=arguments.callee.length){
            alert(
"Number of arguments is wrong,so you won't get a correct result!");
            
return;
        }
        
return a+b;
    }
    
    
    alert(sum(
4,5));
    sum(
7,8,9);
            
    
</script>

下面例子介绍了call和apply用法:

<html>
    
</html>

<script>
    
    
var sum=function(){
        
var size=arguments.length;
        
var total=0;
        
while(size>0){
            size
--;
            total
+=arguments[size];
        }
        
return total;
    }
    
    
var aobject =new Object();
    
    aobject.a
=5;
    aobject.b
=10;
    aobject.c
=[1,2,3,4,5,6,7,8,9,10];
    
    alert(
"Invoke sum' call with null object and result is: "+sum.call(null,11,12));
    alert(
"Invoke sum' call with aobject and result is: "+sum.call(aobject,aobject.a,aobject.b));
    alert(
"Invoke sum' apply with aobject and result is: "+sum.apply(aobject,aobject.c));
    
    
//alert(aobject.sum(5,6));
            
    
</script>



call和apply的等价代码如下:

aobject.func=max;
aobject.func(5,6);
delete aobject.func;



五 function自定义的属性和方法,作用域的说明

在function内部可以自定义属性和方法,如下:

<html>
    
</html>

<script>
    
    
var sum=function(){
        
//Define local variable.
        var size=arguments.length;
        
        
//Define local method hello.
        var hello=function(){
            alert(
"Welcome to max method");            
        };
        
        
        
//Invoking hello method.
        hello();
                        
        
var total=0;
        
while(size>0){
            size
--;
            total
+=arguments[size];
        }
        alert(
"sum is:= "+total);
    }
    
    sum(
1,2,3,4,5);
            
    
</script>

你可以看到,在function内部可以再定义function,既然如此,就涉及到scope(作用域)的问题,思考下面这段代码:
<html>
    
</html>

<script>
    
    
var sum=function(){
        
//Define local variable.
        var size=arguments.length;
        
        
//Define local method check.
        function check(){
            
var k=0;
            
for(k;k<size;k++){
                
if(typeof arguments[k]!="number"){
                    alert(
"All arguments must be number!");
                    
return false;
                }
            }
            
return true;
        };
        
        
//Invoking check method.
        if(!check()){
            
return;
        }
                        
        
var total=0;
        
while(size>0){
            size
--;
            total
+=arguments[size];
        }
        alert(
"sum is:= "+total);
    }
    
    sum(
1,2,3,4,5);            
    
</script>

乍一看,这段代码的合理性不容置疑.在计算sum之前,先用check函数检查所有参数的类型是否正确.但是如果你运行这段代码,会发现总是提示"All arguments must be number!",得不到正确的结果.为什么?

这就是嵌套函数的作用域问题.sum函数有arguments,可别忘了check函数也有arguments!check函数的变量arguments和sum函数的arguments是不同的!

所以,正确的代码实现如下:

<html>
    
</html>

<script>
    
    
var sum=function(){
        
//Define local variable.
        var size=arguments.length;
        
        
//Define local method check.
        var inputs=arguments;
        
function check(){
            
var k=0;
            
for(k;k<size;k++){
                
if(typeof inputs[k]!="number"){
                    alert(
"All arguments must be number!");
                    
return false;
                }
            }
            
return true;
        };
        
        
//Invoking check method.
        if(!check()){
            
return;
        }
                        
        
var total=0;
        
while(size>0){
            size
--;
            total
+=arguments[size];
        }
        alert(
"sum is:= "+total);
    }
    
    sum(
1,2,3,4,5);            
    sum(
1,2,3,"s");
    
</script>

函数能够访问全局变量是软件设计的常识.事实上我们经常用全局变量来共享数据,在一些非常OO的编程语言里,这不是什么问题.但回忆当你初学C语言的时候,无论哪本教材都会对你谆谆善诱:不要使用全局变量.

为什么?为什么在Java的类里,你经常用一些全局变量,而在C语言却不能?

答案是,在Java这类非常OO的编程语言,有包,有类等机制对变量的作用域作了限制.你在类里写的"全局变量",其实仅仅是局部变量,对该变量的改变只是在类/包的范围,对其他程序不影响.但C语言并不是这样,有一个真正的全局作用域,所有include的C文件都共享这个作用域.如果你把该作用域的变量值改了,那么所有其他文件都会受影响.

JavaScript的情况类似于C,请看看如下例子:

<html>
    
</html>

<script>
    
var s="aaa";
    
var a_func=function(){
        alert(s);
        s
="bbb";        
    }
    
var b_func=function(){
        alert(s);
    }
    
    a_func();
    b_func();
    
</script>

在这个简单的例子里,改变全局变量似乎没导致什么大不了的错误.但请你假想以下的情况,某个html导入了a和b两个js文件,a_func位于a文件由你设计,而b_func位于b文件由你同事完成.他的程序代码依赖于变量s,而很不幸s被你改变了.那么你这个可怜的同事估计要花许多时间去检查代码,最后才发现原来是你的代码导致了bug.

如果实在需要使用全局变量,这是一种变通的做法:
<html>
    
</html>

<script>
    
    
var global_object={
        a:
"aaa"
        ,b:
"bbb"
    };
    
    
var a_func=function(){
        alert(global_object.a);
        global_object.a
="kkk";        
    }
    
var b_func=function(){
        alert(global_object.b);
        global_object.b
="sss";
    }
    
    a_func();
    b_func();
    
</script>

这种做法很好.但是如果global_object有几十个变量那怎么办?难保有人不会无意改变其中某个值.

脑筋聪明的程序员借鉴了OO编程语言的包概念,模拟实现了JS的包机制,将变量限制在"包"内,大大减少了全局变量出错的可能.以下代码模仿了Yahoo包机制的实现:

<html>
    
</html>

<script>
    
    
var DGEN={};
    
    
var namespace=function(str){
        
var arr=str.split(".");
        
var size=arr.length;
        
var i=0;
        
var parent;
        
for(i;i<size;i++){
            
if(i==0){
                DGEN[arr[
0]]=DGEN[arr[0]]||{};
                parent
=DGEN[arr[0]];
                
continue;
            }
            parent[arr[i]]
=parent[arr[i]]||{};
            parent
=parent[arr[i]];
        }
    }
    
    
var a_func=function(){
        namespace(
"org.diegochen.hobby");
        DGEN.org.diegochen.hobby.name
="soccer";
        alert(
"DiegoChen's hobby is: "+DGEN.org.diegochen.hobby.name);
    }
    
var b_func=function(){
        namespace(
"com.sun.technique");
        DGEN.com.sun.technique.famous
="Java";
        alert(
"Best thing from sun is: "+DGEN.com.sun.technique.famous);
    }
    
    a_func();
    b_func();
    
</script>

酷吧,现在除非你同事恶意改你的代码,否则全局变量出错的可能性几乎没有了.




六 constructor,this和prototype

运行如下代码

<html></html>
<script>    
    
    
var ainst =new Object();    
    alert(
"a's constructor is\n"+ainst.constructor);
        
    
function MyClass(){};
    
var binst=new MyClass();
    alert(
"b's constructor is\n"+binst.constructor);
    
    
var cinst={};
    alert(
"c's constructor is\n"+cinst.constructor);
    
    
var dinst="s";
    alert(
"d's constructor is\n"+dinst.constructor);
    
    
var einst=5;
    alert(
"d's constructor is\n"+einst.constructor);
    
</script>

很有趣,你会看到对于Object,String,Number这些内建类型,他们的constructor是如下代码:

function Object(){
  [native code]
}
functionNumber(){
  [native code]
}
//.....

在进行OO程序设计时,一般情况下你先要写一堆代码.按照OO术语,这段代码称为class("类"或"类模板").然后就可以用something = new classname()的方式创建对象.这个新创建的对象something称为classinstance(类的实例),而classname称为类的constructor(构造函数).如下Java程序所示:

MyClass something = new MyClass();

这和我们的JS代码很类似

var binst=new MyClass();

MyClass本身是一个普通的JavaScript函数,而它又可以作为constructor 创建其他对象.所以说,JavaScript语言本身是具备OO设计能力的.在上述例子,MyClass是类名,而binst是类MyClass的实例.

类和实例都有了,怎么添加属性(JS里属性和方法等价)?观察以下例子:

<html></html>

<script>
    
    
var People=function(n){
        
this.name=n;
        
var age=30;
    }
    
    People.canSpeak
=true;
    
    
var a=new People("Chen");
    
var b=new People("Li");
    
    a.name
="DiegoChen";
    a.hobby
="football";
    
    alert(
"a's name:"+a.name+" ,a's age:"+a.age+" ,a's hobby:"+a.hobby+" ,a's canSpeak:"+a.canSpeak);    
    alert(
"b's name:"+b.name+" ,b's age:"+b.age+" ,b's hobby:"+b.hobby+" ,b's canSpeak:"+b.canSpeak);
        
    
var diego =new People("Diego");
    diego.working();
    diego.saying();
        
    
</script>


关键字this,表示正在调用构造函数的对象.在JavaScript设计时,实例的属性都通过this来添加.

从如上代码还可以看到,往类里添加的属性并不能被所有类实例引用.为此,JS引入prototype关键字.所有函数都有一个名为prototype的属性,值是一个空对象.添加到prototype里的属性,都能被类的所有实例共享.观察如下例子:

<html></html>

<script>
    
    
var People=function(){this.hello="hello";}
    
    alert(
"People.prototype.age:= "+People.prototype.age);
    
    People.prototype.age
=30;
    
    alert(
"People.prototype.age:= "+People.prototype.age);
    
    
var a =new People();
    
var b=new People();
    alert(
"a has own hello?:= "+a.hasOwnProperty("hello"));
    alert(
"a has own age?:= "+a.hasOwnProperty("age"));
    alert(
"a.age:= "+a.age);
    a.age
=40;
    alert(
"b.age:= "+b.age);
    
    
    People.prototype.age
=50;
    
    alert(
"After People.prototype.age=50, a.age:= "+a.age);
    alert(
"After People.prototype.age=50, b.age:= "+b.age);
    
        
    
</script>

你可以看到,实例a并没有age属性,但是通过a.age却能神奇的得到age的值.而在类的prototype.age改变之后,a.age却不起变化.这是prototype的机制所导致的.prototype的机制可简要描述如下:
  • 所有类实例共享prototype的属性.
  • 由于属性是共享的,所以prototype的属性并不存在于实例之中.假如你访问实例的a属性,JavaScript会先检查实例本身有没有这个属性,如果没有就到类的prototype里寻找.这就是上述代码为什么hasOwnProperty方法返回false而属性却能被访问的原因.
  • 请牢记JS是弱类型语言,对象可以在运行时添加属性.在实例添加了某个属性之后,该属性就会覆盖prototype的同名属性.这解释了为什么上述代码改变了类的prototype.age而a.age却不起作用.
简单记住,prototype是为了实现实例共享类变量这个目的而引入的,如果没有prototype,要达到这个目的就会费许多力气.观察这个例子以加深理解:

<html></html>

<script>
    
    
var People=function(n){
        
this.name=n;
        
    }
    
    People.hello
="hello,world.";
    
    
function getValue(obj,prop){        
        
return obj[prop]||People[prop];
    }
    
    
var a = new People("Diego");
    
    alert(a.hello);
    alert(getValue(a,"name"));
    alert(getValue(a,
"hello"));
        
    
</script>


看,为了调用实例属性我们写出了多么难看的代码,但你不能不这么写,要不你无法使所有实例共享类的变量.有了prototype,生活美好多了. :-)


posted on 2009-06-16 23:17 Chenyunshi 阅读(449) 评论(0)  编辑 收藏 引用 所属分类: JavaScript

只有注册用户登录后才能发表评论。
<2008年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(7)

随笔分类

随笔档案

文章分类

相册

搜索

最新评论

阅读排行榜

评论排行榜