跳至主要内容

83 篇文章 含有標籤「前端」

檢視所有標籤

Vue.js 3 官方入門語法教學筆記 [4] - Form Bindings 表單綁定 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

Form Bindings 表單綁定
使用 v-bindv-on 結合,我們可以對表單輸入元素創建雙向綁定:

<template>
<input :value="text" @input="onInput">
<p>{{ text }}</p>
</template>

<script setup>
import { ref } from 'vue'

const text = ref('')

function onInput(e) {
// v-on 處理器接收原生 DOM 事件作為參數
text.value = e.target.value
}
</script>

當您在輸入框中輸入時,您應該會看到 <p> 中的文字隨之更新。


為了簡化雙向綁定,Vue 提供了 v-model 指令,它本質上是上述代碼的語法糖:

<template>
<input v-model="text">
<p>{{ text }}</p>
</template>

<script setup>
import { ref } from 'vue'

const text = ref('')
</script>

v-model 會自動同步 <input> 的值與綁定的狀態,因此我們不再需要為此使用事件處理器。


v-model 不僅適用於文本輸入,還適用於其他輸入類型,如復選框 (checkbox)、單選按鈕 (radio button) 和下拉選單 (select dropdown)。有關更多細節,請參閱官方指南 - 表單綁定


現在,我們試著將代碼重構為使用 v-model

SFC/Composition 版本:

<script setup>
import { ref } from 'vue'

const text = ref('')
</script>

<template>
<input v-model="text" placeholder="Type here">
<p>{{ text }}</p>
</template>

SFC/Options 版本:

<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>

<template>
<input :value="text" @input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>

HTML/Options 版本:

<script type="module">
import { createApp } from 'vue'

createApp({
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}).mount('#app')
</script>

<div id="app">
<input :value="text" @input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</div>

HTML/Composition 版本:

<script type="module">
import { createApp, ref } from 'vue'

createApp({
setup() {
const text = ref('')

function onInput(e) {
text.value = e.target.value
}

return {
text,
onInput
}
}
}).mount('#app')
</script>

<div id="app">
<input :value="text" @input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</div>

Vue.js 3 官方入門語法教學筆記 [3] - Event Listeners 事件監聽器 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

Event Listeners 事件監聽器
在 Vue 中,我們可以使用 v-on 指令監聽 DOM 事件:

<template>
<button v-on:click="increment">{{ count }}</button>
</template>

由於 v-on 的使用頻率很高,Vue 提供了一個簡寫語法:

<template>
<button @click="increment">{{ count }}</button>
</template>

在這裡,increment 是在 <script setup> 中定義的一個函式:

<script setup>
import { ref } from 'vue'

// 定義響應式狀態
const count = ref(0)

// 定義函式來更新狀態
function increment() {
// 更新組件的狀態
count.value++
}
</script>

在函式內,我們可以透過修改 ref 的值來更新組件的狀態。


事件處理器也可以使用內聯表達式,並透過修飾符簡化常見任務。這些細節在指南 - 事件處理中有詳細說明。


現在,我們可以試著自己實作 increment 函式,並使用 v-on 將它綁定到按鈕。

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
count.value++
}
</script>

<template>
<button @click="increment">Count is: {{ count }}</button>
</template>

Vue.js 3 官方入門語法教學筆記 [2] - Attribute Bindings 屬性綁定 | 學習筆記

· 閱讀時間約 1 分鐘
kdchang

Attribute Bindings 屬性綁定
在 Vue 中,Mustache 語法只能用於文字插值。要將屬性綁定到動態值,我們需要使用 v-bind 指令:

<template>
<div v-bind:id="dynamicId"></div>
</template>

指令是一種特殊的屬性,以 v- 前綴開頭,屬於 Vue 的模板語法的一部分。與文字插值類似,指令的值是 JavaScript 表達式,可以訪問組件的狀態。有關 v-bind 和指令語法的完整細節,請參閱官方說明指南 - 模板語法

