跳至主要内容

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

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

· 閱讀時間約 4 分鐘
kdchang

什麼是閉包(Closure)?

閉包是 JavaScript 中的一個重要概念,指的是函式在創建時,能夠記住並存取其外部作用域的變數,即使該作用域已經執行完畢。這種特性使得 JavaScript 的函式可以擁有「記憶」的能力,允許函式保持對外部變數的存取權。

閉包的概念建立在 JavaScript 的**詞法作用域(Lexical Scope)**之上,也就是函式可以存取其被定義時所在的作用域中的變數,而不是函式執行時的作用域。


1. 基本範例

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

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

return innerFunction;
}

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

解析

  1. outerFunction 內部宣告了一個變數 outerVariable,並定義了一個 innerFunction
  2. innerFunction 存取 outerVariable,並被 outerFunction 返回。
  3. closureExample 執行時,即使 outerFunction 早已執行完畢,innerFunction 仍然能存取 outerVariable,這就是閉包的作用。

2. var 變數與閉包的問題

JavaScript 在 ES6 之前使用 var 來宣告變數,但 var 具有函式作用域(Function Scope),這可能會導致閉包相關的陷阱。

問題:for 迴圈與 var

function varClosureExample() {
var functions = [];

for (var i = 0; i < 3; i++) {
functions.push(function() {
console.log(i);
});
}

return functions;
}

var closures = varClosureExample();
closures[0](); // 輸出: 3
closures[1](); // 輸出: 3
closures[2](); // 輸出: 3

為什麼會這樣?

  1. var函式作用域,它在整個 varClosureExample 內部是共享的。
  2. closures[0]()closures[1]()closures[2]() 執行時,它們都參考同一個 i,但 i 已經變成 3(因為 for 迴圈已經執行完畢)。
  3. 所有函式都會輸出 3,而不是 0, 1, 2

3. 如何修正 var 的問題

解決方案 1:使用 let(區塊作用域)

function letClosureExample() {
var functions = [];

for (let i = 0; i < 3; i++) {
functions.push(function() {
console.log(i);
});
}

return functions;
}

var closures = letClosureExample();
closures[0](); // 輸出: 0
closures[1](); // 輸出: 1
closures[2](); // 輸出: 2

為什麼 let 有效?

  • let 具有區塊作用域(Block Scope),每次迴圈執行時,i 都是一個新的變數,因此閉包綁定的 i 值不會變化。

解決方案 2:使用 IIFE(立即執行函式)

如果只能使用 var,可以使用 IIFE(Immediately Invoked Function Expression)

function varClosureFixedExample() {
var functions = [];

for (var i = 0; i < 3; i++) {
(function(i) {
functions.push(function() {
console.log(i);
});
})(i);
}

return functions;
}

var closures = varClosureFixedExample();
closures[0](); // 輸出: 0
closures[1](); // 輸出: 1
closures[2](); // 輸出: 2

為什麼 IIFE 有效?

  • IIFE 會立即執行,並建立一個新的作用域,確保每次迭代時 i 都是獨立的。

4. 閉包的實際應用

(1) 模擬私有變數

