本文主要解釋在JS裏面this關鍵字的指向問題(在瀏覽器環境下)。javascript
閱讀此文章,還須要心平氣和的閱讀完,相信必定會有所收穫,我也會不按期的發佈,分享一些文章,共同窗習html
首先,必須搞清楚在JS裏面,函數的幾種調用方式:java
普通函數調用git
做爲方法來調用es6
做爲構造函數來調用github
使用apply/call方法來調用web
Function.prototype.bind方法瀏覽器
es6箭頭函數app
可是無論函數是按哪一種方法來調用的,請記住一點:誰調用這個函數或方法,this關鍵字就指向誰。函數
接下來就分狀況來討論下這些不一樣的狀況:
1 function person(){ 2 this.name="xl"; 3 console.log(this); 4 console.log(this.name); 5 } 6 7 person(); //輸出 window xl 8
在這段代碼中person
函數做爲普通函數調用,實際上person
是做爲全局對象window
的一個方法來進行調用的,即window.person()
;
因此這個地方是window
對象調用了person
方法,那麼person
函數當中的this
即指window
,同時window
還擁有了另一個屬性name
,值爲xl
.
1 var name="xl"; 2 function person(){ 3 console.log(this.name); 4 } 5 person(); //輸出 xl
一樣這個地方person
做爲window
的方法來調用,在代碼的一開始定義了一個全局變量name
,值爲xl
,它至關於window
的一個屬性,即window.name="xl"
,又由於在調用person
的時候this
是指向window
的,所以這裏會輸出xl
.
在上面的代碼中,普通函數的調用便是做爲window
對象的方法進行調用。顯然this
關鍵字指向了window
對象.
再來看下其餘的形式
var name="XL"; var person={ name:"xl", showName:function(){ console.log(this.name); } } person.showName(); //輸出 xl //這裏是person對象調用showName方法,很顯然this關鍵字是指向person對象的,因此會輸出name var showNameA=person.showName; showNameA(); //輸出 XL //這裏將person.showName方法賦給showNameA變量,此時showNameA變量至關於window對象的一個屬性,所以showNameA()執行的時候至關於window.showNameA(),即window對象調用showNameA這個方法,因此this關鍵字指向window
再換種形式:
var personA={ name:"xl", showName:function(){ console.log(this.name); } } var personB={ name:"XL", sayName:personA.showName } personB.sayName(); //輸出 XL //雖然showName方法是在personA這個對象中定義,可是調用的時候倒是在personB這個對象中調用,所以this對象指向
1 function Person(name){ 2 this.name=name; 3 } 4 var personA=Person("xl"); 5 console.log(personA.name); // 輸出 undefined 6 console.log(window.name);//輸出 xl 7 //上面代碼沒有進行new操做,至關於window對象調用Person("xl")方法,那麼this指向window對象,並進行賦值操做window.name="xl". 8 9 var personB=new Person("xl"); 10 console.log(personB.name);// 輸出 xl 11 //這部分代碼的解釋見下
//下面這段代碼模擬了new操做符(實例化對象)的內部過程 function person(name){ var o={}; o.__proto__=Person.prototype; //原型繼承 Person.call(o,name); return o; } var personB=person("xl"); console.log(personB.name); // 輸出 xl
這段代碼涉及到了_proto_及prototype的概念,若是有須要瞭解,請點擊連接
在person
裏面首先建立一個空對象o,將o的proto指向Person.prototype完成對原型的屬性和方法的繼承
Person.call(o,name)
這裏即函數Person
做爲apply/call
調用(具體內容下方),將Person
對象裏的this
改成o,即完成了o.name=name
操做
返回對象o。
所以`person("xl")`返回了一個繼承了`Person.prototype`對象上的屬性和方法,以及擁有`name`屬性爲"xl"的對象,並將它賦給變量`personB`. 因此`console.log(personB.name)`會輸出"xl"
在JS裏函數也是對象,所以函數也有方法。從Function.prototype上繼承到Function.prototype.call/Function.prototype.apply
方法call/apply
方法最大的做用就是能改變this
關鍵字的指向.
Obj.method.apply(AnotherObj,arguments);
1 var name="XL"; 2 var Person={ 3 name:"xl", 4 showName:function(){ 5 console.log(this.name); 6 } 7 } 8 Person.showName.call(); //輸出 "XL" 9 //這裏call方法裏面的第一個參數爲空,默認指向window。 10 //雖然showName方法定義在Person對象裏面,可是使用call方法後,將showName方法裏面的this指向了window。所以最後會輸出"XL"; 11 funtion FruitA(n1,n2){ 12 this.n1=n1; 13 this.n2=n2; 14 this.change=function(x,y){ 15 this.n1=x; 16 this.n2=y; 17 } 18 } 19 20 var fruitA=new FruitA("cheery","banana"); 21 var FruitB={ 22 n1:"apple", 23 n2:"orange" 24 }; 25 fruitA.change.call(FruitB,"pear","peach"); 26 27 console.log(FruitB.n1); //輸出 pear 28 console.log(FruitB.n2);// 輸出 peach
FruitB
調用fruitA
的change
方法,將fruitA
中的this
綁定到對象FruitB
上。
var name="XL"; function Person(name){ this.name=name; this.sayName=function(){ setTimeout(function(){ console.log("my name is "+this.name); },50) } } var person=new Person("xl"); person.sayName() //輸出 「my name is XL」; //這裏的setTimeout()定時函數,至關於window.setTimeout(),由window這個全局對象對調用,所以this的指向爲window, 則this.name則爲XL
那麼如何才能輸出"my name is xl"
呢?
var name="XL"; function Person(name){ this.name=name; this.sayName=function(){ setTimeout(function(){ console.log("my name is "+this.name); }.bind(this),50) //注意這個地方使用的bind()方法,綁定setTimeout裏面的匿名函數的this一直指向Person對象 } } var person=new Person("xl"); person.sayName(); //輸出 「my name is xl」;
這裏setTimeout(function(){console.log(this.name)}.bind(this),50);
,匿名函數使用bind(this)
方法後建立了新的函數,這個新的函數無論在什麼地方執行,this
都指向的Person
,而非window
,所以最後的輸出爲"my name is xl"而不是"my name is XL"
另外幾個須要注意的地方:setTimeout/setInterval/匿名函數執行
的時候,this
默認指向window對象
,除非手動改變this的指向。在《javascript高級程序設計》當中,寫到:「超時調用的代碼(setTimeout
)都是在全局做用域中執行的,所以函數中的this的值,在非嚴格模式下是指向window對象,在嚴格模式下是指向undefined」。本文都是在非嚴格模式下的狀況。
var name="XL"; function Person(){ this.name="xl"; this.showName=function(){ console.log(this.name); } setTimeout(this.showName,50); } var person=new Person(); //輸出 "XL" //在setTimeout(this.showName,50)語句中,會延時執行this.showName方法 //this.showName方法即構造函數Person()裏面定義的方法。50ms後,執行this.showName方法,this.showName裏面的this此時便指向了window對象。則會輸出"XL";
修改上面的代碼:
1 var name="XL"; 2 function Person(){ 3 this.name="xl"; 4 var that=this; 5 this.showName=function(){ 6 console.log(that.name); 7 } 8 setTimeout(this.showName,50) 9 } 10 var person=new Person(); //輸出 "xl" 11 12 13 14 //這裏在Person函數當中將this賦值給that,即讓that保存Person對象,所以在setTimeout(this.showName,50)執行過程中,console.log(that.name)即會輸出Person對象的屬性"xl"
匿名函數:
1 var name="XL"; 2 var person={ 3 name:"xl", 4 showName:function(){ 5 console.log(this.name); 6 } 7 sayName:function(){ 8 (function(callback){ 9 callback(); 10 })(this.showName) 11 } 12 } 13 person.sayName(); //輸出 XL 14 var name="XL"; 15 var person={ 16 name:"xl", 17 showName:function(){ 18 console.log(this.name); 19 } 20 sayName:function(){ 21 var that=this; 22 (function(callback){ 23 callback(); 24 })(that.showName) 25 } 26 } 27 person.sayName() ; //輸出 "xl" 28 //匿名函數的執行一樣在默認狀況下this是指向window的,除非手動改變this的綁定對象
該函數執行的時候,this綁定到當前做用域的對象上
var name="XL"; var person={ name:"xl", showName:function(){ eval("console.log(this.name)"); } } person.showName(); //輸出 "xl" var a=person.showName; a(); //輸出 "XL"
es6
裏面this
指向固定化,始終指向外部對象,由於箭頭函數沒有this
,所以它自身不能進行new
實例化,同時也不能使用call, apply, bind
等方法來改變this
的指向
function Timer() { this.seconds = 0; setInterval( () => this.seconds ++, 1000); } var timer = new Timer(); setTimeout( () => console.log(timer.seconds), 3100); // 3 // 在構造函數內部的setInterval()內的回調函數,this始終指向實例化的對象,並獲取實例化對象的seconds的屬性,每1s這個屬性的值都會增長1。不然最後在3s後執行setTimeOut()函數執行後輸出的是0