[JS]大話this指標

丨大麥發表於2018-05-27

5分鐘快速掌握版

在剛開始學習JavaScript的時候,經常被js中的this及其相關的方法,例如:bind,apply,call方法迷惑。其實我們只要根據包含this指標的方法的用途加以區分,就能瞭解this指標所指向的物件:

1、直接函式呼叫,this指標指向全域性環境,即Window物件,例如

2、物件函式呼叫,this指標指向呼叫函式的物件本身,例如:

var object={
    'name':"vicky",
    'sayName':function(){ console.log(this.name)}
};
object.sayName();   //this指標指向object物件,因此輸出vicky
複製程式碼

3、建構函式呼叫,this指標指向新建立的物件,例如:

function object(name){
    this.name=name;
    console.log(this);      //由於this指向新建立的物件本身,輸出:Object { name: "vikcy" }
    console.log(this.name);  //輸出:"vicky"
}
var myObject=new Object('vicky');  //由於this指向新建立的物件本身
複製程式碼

4、間接函式呼叫,例如call、apply方法,還有個特殊的bind方法

詳細分析this指標

1、直接函式呼叫

this指標指向全域性環境,即Window物件

在全域性環境中直接呼叫函式時,this物件會指向Window物件,例如:

var name='vicky'
function sayName(name){
    console.log(this.name);
}
sayName();   //this指向window物件,而因為在全域性環境中定義了var name='vicky';所以this.name輸出:vicky
window.sayName();  //sayName()效果等同於window.sayName()
複製程式碼

2、物件函式呼叫

this指標指向呼叫函式的物件本身

var name='Bob';
function sayName(){
    console.log(this.name);
}
var object={'name':'vicky'};
object.sayName=sayName;          //sayName沒有寫成sayName(),表示不是執行函式,而是將sayName的指標賦值給object.sayName
object.sayName();               //由於物件函式呼叫方法,this指向物件本身,所以輸出:'vicky'
sayName();                     //由於全域性環境呼叫sayName()等同於window.sayName();輸出:'Bob'
複製程式碼

上面的例子已經很好的說明了在全域性環境直接調函式呼叫和物件函式呼叫時this指標指向的區別。

3、建構函式呼叫

this指標指向新建立的物件

function object(name){
    this.name=name;
    console.log(this);      //由於this指向新建立的物件本身,輸出:Object { name: "vikcy" }
    console.log(this.name);  //輸出:"vicky"
}
var myObject=new Object('vicky');  //由於this指向新建立的物件本身
複製程式碼

開篇的例子已經很好的說明了建構函式呼叫的時候,this指標指向新建立物件的本身。

擴充new關鍵詞(可跳過)

但在這裡我想擴充一下知識,想跟大家聊聊為什麼建構函式呼叫的時候,this指標為什麼指向建立物件的本身,new關鍵字到底做了什麼?

其實在通過new關鍵詞去呼叫建構函式建立物件的時候,經歷瞭如下的過程:

1、建立新物件,繼承建構函式的prototype

2、呼叫call()方法,接受引數並修改this的指向,指向這個新物件

3、執行建構函式,如果建構函式有返回物件,則這個物件會取代步驟1建立的新物件,如果建構函式沒有返回值,則返回步驟1建立的物件。

如果用程式碼模擬這個過程,可以大致參考:

var newObject=function(func){
    var o={};  //建立一個新物件
    o.__proto__=func.prototype;  //繼承建構函式prototype
    var k=func.call(o);         //呼叫call方法,修改this指向,指向這個新建立的物件
    if(typeof k=== 'object'){
        //如果建構函式有返回物件,則取代步驟1建立的物件,返回建構函式所返回的物件
        return k;
    }else{
        //如果建構函式沒有返回物件,則直接返回步驟1建立的物件
        return o;
    }
}
複製程式碼

這段模擬的程式碼有一點缺陷,缺陷就是call方法的時候沒有模擬接受引數的步驟,不過不影響我們對new關鍵詞的理解。

現在我們通過例項,來理解下上面的new關鍵詞運作:

function Dog(name){
    this.name=name;
    console.log(this.name);    //輸出'泰迪'
    console.log(this);        //輸出:Object { name: "泰迪" }
}
Dog.prototype.sayName=function sayName(){
console.log("my name is "+this.name);
}
var animal=new Dog("泰迪");
animal.sayName();       //輸出:'my name is 泰迪'
複製程式碼

