Zydecx's Site

Debug code, debug life, debug today!

JavaScript函数

Time: , by zydecx

定义函数

最常见的函数定义方法如下所示,该示例定义了一个函数——sayHi。

sayHi();
function sayHi(name, message) {
    alert("hello " + name + "," + message);
}

还可以通过函数表达式定义一个函数,如下例所示,同样定义了函数sayHi,只不过sayHi在这里是作为一个变量出现的,使用typeof sayHi检测得到"function"。但由于JavaScript变量初始化顺序的问题,这种定义方式要求函数必须在声明后才能使用,而不能像前面的示例一样在声明前使用。而且,从这种定义方式,也就不难理解另一个现象:函数可以被覆盖,就像变量被重新赋值一样。

var sayHi = function(name, message) {
    alert("hello " + name + "," + message);
}
sayHi();

函数的参数

  • 没有重载

    JavaScript的函数没有重载。以前面定义的sayHi函数为例,虽然函数声明了namemessage两个参数,但下面几种调用方式均不会出错,即实际传入的参数数量并不需要和函数声明中的形参一致,未传入的参数值为undefined,多余的参数无法直接通过形参访问。从这个角度,JavaScript的函数没有重载的概念。

    sayHi();    // =hello undefined,undefined
    sayHi("John");  // =hello John,undefined
    sayHi("John", "welcome to javascript"); // =hello John,welcome to javascript
    sayHi("John", "welcome to javascript", "2015/08/26")    // =hello John,welcome to javascript
  • arguments对象

    如上所述,实际传入函数的参数数量可以和函数声明中的形参不一致。其实,在调用函数时,会生成一个参数数组对象——arguments,在函数内部可以访问该对象来获取每一个参数。它的特点有:

    • 与数组类似(然而并不是Array实例),可以通过下标访问元素,如arguments[0]访问第一个参数,arguments[1]访问第二个参数,依次类推
    • 参数数组长度不可改变,由传入的参数个数决定的
    • 若有定义形参,则它的值与对应形参的值保持同步,即在函数内对形参的修改会同步到对应的arguments元素上,反之亦然。需要强调的是,形参与arguments对应不同的内存空间,只不过它们的值会同步

函数作为变量

因为函数名本身就是变量,所以函数也可以作为值来使用。即函数既可以被赋值给一个变量,也可以作为参数传递给另一个函数,或者被另一个函数返回。如下面的示例,函数runWithOne接受另一个函数作为参数,并运行传入的函数。

function runWithOne(f) {
    f(1);
}
funWithOne(function(a) {
    alert(a);
});

this属性

this引用

this是函数的一个内部属性,引用的是执行函数的环境对象,当在网页的全局作用域中调用函数时,this对象引用的就是window。需要强调的是,在调用函数之前,this的值是不确定的。如下面的示例所示:

window.color = "red";
var o = {color: "blue"};

function sayColor() {
    alert(this.color);
}

sayColor(); // =red; this = window

o.sayColor = sayColor;
o.sayColor();   // =blue; this = o

改变this引用

可以通过applycallbind方法改变函数的this引用。具体方法为:

  • apply

    函数的内部方法,第一个参数为作用域,第二个参数为参数数组,以前面的递归函数factorial为例。其中,下面的两种调用方式均可。

    function callFactorial(num) {
        return factorial.apply(this, arguments);
        // return factorial.apply(this, [num]);
    }
  • call

    函数的内部方法,第一个参数为作用域,其余的参数直接以形参的形式传递给要调用的函数,而不是参数数组(这是与apply方法唯一的区别)。以前面的递归函数factorial为例。

    function callFactorial(num) {
        return factorial.call(this, num);
    }
  • bind

    该方法创建一个函数实例,唯一的参数为作用域,如下例所示。

    function callFactorial(num) {
        var newFactorial = factorial.bind(this);
        return newFactorial(num);
    }

构造函数

当使用new创建一个函数对象时,会将函数的作用域赋给新建的对象,因此,那时候,this指向的不再是执行函数的环境对象。该问题在后续会继续介绍。

其他函数内部属性

  • arguments

    前面介绍过,arguments存放函数的参数,此外,它还有一个callee属性,指向函数本身。比如下面是一个阶乘算法的示例。

    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * factorial(num - 1);
        }
    }

    但该实现的一个很大缺点就是函数的实现与函数名factorial紧密耦合,如果需要修改函数名,则还需要修改对应的函数实现。下面展示了如何通过callee来解决这个问题:

    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * arguments.callee(num - 1);
        }
    }

    在严格模式下,访问arguments.callee会出错

  • caller

    caller保存了调用当前函数的函数的引用。如果在全局作用域中调用该函数,则其值为null。调用方式如下例所示,其中,使用arguments.callee.caller可以实现更松散的耦合。

    function outer() {
        inner();
    }
    
    function inner() {
        alert(inner.caller);
        alert(arguments.callee.caller);
    }
    
    outer();

This is a magic phrase. You CANNOT see it(I'll really FULE you if you do that), but it does work. Why? You may feel confused. OK, at least it doesn't afftect your experience and it works. That is what we call MAGICE!