JS中函數(shù)內(nèi)部this的探究

本文參考鏈接 徹底理解js中this的指向知乎回答 以及 JavaScript中的對象查找

下面所說的全部為標(biāo)準(zhǔn)模式下的情況,至于嚴(yán)格模式下的this,請參閱另一篇文章最后一部分內(nèi)容

首先必須要說的是,this的指向在函數(shù)定義的時候是確定不了的,只有函數(shù)執(zhí)行的時候才能確定this到底指向誰,實際上this的最終指向的是那個調(diào)用它的對象。

既然this最終指向調(diào)用它的對象,那么我就首先看下,JS中函數(shù)的調(diào)用分那幾種情況。

JS中函數(shù)調(diào)用共分為四種類型

一:Function Invocation Pattern

諸如foo()的調(diào)用形式被稱為Function Invocation Pattern,是函數(shù)最直接的使用形式,注意這里的foo是作為單獨的變量出現(xiàn),而不是屬性,前面并無調(diào)用對象。
在這種模式下,foo函數(shù)體中的this永遠(yuǎn)為Global對象,在瀏覽器中就是window對象。比如下面的代碼:

function foo(){
    var user = "追夢子";
    console.log(this.user); //undefined
    console.log(this); //Window
}
foo();

即使在函數(shù)內(nèi)部調(diào)用,由于前面并無調(diào)用對象,this還是指向window全局對象,如以下代碼

function printThis() {
  console.log(this)
  let print = function () { console.log(this) };
  print();  
}
printThis()  // 第一行打印 window,第二行打印window
printThis.call([1]);  // 第一行打印[1],第二行打印window
printThis.call([2]);  // 第一行打印[2],第二行打印window

在上面的代碼中,第一條將打印printThis的this,當(dāng)通過call函數(shù)的時候,參數(shù)作為this傳遞到函數(shù)中,因此分別打印[1]和[2]。第二條因為沒有對象調(diào)用print,因此一直打印window。
即使將print改為匿名函數(shù),比如下面的代碼,結(jié)果依然沒有變化。因此此時匿名函數(shù)依然由window調(diào)用

function printThis() {
  console.log(this)
  (function () {
    console.log(this)
  })(); // 匿名函數(shù)和上面的print都由window調(diào)用
}
printThis()  // 第一行打印 window,第二行打印window
printThis.call([1]);  // 第一行打印[1],第二行打印window
printThis.call([2]);  // 第一行打印[2],第二行打印window

但是在ES6中,隨著箭頭函數(shù)的引入,這種情況有所變化,將上面的函數(shù)改為下面的格式:

function printThis() {
  console.log(this)
  let print = () => console.log(this);
  print();
}
printThis()  // 第一行打印 window,第二行打印window
printThis.call([1]);  // 第一行打印[1],第二行打印[1]
printThis.call([2]);  // 第一行打印[2],第二行打印[2]

此時的結(jié)果和上面就有所不同,這是因為在箭頭函數(shù)中,拋棄了自己的this屬性,而是直接使用封閉執(zhí)行上下文的this值。所謂的封閉執(zhí)行上下文,就是箭頭函數(shù)出現(xiàn)的地方的代碼域。此時第一條和第二條打印內(nèi)容相同。
在箭頭函數(shù)中,所有的this原則,無論是標(biāo)準(zhǔn)模式還是嚴(yán)格模式下,都不再生效。

二:Method Invocation Pattern

