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
callthis.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()

callapply 是很類似的方法,這兩種都是能夠呼叫 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

callapply 就是你第一個參數傳什麼,裡面 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)() ,會替我們將 functhis 暫時指向我們所設定的 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/