在 JavaScript 中,我們知道一個函式在宣告後,可以在不同的 execution context 中呼叫使用,例如在全域宣告了 A、B 兩個函式,可以在 B 函式內呼叫 A,被呼叫的 A 要知道當前 execution context 的話,就必須設計一個東西讓它去取得,而這也牽涉到為什麼要設計 this 關鍵字。
this 會在創建 execution context 時被綁定,不會指向一個固定的值,它和一般變數、函式不同,不適用 Scope 的概念去判斷它的值,用來指向函式執行时所在的環境,因此這篇文章中要來針對 this 的指向值做個釐清,瞭解在各種情況下 this 會指向哪裡。
上篇提到的 Global Execution Context,也就是全域的執行環境中,this 會指向全域物件 window。
範例:
console.log(this); // window object
function a() {
console.log(this); // window object
console.log(this === window); // true
}
a();
以上是沒有設定嚴格模式的情況,但 this 在嚴格模式下執行以下程式會是這樣:
console.log(this); // window object
function a() {
console.log(this); // undefined
console.log(this === window); // false
}
a();
非嚴格模式,node.js 底下是 global。
這種情況下,this 會指向於調用包含 this 的該函式之物件,也就是指向調用者,第一點我們也可以想成調用者是 window。
const person = {
name: "Jay",
age: 24,
showThis() {
console.log(this);
}
};
person.showThis(); // person object
const showThisFunc = person.showThis; // 只是傳送記憶體位置給 showThisFunc 變數
showThisFunc(); // window object
在這三個函式傳入的第一個參數,都會變成 this 的指向。
註: bind() 是產生的新函數會與 bind 的第一個參數綁定起來
範例1:
const name = "Ray";
const person = {
name: "Jim",
age: 20,
sayHi: function() {
console.log(this); // window object
console.log("hi " + this.name); // hi
}.bind(window),
sayHiNoBind: function() {
console.log(this); // person object
console.log("hi " + this.name); // hi Jim
},
};
person.sayHi();
person.sayHiNoBind();
範例2:
const obj = { a: 'Custom' };
// 此屬性a為全域物件
const a = 'Global';
function whatsThis(arg) {
console.log(this.a); // this 值取決於此函數如何被呼叫
}
whatsThis(); // undefined
whatsThis.call(obj); // Custom,this 指向 obj 物件
當一個函式被以 new 的方式呼叫時,會創造一個新的物件,this 指向被創造的那個物件。
function Person(name, age) {
this.name = name;
this.age = age;
console.log(this);
}
const person1 = new Person("Tom", 25);
// 印出 Person { name: 'person1', age: 55 }
此時 this 所指向的則是該 DOM 元素,所以會看到 showMessage 函式裡 this === e.target
為 true。
至於為什麼 showMessage 沒有傳入參數卻能接收到 event?
是因為當使用者觸發 click 時,瀏覽器會產生 event 物件,addEventListener 傳入的 callback function 會接收到該 event 當作參數,可參考 Callback 在 EventListener 的運作
// HTML: <button>Click</button>
class Printer {
message = 'This works!';
showMessage(e) {
console.log(this.message); // undefined
console.log(this === e.target); // true
}
}
const p = new Printer();
const button = document.querySelector('button');
button.addEventListener('click', p.showMessage);
調整:
button.addEventListener('click', p.showMessage.bind(p));
由於 callback function 在不同的執行環境執行,所以 this 會有和在建構函式內 this 指向不一樣的情況,不過可以透過 bind()、箭頭函式改善。
function Person(name, age) {
this.name = name;
this.age = age;
setTimeout(function() {
console.log(this); // 印出 window
}, 100)
// setTimeout(() => {
// console.log(this); // 印出 person1 物件
// }, 100)
// setTimeout(function() {
// console.log(this); // 印出 person1 物件
// }.bind(this), 100)
}
const person1 = new Person("Tom", 25);
setTimeout 用在這裡可能有點奇怪,但就只是做個 Callback function 的舉例
上面講了很多 this 在各種狀況下指向的值,那它們可以怎麼運用呢,常見的作用有以下幾點:
第一點在上個段落 this 的第二種指向(透過物件的方法調用 this)已經有說過,這邊舉例第二點。
例如以下有兩個物件,obj1 和 obj2 它們都可以使用 showName 函式。
function showName() {
console.log(this.name);
}
const obj1 = { name: 'Chloe', showName };
const obj2 = { name: 'Make', showName };
obj1.showName(); // Chloe
obj2.showName(); // Make
此段程式的 this 為 ladder 物件,function chaining 可以讓程式碼看起來更加簡潔。
let ladder = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep() {
console.log(this.step);
return this;
}
};
ladder.up().up().down().showStep().down().showStep(); // 印出 1 然後再印出 0
這次關於 this 的介紹就到這邊,舉了六種 this 指向的情況,不過因為篇幅關係,還沒介紹箭頭函式調用 this 時的情況,將會於明天的文章做介紹。