Javascript的函式呼叫與this

weixin_33686714發表於2017-11-24

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。以上即為四種函式的呼叫方法。

相關文章