2009:ECMAScript 5——嚴格模式與 JSON#
事件#
2009年12月,ECMAScript 5(ECMA-262 第五版) 發佈。這是 ES3 之後十年來的第一個新版本,由 ES4 內戰失敗後的務實路線(ES3.1)演變而來。
新語法與特性#
1. 嚴格模式("use strict")#
- 為何加入:ES3 的語言有大量歷史陷阱(隱式全域變數、
with、重複參數、this混亂)。ES4 失敗後,社群共識是「與其重造,不如提供一個修正版模式」。 - 理論原因:語言演化中的「漸進修正」策略——新語義放進 opt-in 模式,舊程式碼不受影響(向後相容是 Web 的鐵律)。
- 實用原因:在嚴格模式下,
foo = 1(未宣告)會報錯而非默默建立全域變數,可在開發期捕捉錯誤。 - 彌補缺陷:靜默錯誤(silent errors)——JS 最惡名昭彰的除錯噩夢。
2. JSON 原生支援(JSON.parse / JSON.stringify)#
- 為何加入:JSON(2001,Crockford)已成為 Ajax 時代的事實標準,但各家瀏覽器要靠外部函式庫解析(且
eval()解析有安全風險)。 - 彌補缺陷:
eval()解析 JSON 的安全性缺陷(任意程式碼執行)與效能問題。
3. 物件屬性的 getters/setters 與屬性描述器(Object.defineProperty)#
- 為何加入:Firefox 已有非標準的 getter/setter,需要標準化;屬性描述器讓「屬性可否寫入、可否列舉、可否重新定義」變成可程式化的。
- 理論原因:補齊物件模型——ES3 的屬性全是「可寫可刪」,無法做封裝(encapsulation)與不變性(immutability)。
- 實用原因:函式庫(如 jQuery)需要防篡改的物件;框架需要存取器攔截(後來 Vue 2 的響應式系統就建立在
Object.defineProperty上)。 - 彌補缺陷:物件屬性無封裝、無存取控制。
4. 陣列高階方法:map / filter / reduce / forEach / some / every#
- 為何加入:直接借鑑 LISP/ML 的 list operation 傳統(map/reduce 源自1950年代 LISP 與 APL),以函數式思維取代手寫 for 迴圈。
- 實用原因:Ajax 時代的資料處理(過濾清單、轉換格式)用 for 迴圈寫既冗長又易錯(
for (var i = 0; i < arr.length; i++)的閉包陷阱)。 - 彌補缺陷:迴圈樣板程式碼(boilerplate)與閉包陷阱。
5. Function.prototype.bind#
- 為何加入:
this依呼叫方式動態決定是 ES1 的設計缺陷,回呼函數的this經常丟失。 - 理論原因:部分應用(partial application)是函數式程式設計的基礎概念。
- 彌補缺陷:回呼中
this丟失、需要var self = this的駭客寫法。
6. 其他#
String.trim、Array.isArray、Date.now、Object.keys、保留字放寬。
理論總結#
ES5 的哲學是「務實補丁」:放棄 ES4 的革命(類別、型別、命名空間),改用嚴格模式做漸進修正,補上函數式工具與物件封裝。它證明了「向後相容 + opt-in 新語義」是 Web 語言演化的唯一可行路徑。
實用總結#
ES5 + Node.js(同年問世,見 2009-Nodejs問世)+ Chrome V8(2008)構成 JavaScript 重生的三駕馬車。ES5 的 map/filter/reduce 成為後來函數式風格(Redux、React)的基礎詞彙。
相關條目#
程式範例#
1. 嚴格模式:靜默錯誤變成明確報錯#
// ES5 之前(非嚴格):靜默錯誤
function sloppy() {
oops = 1; // 沒宣告 → 默默建立「全域變數」!
}
sloppy();
oops; // 1 —— 汙染全域,誰改的都查不到
// ES5:嚴格模式(檔案或函數開頭加字串)
function strict() {
"use strict";
oops = 1; // ReferenceError —— 開發期立即報錯
}
// 其他修正:重複參數報錯、八進位字面量報錯、with 禁用
2. 陣列高階方法:取代手寫 for 迴圈#
var users = [
{ name: "Ada", age: 36, admin: true },
{ name: "Alan", age: 41, admin: false },
{ name: "Grace", age: 85, admin: true }
];
// ES5 之前:手寫 for 迴圈
var admins = [];
for (var i = 0; i < users.length; i++) {
if (users[i].admin) admins.push(users[i].name);
}
// ES5:函數式一行(map/filter/reduce 源自 LISP 1950年代)
users.filter(function (u) { return u.admin; })
.map(function (u) { return u.name; }); // ["Ada", "Grace"]
users.reduce(function (sum, u) { return sum + u.age; }, 0); // 162
3. this 丟失與 bind#
"use strict";
var counter = {
count: 0,
incrementLater: function () {
setTimeout(function () {
this.count++; // this 是 undefined(嚴格模式)——回呼丟失 this!
}, 100);
}
};
// ES5 解方:bind(部分應用)
incrementLater: function () {
setTimeout(function () { this.count++; }.bind(this), 100); // 正確
}
// 或 ES5 時代的駭客寫法:var self = this; 用 self 代替
4. 物件封裝:defineProperty 與 getter/setter#
var user = {};
Object.defineProperty(user, "name", {
value: "Ada",
writable: false, // 不可寫
enumerable: true, // 可列舉
configurable: false // 不可刪除/重定義
});
user.name = "Bob"; // 靜默失敗(嚴格模式下報 TypeError)
var temp = { _c: 0 };
Object.defineProperty(temp, "celsius", {
get: function () { return this._c; },
set: function (v) { this._c = v; } // 存取器攔截——Vue 2 響應式的基礎
});