function createCounter() {
var count = 0; // 外部變數

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

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

解析

  • countcreateCounter 內部變數,外部無法直接存取,只能透過 incrementdecrement 方法修改,形成了私有變數的概念

(2) 函式工廠(Function Factory)

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

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

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

解析

  • double 變數是一個閉包,記住了 multiplier2
  • triple 變數是一個閉包,記住了 multiplier3
  • 這種模式可以用來建立彈性的函式生成器。

(3) 延遲執行與事件處理

function delayedMessage(message, delay) {
setTimeout(function() {
console.log(message);
}, delay);
}

delayedMessage("Hello, JavaScript!", 2000); // 2 秒後輸出 "Hello, JavaScript!"

解析

  • setTimeout 內部的函式形成閉包,記住 message 變數,即使 delayedMessage 已經執行完畢。

5. 總結

閉包的核心概念

  • 函式可以存取其外部函式的變數,即使外部函式已經執行完畢。
  • var 變數的作用域為函式作用域,可能導致閉包內變數的值不如預期。
  • 使用 let 或 IIFE 可以避免 var 造成的閉包問題。

閉包的應用

  1. 私有變數(避免全域變數污染)
  2. 函式工廠(建立可重複使用的函式)
  3. 事件處理與回調函式
  4. 延遲執行與計時器

閉包是 JavaScript 的核心概念之一,它允許函式記住外部變數,即使作用域已經離開。var 宣告的變數可能會導致作用域問題,例如 for 迴圈內的閉包錯誤,而 let 或 IIFE 可以解決這個問題。

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

Kendo UI 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在現代前端開發中,不管是使用函式庫或是框架進行開發,使用 UI 元件庫可以大幅提升開發效率與使用者體驗。Kendo UI 是由 Progress 公司推出的前端元件庫,支援多種 JavaScript 框架(如 jQuery、Angular、React、Vue),提供豐富的 UI 元件,包括表格(Grid)、圖表(Chart)、日曆(Calendar)、表單元件、對話框等。Kendo UI 以高效能、易用性與高度可客製化著稱,廣泛應用於企業級應用系統。

本文將介紹 Kendo UI 的核心概念、重要特色,並以實際範例展示如何快速上手使用 Kendo UI 建立一個簡單的資料表格。


重點摘要

  • 多框架支援 Kendo UI 提供了多個版本,包含基於 jQuery 的 Kendo UI Core(免費)與商業版 Kendo UI Professional,以及針對 Angular、React、Vue 的專屬元件套件。

  • 元件豐富且成熟 擁有超過 70 種元件,涵蓋表格、圖表、日期時間選擇器、下拉選單、進度條、通知、編輯器等,能滿足多種開發需求。

  • 高效能資料操作 內建支援分頁、排序、篩選、群組功能的 Grid,並能與遠端 API 整合,進行即時資料呈現。

  • 高度客製化與主題支持 元件可調整外觀與行為,支援 Sass 變數,可自訂主題風格以符合企業品牌需求。

  • 國際化與無障礙設計 支援多語系與區域設定,遵循無障礙標準,提升應用的普及度與可及性。

  • 豐富文件與社群支援 官方提供完整 API 文件、教學範例與論壇,方便開發者學習與問題解決。


Kendo UI 基本使用範例(以 jQuery 版本為例)

以下範例示範如何快速在網頁中引入 Kendo UI,並使用 Grid 元件顯示靜態資料。

1. 環境準備

在 HTML 頁面中透過 CDN 方式引入 Kendo UI CSS 和 JavaScript 檔案,並引入 jQuery。

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<title>Kendo UI Grid 範例</title>
<!-- Kendo UI CSS -->
<link
rel="stylesheet"
href="https://kendo.cdn.telerik.com/2025.1.119/styles/kendo.default-v2.min.css"
/>
</head>
<body>
<div id="grid"></div>

<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Kendo UI JS -->
<script src="https://kendo.cdn.telerik.com/2025.1.119/js/kendo.all.min.js"></script>

<script>
$(document).ready(function () {
// 建立資料來源
var data = [
{ ProductID: 1, ProductName: '蘋果', Price: 120, InStock: true },
{ ProductID: 2, ProductName: '香蕉', Price: 90, InStock: false },
{ ProductID: 3, ProductName: '橘子', Price: 110, InStock: true },
{ ProductID: 4, ProductName: '芒果', Price: 150, InStock: true },
];

// 初始化 Grid
$('#grid').kendoGrid({
dataSource: {
data: data,
pageSize: 3,
},
pageable: true,
sortable: true,
filterable: true,
columns: [
{ field: 'ProductID', title: '產品編號', width: '100px' },
{ field: 'ProductName', title: '產品名稱' },
{ field: 'Price', title: '價格', format: '{0} 元', width: '120px' },
{
field: 'InStock',
title: '是否有貨',
template: "#= InStock ? '有貨' : '缺貨' #",
width: '100px',
},
],
});
});
</script>
</body>
</html>

2. 範例說明

  • 透過 <link> 引入 Kendo UI 樣式檔案,確保元件外觀正常呈現。
  • 使用 <script> 引入 jQuery 與 Kendo UI 主要功能腳本。
  • 透過 $("#grid").kendoGrid() 初始化一個 Grid 元件,設定資料來源為靜態陣列 data
  • 啟用分頁(pageable)、排序(sortable)及篩選(filterable)功能。
  • 定義欄位,包括產品編號、名稱、價格,以及是否有貨的自訂範本顯示。

3. 進階擴充

Kendo UI 的 Grid 支援各種複雜功能,例如:

  • 遠端資料讀取:可設定 dataSource.transport.read 指向 API,實現資料即時載入。
  • 編輯功能:支援內嵌或彈出編輯表單,輕鬆實現 CRUD 操作。
  • 群組與聚合:支援資料分組顯示與計算欄位總和、平均值。
  • 導出功能:支援匯出 Excel、PDF,方便報表產出。

總結

Kendo UI 是一套功能強大且彈性極高的前端 UI 元件庫,適合企業級應用開發,能有效提升開發效率與產品品質。從簡單的資料表格到複雜的資料視覺化與互動操作,Kendo UI 都能提供完善的解決方案。

若是新手入門建議先從官方文件及基礎元件(如 Grid、Button、DatePicker)開始熟悉,並搭配範例實作加深理解。透過不斷練習與調整設定,能快速掌握其靈活的客製化能力。

Vue3 Options API 和 Composition API 風格差異教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

根據官方文件的說明,一般來說 Vue 元件可以用兩種不同的 API 風格來撰寫:Options APIComposition API


Options API

使用 Options API 時,我們透過一個包含 datamethodsmounted 等選項的物件來定義元件邏輯。選項中定義的屬性會在函式內透過 this 曝露,this 指向的是元件實例。

<script>
export default {
// 從 data() 返回的屬性成為響應式狀態,
// 並會透過 `this` 曝露。
data() {
return {
count: 0
}
},

// Methods 是變更狀態並觸發更新的函式,
// 可作為模板中的事件處理程序綁定。
methods: {
increment() {
this.count++
}
},

// 生命週期鉤子會在組件的不同階段被調用。
// 此函式會在組件掛載時調用。
mounted() {
console.log(`初始計數值為 ${this.count}`)
}
}
</script>

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

Composition API

使用 Composition API 時,我們透過導入的 API 函式來定義組件邏輯。在單文件組件 (SFC) 中,Composition API 通常搭配 <script setup> 使用。setup 屬性是一個提示,它讓 Vue 執行編譯時轉換,從而減少樣板代碼。舉例來說,在 <script setup> 中宣告的導入、第一層變數和函式可直接用於模板中。

以下是相同的組件,模板保持不變,但改用 Composition API 和 <script setup>

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

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

// 改變狀態並觸發更新的函式
function increment() {
count.value++
}

// 生命週期鉤子
onMounted(() => {
console.log(`初始計數值為 ${count.value}`)
})
</script>

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

該選擇哪種風格?

這兩種 API 風格都能滿足常見的使用場景。它們是基於相同底層系統的不同介面。實際上,Options API 是基於 Composition API 實現的!Vue 的核心概念和知識在兩種風格中是共通的。

  1. Options API

    • 圍繞「組件實例」(如範例中的 this)進行設計,對於來自 OOP 語言背景的使用者,這種風格通常更符合以類為基礎的思維模型。
    • 初學者友好,通過選項分組的方式隱藏了響應式細節,簡化程式碼組織。
  2. Composition API

    • 在函式作用域中直接宣告響應式狀態變數,並透過組合多個函式來管理複雜性。
    • 更加靈活,但需要對 Vue 的響應式機制有深入理解才能有效使用。
    • 適合組織和重用邏輯的更強大模式。

學習上的建議

  • 新手學習:選擇對您來說更容易理解的風格。大多數核心概念在兩種風格中是共通的,您之後可以再學習另一種風格。
  • 生產使用
    • 如果不使用建構工具,或計畫主要用於低複雜度場景(如漸進式增強),建議使用 Options API
    • 如果計畫使用 Vue 構建完整應用,建議使用 Composition API + 單文件組件

在學習階段,我們也不需要僅限於某一種風格。可以根據適合的情境使用適合的 API 風格。

關於 Web 網頁後端工程師 Backend Engineer 的 10 個關鍵字

· 閱讀時間約 2 分鐘
kdchang

以下整理了後端工程師相關的 10 個關鍵字,涵蓋技能、工具與工作方式:

  1. Node.js / Python / Java / Ruby / PHP:常見的後端程式語言,用於構建伺服器端應用程式。

  2. Database(資料庫):包括 SQL(如 MySQL, PostgreSQL)NoSQL(如 MongoDB, Redis) 的操作與優化。

  3. REST API / GraphQL:設計與實現伺服器端 API 以支援前端資料請求。

  4. Authentication & Authorization(認證與授權):如 OAuth、JWT、Session,確保使用者安全與數據保護。

  5. Docker / Kubernetes:容器化與編排工具,用於應用部署與資源管理。

  6. Cloud Services:如 AWS、Google Cloud、Microsoft Azure,支援應用程式的雲端部署與運行。

  7. Microservices(微服務架構):設計可拆分且可獨立部署的服務模組。

  8. Message Queues(訊息佇列):如 RabbitMQ、Kafka,用於分布式系統的非同步通訊。

  9. Version Control(版本控制):如 Git / GitHub,用於團隊協作與程式碼管理。

  10. Performance Optimization(效能優化):針對伺服器與資料庫的效能調優,提升系統效率。

以上關鍵字為後端工程師平常工作或是職涯的核心工作內容和技能樹,透過不斷累積相關經驗和專案開發能力,可以讓自己成為更優秀的軟體工程師。

關於 Web 網頁前端工程師 Frontend Engineer 的 10 個關鍵字

· 閱讀時間約 2 分鐘
kdchang

以下整理了前端工程師相關的 10 個關鍵字,涵蓋技能、工具與工作方式:

  1. HTML:前端結構的基礎語言,用於定義網頁的內容和結構。

  2. CSS:用於設計網頁的樣式,包括佈局、配色和字體。

  3. JavaScript:前端開發的核心語言,為網頁添加互動性。

  4. React / Vue / Angular:主流的前端框架和函式庫,用於構建動態、模組化的應用程式。

  5. Responsive Design(響應式設計):確保網站在不同裝置和螢幕大小下的最佳顯示效果。

  6. REST API / GraphQL:前端與後端通訊的重要工具,用於前後端資料請求與傳輸。

  7. Webpack / Vite / Parcel:模組打包工具,用於構建和優化前端資源。

  8. Git / GitHub / GitLab:版本控制工具,用於協作開發與代碼管理。

  9. Cross-Browser Compatibility(跨瀏覽器相容性):確保網站在不同瀏覽器上的一致性表現。

  10. UI/UX Design(使用者介面/使用者體驗設計):理解使用者需求,優化網頁的易用性與美觀度。

以上關鍵字為前端工程師平常工作或是職涯的核心工作內容和技能樹,透過不斷累積相關經驗和專案開發能力,可以讓自己成為更優秀的軟體工程師。

concurrently.js 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在前端與全端開發中,我們經常需要同時執行多個指令,例如一邊啟動前端開發伺服器,一邊啟動後端 API 伺服器,或一邊監看 Sass 編譯,一邊執行 TypeScript 編譯。在這樣的情境下,concurrently 是一個非常實用的工具,它能讓我們在同一個命令列同時執行多個命令。

本篇筆記將介紹 concurrently 是什麼、如何安裝、基本用法,以及實際應用範例。


什麼是 concurrently?

concurrently 是一個 Node.js 套件,可以在一個命令中同時執行多個命令行指令,而且會把各個執行的結果分別以不同顏色標示,方便辨認。它常用來取代 npm-run-allgulp 等工具中針對「平行執行任務」的需求。

官方說明:“Run multiple commands concurrently.”

例如你想同時執行一個 React 專案的前端開發伺服器與後端 API 伺服器:

npm run start-frontend & npm run start-backend

在不同系統或 shell 可能有兼容性問題,用 concurrently 則會簡單許多。


1️⃣ 安裝 concurrently

首先,你需要在專案中安裝 concurrently

npm install concurrently --save-dev

或者使用 yarn:

yarn add concurrently --dev

安裝完成後,你可以在 package.jsonscripts 區塊中使用它。


2️⃣ 基本用法

最基本的用法如下:

npx concurrently "command1" "command2"

例如,同時執行 npm run servernpm run client

npx concurrently "npm run server" "npm run client"

執行時會在終端顯示兩個任務的 log,並自動用不同顏色標示來源。


3️⃣ 在 package.json 中使用

通常我們會把 concurrently 寫在 package.json 裡的 scripts 區塊:

{
"scripts": {
"server": "node server.js",
"client": "react-scripts start",
"dev": "concurrently \"npm run server\" \"npm run client\""
}
}

執行 npm run dev,就會同時執行 server.js(後端)與 react-scripts start(前端)。

這樣只需要一個指令就能同時啟動兩個服務,對開發非常方便。


4️⃣ 實際範例:React + Express 同時啟動

假設你有一個專案結構如下:

my-app/
client/
(React 專案)
server/
(Express 後端)

server/package.json

{
"name": "server",
"version": "1.0.0",
"scripts": {
"start": "node index.js"
}
}

client/package.json

{
"name": "client",
"version": "1.0.0",
"scripts": {
"start": "react-scripts start"
}
}

在專案根目錄的 package.json 中加入:

{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"start": "concurrently \"npm start --prefix server\" \"npm start --prefix client\""
},
"devDependencies": {
"concurrently": "^8.0.0"
}
}
  • --prefix 是讓 npm 到指定資料夾執行
  • npm start --prefix server 會到 server/ 資料夾執行 npm start
  • npm start --prefix client 會到 client/ 資料夾執行 npm start

