iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 25
1

前言

今天要介紹的東西可以說是全部 JavaScript 中最抽象最難理解的東西了,想當年筆者在學 JavaScript 的時候不曉得被 this 這個東西雷了多少次,每次都不曉得 this 到底等於什麼東西,今天就讓我們來探討一下什麼是 this

關於this

thisJavaScriptfunction 的一個關鍵字,在開始正式介紹 this 之前先來講個重要觀念。

this不是一個固定值

this 會隨著 當前的擺放的位置 而有所變化,正常來說 this 會等於 window ,大家不妨可以直接在 dev tool 上面打 console.log(this) 看看是不是真的跑出 window object ,但在某些情況下 this 的值其實不是等於 window ,這邊用一個例子來說明。

const obj = {
  name: 'Andy',
  getName: function() {
    console.log(this.name)
  }
}
const foo = obj.getName
obj.getName()  // 輸出 'Andy'
foo()          // 輸出 undefined

這時候你可能已經懵了, foo()obj.getName() 兩個不是一樣的嗎?都是 function() { console.log(this.name) } 怎麼一個會輸出正確的值另一個不會。

還記得上面筆者特別粗體的文字嗎: this 會隨著 當前的擺放的位置 而有所變化,雖然兩個都是一樣的 function ,可是對於 foo() 來說它就只是個單純的 function() { console.log(this.name) } ,所以這時候的 this.name 其實就是 window.name 的意思,我們也沒有宣告任何全域變數所以會輸出 undefined 也是正常的。

對於 obj.getName() 來說,這就是進行物件 內部 的操作,這時候的 this 不是 window 而是 物件本身 ,所以這時候的 this.name 其實就是 obj.name 也因此 obj.getName() 會輸出正確的值。

如何判斷this

眼尖的讀者應該有發現筆者特別粗體 內部 兩個字,其實 this 的判斷非常簡單,只要檢查該操作是否是針對 物件本身 進行操作就可以很明確的判斷 this 了,這邊用一個例子來說明,讀者不妨可以思考一下底下會分別輸出什麼。

window.a = 1
const obj = {
  a: 10,
  show: function() {
    console.log(this.a)    // 輸出?
    function foo() {
      console.log(this.a)  // 輸出?
    }
    foo()
  }
}
obj.show()

如果你的答案都是 10 的話,那你可能要重看一下前面提到的觀念了,重新想一下 this的擺放的位置 後應該就會有正確解答了。

正確答案是 101 ,你可能會想說 foo() 在物件 內部 所以 foothis 應該也是指向物件本身吧。

還記得昨天提到的 閉包 嗎?外層與內層的 function 彼此是互相獨立的,如果要使用 內層function 就必須要在 外層 呼叫,這樣呼叫 外層 時才會跑 內層function ,所以 foo() 其實就只是個單純的 function 而已,也因此 foo()this 就會是 window

假如我一定要讓 foo() 可以正確輸出 10 要怎麼做呢?其實很簡單既然我們無法改變 foo()this 那我們就把外層的 this 當作是變數傳給 foo() 用就好啦!就像下面這樣。

window.a = 1
const obj = {
  a: 10,
  show: function() {
    console.log(this.a)    // 輸出10
    let that = this
    function foo() {
      console.log(that.a)  // 輸出10
    }
    foo()
  }
}
obj.show()

常見的this用法

  • call(this, 參數1, 參數2, ...)

    使用給定的 this 讓其他 function 也可以收到不同於自身的 this 以及參數,舉例來說。

    const obj = {
      name: 'Andy'
    }
    const obj2 = {
      name: 'andy',
      getName: function(age) {
        console.log(this.name, age)
      }
    }
    obj2.getName(18)            // 輸出 'andy' 18
    obj2.getName.call(obj, 18)  // 輸出 'Andy' 18
    
  • apply(this, [參數1, 參數2, ...])

    基本上跟 call() 一模一樣,差別只在於 apply() 不能傳入多個參數,參數必須要用陣列包起來。

    const obj = {
      name: 'Andy'
    }
    const obj2 = {
      name: 'andy',
      getName: function(age) {
        console.log(this.name, age)
      }
    }
    obj2.getName(18)              // 輸出 'andy' 18
    obj2.getName.apply(obj, [18]) // 輸出 'Andy' 18
    
  • bind(this, 參數1, 參數2, ...)

    基本上跟 call() 還有 apply() 一模一樣但差別是 bind() 會回傳一個綁定 this 過後的 function ,不像 call()apply() 是不會回傳 function 的,而 bind() 本身也可以接受多個參數。

    const obj = {
      name: 'Andy'
    }
    const obj2 = {
      name: 'andy',
      getName: function(age) {
        console.log(this.name, age)
      }
    }
    const foo = obj2.getName.bind(obj, 18)
    obj2.getName(18)    // 輸出 'andy' 18
    foo()               // 輸出 'Andy' 18
    

