1. 程式人生 > >JS中this關鍵字詳解

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

整理自: