這個 this 到底是什麼?

這個(this)到底是哪個?
基本上這個(this)脫離物件呼叫,就沒有太大的意義。


在 JavaScript 中,this 是一個經常讓人困惑的關鍵字,因為它的值在不同的情況下會有所變化。this 的值取決於函數的呼叫方式,而不是函數定義的地方。本文將介紹 this 在不同情況下的指向,並討論 callapplybind 這三種方法,以及如何解決 this 綁定的問題。

函數呼叫方式

在講解this前,我們要先知道 function 在呼叫時,有幾種方法

  1. 作為函數去呼叫
  2. 作為方法去呼叫
  3. 作為建構式用new的方式呼叫
  4. 透過applycall的方式呼叫
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
function sayByeBye(name) {
return `Bye bye ${name}`
}

/* 1. 作為一個函數去呼叫 */
console.log(sayByeBye('Ben')) // => Bye bye Ben
a = (function (who) {return who})('Ben') //IIFE,(immediately invoked function expression)
console.log(a); // => Ben


let greeting = {
sayHello(name){
return `Hello ${name}`
}
}
/* 2. 作為一個方法去呼叫 */
console.log(greeting.sayHello('Sabrina')) // => Hello Sabrina

/* 3. 作為一個建構式,用new的方式去呼叫*/
function Order(){}
order1 = new Order()
console.log(order1) // => {}

/* 4. 透過apply and call的方式呼叫 */
console.log(sayByeBye.apply(greeting, ['John'])) // => Bye bye John
console.log(sayByeBye.call(greeting, 'John')) // => Bye bye John

一般情況下的 this

全域環境下的 this

在全域範圍中(非嚴格模式),this 指向全域物件(在瀏覽器中是 window)。

例如:

1
console.log(this); // 在瀏覽器中,輸出 window

物件方法中的 this

當函數作為物件的方法呼叫時,this 指向該物件。例如:

1
2
3
4
5
6
7
8
const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
}
};

obj.greet(); // 輸出 'Alice'

在這裡,this指向obj,因此this.name取到的是obj.name

獨立函數中的 this

當函數在全域範圍中獨立呼叫時,this在非嚴格模式下會指向全域物件(瀏覽器中的window)。在嚴格模式下,this則會是undefined

1
2
3
4
5
function showThis() {
console.log(this);
}

showThis(); // 非嚴格模式下,輸出 window

嚴格模式中的 this

在嚴格模式下,this不再自動指向全域物件,如果函數獨立呼叫,this會是undefined

1
2
3
4
5
6
7
'use strict';
function showThis() {
console.log(this);
}

showThis(); // 輸出 undefined

new 建構函數使用 this

當使用new關鍵字來呼叫並建構函數時,this的指向會有所不同。

new會建立一個新的實體物件,並且this會指向新創建的物件,而不是全域物件或其他任何物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person(name) {
this.name = name;

// 正確的 this 綁定:使用箭頭函數
this.introduceArrow = () => {
console.log(`Hi, I'm ${this.name}`); // 這裡的 this 指向 Person 實例
};

// 錯誤的 this 綁定:使用普通函數
this.introduceRegular = function() {
setTimeout(function() {
console.log(`Hi, I'm ${this.name}`); // 這裡的 this 指向全域物件或 undefined(取決於嚴格模式)
}, 1000);
};
}

const person1 = new Person('John');

person1.introduceArrow(); // 正確,輸出 'Hi, I'm John'

person1.introduceRegular(); // 錯誤,1秒後輸出 'Hi, I'm undefined'(或 'Hi, I'm ' 在嚴格模式下)
  1. 使用箭頭函數的正確綁定在introduceArrow方法中,我們使用了箭頭函數。箭頭函數不會自己創建this,而是從其外部環境繼承this。在這裡,外部環境是Person的實例,因此this仍然指向Person實例,能夠正確地存取 name 屬性。

  2. 使用普通函數的錯誤綁定在introduceRegular方法中,我們使用了普通函數。這樣做會導致setTimeout中的回調函數創建自己的this,並且這個this指向全域物件(在瀏覽器中為window)或undefined(在嚴格模式下)。