冒號之後的部分(:id)是指令的「參數」。在這裡,元素的 id 屬性將與組件狀態中的 dynamicId 屬性同步。

由於 v-bind 的使用頻率很高,Vue 提供了專用的簡寫語法:

<template>
<div :id="dynamicId"></div>
</template>

我們可以試著將動態類名綁定到 <h1>,使用 titleClassref 作為值。如果綁定正確,文字應該會變成紅色!

<script setup>
import { ref } from 'vue'

const titleClass = ref('title')
</script>

<template>
<h1 :class="titleClass">Make me red</h1> <!-- add dynamic class binding here -->
</template>

<style>
.title {
color: red;
}
</style>

Vue.js 3 官方入門語法教學筆記 [1] - Declarative Rendering 聲明式渲染 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 Vue3 我們常看到的是 Vue 單文件元件 (SFC)。SFC 是一個可重用的、獨立的程式碼區塊,它將相關的 HTML、CSS 和 JavaScript 封裝在一起,並寫在一個 .vue 文件中。

Vue 的核心特性是聲明式渲染:使用擴展 HTML 的模板語法,我們可以根據 JavaScript 的狀態描述 HTML 應該如何呈現。當狀態發生變化時,HTML 會自動更新。

可以在狀態變化時觸發更新的狀態被認為是響應式的。我們可以使用 Vue 的 reactive() API 來聲明響應式狀態。通過 reactive() 創建的對象是 JavaScript 的 Proxy,它們的行為與普通物件相同:

SFC 版本:

import { reactive } from 'vue'

const counter = reactive({
count: 0
})

console.log(counter.count) // 0
counter.count++

html 版本:

<script type="module">
import { createApp, ref } from 'vue'

createApp({
setup() {
// component logic
// declare some reactive state here.

return {
// exposed to template
}
}
}).mount('#app')
</script>

<div id="app">
<h1>Make me dynamic!</h1>
</div>

reactive() 只能作用於物件(包括陣列和內建類型如 Map 和 Set)。另一方面,ref() 可以接受任何類型的值並創建一物件,其內部值通過 .value 屬性暴露出來:

import { ref } from 'vue'

const message = ref('Hello World!')

console.log(message.value) // "Hello World!"
message.value = 'Changed'

有關 reactive()ref() 的更多細節,可以參考官方教學指南 - 響應式基礎

在組件的 <script setup> 區塊中聲明的響應式狀態可以直接在模板中使用。我們可以基於 counter 對象和 message 的值,使用 Mustache 語法渲染動態文字:

<template>
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</template>

注意,當在模板中訪問 messageref 值時,我們不需要使用 .value:它會自動取值,以提供更簡潔的用法。

Mustache {{ }} 中的內容不限於標識符或路徑 —— 我們可以使用任何有效的 JavaScript 表達式:

<template>
<h1>{{ message.split('').reverse().join('') }}</h1>
</template>

現在,試著自己創建一些響應式狀態,並使用它來為模板中的 <h1> 渲染動態文本內容吧!

<script setup>
import { reactive, ref } from 'vue'

const counter = reactive({ count: 0 })
const message = ref('Hello World!')
</script>

<template>
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</template>

總結

Vue.js 3 提供了更好的性能、更靈活的 API 和更簡潔的開發體驗。無論是使用 Composition API 還是 Options API,都能快速上手並構建強大的前端應用。

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 物件的場合

async/await 入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

asyncawait 是 JavaScript 中處理非同步操作的語法糖,它們使得非同步代碼更加易讀和易寫,避免了傳統回調函數(callback)或 Promise.then() 鏈式調用的冗長性。

1. async 關鍵字

async 是一個關鍵字,用來標記一個函數為「非同步函數」。非同步函數會隱式地返回一個 Promise,並且在函數內部,你可以使用 await 來等待非同步操作的結果。

