如果對JavaScript的關鍵字this
理解不夠深刻,有時候會掉入意想不到的坑。在這裏我們總結了5條通用規則來幫助你判斷this
到底指向什麼。雖然沒有囊括所有的情況,但日常大部分情況都可以使用這些規則來正確推斷。
1. this 的值通常是由所在函數的執行環境決定,也就是說要看函數是如何被調用的;
2. 同一個函數每一次調用,this
都可能指向不同的對象;
全局對象 (Global Object)
打開Chrome瀏覽器開發者面板(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),並且輸入:
console.log(this);
看看輸出了什麼?.
// Window {}
window
對象! 因爲在全局作用域下,this
指向全局對象。在瀏覽器中全局對象就是window
對象。
爲了讓你更加清楚理解爲什麼this
會指向window
對象,我們來看另外一個例子:
var myName = 'Brandon';
我們可以通過在控制檯輸入myName
來訪問其值:
myName
// 輸出 'Brandon'
其實,所有在全局定義的變量都綁定到window
對象。我們來做如下測試:
window.myName
// 輸出 'Brandon'
window.myName === myName
// 輸出 true
現在我們將this
放到函數內部,看看有何效果。
function test(){
return this;
}
test();
你會發現this
依然指向全局的window
對象。因爲this
關鍵字沒有處於一個聲明的對象內部,默認指向全局window
對象。這一點可能對於大部分初學者來說有點難以理解。當讀完這篇文章,你就會豁然開朗。
注意:如果在strcit
模式下,上面的例子中this
爲undefined
。
聲明的對象 (Declared Object)
當this
關鍵字在一個聲明對象內部使用,其值會被綁定到調用該this
的函數的最近的父對象。我們用例子來說明這個問題:
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this.first + ' ' + this.last);
}
};
person.full();
// 輸出 'John Smith'
在被聲明的對象person
中的full
函數裏面使用了this
, 那麼調用this
的full
函數的最近的父對象就是person
, 因此,this
指向person
。
爲了更好的描述this
實際上是指向person
對象的,你可以拷貝如下代碼到瀏覽器控制檯,將this
打印出來。
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this);
}
};
person.full();
// 輸出 Object {first: "John", last: "Smith", full: function}
我們接下來看一個更復雜的例子:
var person = {
first: 'John',
last: 'Smith',
full: function() {
console.log(this.first + ' ' + this.last);
},
personTwo: {
first: 'Allison',
last: 'Jones',
full: function() {
console.log(this.first + ' ' + this.last);
}
}
};
在這裏我們有嵌套的對象,在這個時候,this
分別指向誰呢? 我們將其打印出來看一看:
person.full();
// 輸出 'John Smith'
person.personTwo.full();
// 輸出 'Allison Jones'
你會發現滿足我們前面所描述的規則:其值會被綁定到調用this
的函數的最近的父對象。
new
關鍵字
當使用new
關鍵字構建一個新的對象,this
會綁定到這個新對象。我們來看一個例子:
function Car(make, model) {
this.make = make;
this.model = model;
};
根據第一條規則,你可能會推斷this
指向全局對象。但是如果我們使用new
關鍵字來聲明一個新的變量,Car
函數中的this
將會綁定一個新的空對象,然後初始化this.make
和this.model
的值。
var myCar = new Car('Ford', 'Escape');
console.log(myCar);
// 輸出 Car {make: "Ford", model: "Escape"}
call
, bind
, 和apply
我們可以顯示的在call()
,bind()
,apply()
中設置this
的綁定對象。這三個函數很類似,但是我們需要注意它們微小的區別。
我們來看一個例子:
function add(c, d) {
console.log(this.a + this.b + c + d);
}
add(3,4);
// 輸出 NaN
add
函數輸出NaN
, 因爲this.a
和this.b
未定義。
現在我們引入對象,並且使用call()
和apply()
來調用:
function add(c, d) {
console.log(this.a + this.b + c + d);
}
var ten = {a: 1, b: 2};
add.call(ten, 3, 4);
// 輸出 10
add.apply(ten, [3,4]);
// 輸出 10
當我們使用add.call()
,第一個參數是this
需要綁定的對象,剩下的是add
函數本來的參數。
因此,this.a
指向ten.a
,this.b
指向ten.b
。add.apply()
也類似,除了第二個參數是一個數組,用於存儲add
函數的參數。
bind()
函數和call()
類似,但是bind()
函數不會立即被調用。bind()
函數會返回一個函數,並且將this
綁定好。接下來我們來用例子來幫助理解bind()
函數的應用場景:
var small = {
a: 1,
go: function(b,c,d){
console.log(this.a+b+c+d);
}
}
var large = {
a: 100
}
small.go(2, 3, 4);
// 輸出 10
如果我們想使用large.a
的值,而不是small.a
呢? 我們可以使用call/apply
:
small.go.call(large, 2, 3, 4);
// 輸出 109
但是,如果我們現在還不知道這三個參數應該傳入什麼值,應該怎麼辦呢? 我們可以使用bind
:
var bindTest = small.go.bind(large, 2);
如果我們將bindTest
在控制檯下打印出來,我們會看到:.
console.log(bindTest);
// 輸出 function (b,c,d){console.log(this.a+b+c+d);}
注意:該函數已經將this
綁定到large
對象,並且傳入了第一個參數b
。所以,我們接下來是需要傳入餘下的參數即可:
bindTest(3, 4);
// 輸出 109
當然談到這還有 ES 6 的箭頭函數,下次文章和大家分享!