跳至主要内容

5 篇文章 含有標籤「this」

檢視所有標籤

JavaScript this 使用情境入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 中,箭頭函式(arrow function)與傳統函式在 this 的行為上有一些不同。箭頭函式不會創建自己的 this,而是繼承外部作用域的 this,這就是為什麼你會遇到 this 的問題。

傳統函式中的 this

在傳統的函式中,this 會指向該函式被調用時的上下文。例如,當函式作為事件處理器時,this 會指向觸發事件的元素。

function regularFunction() {
console.log(this); // 'this' 是調用它的上下文
}

const obj = {
name: 'KD',
show: regularFunction
};

obj.show(); // 'this' 會指向 obj

箭頭函式中的 this

在箭頭函式中,this 並不會綁定到函式的上下文,而是繼承自外部作用域。這通常會讓箭頭函式的 this 變得不同於你預期的結果。

例子:this 的問題

假設有這樣的情境,當你在一個物件的方法中使用箭頭函式作為事件處理器,this 會指向外部作用域,而不是該物件本身。

const obj = {
name: 'KD',
show: function() {
// 使用傳統函式作為事件處理器
document.getElementById('btn').addEventListener('click', function() {
console.log(this); // 'this' 指向的是 'btn' 按鈕元素
});

// 使用箭頭函式作為事件處理器
document.getElementById('btn').addEventListener('click', () => {
console.log(this); // 'this' 指向外部作用域,即 'obj' 物件
});
}
};

obj.show();
  • 在傳統函式中,this 會指向觸發事件的 DOM 元素(這裡是 btn 按鈕)。
  • 在箭頭函式中,this 會指向外部作用域(這裡是 obj 物件),因為箭頭函式不會創建自己的 this

解決方案:確保 this 正確指向

如果你希望 this 指向物件本身,可以使用傳統函式或手動綁定 this

使用傳統函式

如果你希望在事件處理器中讓 this 指向物件本身,可以使用傳統函式,或者使用 bind 顯式綁定 this

const obj = {
name: 'KD',
show: function() {
document.getElementById('btn').addEventListener('click', function() {
console.log(this); // 'this' 指向 obj
}.bind(this)); // 顯式綁定 'this' 到 obj
}
};

obj.show();

另一個選項:箭頭函式和外部 this

如果你希望繼續使用箭頭函式,你可以將物件的 this 儲存到外部變數中,並在箭頭函式中引用它。

const obj = {
name: 'KD',
show: function() {
const self = this; // 保存物件的 'this'
document.getElementById('btn').addEventListener('click', () => {
console.log(self); // 使用外部的 'self',指向 obj
});
}
};

obj.show();

總結

  • 傳統函式 會根據調用上下文決定 this 的值。
  • 箭頭函式 會繼承外部作用域的 this,不會創建自己的 this,這樣在某些情況下會導致 this 不如預期。
  • 如果你需要在事件處理器中使用物件的 this,可以選擇使用傳統函式或顯式綁定 this

JavaScript 多事件處理綁定使用情境入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 中,你可以通過事件處理的方式來為多個 input 元素綁定事件處理器。這樣做可以讓你在父容器上綁定一個事件處理器,並通過 event.target 確定觸發事件的具體 input 元素,而不需要為每個 input 元素單獨綁定事件。

事件委派的概念

事件委派是一種常見的事件處理技術,它將事件綁定到父元素或容器上,然後通過 event.target 來確定哪個子元素觸發了事件。這種方式在動態生成的元素中非常有用,因為無論多少個元素,它們都會使用相同的事件處理器。

例子:為多個 input 元素使用事件委派

假設有多個 input 元素,並且我們希望根據用戶在每個 input 中的輸入執行某些操作,可以像這樣使用 event.target

<div id="inputContainer">
<input type="text" id="input1" />
<input type="text" id="input2" />
<input type="text" id="input3" />
</div>

<script>
// 綁定事件處理器到父容器
document.getElementById('inputContainer').addEventListener('input', function(event) {
// 檢查事件目標是否為 input 元素
if (event.target.tagName.toLowerCase() === 'input') {
console.log('觸發的 input 元素 ID:', event.target.id);
console.log('輸入的值:', event.target.value);
}
});
</script>

