背書
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);
});
複製程式碼
寫在後面
本文是在阮老師的文章基礎加上了我自己的一些想法,強烈推薦阮老師的系列文章
其他文章推薦