跳至主要内容

87 篇文章 含有標籤「前端工程」

檢視所有標籤

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),提升程式的模組化與可讀性。

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 網頁前端工程師 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(使用者介面/使用者體驗設計):理解使用者需求,優化網頁的易用性與美觀度。

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

JavaScript ES6 入門語法教學筆記 | 學習筆記

· 閱讀時間約 5 分鐘
kdchang

ECMAScript 6 又稱 ECMAScript 2015,是 JavaScript 語言的新一代標準,讓 JavaScript 可以更容易撰寫大型複雜的應用程式並避免不必要的錯誤。

以下介紹常用 ES6 入門語法:

一、let & const 變數宣告

  1. let:用於宣告變數,可重新賦值。
let name = 'John';
name = 'Mike'; // 可以重新賦值
  1. const:用於宣告常數,賦值後不可更改。
const pi = 3.14;
pi = 3.1415; // 會報錯

建議預設使用 const,僅需變更時使用 let。兩者作用域為 block scope

ES6 中,let區塊作用域(Block Scope) 是它與舊有的 var 最大的不同之一。


什麼是 Block Scope(區塊作用域)

  • 使用 let 宣告的變數,只能在該程式區塊 {} 內部存取。
  • 區塊作用域指的是任何用 {} 包起來的範圍,例如:
    • ifforwhile 等程式區塊。
    • 一般 {} 花括號內的區域。

範例說明

1. let 在區塊內的作用範圍

{
let x = 10;
console.log(x); // 10
}
console.log(x); // ReferenceError: x is not defined
  • x{} 區塊內宣告,僅在該區塊內有效。
  • 區塊外存取會出錯。

2. var 沒有區塊作用域(舊語法對比)

{
var y = 20;
console.log(y); // 20
}
console.log(y); // 20
  • var 沒有區塊作用域,y 雖在 {} 內宣告,但可在區塊外存取。

3. for 迴圈中的 let

for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ReferenceError: i is not defined
  • i 只在 for 迴圈內有效。

let 的區塊作用域優點

  1. 避免變數污染:let 限制變數在區塊內,避免影響區塊外的程式碼。
  2. 防止重複定義:同一區塊內不能重複宣告相同變數。
    let a = 1;
    let a = 2; // SyntaxError: Identifier 'a' has already been declared
  3. 更安全、可預期的變數管理。

總結

關鍵字區塊作用域重複宣告提升(Hoisting)行為
let不可提升但不初始化(TDZ)
var提升並初始化 undefined

建議盡量用 letconst,避免使用 var
這樣可以減少潛在的 bug,也符合現代 JavaScript 開發的最佳實踐。


二、模板字串(Template Literals)

以前字串串變數要使用 +,現在可以使用反引號 (``) 定義字串,可插入變數。

const name = 'John';
const age = 25;
console.log(`我叫 ${name},今年 ${age}`);

三、箭頭函式(Arrow Functions)

  1. 基本語法:
const add = (a, b) => {
return a + b;
}
  1. 簡寫形式:
const add = (a, b) => a + b;
  1. 單一參數可省略括號:
const square = n => n * n;

箭頭函式不會綁定自己的 this,繼承外層作用域的 this


沒錯!這句話是 箭頭函式(Arrow Function) 很重要的特性之一,這裡幫你拆解得更清楚一點:


什麼是 this

this 代表函式執行時所屬的物件,依照函式被呼叫的方式不同,this 的值也會不同。

例如:

function normalFunction() {
console.log(this);
}
normalFunction(); // 在瀏覽器環境中,this 會是 window 物件

如果這個函式被某個物件呼叫:

const obj = {
name: 'John',
sayHi: function() {
console.log(this.name);
}
};
obj.sayHi(); // John,this 指向 obj

箭頭函式的 this 特性

箭頭函式不會綁定自己的 this,它會「繼承外層作用域」的 this

也就是說:

  • 傳統函式:this 依賴呼叫方式來決定。
  • 箭頭函式:this 取決於箭頭函式宣告時所在的外層作用域的 this

範例說明:

傳統函式 vs 箭頭函式

const obj = {
name: 'John',
normalFunc: function() {
console.log(this.name); // this 指向 obj
},
arrowFunc: () => {
console.log(this.name); // this 指向外層(通常是 window 或 undefined)
}
};

obj.normalFunc(); // John
obj.arrowFunc(); // undefined(或瀏覽器中可能是 window.name)

常見應用場景:回呼函式(callback)中的 this

假設我們有一個計時器:

const obj = {
name: 'John',
timer: function() {
setTimeout(function() {
console.log(this.name); // undefined 或 window.name
}, 1000);
}
};

obj.timer();

因為 setTimeout 裡的傳統函式,它的 this 在執行時會指向 window

若改用箭頭函式:

const obj = {
name: 'John',
timer: function() {
setTimeout(() => {
console.log(this.name); // John
}, 1000);
}
};

obj.timer();

箭頭函式不會綁定自己的 this,會繼承 timer 函式的 this,因此會正確印出 John


常見疑問

為什麼箭頭函式不綁定自己的 this

主要是為了解決回呼函式中 this 易出錯的問題

以前會這樣解法:

const that = this; // 變數 that 保存正確的 this
setTimeout(function() {
console.log(that.name);
}, 1000);

現在有箭頭函式,就不用這麼麻煩。


小結

類型this 綁定方式一般用途
傳統函式 function執行時決定物件方法、建構函式
箭頭函式 =>定義時決定callback 回呼函式、內部函式需要使用外部 this 的情境

總結:

  • 一般物件方法用傳統函式。this 由呼叫的物件決定
  • callback 回呼函式、內部函式用箭頭函式。

這樣就可以避免大部分 this 的混亂狀況!

四、解構賦值(Destructuring)

  1. 陣列解構:
const arr = [1, 2, 3];
const [a, b, c] = arr;
  1. 物件解構:
const person = { name: 'John', age: 25 };
const { name, age } = person;

五、展開運算符(Spread Operator)

  1. 陣列展開:
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
  1. 物件展開:
const obj1 = { name: 'John', age: 25 };
const obj2 = { ...obj1, city: 'Taipei' };

六、預設參數(Default Parameters)

函式參數可設定預設值:

const greet = (name = '訪客') => {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, 訪客!
greet('John'); // Hello, John!

這些 ES6 基礎語法,是現代 JavaScript 開發的常用技巧,掌握這些概念能大幅提升程式撰寫效率。