JS中this關鍵字詳解
this是Javascript語言的一個關鍵字。
它代表函式執行時,自動生成的一個內部物件,只能在函式內部使用。比如,
function test(){
this.x = 1;
}
隨著函式使用場合的不同,this的值會發生變化。但是有一個總的原則,那就是this指的是,呼叫函式的那個物件。
下面分六種情況,詳細討論this的用法。
- 普通函式呼叫
- 作為方法來呼叫
- 作為建構函式來呼叫
- 使用apply/call方法來呼叫
- Function.prototype.bind方法
- es6箭頭函式
一、普通函式呼叫
這是函式的最通常用法,屬於全域性性呼叫,因此this就代表全域性物件Global。請看下面這段程式碼,它的執行結果是1。
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
改變一下:
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x); //0
作為方法來呼叫
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物件指向
作為建構函式來呼叫
function Person(name){
this.name=name;
}
var personA=Person("xl");
console.log(personA.name); // 輸出 undefined
console.log(window.name);//輸出 xl
//上面程式碼沒有進行new操作,相當於window物件呼叫Person("xl")方法,那麼this指向window物件,並進行賦值操作window.name="xl".
var personB=new Person("xl");
console.log(personB.name);// 輸出 xl
//這部分程式碼的解釋見下
new操作符
//下面這段程式碼模擬了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
在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"
使用apply/call方法來呼叫
在JS裡函式也是物件,因此函式也有方法。從Function.prototype上繼承到Function.prototype.call/Function.prototype.apply方法。
call/apply方法最大的作用就是能改變this關鍵字的指向.
var name="XL";
var Person={
name:"xl",
showName:function(){
console.log(this.name);
}
}
Person.showName.call(); //輸出 "XL"
//這裡call方法裡面的第一個引數為空,預設指向window。
//雖然showName方法定義在Person物件裡面,但是使用call方法後,將showName方法裡面的this指向了window。因此最後會輸出"XL";
funtion FruitA(n1,n2){
this.n1=n1;
this.n2=n2;
this.change=function(x,y){
this.n1=x;
this.n2=y;
}
}
var fruitA=new FruitA("cheery","banana");
var FruitB={
n1:"apple",
n2:"orange"
};
fruitA.change.call(FruitB,"pear","peach");
console.log(FruitB.n1); //輸出 pear
console.log(FruitB.n2);// 輸出 peach
FruitB呼叫fruitA的change方法,將fruitA中的this繫結到物件FruitB上。
Function.prototype.bind()方法
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";
修改上面的程式碼:
var name="XL";
function Person(){
this.name="xl";
var that=this;
this.showName=function(){
console.log(that.name);
}
setTimeout(this.showName,50)
}
var person=new Person(); //輸出 "xl"
//這裡在Person函式當中將this賦值給that,即讓that儲存Person物件,因此在setTimeout(this.showName,50)執行過程當中,console.log(that.name)即會輸出Person物件的屬性"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
整理自: