函数、方法、对象和类

  函数是JavaScript语言中的“一等公民”,函数是JavaScript编程里非常重要的一个概念。当使用JavaScript定义一个函数后,实际上可以得到如下4项。
  ➢ 函数:就像Java的方法一样,这个函数可以被调用。
  ➢ 对象:定义一个函数时,系统也会创建一个对象,该对象是Function类的实例。
  ➢ 方法:定义一个函数时,该函数通常都会附加给某个对象,作为该对象的方法。
  ➢ 类:在定义函数的同时,也得到了一个与函数同名的类。
  函数可作为函数被调用,这在前面已经见到过很多例子,此处不再赘述。
  函数不仅可作为函数使用,函数本身也是一个对象,是Function类的实例

例子
<script type="text/javascript">  
    // 定义一个函数,并将它赋给hello变量
    var hello = function(name)
    {
        return name + ",您好";
    }
    // 判断函数是否为Function的实例、是否为Object的实例
    alert("hello是否为Function对象:" + (hello instanceof Function)
        + "\nhello是否为Object对象:" + (hello instanceof Object));
    alert(hello);
</script>  
注意

  如果直接输出函数本身,例如上面程序中的alert(hello);将会输出函数的源代码。

  JavaScript的函数不仅是一个函数,更是一个类,在我们定义一个JavaScript函数的同时,也得到了一个与该函数同名的类,该函数也是该类唯一的构造器。
  因此,当我们定义一个函数后,有如下两种方式来调用函数。
  ➢ 直接调用函数:直接调用函数总是返回该函数体内最后一条return语句的返回值;如果该函数体内不包含return语句,则直接调用函数没有任何返回值。
  ➢ 使用new关键字调用函数:通过这种方式调用总有一个返回值,返回值就是一个JavaScript对象。

例子
<script type="text/javascript">  
    // 定义一个函数
    var test = function(name)
    {
        return "你好," + name ;
    }
    // 直接调用函数
    var rval = test('leegang');
    // 将函数作为类的构造器
    var obj = new test('leegang');
    alert(rval + "\n" + obj); 
</script>  

  下面程序定义了一个Person函数,也就是定义了一个Person类,该Person函数也会作为Person类唯一的构造器。定义Person函数时希望为该函数定义一个方法,程序如下。

例子
<script type="text/javascript">  
    // 定义了一个函数,该函数也是一个类
    function Person(name , age)
    {
        // 将参数name的值赋给name属性
        this.name = name;
        // 将参数age的值赋给age属性
        this.age = age;
        // 为函数分配info方法,使用匿名函数来定义方法
        this.info = function()
        {
            document.writeln("我的名字是:" + this.name + "<br />");
            document.writeln("我的年纪是:" + this.age + "<br />");
        };
    }
    // 创建p对象
    var p = new Person('yeeku' , 29);
    // 执行info方法
    p.info();
</script>  

  上面程序中使用了this关键字,被this关键字修饰的变量不再是局部变量,它是该函数的实例属性。

  正如从上面代码中看到的,JavaScript定义的函数可以“附加”到某个对象上,作为该对象的方法。实际上,如果没有明确指定将函数“附加”到哪个对象上,该函数将“附加”到window对象上,作为window对象的方法。

例子
<script type="text/javascript">  
    // 直接定义一个函数,并未指定该函数属于哪个对象。
    // 该对象默认属于window对象
    var hello = function(name)
    {
        document.write(name + ", 您好<br />");        
    }
    // 以window作为调用者,调用hello函数
    window.hello("孙悟空");
    // 定义一个对象
    var p = {
        // 定义一个函数,该函数属于p对象。
        walk: function()
        {
            for(var i = 0 ; i < 2 ; i++)
            {
                document.write("慢慢地走...");
            }
        }
    }
    p.walk();
</script>  

函数的实例属性和类属性

  由于JavaScript函数不仅仅是一个函数,而且是一个类,该函数还是此类唯一的构造器,只要在调用函数时使用new关键字,就可返回一个Object,这个Object不是函数的返回值,而是函数本身产生的对象。因此在JavaScript中定义的变量不仅有局部变量,还有实例属性和类属性两种。根据函数中声明变量的方式,函数中的变量有3种。
  ➢ 局部变量:在函数中以普通方式声明的变量,包括以var或不加任何前缀声明的变量。
  ➢ 实例属性:在函数中以this前缀修饰的变量。
  ➢ 类属性:在函数中以函数名前缀修饰的变量。
  前面已经对局部变量作了介绍,局部变量是只能在函数里访问的变量。实例属性和类属性则是面向对象的概念:实例属性是属于单个对象的,因此必须通过对象来访问;类属性是属于整个类(也就是函数)本身的,因此必须通过类(也就是函数)来访问。
  同一个类(也就是函数)只占用一块内存,因此每个类属性将只占用一块内存;同一个类(也就是函数)每创建一个对象,系统将会为该对象的实例属性分配一块内存。

