JavaScript this關鍵字

艾倫先生發表於2017-12-14

背書

JavaScript由於其執行期繫結的特性,this 的具體指向取決於函式的呼叫方式

  • 作為函式呼叫
  • 作為物件方法呼叫
  • 作為建構函式呼叫
  • 使用 apply 或 call 呼叫

但是無外乎一個原則:*this指向的永遠是當前呼叫函式的那個物件 *。

下面圍繞著這四種呼叫方式中this的不同含義展開敘述

函式呼叫

函式最常見的呼叫方式的方式就是全域性呼叫,此時this就指向全域性物件Global(瀏覽器中為window物件,nodejs中全域性物件為global object)

function func1() {
    return this;
}
alert(func1() === window);//true
複製程式碼

為了證明this就是全域性物件,我們再來一個小例子

var x = 1;  
function test(){  
  this.x = 0;  
}  
test();
console.log(x);//0
複製程式碼

上面的程式碼test()可以理解為window.test(),this.x=0實際上是將window.x進行了修改

嚴格模式中,函式的this值為undefined,因為嚴格模式中禁止this關鍵字指向全域性物件。 發生直接函式呼叫時,函式的this指向全域性物件,接下來執行this.x=xx,相當於隱式的宣告瞭一個全域性物件x,有時候會帶來副作用

我們再來看一個小例子,也是一道經典的筆試題

var value = 2;
var myObj = {  
    value: 3  
};  
var add = function(a,b){  
    return a+b;  
};  
myObj.double = function(){  
  
    var helper = function(){  
        this.value = add(this.value,this.value);  
        alert(this.value)  
    };  
    helper();
};  

myObj.double();
複製程式碼

請求,上面這道題的輸出是什麼的?

如果你的答案是6那麼久成功掉進this陷阱了,正確的答案是4,解釋一下原因:

以上程式碼達不到目的,因為函式呼叫時,helper函式的內部this被繫結到了全域性變數(不信你可以列印一下這個this)。這是語言設計上的一個錯誤,倘若語言設計正確,那麼當內部函式被呼叫時,this應該繫結到外部函式的this變數。這個設計錯誤的後果就是方法不能利用內部函式來幫助它工作,因為內部函式的this被繫結了錯誤的值,所以不能共享該方法物件的訪問權。

幸運的是,有一個很容易的解決方案:如果該方法定義了一個變數並給他賦值this,那麼內部函式就可以通過那個變數訪問到this. 按照約定,我們可以把那個變數命名that:

var myObj = {  
    value :3  
};  
var add = function(a,b){  
    return a+b;  
};  
myObj.double = function(){  
    var that = this;//解決辦法,將myObj這個正確的this快取起來  
  
    var helper = function(){  
        that.value = add(that.value,that.value);  
        alert(that.value)  
    };  
    helper();//以函式的形式呼叫,this指向window全域性物件  
};  
//以方法的形式呼叫,this指向呼叫者,這裡是myObj物件  
myObj.double();//6  
複製程式碼

作為物件方法的呼叫

此時this指向這個上級物件。

var x = 0;
function test(){  
 console.log(this.x);
}  
var o = {};  
o.x = 1;  
o.m = test;  
o.m(); // 1 
複製程式碼

### 作為建構函式呼叫

如果一個函式前面帶上一個new來呼叫,那麼將會建立一個連線到該函式的prototype屬性的新物件,同時函式的this會繫結到那個新物件上

function test(){  
  this.x = 1;  
}  
var o = new test();  
console.log(o.x); // 1  
複製程式碼

上面呼叫 new test()的時候,test()中的this會指向一個空物件,這個物件的原型會指向test.prototype。

再來看一個稍微複雜點的例子

function Func1(){  
    this.a = 37;  
}  
var o = new Func1();  

console.log(o.a)//37

function Func2(){
    this.a= 37; 
     
    return{
        a:38
    };  
}   
o = new Func2();   
console.log(o.a)// ?
複製程式碼

這個題目比較特殊,因為Func2 中return了一個新的物件,所以this繫結到了新返回的物件中,輸出38

一個函式總會有一個返回值,如果沒有明確指定則返回undefined

apply呼叫

apply()是函式物件的一個方法,允許切換函式執行的上下文環境,即 this 繫結的物件。也就是說apply呼叫可以改變函式的呼叫物件,它的第一個引數就表示改變後的呼叫這個函式的物件。因此,this指的就是這第一個引數。apply()的引數為空時,預設呼叫全域性物件

var x = 0;  
function test(){  
  console.log(this.x);  
}  

var o={};  
o.x = 1;  
o.m = test;  
o.m(); //1,this指向o
o.m.apply(); //0 this指向window
o.m.apply(o); //1 this指向o
複製程式碼

再看個簡單的例子

function add(c, d) {
    return this.a + this.b + c + d;
}
var o = {
    a: 1,
    b: 3
};
add.apply(o, [5, 7]); // 1 + 3 + 5 + 7 = 16
複製程式碼

形象點,就是說呼叫add函式的時候,add函式中有些資料無法直接獲取,需要其他物件,如O的幫助,所以add臨時把自身的身份證交給o,讓o臨時幫助add完成操作。

一些關於this的陷阱和實踐

  • setTimeout

setTimeout的執行環境跟呼叫它的函式的執行環境是分離的,隱刺setTimeout呼叫的函式中的this關鍵字指向的是window或global物件

var name = 'somebody';
var angela = {
    name: 'angela',
    say: function () {
        alert("I'm " + this.name);
    }
};

angela.say();//I'm  angela
setTimeout(angela.say, 1000);  //I'm  somebody
複製程式碼

還有,有些DOM的事件回撥函式中,可能涉及到setTimeout(),例如:

$('#myElement').click(function() {

    setTimeout(function() {

        // 這個this指向的是settimeout函式內部,而非之前的html元素

         $(this).addClass('aNewClass');

  }, 1000);

});
複製程式碼

應對之策還是上文提到的聰明的that

$('#myElement').click(function() {
     var that = this;   //設定一個變數,指向這個需要的this

   setTimeout(function() {

        // 這個this指向的是settimeout函式內部,而非之前的html元素

     $(that).addClass('aNewClass');

    }, 1000);

});
複製程式碼

寫在後面

本文是在阮老師的文章基礎加上了我自己的一些想法,強烈推薦阮老師的系列文章

其他文章推薦

相關文章