函数的独立性

  虽然定义函数时可以将函数定义成某个类的方法,或定义成某个对象的方法。但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讲义 ,详细内容请参阅书籍。