此時執行:

npm start

就會同時啟動 React 前端與 Express 後端,而且都會在終端中顯示 log,方便觀察開發情況。


5️⃣ 進階用法

✅ 自訂 prefix 與顏色

可以自訂 log 標籤與顏色,方便辨識哪一個 log 來自哪個指令:

npx concurrently --names "BACK,FRONT" --prefix-colors "bgBlue.bold,bgMagenta.bold" "npm run server" "npm run client"

效果:

[BACK] server running on port 5000
[FRONT] webpack compiled successfully

✅ 自動終止所有執行中的任務

當一個任務失敗時,讓其他任務也自動停止:

npx concurrently --kill-others "npm run server" "npm run client"

當其中一個 command 錯誤,另一個也會被 kill,避免殘留無用的背景 process。


6️⃣ 常見用途

concurrently 常見的使用情境有:

  • 同時啟動多個 Node.js 服務
  • 一邊監看 Sass/LESS 編譯,一邊執行 webpack
  • 同時跑 test 與 build
  • 同時監看前端與後端程式碼

如果你用 create-react-appNext.jsViteExpressNestJS 這些框架開發全端應用,concurrently 非常適合整合開發流程。


7️⃣ 與其他工具比較

concurrently 的特色在於:

  • 簡單語法(不需要額外配置檔)
  • 支援 Windows、Linux、Mac
  • 輕量、僅做平行執行,不強制任務流程
  • log 輸出自帶標籤與顏色
  • 可搭配 npm script 或 npx 單獨執行

