看懂Javascript的this關鍵字

  this 是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。

  教科書般的解釋,字都認識,怎麼連在一起還是不知道啥意思呢?

  1 this的值究竟是什麼呢?

  函數的不同場合,this有不同值。

  總的來說,this就是函數運行時所在的環境對象。

  1.1 簡單函數調用

  函數的最通常用法,屬全局性調用,因此this就代表全局對象。

  看下面案例

http://img3.sycdn.imooc.com/5e5de87900014c0910760538.jpg


  1.2 作爲對象方法的調用

  函數還可以作爲某個對象的方法調用,這時this就指這個上級對象。

http://img2.sycdn.imooc.com/5e5de879000137d410180848.jpg


  記住一條:當function被作爲method調用時,this指向調用對象。另外,JavaScript並不是OO的,而是object based的一種語言。

  1.3 構造函數

  所謂構造函數,就是通過這個函數,可以生成一個新對象。這時,this就指這個新對象。

  

http://img3.sycdn.imooc.com/5e5de8790001dba308800290.jpg
http://img2.sycdn.imooc.com/5e5de87a00015b5310940456.jpg


  上面兩套代碼等效 可以寫class test,但本質上new test()的時候,還是test構造函數,差不多,class主要是向java之類的語言抄的,可以直接當java的類用,但本質上test還是個構造函數,因爲js一開始就沒有class 只能用構造函數,函數test運行時,內部會自動有一個this對象可以使用。

http://img1.sycdn.imooc.com/5e5de87a0001631709780604.jpg


  運行結果爲1。爲了表明這時this不是全局對象,我們對代碼做一些改變:

http://img1.sycdn.imooc.com/5e5de87b00014f7b09940684.jpg

運行結果爲2,表明全局變量x的值根本沒變。


  1.4 apply 調用

  apply()是函數的一個方法,作用是改變函數的調用對象。它的第一個參數就表示改變後的調用這個函數的對象。因此,這時this指的就是這第一個參數。

http://img1.sycdn.imooc.com/5e5de87b000192d710080860.jpg


  apply()的參數爲空時,默認調用全局對象。因此,這時的運行結果爲0,證明this指的是全局對象。

  如果把最後一行代碼修改爲

  

http://img1.sycdn.imooc.com/5e5de87c0001d2ee08860234.jpg

運行結果就變成了1,證明了這時this代表的是對象obj。


  2 深入內存分析

  學懂 JavaScript 語言,一個標誌就是理解下面兩種寫法,可能有不一樣的結果。

  

http://img1.sycdn.imooc.com/5e5de87c0001655616541436.jpg

上面代碼中,雖然obj.foo和foo指向同一個函數,但是執行結果可能不一樣。請看下面的例子。

http://img1.sycdn.imooc.com/5e5de87d0001247d19261014.jpg


  對於obj.foo()來說,foo運行在obj環境,所以this指向obj

  對於foo()來說,foo運行在全局環境,所以this指向全局環境。所以,兩者的運行結果不一樣。

  爲什麼會這樣?函數的運行環境到底是誰決定的?爲什麼obj.foo()就是在obj環境執行,而一旦var foo = obj.foo,foo()就變成全局環境執行了?

  帶着靈魂的思考,我們深入解析下

  2.1 內存佈局

  

http://img2.sycdn.imooc.com/5e5de88400012b2714380154.jpg

上面的代碼將一個對象賦值給變量obj.


  JS 引擎會先在內存裏面,生成一個對象{ foo: 5 },然後把這個對象的內存地址賦值給變量obj。

http://img2.sycdn.imooc.com/5e5de8840001c91c04590220.jpg


  變量obj是一個地址(reference)。後面如果要讀取obj.foo,引擎先從obj拿到內存地址,然後再從該地址讀出原始的對象,返回它的foo屬性。

  原始的對象以字典結構保存,每一個屬性名都對應一個屬性描述對象。舉例來說,上面例子的foo屬性,實際上是以下面的形式保存的。

http://img2.sycdn.imooc.com/5e5de88500010e2a07610290.jpg


  {

  foo: {

  [[value]]: 5

  [[writable]]: true

  [[enumerable]]: true

  [[configurable]]: true

  }

  }

  注意,foo屬性的值保存在屬性描述對象的value屬性裏面。

  3 函數

  這樣的結構是很清晰的,問題在於屬性的值可能是一個函數。

http://img3.sycdn.imooc.com/5e5de885000194f420840176.jpg


  引擎會將函數單獨保存在內存中,然後再將函數的地址賦值給foo屬性的value屬性

http://img4.sycdn.imooc.com/5e5de886000116a407740349.jpg


  { foo: {

  [[value]]: 函數的地址

  ...

  }

  }

  由於函數是一個單獨的值,所以它可以在不同的環境(上下文)執行。

  

http://img4.sycdn.imooc.com/5e5de8860001545c17781282.jpg


  4 環境變量

  JavaScript 允許在函數體內部,引用當前環境的其他變量。

http://img1.sycdn.imooc.com/5e5de8880001f2c717920540.jpg

上面代碼中,函數體裏面使用了變量x。該變量由運行環境提供。


  現在問題就來了,由於函數可以在不同的運行環境執行,所以需要有一種機制,能夠在函數體內部獲得當前的運行環境(context)。所以,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。

http://img1.sycdn.imooc.com/5e5de8890001a1f418480512.jpg

上面代碼中,函數體裏面的this.x就是指當前運行環境的x。


  var f = function () { console.log(this.x);

  }var x = 1;var obj = { f: f, x: 2,

  };// 單獨執行f() // 1// obj 環境執行obj.f() // 2

  上面代碼中,函數f在全局環境執行,this.x指向全局環境的x。

  

http://img2.sycdn.imooc.com/5e5de88a0001b0a907810625.jpg


  在obj環境執行,this.x指向obj.x。

  

http://img1.sycdn.imooc.com/5e5de88a0001cbc307830617.jpg


  回到本文開頭提出的問題,obj.foo()是通過obj找到foo,所以就是在obj環境執行。一旦var foo = obj.foo,變量foo就直接指向函數本身,所以foo()就變成在全局環境執行。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章