解釋:

  1. 我們將 input 元素的 input 事件綁定到父容器 #inputContainer 上。
  2. 當任何一個 input 元素觸發 input 事件時,事件會冒泡到父容器,並且事件處理器會被執行。
  3. 在事件處理器中,我們使用 event.target 來確定是哪個 input 元素觸發了事件。event.target 會返回實際觸發事件的元素。
  4. 通過 event.target.idevent.target.value,我們可以獲取觸發事件的 input 元素的 ID 和輸入的值。

優點:

  • 減少事件綁定數量:不需要為每個 input 元素單獨綁定事件,減少了冗餘代碼。
  • 動態元素支持:即使後來添加了新的 input 元素,父容器上的事件處理器也會自動處理新元素。

input 元素是動態創建時:

如果你有動態創建的 input 元素,事件委派依然有效,因為事件處理器是綁定在父容器上的,而不需要直接綁定在每個 input 元素上。

// 假設需要動態創建 input 元素
const container = document.getElementById('inputContainer');

for (let i = 1; i <= 5; i++) {
const input = document.createElement('input');
input.type = 'text';
input.id = 'input' + i;
container.appendChild(input);
}

這樣,當你動態創建新的 input 元素時,父容器上的事件處理器會自動處理這些新的 input 元素。

JavaScript 箭頭函式 (Arrow Function)入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 的 箭頭函式 (Arrow Function, =>) 中,this 的行為與傳統的 函式表達式 (Function Expression) 不同,主要特點如下:


箭頭函式的 this 綁定

  1. 箭頭函式不會建立自己的 this,而是繼承定義它的上下文(也稱為 詞法作用域 Lexical Scope)。
  2. 在箭頭函式內部,this 指向的是箭頭函式所處的外部函式的 this

範例

1. 一般函式的 this

function normalFunction() {
console.log(this); // this 取決於調用方式
}

const obj = {
method: normalFunction
};

obj.method(); // this 指向 obj
normalFunction(); // this 指向全域物件 (在瀏覽器是 window,在 Node.js 是 global)

2. 箭頭函式的 this

const arrowFunction = () => {
console.log(this); // 繼承外部作用域的 this
};

const obj2 = {
method: arrowFunction
};

obj2.method(); // this 指向定義時的外部作用域,而不是 obj2

解析:

  • arrowFunction 並未創建自己的 this,所以 this 仍然指向外部作用域的 this,而不是 obj2

箭頭函式適用場景

1. 在物件方法中避免 this 綁定問題

const person = {
name: "John",
sayHello: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // this 繼承 sayHello 的 this,即 person
}, 1000);
}
};

person.sayHello(); // Hello, John

解析:

  • setTimeout 中的箭頭函式不會創建新的 this,它會繼承 sayHello 方法中的 this,所以 this.name 正確指向 person.name

若使用一般函式,this 會指向 window(瀏覽器環境)或 undefined(嚴格模式)。


2. 當作回呼函式 (Callback)

const numbers = [1, 2, 3];

// 使用箭頭函式讓 this 指向外部作用域
const doubled = numbers.map(num => num * 2);

console.log(doubled); // [2, 4, 6]

map() 內的箭頭函式不需要 this,但讓語法更簡潔。


箭頭函式的 this 限制

1. 不能作為建構函式 (Constructor)

const Person = (name) => {
this.name = name; // 錯誤,this 不會指向新建的物件
};

const john = new Person("John"); // TypeError: Person is not a constructor

解法: 必須使用 function 來定義建構函式:

function Person(name) {
this.name = name;
}

const john = new Person("John"); // 正常運作

2. 不能使用 arguments

const sum = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};

sum(1, 2, 3);

解法: 可以使用 展開運算符 ...args

const sum = (...args) => {
console.log(args); // [1, 2, 3]
};

sum(1, 2, 3);

3. 無法使用 .bind() 改變 this

const obj = {
value: 42,
method: () => {
console.log(this.value);
}
};

const newMethod = obj.method.bind({ value: 100 });
newMethod(); // undefined (this 不會變)

箭頭函式的 this 綁定無法透過 bind()call()apply() 來改變


總結

特性一般函式 (Function)箭頭函式 (Arrow Function)
this依呼叫方式決定繼承外部作用域
arguments有 (function 內部)無 (...args 取代)
bind()/call()/apply()可改變 this無效
new 關鍵字可用於建構函式無法當建構函式