如果需要「先後順序執行多個任務」,則可以搭配 npm-run-all


總結

concurrently 是一個適合在開發環境中同時執行多個命令的小工具,對需要前後端同時啟動、前端多個編譯工作並行的開發者非常實用。

它用法簡單、安裝快速、跨平台,推薦將它納入專案開發腳本中,以提升開發效率。

希望這篇筆記能幫助你了解 concurrently 的用途與基本操作!

Django RESTful API 入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

隨著 Web 與行動應用的發展,API(應用程式介面)已成為前後端溝通的橋樑。RESTful API 是目前最常見的 API 設計風格之一,而 Django REST Framework(簡稱 DRF)則是基於 Django 的強大工具,讓開發 RESTful API 變得更簡單。

本篇筆記將帶我們從零開始,快速建立一個 Django RESTful API 專案,實作一個基本的「文章系統」。


1. 安裝 Django 與 Django REST Framework

首先,建立一個虛擬環境並安裝必要套件:

python -m venv venv
source venv/bin/activate # Windows 用戶請使用 venv\Scripts\activate
pip install django djangorestframework

接著,建立 Django 專案:

django-admin startproject myapi
cd myapi

建立一個 app 來處理文章功能:

python manage.py startapp articles

2. 設定專案

myapi/settings.py 中,將 rest_frameworkarticles 加入 INSTALLED_APPS

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'articles',
]

