Javascript的函数调用与this

2,512 阅读4分钟

js函数调用的四种方式

javascript主要有四种函数的调用方式,分别是方法调用、函数调用、构造器调用、Apply调用,不同之处在于this的初始化。对this关键字有解释:一般而言,在Javascript中,this指向函数执行时的当前对象。
结合《JavaScript语言精粹》,下面用实例的方式对四种方式进行总结和解析。

一、方法调用模式

这种方式即为,一个函数被保存为一个对象的属性,在此时此函数被成为一个方法。调用时this关键字被绑定到该对象,实例如下:

var myObject = {
  value: 0,
  increment: function (inc) {
    this.value += typeof inc === 'number' ? inc : 1;
  }
};

myObject.increment();
console.log(myObject.value);    // 1

myObject.increment(2);
console.log(myObject.value);    // 3

在此例中,increment方法对myObject对象中的value属性进行增加操作,increment函数中的this即指向myObject对象本身。

###二、构造器调用模式
先来看这种调用模式的一个实例:

var Quo = function (string) {
  this.status = string;
};

Quo.prototype.get_status = function () {
  return this.status;
};

var myQuo = new Quo("confused");
console.log(myQuo.get_status());    // confused

输出结果为confused,在使用构造函数构造新的对象时,构造函数的调用会创建一个新的对象,新对象会继承构造函数的属性和方法。在这里使用new来调用时,会创建一个连接到该函数的prototype成员的新对象,同时this会被绑定到这个新对象上
即此时我们创建了myQuo对象,其连接到了Quo的prototype,且创建时的this.status = string;中的this指向这个创建的新对象,status为新对象myQuo的属性,而不是Quo函数prototype的属性,可以做如下验证:

console.log(myQuo.hasOwnProperty("status"));    //true

作为对比,我们对Quo函数增加一个属性id:

var Quo = function (string) {
  this.status = string;
};
Quo.prototype.id = 1;

id为Quo构造器函数prototype对象的一个属性,然后再次进行测试:

var myQuo = new Quo("confused");
console.log(myQuo.id);        //1
console.log(myQuo.hasOwnProperty("get_status"));  //false
console.log(myQuo.hasOwnProperty("id"));    //false

这里可以看到在创建新对象myQuo时没有id属性和get_status函数,而这里myQuo继承了Quo的id属性,就通过原型链找到了Quo.prototype.id的值。

三、函数调用模式

先看一个最简单的实例,在浏览器中:

function myFunction() {
    return this;
}
alert(myFunction());    // [object Window]

当函数没有被自身的对象调用时, this 的值就会变成全局对象。在 web 浏览器中全局对象是浏览器窗口(window 对象)。该实例返回 this 的值即为window对象。也就是此函数即为window对象的函数,myFunction()等同于window.myFunction()
但是此处在使用内部函数时存在一个this指向的问题,看下面的例子:

// 注意此处在node环境和浏览器环境下值不同,
// node环境下必须去掉var使value成为全局变量。
value = 4;

var myObject = {
  value: 1,
  double: function () {
    //var that = this;

    var helper = function () {
      this.value = add(this.value, this.value);
    };

    helper();
  }
};

function add(a, b) {
  return a + b;
}

myObject.double();
alert(value);        // 8
alert(myObject.value);    // 1

在这里的myObject对象中,我们在double这个函数中使用了内部函数并赋值给helper,但是此处的this.value的this指向了全局对象,所以在执行这个函数后全局变量value的值变了但Object中的value属性值仍然是1,这不是我们想要的结果。
《js语言精粹》中指出这里是语言设计上的一个错误,this应该仍然绑定到外部函数的this变量中。这个设计错误的后果就是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。
但是我们可以有一个很容易的解决方案去解决这个问题,对myObject进行修改:

var myObject = {
  value: 1,
  double: function () {

    var that = this;

    var helper = function () {
      that.value = add(that.value, that.value);
    };

    helper();
  }
};

myObject.double();
alert(value);        // 4
alert(myObject.value);    // 2

这里使用了一个that变量来指向double方法中this的值即myObject本身,这样就可以对myObject对象的属性value进行修改。

四、Apply调用模式

先来看一个简单的例子:

// Apply and call
var array = [3, 4];

function add(a, b) {
  return a + b;
}

var sum = add.apply(null, array);
console.log(sum);   // 7

这里主要介绍apply方法的使用,此方法可以让我们构建一个参数数组传递给调用函数而且也允许我们选择this的值,apply方法接受两个参数,第一个是要绑定给this的值,第二个就是一个参数数组。和它相似的有方法call(),两者的区别在于第二个参数: apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始):

// 两者相同
var array = [3, 4];
var sum = add.apply(null, array);
var sum = add.call(add, 3, 4);

另外一个结合Quo构造器调用模式使用的例子:

var statusObject = {
  status: 'A-OK'
};

var status = Quo.prototype.get_status.apply(statusObject);
console.log(status);    // A-OK

在这里即使用了apply并将statusObject作为get_status的this,结果即为A-OK。以上即为四种函数的调用方法。