JavaScript THIS




this 是 JavaScript 的一個關鍵字, this 會在不同的情境下指稱到不同的物件。

this 在物件導向裡面,它所代表的就是那個 instance 本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Car {
setName(name) {
this.name = name
}

getName() {
return this.name
}
}

const myCar = new Car()
myCar.setName('Ford')

console.log(myCar.getName()) // Ford

在上面我們宣告了一個 class Car,寫了 setName 跟 getName 兩個方法,在裡面用this.name來取放這個 instance 的屬性,myCar.setName('Ford'),所以 this 就會是myCar。

this 不等於 function

如果直接調用函式,此函式的 this 會指向 window 。

1
2
3
4
5
6
window.say = 'Hi';
function CallSay() {
console.log(this.say); // Hi
}

CallSay();

this 不會指到 CallSay 這個 function,實際上是指向 window 。

再看一個範例:

1
2
3
4
5
6
7
8
9
var call = function() {  
    console.log( this.name );
};
var say = function() {
    var name = 'Felix';
    this.call();
};

say();

在這個範例中, say 可以透過 this.call 取得 call ,是因為 this.call 實際上是指向 window.call。
而 call 的 this.name 並非是 say 中的 Felix ,而是指向 window.name ,所以會得到 undefined 的結果。


下個例子是將 function 內在包覆著 function,但只要是直接呼叫,this 都是屬於全域。

1
2
3
4
5
6
7
8
9
10
11
12
window.say = 'Hi';
function CallSay () {
console.log('call:', this.say); //call: Hi

// function 內的 function
function CallSayNow () {
console.log('call me now:', this.say);//call me now: Hi
}
CallSayNow();
}

CallSay();

來看一下巢狀迴圈中的 this :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var obj = {

func1: function(){
console.log(this); //指到 func1


var func2 = function(){
// 這裡的 this 跟上層不同!
console.log(this);// 指到 window

};

func2();
}
};

obj.func1();

在 obj.func1() 裡面的 this 會指向 func1 ;是因為 func1 透過 obj 來呼叫的。

但 obj.func1() 裡面的 func2() 在執行時的 this 卻會指向 window。

也就是當沒有特定指明 this 的情況下,預設綁定 (Default Binding) this 為 「全域物件」,也就是 window。

以上這幾種情況, this 的值在瀏覽器底下就會是 window ,在 node.js 底下會是 global ,如果是在嚴格模式,this 的值就會是 undefined 。

強制指定 this 的方式

在 JavaScript 有三個可以強制指定 this 的方式,分別是 call() 、 apply() 以及 bind()。

call 跟 apply 是很類似的方法,這兩種都是能夠呼叫 fucntion 的函式

1
2
3
4
5
6
7
8
9
10
11
'use strict';
function funA(a, b){

console.log(this, a, b)

}

funA(1, 2) // undefined 1 2
funA.call("Hector", 1, 2) // Hector 1 2

funA.apply("Ray", [1, 2]) // Ray 1 2

因為是嚴格模式所以 funA(1, 2) 的 this 是 undefined。

call 和 apply 就是你第一個參數傳什麼,裡面 this 的值就會是什麼。儘管原本已經有 this,也依然會被覆蓋:

而兩者的差別只在於 apply 傳進去的參數是一個 array,所以上面這三種呼叫 function 的方式是等價的。除了直接呼叫 function 以外,你也可以用 call 或是 apply 去呼叫,差別在於傳參數的方式不同。

除了以上兩種以外,還有最後一種可以改變 this 的方法:bind。
1
2
3
4
5
6
7
8
'use strict';
function funA() {
console.log(this)
}

const myFunA = funA.bind('Hi')

myFunA() // Hi

在這邊我們把 funA 這個 function 用 Hi 來綁定,所以最後呼叫 myFunA 時會輸出 Hi 。

在看一個例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
LastName: "Lincoln"

};

var func = function () {

console.log(this.LastName);

};

func(); // undefined
func.bind(obj)(); // Lincoln

加上了 bind 之後的 func.bind(obj)() ,會替我們將 func 的 this 暫時指向我們所設定的 obj。
於是 console.log(this.LastName) 的結果自然就是 obj.LastName 也就是 Lincoln 了。

重新指向 this

假設我們今天在某個元素上透過 addEventListener 註冊了 click 事件,而在事件中的 this 指的是「觸發事件的元素」。

要是我們在事件的 callback function 加入 ajax 的請求,那麼根據前面所說的,預設綁定 (Default Binding) 會把這個 callback function 的 this 指定給 global object ,也就是 window 。

如果需調用的則是物件本身的話,可以先用一個變數指向 this,等到調用後再重新使用它。