上面的這段程式碼,已經能夠證實了以下幾點:

1、建構函式this指標指向新建立的物件

2、建立新物件,繼承建構函式的prototype

3、呼叫call()方法,接受引數並修改this的指向,指向這個新物件

對於

執行建構函式,如果建構函式有返回物件,則這個物件會取代步驟1建立的新物件,如果建構函式沒有返回值,則返回步驟1建立的物件。

這一點,我們在看下下面這個例項:

function Cat(){
    console.log(this);   //輸出:Object {  }
    console.log("cat"); 
}
function Dog(name){
    this.name=name;
    console.log(this.name);
    console.log(this);    //輸出:Object { name: "泰迪" }
    return new Cat();    //關鍵程式碼,在建構函式返回了一個物件
}
var animal=new Dog("泰迪");   //輸出:Object {  }
複製程式碼

這段程式碼我們可以證實:

執行建構函式,如果建構函式有返回物件,則這個物件會取代步驟1建立的新物件,如果建構函式沒有返回值,則返回步驟1建立的物件。

這一點,我們在Dog的建構函式最後返回了一個Cat的物件,所以animal指標指向的是建構函式中返回的new Cat()的物件例項。

4、間接函式呼叫,例如call、apply方法,還有個特殊的bind方法

call(this指標要指向的物件,引數1,引數2,.....)

call方法可以動態的設定函式體內的this指向,例如:

function Cat(age,name){
    this.name='cat';
    console.log(this);
    console.log('cat: age:'+age+",name:"+name);
}
var cat=new Cat(4,'Bob');    //輸出:Cat {name: "cat"}和cat: age:4,name:Bob
Cat.call(this,3,'Tom');     //由於呼叫了call方法,輸出:this指向了Window和cat: age:3,name:Tom
複製程式碼

先看

var cat=new Cat(4,'Bob'); 這行程式碼,這裡的this指向的是新建的Cat物件,輸出的“cat: age:4,name:Bob”也是在物件初始化時候傳入的。

Cat.call(this,3,'Tom'); 這行程式碼,這裡的this指向的是Window物件,因為:我們在全域性環境呼叫了 Cat.call(this,3,'Tom'); 方法,這裡call函式第一個引數(this指標要指向的物件)我們傳入了this,而全域性環境下的this指向的正好就是window物件,而第三和第四個引數,我們分別定義了age=>3,name=>'Tom'

apply(this指標要指向的物件,引數陣列或arguments物件)

apply方法和call方法的作用相同,唯一不同的是call方法要將引數一一傳入,而apply方法傳入的是陣列或者arguments物件。arguments物件包含了函式的所有引數。

例項:

function Cat(age,name){
    this.name='cat';
    console.log(this);
    console.log('cat: age:'+age+",name:"+name);
}
var cat=new Cat(4,'Bob');    //輸出:Cat {name: "cat"}和cat: age:4,name:Bob
Cat.apply(this,[3,'Tom']);     //由於呼叫了apply方法,輸出:this指向了Window和cat: age:3,name:Tom
function getCat(age,name){
    Cat.apply(this, arguments);    //arguments包含了函式的引數
}
getCat(5,"kitty");           //由於呼叫了apply方法,輸出:this指向了Window和cat: age:5,name:kitty
複製程式碼
bind(this指標要指向的物件)

bind這個方法會建立一個函式的例項,其 this 值會被繫結到傳給 bind()函式的值。

window.color = "red"; 
var o = { color: "blue" }; 
function sayColor(){ 
    console.log(this.color); 
    
} 
var objectSayColor = sayColor.bind(o); 
objectSayColor(); //由於呼叫了 sayColor.bind(o),bind函式返回的函式例項中的this直接繫結了o這個物件,
                    所以即使在全域性環境呼叫函式objectSayColor,也會輸出:"blue"
window.objectSayColor();   //輸出:"blue"
複製程式碼

最後覆盤:

所以要了解this指向時,我們應該先弄清楚函式呼叫的方式,最後問自己這個this指向的物件是從何而來。

我是大麥,如果喜歡我的文章,請點個贊。

相關文章