前端三部曲:詳解JavaScript中this的指向

啊窩額,發表於2019-01-24

很多初學JavaScript的同學,都找不準函式或者方法中的this到底指向誰,其實並沒有那麼複雜,只需要記住一個口訣就能正確找到this的指向:不管函式或者方法是如何宣告的,要看這個函式或者方法最終是誰呼叫的,誰最終呼叫這個函式或方法,那麼這個函式或方法中的this就是誰.下面從函式呼叫方式著手詳細給大家介紹一下這個口訣.

  1. 作為普通函式中的this
    示例程式碼

    function welcome( ) {
    console.log(“黑馬程式設計師深圳中心前端與移動開發學科歡迎大家!”);
    console.log(this);
    }
    welcome(); //第5行
    window.welcome();//第6行
    大家都知道寫一個全域性函式相當於給window物件新增了一個方法,所以第5行呼叫welcome函式和第6行呼叫welcome函式本質是一樣的,所以welcome函式最終是window物件在呼叫,所以welcome函式內部的this就指向了window物件.執行結果如下.
    在這裡插入圖片描述

  2. 作為物件的方法中的this
    示例程式碼

    var name = “黑馬程式設計師”;
    var obj = {
    name: “傳智播客”,
    sayHi: function () {
    console.log(“深圳就在這裡,黑馬對你不離不棄!”);
    console.log(this);
    }
    };
    obj.sayHi();

最後1行的sayHi方法是由obj物件點出來呼叫的,所以sayHi方法中的this指向obj物件.所以輸出的是obj物件中的name的值, 結果如下:
在這裡插入圖片描述
3. 綜合案例
3.1 案例一

function welcome( ) {
   console.log("黑馬程式設計師深圳中心前端與移動開發學科歡迎大家!");
   console.log(this);
}

var name = "黑馬程式設計師";
var obj = {
   name: "傳智播客",
   sayHi: function () {
       console.log("深圳就在這裡,黑馬對你不離不棄!"); //第10行
       console.log(this);//第11行
  }
};

welcome = obj.sayHi; //第15行
welcome(); //第16行 這個時候,這個welcome函式中的this指向誰呢?

這裡要搞明白的是第15行程式碼的含義, 這句話的作用是把obj物件裡面的sayHi函式的函式體賦值給welcome, 所以在第16行呼叫welcome函式的時候,執行的程式碼其實是obj中sayHi的函式體. 那麼執行的就是第10、11行, 那麼這個this是誰呢? 其實很簡單,不要看這個方法是宣告在obj物件裡面的, 要看這個方法是在第16行呼叫,是window呼叫的,所以此時這個this是window,輸出的結果就是window的name屬性,就是那個全域性name的值, 結果如下:
在這裡插入圖片描述
3.2 案例二

function welcome( ) {
   console.log("黑馬程式設計師深圳中心前端與移動開發學科歡迎大家!"); //第2行
   console.log(this); //第3行
}

var name = "黑馬程式設計師";
var obj = {
   name: "傳智播客",
   sayHi: function () {
       console.log("深圳就在這裡,黑馬對你不離不棄!"); //第10行
       console.log(this);//第11行
  }
};
//變化在這裡.....
obj.sayHi = welcome; //第15行  
obj.sayHi(); //第16行 這個時候,obj.sayH函式中的this指向誰呢?

這裡同樣要搞明白第15行程式碼的含義,這句話的意思是把welcome的函式體賦值給obj物件的sayHi函式, 所以在第16行呼叫obj物件的sayHi函式實際上執行的程式碼是第2行第3行, 那第3行的this是誰呢? 此時不要看這個welcome是怎麼宣告的, 要看這個程式碼是在第16行呼叫的,是obj物件點出來呼叫的,所以this指向obj.結果如下:
在這裡插入圖片描述
3.3 案例三

var name = "黑馬程式設計師";
var p1 = {
   name: "深圳中心",
   dog: {
       name: "旺財",
       sayHi: function(){
           console.log(this);
      }
  }
}
p1.dog.sayHi();