適用場景

適合使用箭頭函式:

  • 短小的回呼函式 (e.g. map, filter, forEach)
  • setTimeout()setInterval()
  • 物件內部方法但不希望 this 被改變

不適合使用箭頭函式:

  • 建構函式
  • 需要動態 this 的方法
  • 使用 arguments 物件的場合

JavaScript 物件導向(Object-oriented programming)入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

1. 物件導向與 new 關鍵字

JavaScript 是基於 原型 (Prototype) 的物件導向語言,而非典型的 類別 (Class) 為基礎的語言。但 ES6 之後,JavaScript 引入了 class 語法,使其更接近傳統的物件導向語言,如 Java 或 C++。

在 JavaScript 中,new 關鍵字用於建立物件,並且會執行以下步驟:

  1. 建立一個新的空物件。
  2. 設定該物件的 __proto__ 指向建構函式 (Constructor) 的 prototype
  3. 執行建構函式內的程式碼,並將 this 綁定到新建立的物件。
  4. 如果建構函式沒有明確返回物件,則回傳該新物件。

範例:

function Person(name, age) {
this.name = name;
this.age = age;
}

const john = new Person("John", 30);
console.log(john.name); // John
console.log(john.age); // 30

2. __proto__ vs prototype

在 JavaScript 中,__proto__prototype 是兩個不同的概念。

prototype

prototype建構函式的一個屬性,它是一個物件,當我們使用 new 建立物件時,該物件的 __proto__ 會指向 prototype

範例:

function Animal(name) {
this.name = name;
}

Animal.prototype.sayHello = function() {
console.log("Hello, I am " + this.name);
};

const dog = new Animal("Dog");
dog.sayHello(); // Hello, I am Dog
console.log(dog.__proto__ === Animal.prototype); // true

__proto__

__proto__ 是物件的內部屬性,指向該物件的原型,即 prototype

範例:

console.log(dog.__proto__); // Animal.prototype
console.log(dog.__proto__.__proto__ === Object.prototype); // true
console.log(dog.__proto__.__proto__.__proto__); // null (最終的原型鏈結束)

關鍵點整理:

  • prototype函式的屬性。
  • __proto__物件的屬性,指向它的 prototype
  • Object.prototype 是所有物件的最終原型。

3. class 關鍵字

ES6 之後,JavaScript 引入了 class 語法,使物件導向的寫法更直覺。

定義類別 (Class)

class Car {
constructor(brand) {
this.brand = brand;
}
drive() {
console.log(this.brand + " is driving");
}
}

const myCar = new Car("Toyota");
myCar.drive(); // Toyota is driving

等同於 ES5 的寫法:

function Car(brand) {
this.brand = brand;
}

Car.prototype.drive = function() {
console.log(this.brand + " is driving");
};

優勢:

  • class 提供更簡潔的語法。
  • 更貼近傳統物件導向語言的語法風格。
  • constructor 方法負責初始化物件。
  • 方法定義在 prototype 上,並不會重複創建。

4. extends 繼承

在 ES6 之前,我們使用 Object.create() 或手動設定 prototype 來實現繼承。

傳統的原型繼承

function Animal(name) {
this.name = name;
}
Animal.prototype.makeSound = function() {
console.log("Some generic sound");
};

function Dog(name, breed) {
Animal.call(this, name); // 繼承屬性
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 繼承方法
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log("Woof!");
};

const myDog = new Dog("Rex", "Golden Retriever");
myDog.makeSound(); // Some generic sound
myDog.bark(); // Woof!

使用 class 的繼承

class Animal {
constructor(name) {
this.name = name;
}
makeSound() {
console.log("Some generic sound");
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // 呼叫父類別的 constructor
this.breed = breed;
}
bark() {
console.log("Woof!");
}
}

const myDog = new Dog("Rex", "Golden Retriever");
myDog.makeSound(); // Some generic sound
myDog.bark(); // Woof!

關鍵點:

  • extends 用於建立類別的繼承。
  • super(name) 呼叫父類別的 constructor,確保 this 正確初始化。
  • 子類別可以新增自己的方法。

