0%

JS核心篇-this到底是誰

this是指向誰

在撰寫前端的專案的時候,時常會遇到 this 指向誰的問題。
所以這邊,就用將書中和網路上的文章讀到的內容,整理並記錄下來。

this什麼時候被決定

在我自己寫的上一篇有提到作用域的部分。
在JavaScript中的function的作用域是靜態作用域,也就是代表在function定義的當下,就被定義了。
而Javascript中的this,就是動態的,這代表什麼,就是function被呼叫的當下,相對應的this才被定義。
在大部分的情況下,this代表的就是呼叫function的物件。
來個範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const getGender = function() {
console.log(this.gender)
}

const ming = {
gender: 'male',
getGender: getGender
}

const tina = {
gender: 'female',
getGender: getGender
}

ming.getGender() // male
tina.getGender() // female

上面這個範例,就可以很好的體現什麼叫做,this代表呼叫的function的物件這個概念。

this 不等於 function本身

在JavaScript中的function本身就是一個物件型別,雖然,它的typeof的結果是function,但它的本質是物件。
如果,我們為一個function新增一個屬性,那此時,在該function中的this所呈現的成員屬性會不會就是我們為該函式新增的成員屬性的值呢?
馬上來個範例

1
2
3
4
5
6
7
8
9
var func = function() {
console.log(this.a)
}

func.a = 0
for(let i=0; i!=5; i++) {
func()
}
console.log(func.a) // ??

答案是什麼呢??
答案是0。 當初我在書上看到這個答案的時候,我也是蠻崩潰的XDD
那我按照書中給的脈絡,來解析一下這一題為什麼是這種結果。
首先,先記得this代表呼叫function的物件 的概念。
第二,我們呼叫func()的時候,並沒有特意用其他物件來呼叫它。
那這時的this就代表window,為什麼呢? 因為,變數func是一個全域變數,它是屬於window物件的成員屬性,
所以,當我們直接呼叫foo()時,其實就等同於我們直接用window來呼叫foo(),跟foo函式本身根本無關。
代表func.a 根本沒有被更改過,所以,就維持原本的0囉。
實際上被更改的a是window的a,而window的a原本是undefined,故在經過計算之後,window.a會變成NaN。

巢狀function裡面的this

在這邊的情境最主要是用到以下兩個概念

  1. JavaScript中,變數最小有效範圍是function
  2. this代表呼叫該function的物件

直接來的範例

1
2
3
4
5
6
7
8
9
10
11
var obj = {
a: 10,
hello: function() {
console.log(this.a)
var test = function() {
console.log(this.a)
}
test()
}
}
obj.hello()

最終的console.log的結果是10 和 undefined。

首先,我們會先用到this代表呼叫該function的物件這個概念
我們利用obj來呼叫hello函式,所以,在hello內部第一層的this代表的就是obj這個物件,而obj的物件的a成員屬性值就是10囉。
第二,我們會用到JavaScript中,變數最小有效範圍是function這個概念
所以,在test函式是一個獨立的區域,它裡面的this和外部的this是不一樣的。 那如果,我們沒有特別對this定義的話,它的預設值
就是window,而window本身並沒有定義a成員屬性,所以,結果會是undefined。

監聽事件裡面再增加其他函式

今天如果我們在addEventListener裡面,再加入一些ajax的callback function或一些其他的function。
那因為js的變數最小有效範圍的概念,會導致內層的函式的this跟外層的this是不一樣的。
此時,我們就可以在外層利用一個變數來儲存外層環境的this,再將這個變數丟到內層的函式中,
就可以在內層的函式使用外層的this囉。

1
2
3
4
5
6
7
8
9
10
11
12
13
---JavaScript---
let el = document.querySelector('div')
el.addEventListener('click', function(ev){
const vm = this; // 儲存外層的this
console.log(this.textContent)

ajax.get(api).then((response) => {
if (response.data.success) {
vm.products = response.data.products // 使用外層的this
}
vm.isLoading = false // 使用外層的this
})
}, false)

使用.call輔助到底this是誰

雖然這邊我還沒有紀錄.call這個功能在幹嘛,而且要怎麼用,但是,在這邊先不要在意它XDD
你就記得我們可以透過.call來呼叫function,並在呼叫的當下同時傳入this的指定對象。
Huli的這篇文章中有特別介紹這篇文章中所介紹的利用call來輔助this是誰。

那這個小撇步地轉換規則就是你在呼叫 function 以前是什麼東西,你就把它放到後面去
來個範例吧

1
2
3
4
5
6
7
8
9
10
11
const obj = {
x: 1,
hello: function() {
console.log(this.x)
var test = function(){
console.log(this.x)
}
test()
}
}
obj.hello() // ?

我們用.call來傳換一下上面的範例

1
2
3
4
5
6
7
8
9
10
11
const obj = {
x: 1,
hello: function() {
console.log(this.x)
var test = function(){
console.log(this.x)
}
test.call(undefined) // 改寫test的呼叫方式
}
}
obj.hello.call(obj) // 改寫obj.hello的呼叫方式

如此,應該就很清楚為什麼最終的結果分別是1 和 undefined了吧~~
在test被呼叫的時候,因為,並沒有特別被哪個物件所呼叫,所以,call裡面就直接放入undefined,而在非嚴謹模式下,
this就會是預設值window囉。

強制指定this的方式

bind, call, apply

舉個bind、call 和 apply用法

1
2
3
4
5
6
7
8
function func(argv0, argv1) {
console.log(this.v, argv0, argv1)
}
var obj = { v: 'I am v of obj' }

func.apply(obj, [1, 2])
func.call(obj, 1, 2)
func.bind(obj)(1, 2)

以上可以看到,這三種方式的使用方法。我們可以直接指定我們想要的this對象是誰。

箭頭函式

箭頭函式的this決定的方式是在定義箭頭函式環境的this,該箭頭函式的this就是誰
舉的範例

1
2
3
4
5
6
7
8
9
const obj = {
a: 10,
hello: function() {
console.log(this.a)
var func = () => console.log(this.a)
func()
}
}
obj.hello()

結果會是10和10。
因為,箭頭函式func在定義的環境的this是obj,所以,在這個箭頭函式內部的this就是obj。
所以,this.a的結果是10。

參考文章

  1. 008天重新認識JavaScript-kuro
  2. https://blog.huli.tw/2019/02/23/javascript-what-is-this/
  3. https://zhuanlan.zhihu.com/p/23804247