一 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(!a || !b || 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称为
class的
instance(类的实例),而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,生活美好多了. :-)