最後一行這裡給人的錯覺好像sayHi方法是p1點出來呼叫的,事實這種想法是錯誤的,p1點出來的只是dog物件, sayHi方法還是由dog點出來呼叫的,所以sayHi方法中的this還是指向dog物件.所以輸出的結果如下:

  1. 建構函式中的this
    4.1 直接呼叫建構函式

    function Student(name, age){
    this.name = name; //第2行
    this.age = age;//第3行
    }

    var s1 = Student(“黑馬”,12); //第6行
    console.log(s1);
    console.log(window.name);
    console.log(window.age);

大家注意看第6行,呼叫Student函式的時候沒有用new關鍵字,那麼這就意味著Student函式是window點出來呼叫的,所以Student函式中的this就是window,那麼第2行第3行就是在給window新增name和age屬性,值分別是黑馬和12. 然後函式內部沒有寫return關鍵字那預設返回值是undefined.所以s1接受到的是undefined. 整個輸出結果如下:
在這裡插入圖片描述
4.2 使用new呼叫建構函式

function Student(name, age){
   this.name = name; //第2行
   this.age = age;//第3行
}

var s1 = new Student("黑馬",12); //第6行
console.log(s1); //第7行

大家都知道new關鍵字會建立一個物件,並且會把建構函式中的this指向這個物件,並且還會把這個物件自動返回, 那麼執行完第6行程式碼, s1就是那個new關鍵字建立並返回的物件.在第2行第3行也已經給這個物件新增了name和age屬性. 所以上面的程式碼執行結果如下:
在這裡插入圖片描述
4.2 為建構函式手動return後的情況
這裡再來看看如果給建構函式新增了return關鍵字會如何,程式碼如下:

    function Student(name, age) {
       this.name = name; //第2行
       this.age = age;//第3行
       console.log(this);
       return [10, 20, 30];
    }

var s1 = new Student("黑馬", 12); //第8行
console.log(s1); //第9行

說到這裡大家就要注意,因為new關鍵字會自動幫我們返回他建立的物件,所以一般在建構函式中是不用寫return返回值的, 那萬一建構函式中就是寫了return返回值會怎麼樣呢?

這個時候就要看return關鍵字後面返回的值是什麼型別的,如果返回的是數值型別/字串型別/布林型別/null/undefined,那麼會忽略這個返回值. 如果返回的值不是這些型別的,比如是一個陣列或者一個物件,那麼這個返回值會覆蓋原來new關鍵字自動返回的那個物件. 所以上面程式碼執行的結果如下:

在這裡插入圖片描述

但是建構函式中的this還是new關鍵字建立的那個物件(驗證看第10行輸出的結果), 只是這個物件返回的時候被return關鍵字返回的陣列給覆蓋了,所以s1接受不到而已.
5. 修改函式中this的預設指向
以上我們討論的是,預設情況下,函式中this的指向。實際上JavaScript提供了一套機制,允許我們對函式中的this指向進行修改,可以讓函式中的this指向我們想要讓他指向的物件。

var obj = {
   name: "張三"
};
function welcome() {
   console.log(this);
}
welcome();

大家現在都知道上述程式碼中welcome函式中this指向window物件, 輸出的是window, 但是我們現在想讓welcome函式中的this指向obj物件,那應該怎麼做呢?

肯定是不能直接修改函式中this的指向的,直接修改會報錯, 因為this被JavaScript設計為只讀,程式碼如下:

var obj = {
   name: "張三"
};
function welcome() {
   this = obj; //這句程式碼在執行的時候,會報錯,無法執行
   console.log(this);
}
welcome();
這個時候,要實現這個效果,要想讓welcome函式中的this指向obj, 就要使用函式上下文呼叫模式,比如call、 apply、 bind。
var obj = {
   name: "張三"
};
function welcome() {
   console.log(this);
}
welcome.call(obj); //使用函式上下文呼叫模式,想要讓welcome中的this指向誰,就把誰傳入.

這個時候,welcome函式被呼叫,但是welcome函式內部的this就指向obj物件了。
在這裡插入圖片描述
關於函式上下文的呼叫模式,具體內容,我們將在後續章節繼續為大家介紹,歡迎大家訂閱關注。

相關文章