5. 物件導向開發的最佳實踐

  1. 使用 class 提供更清晰的結構。
  2. 使用 extends 來建立繼承關係,並呼叫 super() 確保正確初始化。
  3. 方法定義於 prototype 來減少記憶體浪費。
  4. 理解 __proto__prototype 之間的關係,以便更好地管理原型鏈。
  5. 避免過度使用繼承,適時使用組合 (Composition) 來降低耦合度。

6. 總結

特性傳統原型 (Prototype)ES6 class
建立物件new Function()new Class()
方法定義Function.prototype.method = function() {}直接定義於 class
繼承Object.create() + call()extends + super()
this 綁定需要 call()bind()super() 自動綁定

JavaScript 的物件導向概念提供了靈活的方式來組織程式碼,掌握 prototypeclassextendssuper(),可以幫助開發者寫出更具可讀性與可維護性的程式碼。

JavaScript this 入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 中,this 是一個關鍵字,它的值會根據執行環境的不同而改變。以下是 this 在不同情境下的行為:


1. 全域環境 (Global Context)

在瀏覽器中,this 預設指向 window 物件:

console.log(this); // 在瀏覽器中,this 指向 window

在 Node.js 環境下,this 則指向 global

console.log(this); // 在 Node.js 中,this 指向 global

2. 函式內部 (Function Context)

在一般函式中,this 的值取決於是否使用 "use strict"

function showThis() {
console.log(this);
}
showThis(); // 在非嚴格模式下,this 指向 window (瀏覽器) 或 global (Node.js)

"use strict";
function showStrictThis() {
console.log(this);
}
showStrictThis(); // 在嚴格模式下,this 變成 undefined

3. 物件方法 (Object Method)

this 被用在物件的方法內,它指向該物件:

const obj = {
name: "Alice",
greet: function () {
console.log(this.name);
},
};
obj.greet(); // "Alice"

4. 建構函式 (Constructor Function)

在建構函式中,this 會指向新建立的物件:

function Person(name) {
this.name = name;
}
const p = new Person("Bob");
console.log(p.name); // "Bob"

5. 箭頭函式 (Arrow Function)

箭頭函式中的 this 不會 指向它自己的執行環境,而是繼承自外層函式的作用域:

const obj = {
name: "Charlie",
greet: function () {
const arrowFunc = () => {
console.log(this.name);
};
arrowFunc();
},
};
obj.greet(); // "Charlie" (this 繼承自 obj)

6. setTimeout 和 setInterval

setTimeoutsetInterval 內,一般函式的 this 預設指向 window (瀏覽器) 或 global (Node.js):

const obj = {
name: "David",
greet: function () {
setTimeout(function () {
console.log(this.name);
}, 1000);
},
};
obj.greet(); // undefined,因為 this 指向 window/global

解法:改用箭頭函式:

const obj = {
name: "David",
greet: function () {
setTimeout(() => {
console.log(this.name);
}, 1000);
},
};
obj.greet(); // "David"

7. 事件處理器 (Event Handler)

在事件處理函式中,this 指向觸發事件的元素:

document.getElementById("btn").addEventListener("click", function () {
console.log(this); // 指向 <button> 元素
});

如果改用箭頭函式,this 會指向外部作用域:

document.getElementById("btn").addEventListener("click", () => {
console.log(this); // 指向 window
});

8. call、apply 和 bind

可以使用 call()apply()bind() 來改變 this 指向:

call()

function greet() {
console.log(this.name);
}
const person = { name: "Eve" };
greet.call(person); // "Eve"

apply()

apply()call() 類似,但參數是以陣列方式傳入:

function introduce(age, city) {
console.log(`${this.name} is ${age} years old and lives in ${city}.`);
}
const person = { name: "Frank" };
introduce.apply(person, [25, "Taipei"]);

bind()

bind() 會回傳一個新的函式,永久綁定 this

const boundFunc = greet.bind(person);
boundFunc(); // "Eve"

總結

  • 全域環境this 在瀏覽器中指向 window,在 Node.js 指向 global
  • 普通函式:嚴格模式下 thisundefined,否則指向 window
  • 物件方法this 指向該物件
  • 建構函式this 指向新建立的物件
  • 箭頭函式this 繼承外部作用域
  • 事件處理器:普通函式 this 指向事件元素,箭頭函式 this 指向外部作用域
  • callapplybind 可顯式設定 this