# JavaScript 中的 this ## 🔹 this 是什麼? `this` 是 JavaScript 中的一個關鍵字,它的值通常根據呼叫方式決定。箭頭函式例外,它在定義時就繼承外層的 this。 ## 判斷 this 的 簡易版 - 箭頭函式 → 定義時繼承外層 this - 普通函式 → 呼叫時決定 this - 物件呼叫 ➔ 指向物件 - 直接呼叫 ➔ window / undefined(看嚴格模式) ## 🧭 this 的指向規則總覽 | 呼叫方式/情境 | this 指向(在瀏覽器中) | 備註 | |-------------------------------|------------------------------------------|-------------------------------------| | 全域(非嚴格模式) | `window` | Node.js 中為 `global` | | 全域(嚴格模式) | `undefined` | | | 函式直接呼叫 | `window`(非嚴格) / `undefined`(嚴格) | | | 物件方法呼叫 | 呼叫該方法的物件 | 例如 `obj.method()` | | constructor 中 (`new`) | 新建立的實體物件 | `this` 指向 `new` 出來的物件 | | class 中的 method | 該 class 實體 | 類似一般物件的行為 | | callback 函式(一般函式) | `window`(非嚴格) / `undefined`(嚴格) | 例如:`[1,2,3].forEach(function(){})` | | setTimeout/setInterval | `window` | Node.js 中為 `Timeout`(但非預期) | |IIFE(立即執行函式)| `window`(非嚴格) / `undefined`(嚴格) --- ## 箭頭函式 this 行為 >箭頭函式永遠不會自己創造 this,它只會繼承「定義當下」的外層 this ## 箭頭函式的 `this` 行為總表 | 定義位置 / 外層 `this` | 箭頭函式的 `this` 指向(在瀏覽器中) | 備註 | |-----------------------------------------------------|----------------------------------|--------------------------------------------------------------| | **全域** | `window` | 若在全域中定義箭頭函式 | | **class 中的 method** | 該 class 實體 | `this` 繼承外層 class context,例如 constructor 中定義箭頭函式 | | **setTimeout / setInterval 中定義** | 外層 `this` | 例如在物件內使用箭頭函式當定時器 callback,會指向物件本身 | | **callback 函式** | 外層 `this` | 常見於 class 或物件中使用 forEach 來處理資料時 | | **IIFE(立即執行函式)** | 外層 `this` | 箭頭函式沒有自己的作用域,直接吃外面那層 | --- > 🧠 **箭頭函式的 this 核心原則:** > 箭頭函式永遠不會自己創造 `this`,它只會繼承「定義當下」的外層 `this`。 # this 的指向例子 ## 🔸 1. 全域環境下的 this ```javascript var myName = "小明"; console.log(this === window); // true(在瀏覽器中) console.log(this.myName); // "小明" ``` ## 🔸 2. 嚴格模式下的 this ```javascript "use strict"; var myName = "小明"; function fn() { console.log(this); // undefined } fn(); ``` ## 🔸 3. 純函式呼叫(非物件) ```javascript var myName = "小明"; function sayName() { console.log(this); // window(非嚴格) console.log(this.myName); // "小明" } sayName(); ``` ## 🔸 4. 立即執行函式(IIFE) ```javascript var myName = "小明"; (function () { console.log(this); // window(非嚴格) })(); (() => { console.log(this); // window,箭頭函式繼承外層的 this })(); ``` ## 🔸 5. 作為物件方法呼叫 ```javascript const family = { myName: "小明家", callName() { console.log(this.myName); // "小明家" }, }; family.callName(); ``` ## 🔸 6. 物件方法呼叫 ```javascript const family = { myName: "小明家", ming: { myName: "小明", callName() { console.log(this.myName); // "小明" }, }, }; family.ming.callName(); ``` ## 🔸 7. class 中的 this 及constructor 中 (new) ```javascript class Person { constructor(name) { this.name = name; console.log(this);// this 指向 new 出來的物件 } sayHi() { console.log("Hi, I am", this.name); // this 指向呼叫 sayHi() 的實例物件 } } const p1 = new Person("小明"); p1.sayHi(); // this === p1 → "Hi, I am 小明" ``` ## 🔸 8. callback 中的 this(普通函式) ```javascript [1, 2, 3].forEach(function () { console.log(this); // window(非嚴格) }); ``` - 嚴格模式下 ```javascript "use strict"; const arr = [1, 2, 3]; arr.forEach(function (item) { console.log(this); // ❗️在嚴格模式下,這裡的 this 是 undefined }); ``` ## 🔸 9. setTimeout 中的 this 🔹 普通函式寫法(需要保存 this) ```javascript const component = { text: "Hello", getData() { const that = this; setTimeout(function () { that.text = "Hi"; console.log(that.text); // "Hi" }); }, }; component.getData(); ``` 🔹 箭頭函式寫法(自動繼承外層 this) ```javascript const person = { name: "小明", sayHi() { setTimeout(() => { console.log(this.name); // "小明" }, 1000); }, }; person.sayHi(); ``` ## 🔸 10. 箭頭函式的 this(沒有自己的 this) ```javascript window.myName = "全域的小明"; const family = { myName: "小明家", callName: () => { console.log(this.myName); // "全域的小明"(繼承外層的 this = window) }, }; family.callName(); ``` ## 🔸 11. 箭頭函式 + 一般方法包住 ```javascript window.myName = "全域的小明"; const family = { myName: "小明家", callName: function () { (() => { console.log(this.myName); // "小明家" })(); }, }; family.callName(); ``` ## 🔸 12. 展開運算子 (...) 與 this 的共享 - ...component 是淺拷貝 - getData/render/init 等 function 共用同一份參考 ```javascript const component = { text: "預設文字", el: document.getElementById("root"), getData() { const that = this; setTimeout(function () { that.text = "已更新"; that.render(); }); }, init() { this.getData(); }, render() { this.el.innerText = this.text; }, }; const component2 = { ...component, el: document.getElementById("root2"), }; component.init(); component2.init(); ```