跳至主要内容

4 篇文章 含有標籤「closure」

檢視所有標籤

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

· 閱讀時間約 3 分鐘
kdchang

前言

Django 是一個由 Python 編寫的高階 Web 框架,強調快速開發與簡潔設計。它提供完整的功能模組如 ORM、Admin 介面、Form 處理、URL 路由與認證系統,讓開發者能專注於商業邏輯而非重複造輪子。本文將帶你一步一步建立一個基本的 Django 應用程式,並說明其核心概念。

一、環境安裝

首先,請確保你已安裝好 Python(建議 3.8+)與 pip。以下是建立虛擬環境並安裝 Django 的方式:

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

確認安裝成功:

django-admin --version

二、建立 Django 專案

Django 使用專案(project)與應用程式(app)來組織程式碼。先建立一個新的專案:

django-admin startproject mysite
cd mysite

你會看到以下結構:

mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py

三、建立應用程式

應用程式是 Django 專案的功能模組。例如一個部落格、用戶系統或留言板就是一個 app。以下我們建立一個簡單的留言板:

python manage.py startapp guestbook

guestbook/ 資料夾中會看到以下結構:

guestbook/
models.py
views.py
admin.py
apps.py
tests.py
...

接著,在 mysite/settings.py 中註冊這個 app:

INSTALLED_APPS = [
...
'guestbook',
]

四、定義資料模型(Model)

編輯 guestbook/models.py,新增一個留言的資料模型:

from django.db import models

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

def __str__(self):
return f'{self.name} 說:{self.content[:20]}...'

建立資料表:

python manage.py makemigrations
python manage.py migrate

五、啟用後台管理介面

Django 提供強大的 admin 功能。先建立一個 superuser:

python manage.py createsuperuser

然後在 guestbook/admin.py 中註冊 model:

from django.contrib import admin
from .models import Message

admin.site.register(Message)

啟動伺服器並進入後台:

python manage.py runserver

前往瀏覽器開啟 http://127.0.0.1:8000/admin/,使用剛建立的帳號登入,即可操作資料。

六、建立 Views 與 Templates

接下來,我們撰寫一個簡單的留言列表與新增留言的畫面。

views.py

from django.shortcuts import render, redirect
from .models import Message

def message_list(request):
if request.method == "POST":
name = request.POST.get('name')
content = request.POST.get('content')
if name and content:
Message.objects.create(name=name, content=content)
return redirect('message_list')

messages = Message.objects.order_by('-created_at')
return render(request, 'guestbook/message_list.html', {'messages': messages})

templates

建立目錄 guestbook/templates/guestbook/message_list.html

<!DOCTYPE html>
<html>
<head>
<title>留言板</title>
</head>
<body>
<h1>留言板</h1>
<form method="post">
{% csrf_token %}
姓名:<input type="text" name="name"><br>
留言:<br>
<textarea name="content" rows="5" cols="40"></textarea><br>
<input type="submit" value="送出">
</form>
<hr>
{% for msg in messages %}
<p><strong>{{ msg.name }}</strong> 說:</p>
<p>{{ msg.content }}</p>
<p><small>{{ msg.created_at }}</small></p>
<hr>
{% endfor %}
</body>
</html>

七、設定 URL 路由

guestbook/ 建立 urls.py

from django.urls import path
from . import views

urlpatterns = [
path('', views.message_list, name='message_list'),
]

然後到 mysite/urls.py 中加入:

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

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

八、啟動開發伺服器

python manage.py runserver

在瀏覽器開啟 http://127.0.0.1:8000/,你將看到留言板畫面,並能提交留言。


總結

Django 是功能非常齊全且具可擴展性的 Web 框架,初學者可從資料模型、後台管理、表單處理與模板引擎著手。本文僅為入門教學,未來更多進階功能如使用 class-based views、form 類別、自訂 middleware、部署到雲端等,規劃在之後的教學筆記進行介紹。

setTimeout + for loop + closure 核心介紹入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

一、varlet 的作用域差異

宣告方式作用域是否有暫時性死區(TDZ)
var函式作用域
let區塊作用域
const區塊作用域
  • var 宣告的變數在整個函式內都可存取
  • let 則只在所在區塊 {} 中有效

二、閉包(Closure)

閉包是指「函式能記住它被定義時的作用域,即使在外部執行也能存取當時的變數」。

function outer() {
let count = 0;
return function inner() {
console.log(count++);
};
}

const fn = outer();
fn(); // 0
fn(); // 1

這個例子中,inner 函式記住了 outer 中的 count 變數。


三、事件迴圈與 setTimeout

console.log("start");

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

console.log("end");

輸出順序:

start
end
timeout

原因是 setTimeout 是非同步的,它會被放入「事件佇列」(event queue)中,等主程式執行完後才會被處理。


四、面試陷阱:varsetTimeout 搭配

for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}

這段程式的輸出是:

3
3
3

因為:

  • var 沒有區塊作用域,所有的 setTimeout 都引用同一個 i
  • setTimeout 執行時,迴圈已跑完,i 已是 3

五、正確解法:使用 let

for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}

輸出為:

0
1
2

因為 let 有區塊作用域,每次迴圈都建立新的 i,閉包會記住各自的值。


六、另一個解法:IIFE(立即執行函式)

若必須用 var,可以用 IIFE 綁定每次的 i

for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => {
console.log(j);
}, 1000);
})(i);
}

這樣也會正確輸出:

0
1
2

七、總結

  1. var 是函式作用域,容易被非同步邏輯影響
  2. let 是區塊作用域,與閉包搭配可解決常見陷阱
  3. setTimeout 是非同步,會延遲執行
  4. 通常面試會結合這些概念出題,考你對 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 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),提升程式的模組化與可讀性。