代碼回顧
Array.prototype.unique = function() {
var ret = []; // 結果數組
var o = {}; // 查找表(哈希表)
var len = this.length;
for (var i = 0; i < len; i++) {
var v = this[i]; // 當前元素值
if (!o[v]) { // 若查找表中無此值
o[v] = 1; // 標記:已見過
ret.push(v); // 加入結果
}
}
return ret;
};
o[v] = 1 的三層含義
1. 哈希表鍵值對結構
- 鍵:數組元素值
v(如 1、2、"a")
- 值:
1(任意 truthy 值均可,true、"seen"、{} 皆可)
- 作用:利用對象鍵的唯一性實現 O(1) 查重
2. 狀態標記機制
| 階段 | o 狀態 | 條件判斷 | 動作 |
| 初始 | {} | - | - |
| 遇到首個 1 | {"1": 1} | !o[1] → true | push(1), o[1]=1 |
| 遇到第二個 1 | {"1": 1} | !o[1] → false | 跳過 |
| 遇到 2 | {"1":1, "2":1} | !o[2] → true | push(2), o[2]=1 |
3. JavaScript 隱式轉換利用
o[v] 返回 undefined(falsy)→ !undefined === true → 執行 if 內部
o[v] = 1 後,o[v] 返回 1(truthy)→ !1 === false → 跳過
為什麼值寫 1 而非 true?
- 歷史習慣:早期 JS 引擎數字比布爾運算略快(現代引擎無差異)
- 體積極小:
1 比 true 少 3 字節
- 語義足夠:只需 truthy/falsy 區分,無需具體語義值
現代優化寫法 (ES6+)
// Set 自動去重(最推薦,簡潔高效)
const unique = [...new Set(arr)];
// 或保留原型擴展風格
Array.prototype.unique = function() {
return [...new Set(this)];
};
// Map 保持插入順序並支持對象去重
Array.prototype.uniqueBy = function(keyFn) {
const map = new Map();
return this.filter(item => {
const key = keyFn(item);
if (map.has(key)) return false;
map.set(key, true);
return true;
});
};
性能對比 (10萬元素數組)
| 方法 | 時間複雜度 | 實測耗時 | 備註 |
| 雙層循環 (indexOf) | O(n²) | ~1200ms | 極慢,勿用 |
| 對象查找表 (o[v]=1) | O(n) | ~8ms | 經典寫法,兼容舊環境 |
| Set 構造器 | O(n) | ~3ms | 原生優化,首選 |
| Map + filter | O(n) | ~5ms | 支持複雜鍵提取 |
避坑指南
- ❌
o[v] = 1 中 v 為對象/數組 → 鍵會被 toString() 為 "[object Object]" 導致誤判
- ❌
NaN 去重失效:NaN !== NaN,但 Set 可正確處理
- ❌ 原型污染:
Array.prototype.unique 可能與庫衝突,模塊化項目建議用獨立函數
o[v] = 1 是利用對象鍵唯一性實現 O(1) 查重的經典模式,值 1 只是佔位符,核心是 「見過即標記,再見即跳過」 的狀態機邏輯。現代項目優先使用 Set。