例子
<script type="text/javascript">  
    // 定义函数Person
    function Person(national, age)
    {
        // this修饰的变量为实例属性
        this.age = age;
        // Person修饰的变量为类属性
        Person.national =national;
        // 以var定义的变量为局部变量
        var bb = 0;
    }
    // 创建Person的第一个对象p1。国籍为中国,年纪为29
    var p1 = new Person('中国' , 29);
    document.writeln("创建第一个Person对象<br />");
    // 输出第一个对象p1的年纪和国籍
    document.writeln("p1的age属性为" + p1.age + "<br />");
    document.writeln("p1的national属性为" + p1.national + "<br />");
    document.writeln("通过Person访问静态national属性为" 
        + Person.national + "<br />");
    // 输出bb属性
    document.writeln("p1的bb属性为" + p1.bb + "<br /><hr />");
    // 创建Person的第二个对象p2
    var p2 = new Person('美国' , 32);
    document.writeln("创建两个Person对象之后<br />"); 
    // 再次输出p1的年纪和国籍
    document.writeln("p1的age属性为" + p1.age + "<br />");
    document.writeln("p1的national属性为" + p1.national + "<br />");
    // 输出p2的年纪和国籍
    document.writeln("p2的age属性为" + p2.age + "<br />");
    document.writeln("p2的national属性为" + p2.national + "<br />");
    // 通过类名访问类属性
    document.writeln("通过Person访问静态national属性为"
        + Person.national + "<br />");
</script>  

  Person函数的age属性为实例属性,因而每个实例的age属性都可以完全不同,程序应通过Person对象来访问age属性;national属性为类属性,该属性完全属于Person类,因此必须通过Person类来访问national属性,Person对象并没有national属性,所以通过Person对象访问该属性将返回undefined;而bb则是Person的局部变量,在Person函数以外无法访问该变量。

动态增加对象属性

  值得指出的是,JavaScript与Java不一样,它是一种动态语言,它允许随时为对象增加属性和方法,当我们直接为对象的某个属性赋值时,即可视为给对象增加属性。

例子
<script type="text/javascript">  
    function Student(grade , subject)
    {
        // 定义一个grade实例属性,
        // 将grade形参的值赋值给该实例属性。
        this.grade = grade;
        // 定义一个subject静态属性,
        // 将subject形参的值赋值给该静态属性。
        Student.subject = subject;
    }
    s1 = new Student(5, 'Java');
    with(document)
    {
        writeln('s1的grade属性:' + s1.grade + "<br />");
        writeln('s1的subject属性:' + s1.subject + "<br />");
        writeln('Student的subject属性:' + Student.subject + "<br />");
    }
    // 为s1对象的subject属性赋值,即为它增加一个subject属性
    s1.subject = 'Ruby';
    with(document)
    {
        writeln('<hr />为s1的subject属性赋值后<br />');
        writeln('s1的subject属性:' + s1.subject + "<br />");
        writeln('Student的subject属性:' + Student.subject + "<br />");
    }
</script>  

  上面程序为s1的subject属性赋值,赋值后该subject属性值为'Ruby',但这并不是修改Student的subject属性,这行代码仅仅是为s1对象动态增加了一个subject属性。

调用函数的3种方式

  定义一个函数之后,JavaScript提供了3种调用函数的方式。

直接调用函数

  直接调用函数是最常见、最普通的方式。这种方式直接以函数附加的对象作为调用者,在函数后括号内传入参数来调用函数。这种方式是前面最常见的调用方式。当程序使用window对象来调用方法时,可以省略方法前面的window调用者。

以call()方法调用函数

  直接调用函数的方式简单、易用,但这种调用方式不够灵活。有些时候调用函数时需要动态地传入一个函数引用,此时为了动态地调用函数,就需要使用call方法来调用函数了。   假如我们需要定义一个形如each(array, fn)的函数,这个函数可以自动迭代处理array数组元素,而fn函数则负责对数组元素进行处理——此时需要在each函数中调用fn函数,但目前fn函数并未确定,因此无法采用直接调用的方式来调用fn函数,需要通过call()方法来调用函数。

例子
<script type="text/javascript">  
    // 定义一个each函数
    var each = function(array , fn)
    {
        for(var index in array)
        {
            // 以window为调用者来调用fn函数,
            // index、array[index]是传给fn函数的参数
            fn.call(null , index , array[index]);
        }
    }
    // 调用each函数,第一个参数是数组,第二个参数是函数
    each([4, 20 , 3] , function(index , ele)
    {
        document.write("第" + index + "个元素是:" + ele + "<br />");
    });
</script>  

  上面程序中粗体字代码示范了通过call()动态地调用函数,从调用语法来看,不难发现通过call()调用函数的语法格式为:
  函数引用.call(调用者,参数1,参数2...)
  由此可以得到直接调用函数与通过call()调用函数的关系如下:
  调用者.函数(参数1,参数2...)=函数引用.call(调用者,参数1,参数2...)

以apply()方法调用函数

  apply()方法与call()方法的功能基本相似,它们都可以动态地调用函数。apply()与call()的区别如下:
  ➢ 通过call()调用函数时,必须在括号中详细地列出每个参数。
  ➢ 通过apply()动态地调用函数时,可以在括号中以arguments来代表所有参数。

例子
<script type="text/javascript">  
    // 定义一个函数
    var myfun = function(a , b)
    {
        alert("a的值是:" + a
            + "\nb的值是:" + b);
    }
    // 以call()方法动态地调用函数
    myfun.call(window , 12 , 23);
    var example = function(num1 , num2)
    {
        // 直接用arguments代表调用example函数时传入的所有参数
        myfun.apply(this, arguments);
    }
    example(20 , 40);
    // 为apply()动态调用传入数组
    myfun.apply(window , [12 , 23]);
</script>  

  对比上面两行粗体字代码不难发现,当通过call()动态地调用方法时,需要为被调用方法逐个地传入参数;当通过apply()动态地调用方法时,能以arguments一次性地传入多个参数。
  需要指出的是,arguments可代表调用当前函数时传入的所有参数,因此arguments相当于一个数组,所以上面程序使用数组代替了arguments。

注:本博客内容节选自李刚编著的疯狂HTML 5/CSS 3/JavaScript讲义 ,详细内容请参阅书籍。