因此,當回調函數執行時,this.name無法正確取得name屬性,結果是undefined

修正錯誤綁定的方法

如果我們希望修正普通函數中的this綁定,可以使用bind方法或將回調函數改為箭頭函數。

使用bind方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name) {
this.name = name;

this.introduceRegular = function() {
setTimeout(function() {
console.log(`Hi, I'm ${this.name}`); // 這裡的 this 綁定會被 bind
}.bind(this), 1000);
};
}

const person2 = new Person('Jane');

person2.introduceRegular(); // 正確,1秒後輸出 'Hi, I'm Jane'

使用箭頭函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name) {
this.name = name;

this.introduceRegular = function() {
setTimeout(() => {
console.log(`Hi, I'm ${this.name}`); // 這裡的 this 繼承自 Person 實例
}, 1000);
};
}

const person3 = new Person('Doe');

person3.introduceRegular(); // 正確,1秒後輸出 'Hi, I'm Doe'

在以上修正方法中,bind方法可以將this繫結到指定的物件,而箭頭函數可以自動繼承外部環境中的this。這樣可以確保在回調函數中,this可以正確地指向實例對象。


call、apply 與 bind 解決 this 綁定

當我們希望手動指定this的值時,可以使用callapplybind
每個 function 都會自帶這些方法可以呼叫直接呼叫使用。

call

call方法允許我們明確地設定this的值並立即執行該函數。它的語法是:

1
function.call(thisArg, arg1, arg2, ...)。

第一個參數,就是我們要指定的this

1
2
3
4
5
6
7
8
9
function introduce(greeting) {
console.log(`${greeting}, 我是 ${this.name}`);
}

const person = {
name: 'Bob'
};

introduce.call(person, 'Hello'); // 輸出 'Hello, 我是 Bob'

apply

applycall類似,不同的是,apply接受的是一個參數陣列而不是單獨的參數。

1
2
3
4
5
6
7
8
9
10
function introduce(greeting) {
console.log(`${greeting}, 我是 ${this.name}`);
}

const person = {
name: 'Charlie'
};

introduce.apply(person, ['Hi']); // 輸出 'Hi, 我是 Charlie'

apply對於參數的傳遞方式更加靈活,尤其在參數數量不確定時。

bindcallapply不同的是,bind並不會立即執行函數,它會返回一個新的函數,並將this永遠綁定到指定的物件。

1
2
3
4
5
6
7
8
9
10
11
function introduce(greeting) {
console.log(`${greeting}, 我是 ${this.name}`);
}

const person = {
name: 'Diana'
};

const boundIntroduce = introduce.bind(person);
boundIntroduce('Hey'); // 輸出 'Hey, 我是 Diana'


常見問題與解決方法

問題:回調函數中的 this 不正確

在回調函數(例如事件處理器、setTimeout)中,this的值通常會出現問題,因為它可能會指向全域物件或undefined。解決這個問題的方法包括使用bind或箭頭函數。

使用bind

1
2
3
4
5
6
7
8
9
10
const button = document.getElementById('myButton');

const obj = {
name: 'Emily',
handleClick: function() {
console.log(this.name);
}
};

button.addEventListener('click', obj.handleClick.bind(obj));

使用箭頭函數

箭頭函數不會自己綁定this,而是繼承外部環境中的this

1
2
3
4
5
6
7
8
9
10
const obj = {
name: 'Frank',
handleClick: function() {
document.getElementById('myButton').addEventListener('click', () => {
console.log(this.name);
});
}
};

obj.handleClick(); // 點擊時輸出 'Frank'

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
name: 'Alice',
showName: function() {
console.log(this.name);
},
delayedShowName: function() {
setTimeout(function() {
console.log(this.name); // 這裡的 this 指向 window(非嚴格模式下),而不是 obj
}, 1000);
}
};

obj.showName(); // 正確,輸出 'Alice'
obj.delayedShowName(); // 不正確,輸出 'undefined'(因為 this 指向 window)

淺談 JavaScript 頭號難題 this

this和函數呼叫方式