1
2
3
4
5
6
7
8
9
10
11
12
el.addEventListener("click", function(event) {

// 透過 that 參考
var that = this;
console.log( this.textContent );

$ajax('[URL]', function(res) {
// this.textContent => undefined
console.log(that.textContent, res);
});

}, false);

像這樣,我們將事件內的 this 先用一個叫 that 的變數儲存它的參考,那麼在 ajax 的 callback function 就可以透過 that 來存取到原本事件中的 this 了。


如果我們把 call 跟 bind 同時用會怎樣!?

1
2
3
4
5
6
'use strict';
function funA() {
console.log(this)
}
const myFunA = funA.bind('Hi')
myFunA.call('Hello') // Hi

答案是不會改變,一但 bind 了以後值就不會改變了。

在非嚴格模式底下,無論是用 call、apply 還是 bind,你傳進去的如果是基本型別都會被轉成 object 。

舉例來說:

1
2
3
4
5
6
function funA() {
console.log(this)
}
funA.call(1) // Number {1}
const myFunA = funA.bind('Hi')
myFunA() // String {"Hi"}

物件中的 this

最前面我們示範了在物件導向 class 裡面的 this,但在 JavaScript 裡面還有另外一種方式也是物件:

1
2
3
4
5
6
7
8
9
10
11
const obj = {
value: 1,
say: function() {

console.log(this.value)
}
}

obj.say() // 1
const hello = obj.say
hello() // undefined

這種跟一開始的物件導向範例不太一樣,這個範例是直接創造了一個物件而沒有透過 class,所以你也不會看到 new 這個關鍵字的存在。

舉個簡單的例子來幫大家複習一下 Scope Chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var value = 1

function say(){

console.log(value)
}

const obj = {
value: 'value',

say1: function() {
console.log(this.value); // value

say() // 1

},
say2: function() {
console.log(this.value); // value

var value = 2

say() // 1

}
}

say() // 1

obj.say1()

obj.say2()

無論怎麼呼叫 say 這個 function,印出來的 value 永遠都會是全域變數的 value ,因為 say 在自己的作用域底下找不到 value 於是往上一層找,就會找到 global scope ,這跟你在哪裡呼叫 say 一點關係都沒有。 say 這個 function 在「定義」的時候就把 scope 給決定好了。

但 this 卻是完全相反,this 的值會根據你怎麼呼叫它而變得不一樣,像是先前我們剛講過的 call、apply 跟 bind ,你可以用不同的方式去呼叫 function,讓 this 的值變得不同。


this 的值跟作用域跟程式碼的位置在哪裡完全無關,只跟「如何呼叫」有關。

讓我們看複雜一點的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const obj = {
value: 1,
say: function() {

console.log(this.value)
},
func2: {

value: 2,
say: function() {

console.log(this.value)
}
}
}

const obj2 = obj.func2
const say= obj.func2.say

obj.func2.say()

obj2.say()

say()

say 因為沒有傳參數進去,所以是預設綁定,在非嚴格模式底下是 window ,所以會 window.value 也就是 undefined 。

可以透過把 function 的呼叫轉成用 call 的形式,就會較容易看出 this 的值是什麼。

1
2
3
obj.func2.say() // obj.func2.say.call(obj.func2) => 2
obj2.say() // obj2.say.call(obj2) => 2
say() // say.call() => undefined

箭頭函式的 this

從 ES6 開始新增了一種叫做 「箭頭函式表示式」 (Arrow Function expression) 的函式表達式。

而箭頭函式有兩個重要的特性:

  • 更簡短的函式寫法
  • this 變數強制綁定

ES6 新增的箭頭函式中的 this 有不一樣的運作方式,只要記住「在宣告它的地方的 this 是什麼,它的 this 就是什麼」,什麼意思呢?讓我們看個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const obj = {
Firstname: "Vincent",

say: function(){

// 這邊印出來的 this 是什麼,func1 的 this 就會是什麼
// 在宣告它的地方的 this 是什麼,func1 的 this 就是什麼
console.log(this)
const func1 = () => {

console.log(this.Firstname)

}
func1()

}
}

obj.say()
// {Firstname: "Vincent", say: ƒ}
// Vincent

const say = obj.say

say()
// Window
// undefined

我們在 say 這個 function 裡面宣告了 func1 這個箭頭函式,所以 say 的 this 是什麼, func1 的 this 就會是什麼。

箭頭函式的 this 不是取決於在宣告時那個地方的 this。

參考文獻

  1. https://blog.techbridge.cc/2019/02/23/javascript-this/

  2. https://ithelp.ithome.com.tw/articles/10193193

  3. https://wcc723.github.io/javascript/2017/12/12/javascript-this/