this
JavaScript的this都是指向一個物件,至於是指向哪一個物件,是在實際執行的時候才會進行綁定,而不是根據這個函式宣告的位置。
this的指派
this的指派大致有以下四種
- 呼叫物件的方法
- 呼叫一般函式
- 呼叫建構子
- 呼叫
Function.prototype.call
或Function.prototype.apply
呼叫物件的方法
var obj = {
a: 1,
getA: function(){
alert(this === obj); //output: true
alert(this.a); //output: 1
}
}
obj.getA();
呼叫一般函式
當函式不作為物件的屬性被呼叫的時候,就等同於一般函式,這個時候的this就會是全域物件。
在瀏覽器的JavaScript,全域物件就是window
物件
window.name = 'globalName';
var getName = function(){
return this.name;
};
console.log(getName()); //output: globalName
再來看這個例子
window.name = 'globalName';
var myObject = {
name: 'sven',
getName: function(){
return this.name;
}
}
var getName = myObject.getName;
console.log(myObject.getName()); // sven
console.log(getName()); // globalName
strict
模式
如果宣告ECMAScript 5
的strict
模式的話,this預設將不會指向全域物件
function func(){
"use strict"
alert(this); // output: undefined
}
func();
呼叫建構子
JavaScript沒有類別,但可以藉由new
運算符呼叫建構子建立物件,使它看起來更像類別。
大部分的函式都可以作為建構子,建構子的外觀和一般函數的外觀一模一樣,只差在呼叫的方式。
當用new
運算符呼叫函式時,這個函式將回傳一個物件,在這個情況下,建構子裡面的this,就是這個函式回傳的物件。
var MyClass = function(){
this.name = 'sven';
};
var obj = new MyClass();
console.log(obj.name); // output: sven
再來看下一個例子,如果建構子明確回傳了一個物件,則this將會是這個明確宣告的新物件,而不是上一個例子中的this
var MyClass = function(){
this.name = 'sven';
return {
name: 'anne';
}
}
var obj = new MyClass();
console.log(obj.name); // output: anne
如果建構子不回傳資料或是回傳一個非物件類型的資料,就不會產生上面的問題
var MyClass = function(){
this.name = 'sven';
return 'anne';
}
var obj = new MyClass();
console.log(obj.name); // output: sven
Function.prototype.call
或Function.prototype.apply
呼叫Function.prototype.call
或Function.prototype.apply
可以動態傳入函式的this
var obj1 = {
name: 'sven',
getName: function(){
return this.name;
}
}
var obj2 = {
name: 'anne';
};
console.log(obj1.getName()); // output: sven
console.log(obj1.getName.call(obj2)); // output: anne
this
消失的先看以下程式碼
var obj = {
myName: 'sven',
getName: function(){
return this.myName;
}
};
console.log(obj.getName()); // output: sven
var getName2 = obj.getName;
console.log(getName2()); // output: undefined
呼叫obj.getName()
時,函式getName
是作為物件的屬性呼叫的,所以第一個輸出結果是 sven
而getName2是宣告了另一個變數,參考了obj.getName
,因此呼叫getName2
時,被當成是一般函式來使用,這個時候的this
是因為是全域物件,而這個全域物件並沒有myName這個變數,因此輸出undefined
,心裡想的那個this就這樣消失了。
最後再來看這個例子
var getId = document.getElementById;
getId('div1');
執行以上的程式碼後,會出現
這是因為宣告一個變數指向document.getElementById函式後,函式裡的this由原來指向document變成window的緣故。
為了修正這個錯誤,我們可以藉由apply
將document作為this帶入getId
<html>
<head>
...
</head>
<body>
<div id="div1">test</div>
</body>
</html>
document.getElementById = (function(func){
return function(){
return func.apply(document,arguments);
}
})(document.getElementById);
var getId = document.getElementById;
var div = getId('div1');
console.log(div.id); // output: div1
call和apply
區別
兩者作用一樣,差別只有參數。
apply接受兩個參數,第一個是指派this的物件;第二個是要傳遞的參數。
var func = function(a,b,c){
alert([a,b,c]); // output: [1,2,3]
}
func.apply(null,[1,2,3]);
call傳遞的參數數量則不固定,愛傳幾個傳幾個,第一個參數也是要指派的this物件,接著就是參數了。
var func = function(a,b,c){
alert([a,b,c]); // output: [1,2,3]
}
func.call(null,1,2,3);
call和apply的用途
- 改變this指派
- 鳩佔鵲巢
改變this指派
var obj1 = {
name: 'sven'
};
var obj2 = {
name: 'anne'
};
window.name = 'window';
var getName = function(){
console.log(this.name);
}
getName(); //output: window
getName.call(obj1); //output: sven
getName.call(obj2); //output: anne
鳩佔鵲巢
鳩佔鵲巢的使用案例之一是實作一個類似繼承的效果
var A = function(name){
this.name = name;
};
var B = function(){
A.apply(this,arguments);
};
B.prototype.getName = function(){
return this.name;
}
var b = new B('sven');
console.log(b.getName()); //output: sven