函数的独立性
虽然定义函数时可以将函数定义成某个类的方法,或定义成某个对象的方法。但JavaScript的函数是“一等公民”,它永远是独立的,函数永远不会从属于其他类、对象。
例子
<script type="text/javascript">
function Person(name)
{
this.name = name;
// 定义一个info方法
this.info = function()
{
alert("我的name是:" + this.name);
}
}
var p = new Person("yeeku");
// 调用p对象的info方法
p.info();
var name = "测试名称";
// 以window对象作为调用者来调用p对象的info方法
p.info.call(window);
</script>
上面程序为Person类定义了一个info()方法,info()方法只有一行代码,这行代码用于输出this.name实例属性值。程序在第一行粗体字代码处直接通过p对象来调用info()方法,此时p对象的name实例属性为"yeeku",因此程序将会输出"yeeku"。
需要指出的是,JavaScript函数永远是独立的。虽然程序的确是在Person类中定义了info()方法,但这个info()方法依然是独立的,程序只要通过p.info()即可引用这个函数。因此程序在第二行粗体字代码处以call()方法来调用p.info()方法,此时window对象是调用者,因此info()方法中的this代表的就是window对象了,访问this.name将返回"测试名称"。
当使用匿名内嵌函数定义某个类的方法时,该内嵌函数一样是独立存在的,该函数也不是完全作为该类实例的附庸存在,这些内嵌函数也可以被分离出来独立使用,包括成为另一个对象的函数。
例子
<script type="text/javascript">
// 定义Dog函数,等同于定义了Dog类
function Dog(name , age , bark)
{
// 将name、age、bark形参赋值给name、age、bark实例属性
this.name = name;
this.age = age;
this.bark = bark;
// 使用内嵌函数为Dog实例定义方法
this.info = function()
{
return this.name + "的年纪为:" + this.age
+ ",它的叫声:" + this.bark;
}
}
// 创建Dog的实例
var dog = new Dog("旺财" , 3 , '汪汪,汪汪...');
// 创建Cat函数,对应Cat类
function Cat(name,age)
{
this.name = name;
this.age = age;
}
// 创建Cat实例。
var cat = new Cat("kitty" , 2);
// 将dog实例的info方法分离出来,再通过call方法完调用info方法,
// 此时以cat为调用者
alert(dog.info.call(cat));
</script>
上面程序中第一段粗体字代码使用内嵌函数为Dog定义了名为info()的实例方法,但这个info()方法并不完全属于Dog实例,它依然是一个独立函数,所以程序在最后一行将该函数分离出来,并让Cat实例来调用这个info()方法。
函数的参数处理
大部分时候,函数都需要接受参数传递。与Java完全类似,JavaScript的参数传递也全部是采用值传递方式。
基本类型和复合类型的参数传递
对于基本类型参数,JavaScript采用值传递方式,当通过实参调用函数时,传入函数里的并不是实参本身,而是实参的副本,因此在函数中修改参数值并不会对实参有任何影响。
例子
<script type="text/javascript">
// 定义一个函数,该函数接受一个参数
function change(arg1)
{
//对参数值赋值,对实参不会有任何影响
arg1 = 10;
document.write("函数执行中arg1的值为:" + arg1 + "<br/>");
}
// 定义变量x的值为5
var x = 5;
// 输出函数调用之前x的值
document.write("函数调用之前x的值为:" + x + "<br />");
change(x);
document.write("函数调用之后x的值为:" + x + "<br />");
</script>
当使用x变量作为参数调用change()函数时,x并未真正传入change()函数中,传入的仅仅是x的副本,因此在change()中对参数赋值不会影响x的值。
但对于复合类型的参数,实际上采用的依然是值传递方式,只是很容易混淆。而对于复合类型中的变量则只是一个引用(类似于Java的引用变量),该引用指向实际的JavaScript对象,这一点应引起注意。
例子
<script type="text/javascript">
// 定义函数,该函数接受一个参数
function changeAge(person)
{
// 改变person的age属性
person.age = 10;
// 输出person的age属性
document.write("函数执行中person的age值为:"
+ person.age + "<br />");
// 将person变量直接赋为null
person = null;
}
// 使用JSON语法定义person对象
var person = {age : 5};
// 输出person的age属性
document.write("函数调用之前person的age的值为:"
+ person.age + "<br />");
// 调用函数
changeAge(person);
// 输出函数调用后person实例的age属性值
document.write("函数调用之后person的age的值为:"
+ person.age + "<br />");
document.write("person对象为:" + person);
</script>
上面代码中使用了JSON语法创建person对象,传入changeAge()函数中的不再是基本类型变量,而是一个复合类型变量。
我们看到changeAge()函数中最后一行代码,将person对象直接赋值为null,但changeAge()函数执行结束后,后面的person对象依然是一个对象,并不是null,这表明person本身并未传入changeAge()函数中,传入changeAge()函数的依然是副本。
复合类型的变量本身并未持有对象本身,复合类型的变量只是一个引用(类似于Java的引用变量),该引用指向实际的JavaScript对象。当把person复合类型的变量传入changeAge()函数时,传入的依然是person变量的副本——只是该副本和原person变量指向同一个JavaScript对象。因此不管是修改该副本所引用的JavaScript对象,还是修改person变量所引用的JavaScript对象,实际上修改的是同一个对象。
空参数
使用空参数完全没有任何程序问题,程序可以正常执行,只是没有传入实参的参数值将作为undefined处理。
由于JavaScript调用函数时对传入的实参并没有要求,即使定义函数时声明了多个形参,调用函数时也并不强制要求传入相匹配的实参。因此JavaScript没有所谓的函数“重载”,对于JavaScript来说,函数名就是函数的唯一标识。
如果先后定义两个同名的函数,它们的形参列表并不相同,这也不是函数重载,这种方式会导致后面定义的函数覆盖前面定义的函数。
参数类型
JavaScript函数声明的参数列表无须类型声明,这是它作为弱类型语言的一个特征。但JavaScript语言又是基于对象的编程语言,这一点往往非常矛盾,例如下面的代码。
function changeAge(p)
{
p.setAge(34);
}
JavaScript无须类型声明,因此调用函数时,传入的p完全可以是整型变量,或者是布尔型变量,这些类型的数据都没有setAge()方法,但程序强制调用该方法,肯定导致程序出现错误,程序非正常中止。
JavaScript函数定义的参数列表无须类型声明,这一点为函数调用埋下了隐患,这也是JavaScript语言程序不如Java、C语言程序健壮的一个重要原因。
为了解决弱类型语言所存在的问题,弱类型语言方面的专家提出了“鸭子类型(Duck Type)”的概念,他们认为:当你需要一个“鸭子类型”的参数时,由于编程语言本身是弱类型的,所以无法保证传入的参数一定是“鸭子类型”,这时你可以先判断这个对象是否能发出“嘎嘎”声,并具有走路左右摇摆的特征,也就是具有“鸭子类型”的特征——一旦该参数具有“鸭子类型”的特征,即使它不是“鸭子”,程序也可以将它当成“鸭子”使用。
简单地说,“鸭子类型”的理论认为:如果弱类型语言的函数需要接受参数,则应先判断参数类型,并判断参数是否包含了需要访问的属性、方法。只有当这些条件都满足时,程序才开始真正处理调用参数的属性、方法。
例子
<script type="text/javascript">
// 定义函数changeAge,函数需要一个参数
function changeAge(person)
{
// 首先要求person必须是对象,而且person的age属性为number
if (typeof person == 'object'
&& typeof person.age == 'number')
{
//执行函数所需的逻辑操作
document.write("函数执行前person的Age值为:"
+ person.age + "<br />");
person.age = 10;
document.write("函数执行中person的Age值为:"
+ person.age + "<br />");
}
// 否则将输出提示,参数类型不符合
else
{
document.writeln("参数类型不符合" +
typeof person + "<br />");
}
}
// 分别采用不同方式调用函数
changeAge();
changeAge('xxx');
changeAge(true);
// 采用JSON语法创建第一个对象
p = {abc : 34};
changeAge(p);
// 采用JSON语法创建第二个对象
person = {age : 25};
changeAge(person);
</script>
注:本博客内容节选自李刚编著的疯狂HTML 5/CSS 3/JavaScript讲义 ,详细内容请参阅书籍。