JavaScript中this指向的深刻解析

普通函數的this指向

簡單說說

首先,按照慣例,咱們先舉個栗子:函數

var bar = 2;
function foo() {
  this.bar = 1;
  this.getBar = function() {
    console.log(this.bar);
  }
}
var test = new foo();
var getBar = test.getBar;

test.getBar();  //1
getBar();       //2

經過這個例子咱們就能看到,雖然是同一個函數,可是實際上獲得的結果卻不同。這個緣由相信你們都能知道。不知道的也告訴你:this實際上是指向調用該函數的那個對象。那麼當咱們在全局環境中調用的時候,this天然就指向了全局環境。this

那麼到是有個問題:this爲何會隨調用者變化而變化?spa

這可能須要你繼續往下看看設計

深刻說說

那麼若是說深層次的理解this的指向,我以爲大概能夠從數據類型講起指針

咱們都知道,棧中存放的是基本數據類型,也就是StringNumberBooleanSymbolNullUndefined這七種數據類型,固然Symbol是ES6新增的一個數據類型。那麼堆中存放的就是一些引用類型了,如ObejctFunction。實際上當咱們定義一個引用類型的時候,js會同時定義一個地址指針指向內存中的對象code

例如:當咱們聲明一個字面量對象時候let a = {num:1};實際上a中存放的是指向{num:1}的地址對象

定義引用類型時

如今咱們解析一下上面那段代碼是如何執行的繼承

// 在全局環境下定義一個變量bar
var bar = 2;

function foo() {
    //在foo中也聲明瞭一個bar
    this.bar = 1;
    
    //在foo中聲明一個getBar函數
    this.getBar = function() {
        console.log(this.bar);
    }
}

//構造函數模式自定義對象,將foo的this賦予test
var test = new foo();

//將test中的getBar方法賦予getBar
var getBar = test.getBar;

//調用test中的getBar
test.getBar();  //1

//調用getBar
getBar();       //2

如今列出來一看,放佛恍然大悟,終於知道爲啥輸出的是不一樣的結果了。那麼我這裏卻是有幾個問題內存

  • 爲何調用同一個函數卻有不一樣的結果?
  • foo中的this是指向foo的,爲何foo中的函數能夠取得外部的this?
  • 爲何this會隨調用它的對象變化而變化?

ok,其實要弄清楚上述問題,咱們須要明白一點,函數也是個引用類型。那麼咱們上面講過,建立引用類型的時候會同時建立一個地址指針。那麼咱們就能夠這樣理解上面的foo對象rem

this.png

實際上foo中的getBar只是存放了一個函數的地址而已*。那麼這個函數並非foo所私有。什麼東西是foo的呢?一個值爲1的bar和一個指向function() {console.log(this.bar);}函數的getBar而已。

這樣咱們就不難理解,爲何調用同一個函數會有不同的結果了,由於這個函數並非foo所私有。比如內存就是深圳,函數就只是深圳的一套房。getBar就是這套房的鑰匙。那麼一開始foo這我的建好了這房子,就他有這房子的鑰匙,那麼固然只有他能進出該房子,後來有一天他把鑰匙多配了一把給了window這好朋友。因而乎window也能進這套房了。給window配鑰匙的過程:var getBar = test.getBar;這裏只是將該函數的地址賦給全局下的getBar而已,房子也只是一套房子,函數仍是一個函數。

因爲函數能夠在不一樣的運行環境執行,因此須要有一種機制,可以在函數體內部得到當前的運行環境(context)。因此,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。

因此當window調用這個函數的時候,this就不是指向foo了。而是指向window。this是指向他們本身。window的衣服不會在進了foo的房子之後就變成foo的衣服。

ok,咱們如今再把剛剛的代碼從新註釋一下

// 在全局環境下定義一個變量bar
var bar = 2;

function foo() {
    //在foo中也聲明瞭一個bar
    this.bar = 1;
    
    //在foo中聲明一個getBar函數,getBar存放該函數的地址
    this.getBar = function() {
        console.log(this.bar);
    }
}

//構造函數模式自定義對象,將foo的this賦予test
var test = new foo();

//將test中的getBar方法的地址賦予全局的getBar
var getBar = test.getBar;

//調用test中的getBar函數
test.getBar();  //1

//調用getBar函數
getBar();       //2

因而乎咱們就把普通的this指向弄明白了。順便還明白了堆棧的區別。接下來看看不普通的函數this指向是如何的

箭頭函數this指向

箭頭函數內沒有this,箭頭函數的this是父級函數的this

// 在全局環境下定義一個變量bar
var bar = 2;

function foo() {
    //在foo中也聲明瞭一個bar
    this.bar = 1;
    
    //在foo中定義一個箭頭函數,getBar存放該函數的地址
    this.getBar = () => {
        console.log(this.bar);
    }
}

//構造函數模式自定義對象,將foo的this賦予test
var test = new foo();

//將test中的getBar方法的地址賦予全局的getBar
var getBar = test.getBar;

//調用test中的getBar函數
test.getBar();  //1

//調用getBar函數
getBar();       //1

若是定義了箭頭函數的狀況下,this執行就不會隨意的改變了。普通函數的this是會跟隨調用者變化,可是箭頭函數就很特別,他只會繼承父級的this並且一旦創建就不會改變了。因此在這裏咱們就能夠看見,儘管全局下面調用getBar,可是實際上仍是取到了foo的this。

所以箭頭函數不能夠用來看成構造函數。由於它自己是沒有this的!

因此箭頭函數使用的話須要與普通函數區別開這點,它的this指向定義函數時候的父級。

後話

關於this就介紹到這裏,若是有什麼不懂的歡迎隨時提問,我會隨時回答你們的問題。

那麼最後,成功不在一朝一夕,咱們都須要努力

相關文章
相關標籤/搜索