iT邦幫忙

2022 iThome 鐵人賽

DAY 8
2

前言

相信讀這篇文章的你,已經不是第一次看到 JavaScript 的 this 關鍵字了,但有時還是難以搞懂在一段複雜程式碼當中的 this 會指向哪裡是吧?我也曾經有過這種的困擾,所以寫了這篇文章整理了幾個情境,讀者可以先知道這段話:

this 會在創建 execution context 時被綁定,它和一般變數、函式不同,不適用 Scope 的概念去判斷它的值,用來指向函式執行时所在的環境。

這樣就很困擾了,因為它不會指向一個固定的值,為什麼 JS 要這樣設計?為什麼要在 execution context 做綁定?實際上這樣做的話它就可以引用了當前 execution context 內的物件,從而讓函式可以更靈活地操作不同的物件,這使得 JavaScript 更加彈性和可重用。

我們來看以下的範例,然後再看 this 在不同情境的指向。

this 的作用

1. 讓函式可以取得到它自己的物件

第一點在下個段落 this 的第二種指向(透過物件的方法調用 this)會提到。

2. 可以在不同的物件間執行相同的函式

function showName() {
  console.log(this.name);
}

const obj1 = { name: 'Chloe', showName };

const obj2 = { name: 'Make', showName };

obj1.showName(); // Chloe
obj2.showName(); // Make

根據前言提到的粗體字:從而讓函式可以更靈活地操作不同的物件,這使得 JavaScript 更加彈性和可重用。 是不是就從這個案例得到了驗證,obj1 和 obj2 它們都可以使用 showName 函式。

3. 另外,還可以透過 this 去做 function chaining

此段程式的 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 在各種情況下的指向的值

1. 一般函式/全域中的 this(Default Binding)

上篇提到的 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。

2. 透過物件的方法調用 this(隱式綁定、Implicit Binding)

這種情況下,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

3. call()、apply()、bind() 中的 this(顯式綁定、Explicit Binding)

在這三個函式傳入的第一個參數,都會變成 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,因為 window.name 為空字串,宣告 name 變數不會覆蓋此屬性
  }.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 物件

4. new 關鍵字調用 this

當一個函式被以 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 }

5. 事件觸發函式內的 this

此時 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));

6. Callback function & this

由於 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 時的情況,將會於明天的文章做介紹。


參考資料 & 推薦閱讀

this - JavaScript | MDN

[教學] JavaScript this 用法整理


上一篇
Day7-閉包(Closure)介紹
下一篇
Day9-箭頭函式與 this
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
.

2 則留言

1
json_liang
iT邦研究生 4 級 ‧ 2022-09-08 11:25:20

感謝分享 this 概念,這個真的很重要

1
雷N
iT邦研究生 1 級 ‧ 2022-09-08 12:43:08

開始期待有TypeScript

harry xie iT邦研究生 1 級 ‧ 2022-09-08 15:53:51 檢舉

哈哈,這次沒要寫 TS

我要留言

立即登入留言