JavaScript this

JavaScript 中的 this

我終於能夠寫這章東西了。拖這麼久的緣由是由於我在寫業務代碼,加上畢設的代碼結構設計搞得我有點懵逼,加上以前確實有點懈怠,而後去寫了個簡易版的 MVVM 雙向綁定和指令渲染的東西,通過那個小項目我發現我對 內存變量閉包this 有了更近一步的理解,而後加上看了點書有了點本身的理解,因而今天記錄一下。git

我所寫的這些東西只是我本身的理解,不表明就是絕對正確的,是我對 JavaScript 的見解和理解。github

我要講的很雜很亂,由於這節原本就很龐大,涉及到了好多東西,我準備從:變量與內存閉包與 this綁定 this 這幾個方面講如下我本身的見解。緩存

變量、內存

這個其實很簡單,咱們在代碼之中寫下了一堆又一堆的變量:bash

let foo = 'test'
const fn = function () {
  let name = 'Nicholas'
  console.log(name)
}
let handler = {
  addHandler: function (name, handler) {},
  fire: function () {}
}
複製代碼

以上咱們定義了: 普通變量函數對象變量。這三種大概就是咱們平時打代碼過程當中定義的最多的東西了。首先要肯定的是,基本數據類型是直接引用,而對象(引用變量)則被存儲在了內存中,咱們沒法直接訪問到內存,只能取得對象的引用。 這句話意思是:變量或者說對象它就安靜地在內存裏(若是沒被垃圾收集機制清除),而咱們定義地變量名只是對其引用。因而:閉包

let fns = {
  sayHi: function () {
    console.log('Hi')
  },
  fight: function () {
    console.log(`I'll fight with you`) } } let ffight = fns.fight 複製代碼

上述代碼中,我本身認爲:首先確定是有兩個函數在內存中躺着,而後 fns.sayHi 指向了其中一個函數,fns.fight指向了另外一個函數,而 ffight 指向的函數和 fns.sayHi 指向的是同一個函數,它們都只是對內存中存在的對象進行了引用 因此,我認爲:內存和變量實際上較爲獨立又緊密聯繫:變量自己存儲在這裏,它只是指向了內存中的某一個對象而已,並沒有它用。app

閉包與this

在我自認爲理清楚了變量與內存的關係以後,我彷佛對閉包與this也有了一點認識。首先上一節閉包中我其實對閉包是什麼已經有了認知,可是那時候對this這個東西有點模糊,只是依稀知道 在理解this時不可用閉包的詞法做用域來認知。 首先看一段代碼:函數

let fn = function () {
  let name = 'Nicholas'
  return function () {
    console.log(name)
  }
}

function getTimeout () {
  setTimeout(fn(), 1000)
}

getTimeOut() // 1秒後輸出 Nicholas
複製代碼

上面這段代碼的結果應該十分簡單,由於 fn() 獲得的函數保留了對 fn 做用域中的 name 的引用,因此依然能夠訪問到 name。 我本身的理解:最開始我認爲 this 就是對當前的引用,至於當前是什麼,我相信不少人都和我一開始同樣認爲 所謂當前就是這行代碼執行時它能訪問到的變量環境,因而就有了下面這段代碼:學習

var name = 'Bob'

function getTimeout () {
  setTimeout(function () {
    console.log(this.name)
  }, 1000)
}

var obj = {
  name: 'Nicholas',
  getTimeout: getTimeout
}

obj.getTimeout() // 1秒後輸出 Bob
複製代碼

這裏我以爲應該先上結論:this指向調用它的執行環境,毫不能用詞法做用域的方法去找this的指向ui

若是照着咱們最開始的理解,當執行這個函數的時候要去訪問 name,咱們天然而然會順着代碼書寫位置去看,咱們在 obj 函數中找到了name,因而咱們覺得會this就是obj,應該輸出 Nicholas。 可是這裏咱們在看看上面這段代碼之中的變量和內存:首先咱們定義了一個函數 getTimeout,它在內存中,在 setTimeout 的參數中咱們其實又定義了一個匿名函數,它也在內存中,並且obj.getTimeout就是那個匿名函數。而後咱們調用了getTimeout, 這個函數中咱們使用定時器去調用那個匿名函數,因而1秒後,至關於執行了這個函數,這個函數就是普普統統的執行了:this

// 像這樣執行了:
// 這裏咱們先取得這個匿名函數的引用,雖然咱們是從新定義了一個新的函數,爲了演示做用,這裏咱們假設 noName 就是那個匿名函數
function noName () {
  console.log(this.name)
}
// 1秒後咱們調用這個匿名函數
noName()
複製代碼

