跳至主要内容

4 篇文章 含有標籤「js」

檢視所有標籤

深入解析 JavaScript this - 用 apply、call 和 bind 克服函式綁定挑戰

· 閱讀時間約 5 分鐘

在 JavaScript 中,this 的值取決於函式的呼叫上下文,而不是函式的定義位置。這使得 this 的行為有時候很難預測和控制。

首先我們先來複習一下 JavaScript 中 this 的行為:

this

在 JavaScript 中,this 是一個很特別的關鍵字,它的值會根據它所在的環境而改變。簡單來說,this 指的是呼叫函式時的上下文(context),也就是「誰」在呼叫這個函式。

傳統函式中的 this

在傳統函式裡,this 的指向取決於函式是**「怎麼」被呼叫的**

const person = {
name: "Alice",
greet: function () {
console.log(`Hi, I'm ${this.name}`);
},
};

// 這個 greet 函式是透過 person 物件來呼叫的。
// 這意味著,JavaScript 會把 this 綁定到 person,讓 this.name 指向 person.name。
person.greet(); // Hi, I'm Alice

const greetFunction = person.greet;
// 這裡的 this 就指向 window 物件,因為 greetFunction 是直接被呼叫的。
// 這時候 this.name 就會是 undefined 或 window.name 的值。
greetFunction(); // Hi, I'm

箭頭函式中的 this

箭頭函式的 this 行為可以理解為**「綁定」到箭頭函式定義時的詞法環境 (Lexical Environment)**,而不是函式被呼叫時的上下文。

const person = {
name: "Alice",
greet: () => {
console.log(`Hi, I'm ${this.name}`);
},
greet1: function () {
// 這裡的箭頭函式會繼承外層的 this,這個 this 是指向 person
const innerGreet = () => {
console.log(`Hi, I'm ${this.name}`); // 這裡的 this 指向 person
};
innerGreet();
},
};

// 這裡的 this 指向的是全域對象,而不是 person 物件
// person 物件並不是 greet 函式的外層上下文;this 的繼承來源是 greet 函式的定義上下文
person.greet(); // Hi, I'm

person.greet1(); // Hi, I'm Alice

this 的難題主要來自於它的綁定方式,這使得 this 的行為有時候很難預測和控制。 要解決 this 的問題,我們可以使用 Function.prototype.bindcallapply 方法

Function.prototype.bind

Function.prototype.bind 方法可以用來永久地綁定函式的 this 值,並回傳一個新的函式

const person = {
name: "Alice",
greet: function () {
console.log(`Hello, ${this.name}`);
},
};

const greet = person.greet;
greet(); // 輸出 'Hello, undefined',因為 this 指向了全域物件

const boundGreet = person.greet.bind(person);
boundGreet(); // 輸出 'Hello, Alice',因為 this 被綁定到 person

Function.prototype.call 和 Function.prototype.apply

Function.prototype.callFunction.prototype.apply 方法可以用來臨時地綁定函式的 this 值,並立即執行這個函式

callapply 的差別在於傳入參數的方式,call逐個傳入,而 apply以陣列的方式傳入

const person = {
name: "Alice",
};

function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}

greet.call(person, "Hello"); // 輸出 'Hello, Alice'
greet.apply(person, ["Hi"]); // 輸出 'Hi, Alice'

總結

  1. bind():用於創建一個新的函式,這個函式的 this 被固定為指定的物件。適用於需要保證 this 的綁定時機的情境,例如回調函式。
  2. call()apply():用於立即呼叫函式並指定 this 的值。call() 用逗號分隔的參數,apply() 用數組分隔的參數。
  3. 箭頭函式:箭頭函式的 this 是從函式定義時的詞法環境繼承的,適合在函式內部使用時保證 this 的一致性。

這些方法可以幫助你更好地控制和理解 this 的行為,使你的 JavaScript 程式碼更易於維護和預測。

JavaScript ++ vs. += 1,你應該選擇哪個?

· 閱讀時間約 4 分鐘

在 JavaScript 中,我們常常需要對變數進行增值操作,最常見的兩種方式就是使用 ++ 運算符和 += 1。 這兩者看起來都能達到相同的目的:將變數的值增加 1,但實際上它們之間有一些細微的差異。這篇文章將帶你了解這兩者之間的不同之處,幫助你在編寫程式時做出更明智的選擇。

++ 運算符

++ 運算符是一種一元運算符,用於將變數的值增加 1。它有兩種形式:前置遞增和後置遞增。

前置遞增++variable,先將變數的值增加 1,然後回才遞增後的值。

function makeCounter(initialValue = 0) {
let count = initialValue;
return () => {
return count++; // 先回傳 count 的值,然後再將其增加 1
};
}

const counter = makeCounter();
counter(); // 0
counter(); // 1
counter(); // 2

後置遞增variable++,先回傳變數的值,然後再將其增加 1。

function makeCounter(initialValue = 0) {
let count = initialValue;
return () => {
return ++count; // 先將 count 的值增加 1,然後再回傳增加後的值
};
}

const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3

+= 運算符

+= 運算符是一種賦值運算符,用於將變數的值增加指定的數量。它的形式是 variable += value,表示將變數的值增加 value

function makeCounter(initialValue = 0) {
let count = initialValue;
return () => {
return (count += 1); // 將 count 的值增加 1
};
}

const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3

總結

特性x++++xx += 1
操作順序先回傳變數的舊值,再進行自增先進行自增,再回傳變數的新值直接加 1 並回傳結果
回傳結果回傳原本的變數值回傳變數加 1 後的結果回傳變數加 1 後的結果
可讀性可能會讓人有點困惑行為明確,容易理解簡單易懂,適合大多數情況
適用場景需要先用原本的變數值的情況需要馬上使用加 1 後的結果進行簡單的增值操作