語法

async function example() {
// 可以在這裡使用 await
}

當你呼叫這個函數時,它會立即返回一個 Promise。如果函數內的代碼執行成功,這個 Promise 會被解析;如果有錯誤,Promise 會被拒絕。

2. await 關鍵字

await 必須在 async 函數內部使用,它會讓 JavaScript 等待某個 Promise 完成並返回結果。await 會使得後續代碼暫停,直到 Promise 被解決或拒絕(解決是指成功完成操作,拒絕則是發生錯誤)。

語法

const result = await promise; // 等待 Promise 完成並取得結果

如果 Promise 解決(成功),await 會返回結果。如果 Promise 被拒絕(失敗),會拋出錯誤,這通常需要使用 try...catch 來處理。

範例

// 模擬一個非同步操作
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched!");
}, 2000);
});
}

// 使用 async/await
async function getData() {
try {
console.log("Fetching data...");
const data = await fetchData(); // 等待 fetchData 完成
console.log(data); // 顯示結果
} catch (error) {
console.error("Error:", error); // 處理錯誤
}
}

getData(); // 呼叫 async 函數

3. async/await 的特點:

  • 簡化非同步代碼async/await 讓非同步代碼的寫法更像是同步代碼,避免了回調函數的「Callback hell 回調地獄」。
  • 錯誤處理:你可以使用 try...catch 塊來捕獲非同步操作中的錯誤,這使得錯誤處理比傳統的 .catch() 更加簡單直觀。
  • 非阻塞執行:儘管代碼看起來是同步執行的,但非同步操作並不會阻塞主執行線程,其他代碼可以繼續執行。

總結

  • async 將函數標記為非同步函數。
  • await 使代碼等待 Promise 的解決結果,並可以在 async 函數內使用。
  • 使用 async/await 可以使非同步代碼更加簡潔且易於理解。

JavaScript Call Stack 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

JavaScript 開發中,理解 Call Stack(呼叫堆疊) 是掌握執行流程與錯誤除錯的關鍵概念之一。它決定了程式碼執行的順序,也與同步與非同步行為密切相關。本文將深入介紹 Call Stack 的基本原理,並透過實際範例幫助你理解它的運作方式。


1. 什麼是 Call Stack?

Call Stack 是 JavaScript 引擎用來管理函式呼叫的結構,它遵循 LIFO(Last In, First Out,後進先出) 原則。當一個函式被呼叫時,它會被推入(push)堆疊頂部,當函式執行完畢後,會從堆疊中彈出(pop)。

JavaScript 是**單執行緒(single-threaded)**的語言,這代表它一次只能執行一件事。Call Stack 便是 JavaScript 用來管理同步程式碼執行順序的機制。


2. Call Stack 運作原理

讓我們用一個簡單的範例來說明 Call Stack 的運作方式:

function first() {
console.log("First function start");
second();
console.log("First function end");
}

function second() {
console.log("Second function start");
third();
console.log("Second function end");
}

function third() {
console.log("Third function");
}

first();

執行步驟

  1. first() 被呼叫,推入 Call Stack。
  2. first() 內部呼叫 second()second() 推入 Call Stack。
  3. second() 內部呼叫 third()third() 推入 Call Stack。
  4. third() 執行 console.log("Third function"),然後執行完畢並從 Call Stack 移除。
  5. second() 繼續執行 console.log("Second function end"),執行完畢並從 Call Stack 移除。
  6. first() 繼續執行 console.log("First function end"),執行完畢並從 Call Stack 移除。
  7. Call Stack 清空,程式執行結束。

執行輸出

First function start
Second function start
Third function
Second function end
First function end

3. Call Stack 與錯誤訊息

如果函式之間發生無窮遞迴,Call Stack 會不斷推入函式,最終導致 Maximum call stack size exceeded 錯誤。

範例:遞迴導致 Stack Overflow