3. 建立模型

打開 articles/models.py,定義一個 Article 模型:

from django.db import models

class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.title

然後執行遷移:

python manage.py makemigrations
python manage.py migrate

4. 建立序列化器(Serializer)

articles 資料夾中建立 serializers.py

from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'

這樣就完成了資料庫模型與 JSON 之間的轉換設定。


5. 建立 API View

articles/views.py 中加入以下程式:

from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer

class ArticleListCreateView(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer

class ArticleDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer

我們使用了 DRF 的「通用類別視圖」,大幅減少手寫程式碼。


6. 設定 URL

articles 資料夾中建立 urls.py

from django.urls import path
from .views import ArticleListCreateView, ArticleDetailView

urlpatterns = [
path('articles/', ArticleListCreateView.as_view(), name='article-list-create'),
path('articles/<int:pk>/', ArticleDetailView.as_view(), name='article-detail'),
]

接著將 articles/urls.py 加入主專案的 myapi/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('articles.urls')),
]

現在 API 路由已經設定完成。


7. 測試 API

啟動開發伺服器:

python manage.py runserver

打開瀏覽器,造訪 http://127.0.0.1:8000/api/articles/,我們會看到 DRF 提供的漂亮瀏覽介面,可以直接用網頁表單測試 API。

我們也可以用工具如 Postman 或 curl 測試:

建立新文章

curl -X POST -H "Content-Type: application/json" \
-d '{"title": "第一篇文章", "content": "這是一篇測試文章"}' \
http://127.0.0.1:8000/api/articles/

取得文章列表

curl http://127.0.0.1:8000/api/articles/

取得單篇文章

curl http://127.0.0.1:8000/api/articles/1/

更新文章

curl -X PUT -H "Content-Type: application/json" \
-d '{"title": "更新後標題", "content": "更新後內容"}' \
http://127.0.0.1:8000/api/articles/1/

刪除文章

curl -X DELETE http://127.0.0.1:8000/api/articles/1/

8. 總結

透過這篇筆記,我們完成了:

  • 安裝 Django 與 DRF
  • 定義模型與序列化器
  • 使用泛型類別視圖實作 CRUD API
  • 設定 URL 路由
  • 使用瀏覽器或命令列工具測試 API

Django REST Framework 提供了許多自動化與簡化開發的工具,讓我們能快速建立出符合 REST 標準的 API。當然,隨著需求增加,我們也可以進一步學習自定義權限、驗證、過濾與分頁等功能。