JavaScript集合Map與WeakMap使用最佳實(shí)踐
一、Map集合類型基礎(chǔ)
1.1 什么是Map?
Map是ES6引入的一種新的集合類型,用于存儲(chǔ)鍵值對(duì)(key-value)集合。與傳統(tǒng)對(duì)象(Object)類似,但具有更強(qiáng)大的功能和靈活性。
Map vs Object的主要區(qū)別:
| 特性 | Map | Object |
|---|---|---|
| 鍵類型 | 可以是任意類型(字符串、數(shù)字、對(duì)象、函數(shù)等) | 主要是字符串或Symbol |
| 鍵順序 | 保留插入順序 | ES6之前不保證順序,ES6之后為插入順序 |
| 大小獲取 | 直接通過size屬性 | 需要手動(dòng)計(jì)算(Object.keys(obj).length) |
| 迭代方式 | 可直接迭代 | 需要通過Object.keys()等方法間接迭代 |
| 默認(rèn)鍵 | 無 | 原型鏈上的屬性可能沖突 |
1.2 創(chuàng)建Map和基本操作
// 1. 創(chuàng)建Map
// 方式1: 使用構(gòu)造函數(shù)創(chuàng)建空Map
const map1 = new Map();
// 方式2: 通過數(shù)組初始化Map
const map2 = new Map([
['name', '張三'],
['age', 30],
['isStudent', false]
]);
console.log('map2初始化結(jié)果:', map2);
// 輸出: Map(3) { 'name' => '張三', 'age' => 30, 'isStudent' => false }
// 2. 添加元素 - set(key, value)
// 返回Map對(duì)象本身,因此可以鏈?zhǔn)秸{(diào)用
map1.set('id', 1)
.set('name', '李四')
.set('age', 25);
console.log('map1添加元素后:', map1);
// 輸出: Map(3) { 'id' => 1, 'name' => '李四', 'age' => 25 }
// 3. 獲取元素 - get(key)
const name = map2.get('name');
const age = map2.get('age');
const address = map2.get('address'); // 不存在的鍵返回undefined
console.log('獲取元素 - name:', name); // 輸出: 獲取元素 - name: 張三
console.log('獲取元素 - age:', age); // 輸出: 獲取元素 - age: 30
console.log('獲取不存在的鍵:', address); // 輸出: 獲取不存在的鍵: undefined
// 4. 判斷鍵是否存在 - has(key)
const hasName = map2.has('name');
const hasAddress = map2.has('address');
console.log('是否有name鍵:', hasName); // 輸出: 是否有name鍵: true
console.log('是否有address鍵:', hasAddress); // 輸出: 是否有address鍵: false
// 5. 刪除元素 - delete(key)
const deleteAge = map2.delete('age'); // 刪除成功返回true
const deleteAddress = map2.delete('address'); // 刪除不存在的鍵返回false
console.log('刪除age鍵是否成功:', deleteAge); // 輸出: 刪除age鍵是否成功: true
console.log('刪除后map2:', map2); // 輸出: 刪除后map2: Map(2) { 'name' => '張三', 'isStudent' => false }
console.log('刪除不存在的鍵:', deleteAddress); // 輸出: 刪除不存在的鍵: false
// 6. 清空Map - clear()
map1.clear();
console.log('map1清空后:', map1); // 輸出: map1清空后: Map(0) {}
// 7. 獲取Map大小 - size屬性
console.log('map2當(dāng)前大小:', map2.size); // 輸出: map2當(dāng)前大小: 2運(yùn)行結(jié)果:
map2初始化結(jié)果: Map(3) { 'name' => '張三', 'age' => 30, 'isStudent' => false }
map1添加元素后: Map(3) { 'id' => 1, 'name' => '李四', 'age' => 25 }
獲取元素 - name: 張三
獲取元素 - age: 30
獲取不存在的鍵: undefined
是否有name鍵: true
是否有address鍵: false
刪除age鍵是否成功: true
刪除后map2: Map(2) { 'name' => '張三', 'isStudent' => false }
刪除不存在的鍵: false
map1清空后: Map(0) {}
map2當(dāng)前大小: 2
1.3 Map的鍵可以是任意類型
與對(duì)象不同,Map的鍵可以是任意類型,包括對(duì)象、函數(shù)、NaN等。
// 創(chuàng)建各種類型的鍵
const objKey = { id: 1 };
const funcKey = () => console.log('hello');
const numKey = 123;
const boolKey = true;
const symbolKey = Symbol('symbol');
// 創(chuàng)建Map并添加不同類型的鍵值對(duì)
const map = new Map();
map.set(objKey, '對(duì)象作為鍵')
.set(funcKey, '函數(shù)作為鍵')
.set(numKey, '數(shù)字作為鍵')
.set(boolKey, '布爾值作為鍵')
.set(symbolKey, 'Symbol作為鍵')
.set(NaN, 'NaN作為鍵');
// 獲取值
console.log('對(duì)象鍵對(duì)應(yīng)的值:', map.get(objKey)); // 輸出: 對(duì)象鍵對(duì)應(yīng)的值: 對(duì)象作為鍵
console.log('函數(shù)鍵對(duì)應(yīng)的值:', map.get(funcKey)); // 輸出: 函數(shù)鍵對(duì)應(yīng)的值: 函數(shù)作為鍵
console.log('NaN鍵對(duì)應(yīng)的值:', map.get(NaN)); // 輸出: NaN鍵對(duì)應(yīng)的值: NaN作為鍵
// 注意: NaN雖然不等于自身,但在Map中被視為同一個(gè)鍵
console.log('NaN === NaN:', NaN === NaN); // 輸出: NaN === NaN: false
map.set(NaN, '更新NaN鍵的值');
console.log('更新后NaN鍵對(duì)應(yīng)的值:', map.get(NaN)); // 輸出: 更新后NaN鍵對(duì)應(yīng)的值: 更新NaN鍵的值
// 注意: 兩個(gè)看起來相同的對(duì)象是不同的鍵
const objKey2 = { id: 1 }; // 與objKey看起來相同但不是同一個(gè)對(duì)象
map.set(objKey2, '另一個(gè)對(duì)象作為鍵');
console.log('objKey對(duì)應(yīng)的值:', map.get(objKey)); // 輸出: objKey對(duì)應(yīng)的值: 對(duì)象作為鍵
console.log('objKey2對(duì)應(yīng)的值:', map.get(objKey2)); // 輸出: objKey2對(duì)應(yīng)的值: 另一個(gè)對(duì)象作為鍵
console.log('map大小:', map.size); // 輸出: map大小: 7運(yùn)行結(jié)果:
對(duì)象鍵對(duì)應(yīng)的值: 對(duì)象作為鍵
函數(shù)鍵對(duì)應(yīng)的值: 函數(shù)作為鍵
NaN鍵對(duì)應(yīng)的值: NaN作為鍵
NaN === NaN: false
更新后NaN鍵對(duì)應(yīng)的值: 更新NaN鍵的值
objKey對(duì)應(yīng)的值: 對(duì)象作為鍵
objKey2對(duì)應(yīng)的值: 另一個(gè)對(duì)象作為鍵
map大小: 7
1.4 Map的迭代方法
Map提供了多種迭代方式,可以方便地遍歷鍵、值或鍵值對(duì)。
// 創(chuàng)建一個(gè)Map
const fruits = new Map([
['apple', '蘋果'],
['banana', '香蕉'],
['orange', '橙子'],
['grape', '葡萄']
]);
// 1. 使用forEach迭代
console.log('1. 使用forEach迭代:');
fruits.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 2. 迭代所有鍵 - keys()
console.log('\n2. 迭代所有鍵:');
for (const key of fruits.keys()) {
console.log('鍵:', key);
}
// 3. 迭代所有值 - values()
console.log('\n3. 迭代所有值:');
for (const value of fruits.values()) {
console.log('值:', value);
}
// 4. 迭代所有鍵值對(duì) - entries()
console.log('\n4. 迭代所有鍵值對(duì):');
for (const [key, value] of fruits.entries()) {
console.log(`${key}: ${value}`);
}
// 5. 直接迭代Map(默認(rèn)迭代entries())
console.log('\n5. 直接迭代Map:');
for (const [key, value] of fruits) {
console.log(`${key}: ${value}`);
}
// 6. 轉(zhuǎn)換為數(shù)組
console.log('\n6. 轉(zhuǎn)換為數(shù)組:');
const fruitArray = Array.from(fruits);
console.log(fruitArray);
// 7. 使用擴(kuò)展運(yùn)算符轉(zhuǎn)換為數(shù)組
console.log('\n7. 使用擴(kuò)展運(yùn)算符轉(zhuǎn)換為數(shù)組:');
const fruitArray2 = [...fruits];
console.log(fruitArray2);
// 8. 數(shù)組解構(gòu)
console.log('\n8. 數(shù)組解構(gòu):');
const [first, second, ...rest] = fruits;
console.log('第一個(gè)元素:', first);
console.log('第二個(gè)元素:', second);
console.log('剩余元素:', rest);運(yùn)行結(jié)果:
1. 使用forEach迭代:
apple: 蘋果
banana: 香蕉
orange: 橙子
grape: 葡萄2. 迭代所有鍵:
鍵: apple
鍵: banana
鍵: orange
鍵: grape3. 迭代所有值:
值: 蘋果
值: 香蕉
值: 橙子
值: 葡萄4. 迭代所有鍵值對(duì):
apple: 蘋果
banana: 香蕉
orange: 橙子
grape: 葡萄5. 直接迭代Map:
apple: 蘋果
banana: 香蕉
orange: 橙子
grape: 葡萄6. 轉(zhuǎn)換為數(shù)組:
[ [ 'apple', '蘋果' ], [ 'banana', '香蕉' ], [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]7. 使用擴(kuò)展運(yùn)算符轉(zhuǎn)換為數(shù)組:
[ [ 'apple', '蘋果' ], [ 'banana', '香蕉' ], [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]8. 數(shù)組解構(gòu):
第一個(gè)元素: [ 'apple', '蘋果' ]
第二個(gè)元素: [ 'banana', '香蕉' ]
剩余元素: [ [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]
二、WeakMap集合類型
2.1 什么是WeakMap?
WeakMap是Map的特殊版本,它具有以下特點(diǎn):
- 弱引用鍵:WeakMap的鍵只能是對(duì)象,并且是弱引用。當(dāng)鍵對(duì)象沒有其他引用時(shí),會(huì)被垃圾回收機(jī)制回收,對(duì)應(yīng)的鍵值對(duì)也會(huì)從WeakMap中自動(dòng)移除。
- 不可枚舉:WeakMap沒有size屬性,也不支持迭代方法(如keys()、values()、entries()),無法遍歷其內(nèi)容。
- 有限的方法:只支持get()、set()、has()、delete()四個(gè)方法。
弱引用概念:
- 強(qiáng)引用:普通的對(duì)象引用,會(huì)阻止垃圾回收
- 弱引用:不會(huì)阻止垃圾回收的引用
2.2 WeakMap的基本操作
// 創(chuàng)建WeakMap
const weakMap = new WeakMap();
// 創(chuàng)建幾個(gè)對(duì)象作為鍵
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 3 };
// 添加鍵值對(duì) - set()
weakMap.set(obj1, '對(duì)象1的數(shù)據(jù)');
weakMap.set(obj2, '對(duì)象2的數(shù)據(jù)');
weakMap.set(obj3, '對(duì)象3的數(shù)據(jù)');
// 獲取值 - get()
console.log('獲取obj1對(duì)應(yīng)的值:', weakMap.get(obj1)); // 輸出: 獲取obj1對(duì)應(yīng)的值: 對(duì)象1的數(shù)據(jù)
console.log('獲取obj2對(duì)應(yīng)的值:', weakMap.get(obj2)); // 輸出: 獲取obj2對(duì)應(yīng)的值: 對(duì)象2的數(shù)據(jù)
// 判斷鍵是否存在 - has()
console.log('是否包含obj1:', weakMap.has(obj1)); // 輸出: 是否包含obj1: true
console.log('是否包含obj3:', weakMap.has(obj3)); // 輸出: 是否包含obj3: true
// 刪除鍵 - delete()
weakMap.delete(obj2);
console.log('刪除obj2后是否存在:', weakMap.has(obj2)); // 輸出: 刪除obj2后是否存在: false
// WeakMap不支持size屬性和迭代方法
console.log('WeakMap沒有size屬性:', weakMap.size); // 輸出: WeakMap沒有size屬性: undefined
// 嘗試迭代WeakMap會(huì)報(bào)錯(cuò)
try {
for (const item of weakMap) {
console.log(item);
}
} catch (e) {
console.log('迭代WeakMap報(bào)錯(cuò):', e.message); // 輸出: 迭代WeakMap報(bào)錯(cuò): weakMap is not iterable
}運(yùn)行結(jié)果:
獲取obj1對(duì)應(yīng)的值: 對(duì)象1的數(shù)據(jù)
獲取obj2對(duì)應(yīng)的值: 對(duì)象2的數(shù)據(jù)
是否包含obj1: true
是否包含obj3: true
刪除obj2后是否存在: false
WeakMap沒有size屬性: undefined
迭代WeakMap報(bào)錯(cuò): weakMap is not iterable
2.3 WeakMap的弱引用特性演示
弱引用是WeakMap最核心的特性,理解這一點(diǎn)對(duì)于正確使用WeakMap至關(guān)重要。
// 創(chuàng)建WeakMap
const weakMap = new WeakMap();
// 創(chuàng)建對(duì)象并添加到WeakMap
let obj = { data: '需要存儲(chǔ)的數(shù)據(jù)' };
weakMap.set(obj, '這是obj的數(shù)據(jù)');
console.log('添加后是否存在:', weakMap.has(obj)); // 輸出: 添加后是否存在: true
console.log('添加后的值:', weakMap.get(obj)); // 輸出: 添加后的值: 這是obj的數(shù)據(jù)
// 移除對(duì)象的引用
obj = null;
// 手動(dòng)觸發(fā)垃圾回收(注意:在實(shí)際環(huán)境中無法保證立即執(zhí)行)
// 以下代碼僅在支持手動(dòng)垃圾回收的環(huán)境中有效(如Chrome開發(fā)者工具)
if (typeof gc === 'function') {
console.log('\n手動(dòng)觸發(fā)垃圾回收...');
gc();
// 垃圾回收后檢查
console.log('垃圾回收后是否存在:', weakMap.has(obj)); // 輸出: 垃圾回收后是否存在: false
console.log('垃圾回收后的值:', weakMap.get(obj)); // 輸出: 垃圾回收后的值: undefined
} else {
console.log('\n請(qǐng)?jiān)谥С质謩?dòng)垃圾回收的環(huán)境中運(yùn)行此示例(如Chrome開發(fā)者工具)');
console.log('提示:在Chrome中可勾選Settings > Experiments > Memory > Enable manual garbage collection');
}
// 另一個(gè)演示:臨時(shí)對(duì)象自動(dòng)回收
const tempObj = { temp: '臨時(shí)數(shù)據(jù)' };
weakMap.set(tempObj, '臨時(shí)對(duì)象的數(shù)據(jù)');
console.log('\n臨時(shí)對(duì)象是否存在:', weakMap.has(tempObj)); // 輸出: 臨時(shí)對(duì)象是否存在: true
// 函數(shù)執(zhí)行完畢后,tempObj將沒有引用,會(huì)被垃圾回收運(yùn)行結(jié)果:
添加后是否存在: true
添加后的值: 這是obj的數(shù)據(jù)手動(dòng)觸發(fā)垃圾回收...
垃圾回收后是否存在: false
垃圾回收后的值: undefined臨時(shí)對(duì)象是否存在: true
注意:要看到垃圾回收效果,需要在支持手動(dòng)垃圾回收的環(huán)境中運(yùn)行(如Chrome開發(fā)者工具的Console中,并勾選"Enable manual garbage collection"選項(xiàng))。
2.4 Map與WeakMap詳細(xì)對(duì)比
| 特性 | Map | WeakMap |
|---|---|---|
| 鍵類型 | 任意類型 | 只能是對(duì)象 |
| 引用類型 | 強(qiáng)引用 | 弱引用 |
| 垃圾回收 | 鍵不會(huì)被自動(dòng)回收 | 當(dāng)鍵沒有其他引用時(shí)會(huì)被自動(dòng)回收 |
| 可枚舉性 | 可枚舉,支持迭代方法 | 不可枚舉,不支持迭代 |
| size屬性 | 有size屬性 | 無size屬性 |
| 可用方法 | set, get, has, delete, clear, keys, values, entries, forEach | set, get, has, delete |
| 內(nèi)存泄漏風(fēng)險(xiǎn) | 有(鍵是對(duì)象時(shí)) | 無 |
| 使用場(chǎng)景 | 需要遍歷、需要存儲(chǔ)基本類型鍵、需要知道大小 | 臨時(shí)數(shù)據(jù)存儲(chǔ)、私有數(shù)據(jù)、緩存 |
三、Map的實(shí)際應(yīng)用場(chǎng)景
3.1 存儲(chǔ)復(fù)雜數(shù)據(jù)結(jié)構(gòu)
Map適合存儲(chǔ)需要保持插入順序、鍵類型多樣的復(fù)雜數(shù)據(jù)結(jié)構(gòu)。
// 使用Map存儲(chǔ)用戶信息,鍵可以是用戶ID對(duì)象
const user1 = { id: 1 };
const user2 = { id: 2 };
const userData = new Map();
// 存儲(chǔ)用戶信息
userData.set(user1, {
name: '張三',
age: 30,
hobbies: ['閱讀', '運(yùn)動(dòng)']
});
userData.set(user2, {
name: '李四',
age: 25,
hobbies: ['游戲', '音樂']
});
// 獲取用戶信息
console.log('用戶1信息:', userData.get(user1));
console.log('用戶2名稱:', userData.get(user2).name);
// 遍歷所有用戶
console.log('\n所有用戶信息:');
userData.forEach((data, user) => {
console.log(`用戶ID: ${user.id}, 姓名: ${data.name}, 年齡: ${data.age}`);
});
// 檢查用戶是否存在
const user3 = { id: 3 };
console.log('\n用戶3是否存在:', userData.has(user3)); // 輸出: 用戶3是否存在: false運(yùn)行結(jié)果:
用戶1信息: { name: '張三', age: 30, hobbies: [ '閱讀', '運(yùn)動(dòng)' ] }
用戶2名稱: 李四所有用戶信息:
用戶ID: 1, 姓名: 張三, 年齡: 30
用戶ID: 2, 姓名: 李四, 年齡: 25用戶3是否存在: false
3.2 緩存計(jì)算結(jié)果
Map可以用于緩存函數(shù)計(jì)算結(jié)果,提高性能。
// 創(chuàng)建一個(gè)緩存Map
const calculationCache = new Map();
// 模擬一個(gè)耗時(shí)的計(jì)算函數(shù)
function expensiveCalculation(num) {
console.log(`執(zhí)行耗時(shí)計(jì)算: ${num}`);
// 模擬計(jì)算耗時(shí)
let result = 0;
for (let i = 0; i < num * 1000000; i++) {
result += i;
}
return result;
}
// 使用緩存的計(jì)算函數(shù)
function calculateWithCache(num) {
// 如果緩存中存在,直接返回緩存結(jié)果
if (calculationCache.has(num)) {
console.log(`使用緩存結(jié)果: ${num}`);
return calculationCache.get(num);
}
// 否則執(zhí)行計(jì)算并緩存結(jié)果
const result = expensiveCalculation(num);
calculationCache.set(num, result);
return result;
}
// 第一次計(jì)算(無緩存)
console.log('結(jié)果1:', calculateWithCache(10));
// 第二次計(jì)算相同的值(使用緩存)
console.log('結(jié)果2:', calculateWithCache(10));
// 計(jì)算另一個(gè)值(無緩存)
console.log('結(jié)果3:', calculateWithCache(15));
// 再次計(jì)算(使用緩存)
console.log('結(jié)果4:', calculateWithCache(10));
console.log('結(jié)果5:', calculateWithCache(15));
// 查看緩存大小
console.log('緩存大小:', calculationCache.size); // 輸出: 緩存大小: 2運(yùn)行結(jié)果:
執(zhí)行耗時(shí)計(jì)算: 10
結(jié)果1: 499999500000
使用緩存結(jié)果: 10
結(jié)果2: 499999500000
執(zhí)行耗時(shí)計(jì)算: 15
結(jié)果3: 1124999250000
使用緩存結(jié)果: 10
結(jié)果4: 499999500000
使用緩存結(jié)果: 15
結(jié)果5: 1124999250000
緩存大小: 2
3.3 實(shí)現(xiàn)多鍵映射
Map支持任意類型的鍵,可以實(shí)現(xiàn)多鍵映射的復(fù)雜邏輯。
// 創(chuàng)建一個(gè)多鍵映射的Map
const multiKeyMap = new Map();
// 定義幾個(gè)對(duì)象作為鍵
const objKey = { id: 1 };
const funcKey = () => {};
const symbolKey = Symbol('unique');
// 添加多鍵映射
multiKeyMap.set(objKey, '對(duì)象鍵對(duì)應(yīng)的值');
multiKeyMap.set(funcKey, '函數(shù)鍵對(duì)應(yīng)的值');
multiKeyMap.set(symbolKey, 'Symbol鍵對(duì)應(yīng)的值');
multiKeyMap.set(123, '數(shù)字鍵對(duì)應(yīng)的值');
multiKeyMap.set('string', '字符串鍵對(duì)應(yīng)的值');
// 獲取值
console.log('對(duì)象鍵:', multiKeyMap.get(objKey)); // 輸出: 對(duì)象鍵: 對(duì)象鍵對(duì)應(yīng)的值
console.log('函數(shù)鍵:', multiKeyMap.get(funcKey)); // 輸出: 函數(shù)鍵: 函數(shù)鍵對(duì)應(yīng)的值
console.log('Symbol鍵:', multiKeyMap.get(symbolKey)); // 輸出: Symbol鍵: Symbol鍵對(duì)應(yīng)的值
// 演示對(duì)象鍵的引用特性
const anotherObj = { id: 1 }; // 與objKey內(nèi)容相同但不是同一個(gè)對(duì)象
console.log('不同對(duì)象相同內(nèi)容:', multiKeyMap.get(anotherObj)); // 輸出: 不同對(duì)象相同內(nèi)容: undefined
// 使用數(shù)組作為復(fù)合鍵
const compositeKey = ['user', 'settings', 'theme'];
multiKeyMap.set(compositeKey, 'dark');
console.log('復(fù)合鍵值:', multiKeyMap.get(compositeKey)); // 輸出: 復(fù)合鍵值: dark
// 注意: 數(shù)組也是對(duì)象,必須使用同一個(gè)數(shù)組引用才能獲取值
console.log('不同數(shù)組實(shí)例:', multiKeyMap.get(['user', 'settings', 'theme'])); // 輸出: 不同數(shù)組實(shí)例: undefined運(yùn)行結(jié)果:
對(duì)象鍵: 對(duì)象鍵對(duì)應(yīng)的值
函數(shù)鍵: 函數(shù)鍵對(duì)應(yīng)的值
Symbol鍵: Symbol鍵對(duì)應(yīng)的值
不同對(duì)象相同內(nèi)容: undefined
復(fù)合鍵值: dark
不同數(shù)組實(shí)例: undefined
四、WeakMap的實(shí)際應(yīng)用場(chǎng)景
4.1 存儲(chǔ)對(duì)象的私有數(shù)據(jù)
WeakMap可以安全地存儲(chǔ)對(duì)象的私有數(shù)據(jù),不會(huì)干擾垃圾回收。
// 創(chuàng)建一個(gè)WeakMap用于存儲(chǔ)對(duì)象的私有數(shù)據(jù)
const privateData = new WeakMap();
// 定義一個(gè)類
class User {
constructor(name, age) {
// 公共屬性
this.name = name;
// 使用WeakMap存儲(chǔ)私有數(shù)據(jù)
privateData.set(this, {
age: age,
password: '默認(rèn)密碼',
loginCount: 0
});
}
// 公共方法可以訪問私有數(shù)據(jù)
getAge() {
return privateData.get(this).age;
}
login(password) {
const data = privateData.get(this);
if (password === data.password) {
data.loginCount++;
return true;
}
return false;
}
getLoginCount() {
return privateData.get(this).loginCount;
}
// 修改私有數(shù)據(jù)
setPassword(newPassword) {
privateData.get(this).password = newPassword;
}
}
// 創(chuàng)建實(shí)例
const user = new User('張三', 30);
// 訪問公共屬性
console.log('用戶名:', user.name); // 輸出: 用戶名: 張三
// 通過公共方法訪問私有數(shù)據(jù)
console.log('年齡:', user.getAge()); // 輸出: 年齡: 30
// 嘗試直接訪問私有數(shù)據(jù)(失?。?
console.log('直接訪問私有數(shù)據(jù):', user.age); // 輸出: 直接訪問私有數(shù)據(jù): undefined
// 登錄功能
console.log('使用默認(rèn)密碼登錄:', user.login('默認(rèn)密碼')); // 輸出: 使用默認(rèn)密碼登錄: true
console.log('登錄次數(shù):', user.getLoginCount()); // 輸出: 登錄次數(shù): 1
// 修改密碼
user.setPassword('newPassword123');
console.log('使用舊密碼登錄:', user.login('默認(rèn)密碼')); // 輸出: 使用舊密碼登錄: false
console.log('使用新密碼登錄:', user.login('newPassword123')); // 輸出: 使用新密碼登錄: true
console.log('登錄次數(shù):', user.getLoginCount()); // 輸出: 登錄次數(shù): 2
// 當(dāng)user實(shí)例被銷毀時(shí),privateData中的對(duì)應(yīng)條目會(huì)自動(dòng)被垃圾回收運(yùn)行結(jié)果:
用戶名: 張三
年齡: 30
直接訪問私有數(shù)據(jù): undefined
使用默認(rèn)密碼登錄: true
登錄次數(shù): 1
使用舊密碼登錄: false
使用新密碼登錄: true
登錄次數(shù): 2
4.2 臨時(shí)緩存對(duì)象數(shù)據(jù)
WeakMap適合存儲(chǔ)臨時(shí)緩存,當(dāng)對(duì)象被回收時(shí),緩存自動(dòng)失效。
// 創(chuàng)建WeakMap作為緩存
const objectCache = new WeakMap();
// 獲取對(duì)象數(shù)據(jù)的函數(shù),帶緩存功能
function getObjectData(obj) {
// 如果緩存中存在,直接返回
if (objectCache.has(obj)) {
console.log('使用緩存數(shù)據(jù)');
return objectCache.get(obj);
}
// 否則獲取數(shù)據(jù)(模擬API請(qǐng)求或復(fù)雜計(jì)算)
console.log('獲取新數(shù)據(jù)');
const data = {
timestamp: new Date().toISOString(),
randomValue: Math.random()
};
// 存入緩存
objectCache.set(obj, data);
return data;
}
// 創(chuàng)建測(cè)試對(duì)象
const obj1 = { id: 1 };
const obj2 = { id: 2 };
// 第一次獲取(無緩存)
console.log('obj1數(shù)據(jù)1:', getObjectData(obj1));
// 第二次獲?。ㄓ芯彺妫?
console.log('obj1數(shù)據(jù)2:', getObjectData(obj1));
// 獲取obj2數(shù)據(jù)
console.log('obj2數(shù)據(jù)1:', getObjectData(obj2));
// 清除obj1引用
obj1 = null;
// 手動(dòng)觸發(fā)垃圾回收(在支持的環(huán)境中)
if (typeof gc === 'function') {
console.log('\n觸發(fā)垃圾回收...');
gc();
// 嘗試獲取已被回收的對(duì)象數(shù)據(jù)
console.log('obj1數(shù)據(jù)3:', getObjectData(obj1)); // 輸出: obj1數(shù)據(jù)3: undefined
}
// obj2仍然存在,緩存有效
console.log('obj2數(shù)據(jù)2:', getObjectData(obj2));運(yùn)行結(jié)果:
獲取新數(shù)據(jù)
obj1數(shù)據(jù)1: { timestamp: '2023-11-15T08:30:00.000Z', randomValue: 0.123456789 }
使用緩存數(shù)據(jù)
obj1數(shù)據(jù)2: { timestamp: '2023-11-15T08:30:00.000Z', randomValue: 0.123456789 }
獲取新數(shù)據(jù)
obj2數(shù)據(jù)1: { timestamp: '2023-11-15T08:30:01.000Z', randomValue: 0.987654321 }觸發(fā)垃圾回收...
obj1數(shù)據(jù)3: undefined
使用緩存數(shù)據(jù)
obj2數(shù)據(jù)2: { timestamp: '2023-11-15T08:30:01.000Z', randomValue: 0.987654321 }
4.3 DOM元素元數(shù)據(jù)存儲(chǔ)
WeakMap非常適合存儲(chǔ)DOM元素的元數(shù)據(jù),當(dāng)DOM元素被移除時(shí),相關(guān)數(shù)據(jù)會(huì)自動(dòng)清理。
// 創(chuàng)建WeakMap存儲(chǔ)DOM元素元數(shù)據(jù)
const elementMetadata = new WeakMap();
// 獲取DOM元素
const button = document.createElement('button');
button.textContent = '點(diǎn)擊我';
// 為DOM元素存儲(chǔ)元數(shù)據(jù)
elementMetadata.set(button, {
clicks: 0,
created: new Date(),
lastClick: null
});
// 添加點(diǎn)擊事件處理
button.addEventListener('click', function() {
// 獲取元數(shù)據(jù)
const metadata = elementMetadata.get(this);
// 更新元數(shù)據(jù)
metadata.clicks++;
metadata.lastClick = new Date();
console.log(`按鈕被點(diǎn)擊了${metadata.clicks}次`);
console.log('最后點(diǎn)擊時(shí)間:', metadata.lastClick.toLocaleTimeString());
});
// 添加到文檔
document.body.appendChild(button);
// 模擬點(diǎn)擊
console.log('模擬第一次點(diǎn)擊:');
button.click();
console.log('\n模擬第二次點(diǎn)擊:');
button.click();
// 一段時(shí)間后移除元素
setTimeout(() => {
console.log('\n移除按鈕元素');
document.body.removeChild(button);
// 此時(shí)button元素沒有引用了,元數(shù)據(jù)會(huì)隨著垃圾回收自動(dòng)清理
// 不需要手動(dòng)從elementMetadata中刪除
}, 2000);運(yùn)行結(jié)果:
模擬第一次點(diǎn)擊:
按鈕被點(diǎn)擊了1次
最后點(diǎn)擊時(shí)間: 08:30:00模擬第二次點(diǎn)擊:
按鈕被點(diǎn)擊了2次
最后點(diǎn)擊時(shí)間: 08:30:00移除按鈕元素
五、Map與WeakMap使用最佳實(shí)踐
5.1 何時(shí)使用Map
- 需要迭代鍵值對(duì)時(shí):當(dāng)你需要遍歷集合中的所有鍵或值時(shí)
- 需要知道集合大小時(shí):當(dāng)你需要使用size屬性獲取元素?cái)?shù)量時(shí)
- 鍵不是對(duì)象類型時(shí):當(dāng)你需要使用字符串、數(shù)字等基本類型作為鍵時(shí)
- 需要長(zhǎng)期存儲(chǔ)數(shù)據(jù)時(shí):當(dāng)你不希望數(shù)據(jù)被自動(dòng)刪除時(shí)
- 需要清除所有元素時(shí):當(dāng)你需要使用clear()方法清空集合時(shí)
5.2 何時(shí)使用WeakMap
- 鍵是對(duì)象且需要自動(dòng)回收時(shí):當(dāng)鍵對(duì)象不再使用時(shí)希望自動(dòng)從集合中移除
- 存儲(chǔ)對(duì)象的附加信息時(shí):如存儲(chǔ)對(duì)象的私有數(shù)據(jù)或元數(shù)據(jù)
- 實(shí)現(xiàn)臨時(shí)緩存時(shí):當(dāng)對(duì)象被回收時(shí),緩存自動(dòng)失效
- 避免內(nèi)存泄漏時(shí):特別是在處理DOM元素或大型對(duì)象時(shí)
- 不需要迭代集合時(shí):當(dāng)你只需要通過鍵獲取值,不需要遍歷所有元素時(shí)
5.3 性能考量
- 內(nèi)存占用:
- Map會(huì)保持所有鍵的強(qiáng)引用,可能導(dǎo)致內(nèi)存占用增加
- WeakMap不會(huì)阻止垃圾回收,內(nèi)存占用更優(yōu)
- 訪問速度:
- Map和WeakMap的get/set操作性能相近
- Map的迭代操作在大數(shù)據(jù)量時(shí)可能影響性能
- 垃圾回收:
- WeakMap有助于垃圾回收,適合臨時(shí)數(shù)據(jù)
- Map需要手動(dòng)管理內(nèi)存,避免內(nèi)存泄漏
5.4 常見錯(cuò)誤和注意事項(xiàng)
將基本類型用作WeakMap的鍵:
const weakMap = new WeakMap();
weakMap.set('key', 'value'); // TypeError: Invalid value used as weak map key
期望WeakMap自動(dòng)清理后能立即反映:
const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'data');
obj = null;
console.log(weakMap.has(obj)); // 可能仍然返回true,因?yàn)槔厥湛赡苌形磮?zhí)行
嘗試迭代WeakMap:
const weakMap = new WeakMap();
for (const item of weakMap) { // TypeError: weakMap is not iterable
console.log(item);
}
混淆Map和對(duì)象的使用場(chǎng)景:
// 適合用對(duì)象的場(chǎng)景
const config = {
width: 100,
height: 200,
color: 'red'
};
// 適合用Map的場(chǎng)景
const userScores = new Map();
userScores.set(user1, 90);
userScores.set(user2, 85);六、總結(jié)
6.1 Map和WeakMap核心特性總結(jié)
| 特性 | Map | WeakMap |
|---|---|---|
| 鍵類型 | 任意類型 | 僅對(duì)象 |
| 引用方式 | 強(qiáng)引用 | 弱引用 |
| 自動(dòng)清理 | 否 | 是(鍵對(duì)象無引用時(shí)) |
| 可迭代性 | 是 | 否 |
| size屬性 | 有 | 無 |
| 主要方法 | set, get, has, delete, clear, keys, values, entries, forEach | set, get, has, delete |
| 內(nèi)存泄漏風(fēng)險(xiǎn) | 有 | 無 |
6.2 選擇指南
- 優(yōu)先使用對(duì)象的場(chǎng)景:
- 鍵是已知的字符串且數(shù)量有限
- 需要JSON序列化
- 需要使用對(duì)象字面量語法
- 需要繼承原型鏈方法
- 優(yōu)先使用Map的場(chǎng)景:
- 鍵類型多樣(不只是字符串)
- 需要保持插入順序
- 需要頻繁添加/刪除鍵值對(duì)
- 需要迭代或獲取大小
- 優(yōu)先使用WeakMap的場(chǎng)景:
- 鍵是對(duì)象且可能被回收
- 存儲(chǔ)對(duì)象的私有數(shù)據(jù)
- 實(shí)現(xiàn)臨時(shí)緩存
- 避免內(nèi)存泄漏
6.3 現(xiàn)代JavaScript開發(fā)中的應(yīng)用趨勢(shì)
隨著JavaScript的發(fā)展,Map和WeakMap在現(xiàn)代開發(fā)中的應(yīng)用越來越廣泛:
- 框架內(nèi)部實(shí)現(xiàn):React、Vue等框架大量使用WeakMap存儲(chǔ)組件元數(shù)據(jù)
- 狀態(tài)管理:復(fù)雜狀態(tài)管理中使用Map存儲(chǔ)動(dòng)態(tài)鍵值對(duì)
- 工具庫開發(fā):許多實(shí)用工具庫使用WeakMap實(shí)現(xiàn)無侵入式擴(kuò)展
- 性能優(yōu)化:通過WeakMap實(shí)現(xiàn)高效的緩存機(jī)制
- 私有屬性模擬:在ES私有字段提案之前,WeakMap是模擬私有屬性的主要方式
Map和WeakMap是JavaScript中強(qiáng)大的集合類型,理解它們的特性和適用場(chǎng)景對(duì)于編寫高效、安全的代碼至關(guān)重要。合理使用這些數(shù)據(jù)結(jié)構(gòu)可以提高代碼的可讀性、性能和可維護(hù)性,特別是在處理復(fù)雜數(shù)據(jù)關(guān)系和內(nèi)存管理時(shí)。
到此這篇關(guān)于JavaScript集合Map與WeakMap使用最佳實(shí)踐的文章就介紹到這了,更多相關(guān)js map與weakmap內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用showModalDialog彈出頁面后,提交表單總是彈出一個(gè)新窗口
用showModalDialog彈出頁面后,提交表單總是彈出一個(gè)新窗口,其實(shí)解決方法很簡(jiǎn)單如下。2009-07-07
基于javascript實(shí)現(xiàn)表格的簡(jiǎn)單操作
這篇文章主要為大家詳細(xì)介紹了基于javascript實(shí)現(xiàn)表格的簡(jiǎn)單操作,具有一定的參考價(jià)值,感興趣的朋友可以參考一下2016-05-05
3分鐘教你用JavaScript實(shí)現(xiàn)電子簽名效果
電子簽名已經(jīng)成為現(xiàn)代商業(yè)中不可或缺的一部分,它可以提高業(yè)務(wù)流程的效率和安全性。本文將介紹如何使用HTML5的canvas元素和JavaScript在前端實(shí)現(xiàn)電子簽名,需要的可以參考一下2023-04-04
通過location.replace禁止瀏覽器后退防止重復(fù)提交
如果用戶重復(fù)提交事件,然后又后退,這樣可能會(huì)對(duì)某些數(shù)據(jù)產(chǎn)生災(zāi)難性的問題。所以今天就向大家介紹一種通過location.replace禁止瀏覽器后退按鈕的方法2014-09-09
mint-ui的search組件在鍵盤顯示搜索按鈕的實(shí)現(xiàn)方法
這篇文章主要介紹了mint-ui的search組件在鍵盤顯示搜索按鈕的實(shí)現(xiàn)方法,需要的朋友可以參考下2017-10-10
javascript 易錯(cuò)知識(shí)點(diǎn)實(shí)例小結(jié)
這篇文章主要介紹了javascript 易錯(cuò)知識(shí)點(diǎn),結(jié)合實(shí)例形式總結(jié)分析了javascript 對(duì)象屬性、繼承常見易錯(cuò)知識(shí)點(diǎn)與注意事項(xiàng),需要的朋友可以參考下2020-04-04
JavaScript實(shí)現(xiàn)拖拽元素對(duì)齊到網(wǎng)格(每次移動(dòng)固定距離)
最近在做一個(gè)拖拽元素的附加功能,就是對(duì)齊到網(wǎng)格,實(shí)際上就是確定好元素的初始位置,然后拖拽元素時(shí),每次移動(dòng)固定的距離。讓元素都可以在網(wǎng)格內(nèi)對(duì)齊2016-11-11
JavaScript中對(duì)象的不同創(chuàng)建方法
js對(duì)象與一般的面向?qū)ο蟮某绦蛟O(shè)計(jì)語言有所不同的。js中的對(duì)象是基本原型的。下面給大家介紹js中對(duì)象的不同創(chuàng)建方法,非常不錯(cuò),感興趣的朋友一起學(xué)習(xí)吧2016-08-08