function recursiveFunction() {
recursiveFunction();
}

recursiveFunction();

錯誤訊息

Uncaught RangeError: Maximum call stack size exceeded

這是因為 recursiveFunction() 不斷呼叫自己,導致 Call Stack 無法清空,最終超過瀏覽器設定的堆疊大小。


4. Call Stack 與非同步

JavaScript 本質上是單執行緒的語言,但它透過 Event Loop(事件迴圈) 來處理非同步程式碼,例如 setTimeoutfetch 等。

範例:setTimeout 與 Call Stack

console.log("Start");

setTimeout(() => {
console.log("Inside setTimeout");
}, 0);

console.log("End");

執行輸出

Start
End
Inside setTimeout

執行流程解析

  1. console.log("Start") 直接執行,輸出 Start
  2. setTimeout 被呼叫,但它是非同步函式,會被放入 Web API(瀏覽器環境),不會影響 Call Stack。
  3. console.log("End") 直接執行,輸出 End
  4. 當 Call Stack 清空後,事件迴圈(Event Loop)會將 setTimeout 內的回呼函式放入 Call Stack,執行 console.log("Inside setTimeout")

這說明了 Call Stack 只負責同步程式碼,非同步程式碼會透過 Web API 與 Event Loop 處理。


5. 使用開發者工具檢查 Call Stack

大多數現代瀏覽器(如 Chrome、Firefox)都內建開發者工具,可用來觀察 Call Stack。

如何使用 Chrome DevTools

  1. 開啟 Chrome 瀏覽器,按 F12Ctrl + Shift + I(Mac 使用 Cmd + Option + I)。
  2. 進入 Sources 面板。
  3. 設置 斷點(breakpoint) 在 JavaScript 代碼內的某行。
  4. 重新載入頁面,當程式執行到該行時會暫停。
  5. 在右側 Call Stack 面板 中查看目前堆疊狀態。

這個工具能幫助開發者更直觀地理解 Call Stack 的運作方式。


6. 總結

Call Stack 是 JavaScript 引擎管理函式執行順序的核心機制,掌握它的運作原理對於理解 JavaScript 的同步與非同步行為至關重要。總結要點如下:

  1. Call Stack 採用 LIFO(後進先出)原則,函式執行時會推入堆疊,結束後會移除。
  2. 過多遞迴可能導致 Stack Overflow,應確保遞迴函式有適當的終止條件。
  3. JavaScript 是單執行緒的語言,但透過 Web API 和 Event Loop 可處理非同步操作。
  4. 開發者工具能幫助分析 Call Stack,對於除錯非常有幫助。

透過理解 Call Stack 的運作,開發者可以更有效地編寫、優化與除錯 JavaScript 程式碼。

JavaScript let 閉包(Closure)入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

什麼是閉包(Closure)?

閉包是 JavaScript 中的一個強大概念,它允許函式「記住」其外部作用域(lexical scope),即使該作用域已經執行完畢並離開了執行環境。閉包使得函式能夠存取其外部函式內部的變數,而這些變數通常在外部函式執行結束後仍然可以被存取。

在 JavaScript 中,每當一個函式被創建時,它都會自動獲得對其外部變數的存取權,這就是閉包的核心概念。


閉包的基本概念

閉包的最基本形式是函式內部返回另一個函式,而返回的函式仍然能夠存取外部函式的變數,即使外部函式已經執行完畢。例如:

function outerFunction() {
let outerVariable = "我是外部變數";

function innerFunction() {
console.log(outerVariable);
}

return innerFunction;
}

const closureExample = outerFunction();
closureExample(); // 輸出: "我是外部變數"

在這個例子中:

  1. outerFunction 內部定義了一個變數 outerVariable,並宣告了一個 innerFunction
  2. innerFunction 存取 outerVariable,然後被 outerFunction 返回。
  3. closureExample 執行時,即使 outerFunction 已經執行完畢,它仍然可以存取 outerVariable,因為它形成了一個閉包。