總的來說,++ 運算符和 += 1 運算符都是用來對變數進行增值操作的方法,但它們之間有一些細微的差異。在選擇使用哪一種方法時,你可以根據自己的需求和程式的可讀性來做出選擇。

如何在 JavaScript 中檢查物件是否有某個屬性

· 閱讀時間約 3 分鐘

在 JavaScript 中,我們經常需要檢查物件是否有某個屬性,以便進行相應的操作。有幾種方法可以實現這個功能,下面介紹其中幾種常用的方法:

TL;DR

方法是否能檢查原型鏈上的屬性說明
in 運算子檢查物件自身及其原型鏈上的屬性,若存在則回傳 true,否則回傳 false
Reflect.has()檢查物件自身及其原型鏈上的屬性,行為類似於 in 運算子。
Object.prototype.hasOwnProperty()僅檢查物件自身的屬性,若存在則回傳 true,否則回傳 false
Object.hasOwn()類似於 hasOwnProperty,但語法更簡潔,僅檢查物件自身的屬性。

in 運算子

in 運算子是 JavaScript 中用來檢查物件是否有指定屬性的方法之一。這個運算子會檢查物件自身及其原型鏈上是否有指定的屬性,如果有則回傳 true,否則回傳 false

const parent = {
type: "Person",
};

const city = Symbol("city");

const child = Object.create(parent);
child.name = "Kevin";
child.age = 30;
child[city] = "Taipei";

console.log("name" in child); // true
console.log("type" in child); // true
console.log(city in child); // true

Reflect.has() 方法

Reflect.has() 方法是 JavaScript 中用來檢查物件是否有指定屬性的方法之一。這個方法和 in 運算子類似,會檢查物件自身及其原型鏈上是否有指定的屬性。

const parent = {
type: "Person",
};

const city = Symbol("city");

const child = Object.create(parent);
child.name = "Kevin";
child.age = 30;
child[city] = "Taipei";

console.log(Reflect.has(child, "name")); // true
console.log(Reflect.has(child, "type")); // true
console.log(Reflect.has(child, city)); // true

Object.prototype.hasOwnProperty() 和 Object.hasOwn()

Object.prototype.hasOwnProperty() 是 JavaScript 中用來檢查物件是否有指定屬性的方法之一。這個方法會檢查物件自身是否有指定的屬性,如果有則回傳 true,否則回傳 false。需要注意的是,Object.prototype.hasOwnProperty() 方法不會檢查原型鏈上的屬性。 如果你想要更簡潔的方式來檢查物件是否有指定屬性,可以使用 Object.hasOwn()

const parent = {
type: "Person",
};

const city = Symbol("city");

const child = Object.create(parent);
child.name = "Kevin";
child.age = 30;
child[city] = "Taipei";

console.log(Object.prototype.hasOwnProperty.call(child, "name")); // true
console.log(Object.prototype.hasOwnProperty.call(child, "type")); // false
console.log(Object.prototype.hasOwnProperty.call(child, city)); // true

console.log(Object.hasOwn(child, "name")); // true
console.log(Object.hasOwn(child, "type")); // false
console.log(Object.hasOwn(child, city)); // true

如何在 JavaScript 中獲取物件的 keys

· 閱讀時間約 3 分鐘

在 JavaScript 中,我們經常需要取得物件的 keys 來進行迴圈操作或其他資料處理。不同的方法會帶來不同的結果,根據需求的不同,我們可以選擇適合的方法來取得物件的 keys。以下介紹幾種常用的方法:

TL;DR

方法是否能輸出 Symbol 屬性是否能輸出原型鏈上的屬性說明
Object.keys(obj)回傳物件自身的可枚舉字符串屬性的鍵的陣列,不包含 Symbol 屬性和原型鏈上的屬性
for...in迭代物件自身及其原型鏈上的所有可枚舉屬性,不包含 Symbol 屬性
Reflect.ownKeys(obj)回傳物件所有屬性鍵的陣列,包括可枚舉和不可枚舉屬性以及 Symbol 屬性,不包含原型鏈上的屬性

Object.keys()

Object.keys() 是 JavaScript 中最常用來取得物件 keys 的方法之一。這個方法會回傳一個陣列,裡面包含了該物件所有的可枚舉屬性的 key。需要注意的是,它只會回傳物件自身的屬性,不會包含繼承自原型鏈的屬性,也不會包含 Symbol 類型的屬性。

const parent = {
type: "Person",
};
const city = Symbol("city");

const child = Object.create(parent);
child.name = "Kevin";
child.age = 30;
child[city] = "Taipei";

const keys = Object.keys(child);

console.log(keys); // [ 'name', 'age']

for...in 迴圈

for...in 迴圈是一種用來迭代物件屬性的方法,不僅會迭代物件自身的屬性,還會包含那些繼承自原型鏈的屬性。不過,和 Object.keys() 一樣,它也不會列舉 Symbol 類型的屬性。

const parent = {
type: "Person",
};
const city = Symbol("city");

const child = Object.create(parent);
child.name = "Kevin";
child.age = 30;
child[city] = "Taipei";

for (const key in obj) {
console.log(key); // name, age, type
}

Reflect.ownKeys()

Reflect.ownKeys() 是一個功能更強大的方法,它會回傳一個包含物件所有屬性的陣列,這其中包括了可枚舉和不可枚舉屬性,也包括 Symbol 屬性。

const parent = {
type: "Person",
};
const city = Symbol("city");

const child = Object.create(parent);
child.name = "Kevin";
child.age = 30;
child[city] = "Taipei";

const keys = Reflect.ownKeys(child);

console.log(keys); // [ 'name', 'age', Symbol(city) ]