這樣看,結果就比較明顯了,這裏咱們天然能夠去看 name 就是全局的 Bob。 而咱們最多見的作法是緩存this:

var name = 'Bob'

function getTimeout () {
  var that = this
  setTimeout(function () {
    console.log(that.name)
  }, 1000)
}

var obj = {
  name: 'Nicholas',
  getTimeout: getTimeout
}

obj.getTimeout() // 1秒後輸出 Nicholas
複製代碼

咱們平時就是用這種綁定的方式獲得了咱們想要的結果,咱們理一下思路:

咱們知道this的綁定是動態的即它在執行前才能肯定指向,就是由於多個變量能夠指向同一個變量纔有這種說法,obj.getTimeout() 這行代碼執行了內存中的代碼,可是執行這串代碼的環境確實 obj,因而這時候 getTimeout 這個函數中的 this 天然就是 obj

緊接着咱們緩存了 thisthat 變量中,而後1秒以後咱們調用了匿名函數,該函數是個閉包,它擁有了 getTimeout 函數的變量對象的訪問權限,天然就能夠訪問到 that 變量,因而這個 that 就指向了 obj,obj.name天然就是 Nicholas了。

因而咱們獲得了那個結論:函數中的this,實際上是在運行時決定的,雖然咱們運行的是同一段代碼,可是調用這段代碼的方式是不同的,就爲這段代碼中的this綁定了不一樣的值。

綁定this

咱們須要綁定this,才能獲得咱們想要的結果。

  • 隱式綁定:obj.fn(),這個時候咱們經過 obj 調用 fn,其中this天然被 obj 綁定了
  • 硬綁定:fn.call(obj, args)或者fn.apply(obj, args),這樣咱們其實至關於傳入一個對象使它做爲 fn 函數運行時的this。
  • bind函數:bind 是每一個 Function 對象都有的方法,其返回結果是一個函數,使用方法:let foo = bar.bind(obj),這個時候只是將this的指向綁定成obj。
  • new操做符:let bar = new Fn(),咱們在第六章講過 new發生過什麼,因此這邊的this指向就是返回的對象。

既然有多種方式,天然分出優先級:

  1. new
  2. bind
  3. 硬綁定
  4. 隱式綁定

證實:

// 咱們定義一個函數
var name = 'Bob'
var obj1 = {
  name: 'Nicholas',
  test: test
}
var obj2 = {
  name: 'Peter',
}
var obj3 = {
  name: 'Shally'
}
function test () {
  console.log(this.name)
}
// 隱式綁定和硬綁定:
obj1.test() // Nicholas
obj1.test.call(obj2) // Peter
// bind和硬綁定
let fn = test.bind(obj3)
fn() // Shally
fn.call(obj2) // Shally
// new和bind 我以爲這兩個其實沒什麼比較,由於new出來是個對象,這個對象就是this,具體理解仍是看代碼吧
function Person (name) {
  this.name = name
}
// 首先咱們試圖建立另外一個構造函數,讓這個構造函數的this經過bind綁定一個對象
// 所以咱們須要一個空對象
var testObj = {}
// 接着咱們建立另外一個構造函數
var AnotherFn = Person.bind(testObj)
// 緊接着咱們調用這個AnotherFn
AnotherFn('Nicholas')
console.log(testObj.name) // Nicholas
// 接着咱們經過另外一個構造函數 new 一個對象出來
var person = new AnotherFn('Bob')
// 這個結果其實說明,new 這個操做符其實餅不是修改一個對象,而是先建立這個對象,而後this就是這個對象,而後返回這個對象
// 若是bind優先級更高,則這時候 testObj.name 應該是 Bob,而實際狀況像咱們說明並不是如此。
console.log(testObj.name) // Nicholas
console.log(person.name) // Bob
複製代碼

以上就是幾種綁定this的狀況。

結語

寫這一節真的是要死要活。由於其實到寫以前我都不是很清楚this的語法,只是模棱兩可,如今有些理解,就趕忙記錄一下。可是我以爲我如今的理解應該也是瑕疵挺多的吧。一步一步來,慢慢學習。

應該會挑個時間,寫一下前兩天寫的 MVVM 的那個思路,感受仍是有收穫的,雖然別人以爲很簡單,可是本身寫起來仍是印象深入些。

持續更新在github

相關文章
相關標籤/搜索