閉包的實際應用

閉包在 JavaScript 中有許多實際用途,包括資料封裝、模擬私有變數、事件處理,以及避免全域變數污染等。

1. 資料封裝與模擬私有變數

在 JavaScript 中,沒有內建的 private 修飾符,但可以透過閉包來模擬私有變數:

function createCounter() {
let count = 0; // 私有變數

return {
increment: function() {
count++;
console.log(`計數器值: ${count}`);
},
decrement: function() {
count--;
console.log(`計數器值: ${count}`);
},
getCount: function() {
return count;
}
};
}

const counter = createCounter();
counter.increment(); // 計數器值: 1
counter.increment(); // 計數器值: 2
console.log(counter.getCount()); // 2
counter.decrement(); // 計數器值: 1

在這個例子中:

  • count 變數是 createCounter 的內部變數,外部無法直接存取。
  • incrementdecrementgetCount 方法則形成閉包,允許我們操作 count

這種方法可以防止外部直接修改 count,達到變數封裝的效果。


2. 事件處理中的閉包

閉包在事件處理中特別有用,例如當我們需要讓事件處理函式記住某些狀態時:

function attachEventListener() {
let count = 0;

document.getElementById("clickButton").addEventListener("click", function() {
count++;
console.log(`按鈕點擊次數: ${count}`);
});
}

attachEventListener();

這裡:

  • click 事件處理函式記住了 count 變數,即使 attachEventListener 已執行完畢,每次點擊按鈕時,count 仍然會被持續累加。

3. 立即函式(IIFE, Immediately Invoked Function Expression)

立即函式是一種使用閉包的技術,可用於模擬私有作用域,避免變數污染全域空間:

const counter = (function() {
let count = 0;

return {
increment: function() {
count++;
console.log(`計數器值: ${count}`);
},
decrement: function() {
count--;
console.log(`計數器值: ${count}`);
},
getCount: function() {
return count;
}
};
})();

counter.increment(); // 計數器值: 1
counter.increment(); // 計數器值: 2
console.log(counter.getCount()); // 2
counter.decrement(); // 計數器值: 1

這裡:

  • (function() {...})() 是一個立即執行函式,它執行後返回了一個對象,該對象內部的函式形成閉包,能夠存取 count

這種技術在早期 JavaScript 程式設計中常被用來創建模組化的程式碼,避免全域變數污染。


4. 用於函式工廠(Function Factory)

閉包可以用來創建不同的函式行為,例如建立不同的乘法器:

function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

這裡:

  • createMultiplier 接受 multiplier 作為參數,返回一個新的函式。
  • 該函式形成閉包,記住 multiplier,即使 createMultiplier 已經執行完畢。

這種模式在高階函式設計中非常常見。


閉包的注意事項

雖然閉包提供了強大的功能,但如果不當使用,可能會導致記憶體洩漏。例如:

function createLargeClosure() {
let largeArray = new Array(1000000).fill("資料");
return function() {
console.log(largeArray.length);
};
}

const closure = createLargeClosure();
// 如果 closure 持續存在,largeArray 也不會被回收
  • largeArray 變數被閉包記住,無法被垃圾回收機制(GC)回收,可能導致記憶體洩漏。
  • 解決方案是確保不再使用閉包時,讓變數參考變為 null 或適時使用 WeakMap 來管理記憶體。

結論

閉包是 JavaScript 的核心概念之一,理解閉包有助於寫出更靈活、可維護的程式碼。它主要用於:

  • 變數封裝(模擬私有變數)
  • 事件處理(記住狀態)
  • 函式工廠(創建可重複使用的函式)
  • IIFE(避免變數污染)

掌握閉包後,可以更進一步學習 JavaScript 中的函式式程式設計(Functional Programming),提升程式的模組化與可讀性。

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