JS中this關鍵字詳解

本文主要解釋在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操做符

  //下面這段代碼模擬了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" 

call/apply方法的調用

在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調用fruitAchange方法,將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";

 

修改上面的代碼:

 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的綁定對象

 

Eval函數

該函數執行的時候,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

 參考資料

相關文章
相關標籤/搜索