this番外篇1

既然都進入 ES6 的時代了,而 ES6 最著名寫 function 的方式就是 arrow function 了,所以這邊當然要來提一下 arrow functionthis 之間的關係,這邊筆者故意把程式碼寫的詳細一點方便大家好思考輸出的結果可能會是什麼,大家再往下看輸出解答之前不妨先好好思考一下。

const obj = {
  name: 'Andy',
  getName: () => console.log(this.name)
}
obj.getName()  // 輸出?

如果你的答案是 'Andy' 的話,恭喜你!!你已經掉入 this 的陷阱了。

正確答案是 undefined

因為 arrow function 自己沒有 this

因為 arrow function 自己沒有 this

因為 arrow function 自己沒有 this

因為很重要所以要講三次,那上面的程式碼要怎麼改寫才能正確的顯示出數值呢?

由於 arrow function 沒有 this 也因此無法藉由 call()apply()bind() 來使用其他的 this ,這時候只能回歸最原始的寫法在外層乖乖用 function() {} 吧!

當然你也可以使用 arrow function 只是這樣就會變成閉包的寫法而且這個閉包還沒有甚麼作用,就變得多此一舉。

const obj = {
  name: 'Andy',
  getName: function() {
    return () => console.log(this.name)
  }
}
const foo = obj.getName()
foo()    //輸出 'Andy'

this番外篇2

ES6 的時代裡面也提到了一個新的語法 classclass 可以說是用 this 用得最兇的語法了,這邊筆者稍微用個範例來介紹一下 class 內的 this

class Rectangle {
  constructor(width, height) {
    this.width = width
    this.height = height
  }
  
  getRectInfo() {
    return console.log(this.width, this.height)
  }
}
const newRect = new Rectangle(30, 20)
newRect.getRectInfo()    // 輸出?

相信大家應該很快就可以猜到會輸出 30 20 了,其實 classthis 相當直觀, classthis 就是 class自身 ,如果上面 物件 中的 this 有看懂這邊 class 應該是沒有問題的。

這邊筆者稍微談一下 class 裡面寫到的 constructor

constructorclass 的建構子,我覺得這個翻譯真的不錯,建構就是建立結構,所以當我們要初始化一個 class 的時候第一個跑的就是 constructor ,而 constructor 有個優點就是在初始化後會自動把 constructorthis 所定義到的內容生成一個物件,所以 const newRect = new Rectangle(30, 20) 這時候 newRect 就會是個 {width: 30, height: 20} 的物件。

this解法

最後來總結一下上面提到的各種 this 用法,還記得這個例子嗎?

const obj = {
  a: 10,
  show: function() {
    console.log(this.a)    // 輸出10
    let that = this
    function foo() {
      console.log(that.a)  // 輸出10
    }
    foo()
  }
}
obj.show()

其實除了將外層的 this 傳遞給內層使用外還有其他的解法。

  • bind外層的this

    只要把物件的 this 利用 bind() 來綁定給內層的 foo() 就可以達到 foo()this 與物件自身的 this 是一樣的,自所以不用 call()apply() 的原因是因為這兩個都不會回傳一個新的且被綁定 this 之後的 function ,所以上面的範例碼就可以改成這樣。

    const obj = {
      a: 10,
      show: function() {
        console.log(this.a)    // 輸出10
        function foo() {
          console.log(this.a)  // 輸出10
        }
        const newFoo = foo.bind(this)
        newFoo()
      }
    }
    obj.show()
    

    筆者故意寫得詳細一點以方便讀者了解 foo.bind(this) 這個 this 就是物件自身的 this 而非 window

  • arrow function

    還記得筆者特別強調 arrow function 沒有 this 這件事嗎?這時候就派上用場了,只要把 foo() 變成 arrow function ,這時候因為 arrow function 本身沒有 this 所以在 arrow function 寫的 this 就會被判定為物件自身的 this ,寫法就會像這樣。

    const obj = {
      a: 10,
      show: function() {
        console.log(this.a)    // 輸出10
        return () => console.log(this.a)  // 輸出10
      }
    }
    const foo = obj.show()
    foo()
    

總結

今天介紹了 this ,其實 this 真的很抽象很難懂,筆者自己也花了超多時間才慢慢搞懂 this 到底指向哪些東西,但不得不說 thisJavaScript 裡面真的是個很重要的存在,尤其在 class 內部基本上就是會一直使用到 this ,基本上 this 的全部觀念都在文章中了,如果讀者有哪邊不懂歡迎在底下留言,筆者會一一解答疑惑。


上一篇
Day24-閉包!closure
下一篇
Day26-終端機操作
系列文
從0開始的網頁生活!30天從網頁新手到網頁入門30

尚未有邦友留言

立即登入留言