諸如foo.bar()的調(diào)用形式被稱為Method Invocation Pattern,注意其特點是被調(diào)用的函數(shù)作為一個對象的屬性出現(xiàn),必然會有“.”或者“[]”這樣的關(guān)鍵符號。
在這種模式下,bar函數(shù)體中的this永遠(yuǎn)為“.”或“[”前的那個對象,如上例中就一定是foo對象。比如下面代碼:

var o = {
  a:10,
  b:{
    a:12,
    fn:function(){
      console.log(this.a); //12
    }
  }
}
o.b.fn(); 

雖然首先調(diào)用的對象為o,但最終的函數(shù)fn是對象b的一個屬性,因此this.a為12。
然后我們再看下下面的代碼

var o = {
  a:10,
  b:{
    a:12,
    fn:function(){
      console.log(this.a); //12
    }
  }
}
var j = o.b.fn;  // 只是定義,并未調(diào)用執(zhí)行,真正的調(diào)用執(zhí)行在下面的j()
j(); 

此時的this.a應(yīng)該是多少呢?此時的this其實為window,this.a為undefined。為什么如此呢?回顧一下,在文章的最開始,我們就提到了this的最終指向的是那個調(diào)用它的對象。而我們在使用o.b.fn的時候,并沒有調(diào)用執(zhí)行,真正的調(diào)用執(zhí)行是在下面的j(),此時的情況和第一種方式一樣。

三:Constructor Pattern

new foo()這種形式的調(diào)用被稱為Constructor Pattern,其關(guān)鍵字new就很能說明問題,非常容易識別。
在這種模式下,foo函數(shù)內(nèi)部的this永遠(yuǎn)是new foo()返回的對象。比如下面的代碼:

function Foo () {
  this.x = 1;
}
Foo.prototype.print = function () {
  console.log(this.x);
}

let foo = new Foo();
foo.print(); 
foo.print.call({x: 2});

此時,第一條打印為1,表示this為foo實例對象。this.x為constructor函數(shù)中賦值的1。第二條打印為2,因此為使用了{x: 2}作為對象替換了foo實例對象中的this。
在這里需要注意Function創(chuàng)建對象的一種特殊情況

function Foo () {
  this.x = 1;
  return {x: 2};
}
Foo.prototype.print = function () {
  console.log(this.x);
}
let foo = new Foo();
console.log(foo.x);  // 2
foo.print();  // funciton print undefined

如果構(gòu)建函數(shù)Foo返回的是一個對象,那么foo就會被這個對象所替換掉,此時foo為{x: 2},其中只有一個x屬性等于2,并無print這個方法屬性。但是如果我們修改下代碼,改成下面的格式:

function Foo () {
  this.x = 1;
  return 1;
}
Foo.prototype.print = function () {
  console.log(this.x);
}
let foo = new Foo();
console.log(foo.x);  // 1
foo.print();  // 1

因為構(gòu)建函數(shù)Foo返回的是并不是一個對象,那么foo就不會被替換掉,依然是Foo的一個實例對象,此時的foo.xthis.x全部為1。

四:Apply Pattern

foo.call(thisObject)foo.apply(thisObject)的形式被稱為Apply Pattern,使用了內(nèi)置的callapply函數(shù)。
在這種模式下,callapply的第一個參數(shù)就是foo函數(shù)體內(nèi)的this,如果thisObject是nullundefined,那么會變成window對象。具體代碼,我們在上面的三種模式中已經(jīng)順帶闡述,因此不再贅述。


練習(xí)部分

查看下下面的代碼,分析下打印的結(jié)果,然后實際運行下,看下是否和結(jié)果一致

var x = 0;
function Foo () {
  this.x = 1;
}
Foo.prototype.print = function () {
  console.log(this);
  console.log(this.x);
  (function () {
    console.log(this);
    console.log(this.x)
  })()
}

let foo = new Foo();
foo.print.call({x: 2});

查看代碼,首先注意到Foo對象的print中,第三條和第四條打印是在一個匿名函數(shù)中,此時該匿名函數(shù)的調(diào)用者為window全局變量,因此第四條打印中的this.xwindow.x = 0。再往下看,發(fā)現(xiàn)使用{x:2}代替了print函數(shù)中的this,因此第一條打印為{x: 2},第二條打印為2。

如果修改下代碼,將print的定義改為箭頭函數(shù)呢?結(jié)果又如何?代碼如下:

var x = 0;
function Foo () {
  this.x = 1;
}
Foo.prototype.print = () => {
  console.log(this);
  console.log(this.x);
  (function () {
    console.log(this);
    console.log(this.x)
  })()
}

let foo = new Foo();
foo.print.call({x: 2});

首先,我們知道箭頭函數(shù)不包含this,它的this為執(zhí)行上下文中的this。而且箭頭函數(shù)的執(zhí)行上下文的判定,就在其定義的時刻決定,我們發(fā)現(xiàn),它是在腳本中定義的,此時的作用域為全局,因此此時,前兩條打印和后兩條打印一樣,都為0和window。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,537評論 19 139
  • 函數(shù)參數(shù)的默認(rèn)值 基本用法 在ES6之前,不能直接為函數(shù)的參數(shù)指定默認(rèn)值,只能采用變通的方法。 上面代碼檢查函數(shù)l...
    呼呼哥閱讀 3,703評論 0 1
  • 1.概念 在JavaScript中,this 是指當(dāng)前函數(shù)中正在執(zhí)行的上下文環(huán)境,因為這門語言擁有四種不同的函數(shù)調(diào)...
    BluesCurry閱讀 1,236評論 0 2
  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,944評論 0 5
  • 大多數(shù)人都是需要口語和聽力,單詞量又是基礎(chǔ),如何兼顧? 每天一篇聽力,和跟讀。 單詞不用刻意背,堅持讀書。 有病就...
    Nick_k哥閱讀 273評論 0 0

友情鏈接更多精彩內(nèi)容