欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript集合Map與WeakMap使用最佳實踐

 更新時間:2025年08月12日 11:42:46   作者:時間sk  
Map和WeakMap是JavaScript中強大的集合類型,理解它們的特性和適用場景對于編寫高效、安全的代碼至關(guān)重要,本文給大家介紹JavaScript集合Map與WeakMap使用最佳實踐,感興趣的朋友一起看看吧

一、Map集合類型基礎(chǔ)

1.1 什么是Map?

Map是ES6引入的一種新的集合類型,用于存儲鍵值對(key-value)集合。與傳統(tǒng)對象(Object)類似,但具有更強大的功能和靈活性。

Map vs Object的主要區(qū)別

特性MapObject
鍵類型可以是任意類型(字符串、數(shù)字、對象、函數(shù)等)主要是字符串或Symbol
鍵順序保留插入順序ES6之前不保證順序,ES6之后為插入順序
大小獲取直接通過size屬性需要手動計算(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對象本身,因此可以鏈?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

運行結(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的鍵可以是任意類型

與對象不同,Map的鍵可以是任意類型,包括對象、函數(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并添加不同類型的鍵值對
const map = new Map();
map.set(objKey, '對象作為鍵')
   .set(funcKey, '函數(shù)作為鍵')
   .set(numKey, '數(shù)字作為鍵')
   .set(boolKey, '布爾值作為鍵')
   .set(symbolKey, 'Symbol作為鍵')
   .set(NaN, 'NaN作為鍵');
// 獲取值
console.log('對象鍵對應(yīng)的值:', map.get(objKey)); // 輸出: 對象鍵對應(yīng)的值: 對象作為鍵
console.log('函數(shù)鍵對應(yīng)的值:', map.get(funcKey)); // 輸出: 函數(shù)鍵對應(yīng)的值: 函數(shù)作為鍵
console.log('NaN鍵對應(yīng)的值:', map.get(NaN));     // 輸出: NaN鍵對應(yīng)的值: NaN作為鍵
// 注意: NaN雖然不等于自身,但在Map中被視為同一個鍵
console.log('NaN === NaN:', NaN === NaN); // 輸出: NaN === NaN: false
map.set(NaN, '更新NaN鍵的值');
console.log('更新后NaN鍵對應(yīng)的值:', map.get(NaN)); // 輸出: 更新后NaN鍵對應(yīng)的值: 更新NaN鍵的值
// 注意: 兩個看起來相同的對象是不同的鍵
const objKey2 = { id: 1 }; // 與objKey看起來相同但不是同一個對象
map.set(objKey2, '另一個對象作為鍵');
console.log('objKey對應(yīng)的值:', map.get(objKey));   // 輸出: objKey對應(yīng)的值: 對象作為鍵
console.log('objKey2對應(yīng)的值:', map.get(objKey2)); // 輸出: objKey2對應(yīng)的值: 另一個對象作為鍵
console.log('map大小:', map.size); // 輸出: map大小: 7

運行結(jié)果

對象鍵對應(yīng)的值: 對象作為鍵
函數(shù)鍵對應(yīng)的值: 函數(shù)作為鍵
NaN鍵對應(yīng)的值: NaN作為鍵
NaN === NaN: false
更新后NaN鍵對應(yīng)的值: 更新NaN鍵的值
objKey對應(yīng)的值: 對象作為鍵
objKey2對應(yīng)的值: 另一個對象作為鍵
map大小: 7

1.4 Map的迭代方法

Map提供了多種迭代方式,可以方便地遍歷鍵、值或鍵值對。

// 創(chuàng)建一個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. 迭代所有鍵值對 - entries()
console.log('\n4. 迭代所有鍵值對:');
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. 使用擴展運算符轉(zhuǎn)換為數(shù)組
console.log('\n7. 使用擴展運算符轉(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('第一個元素:', first);
console.log('第二個元素:', second);
console.log('剩余元素:', rest);

運行結(jié)果

1. 使用forEach迭代:
apple: 蘋果
banana: 香蕉
orange: 橙子
grape: 葡萄

2. 迭代所有鍵:
鍵: apple
鍵: banana
鍵: orange
鍵: grape

3. 迭代所有值:
值: 蘋果
值: 香蕉
值: 橙子
值: 葡萄

4. 迭代所有鍵值對:
apple: 蘋果
banana: 香蕉
orange: 橙子
grape: 葡萄

5. 直接迭代Map:
apple: 蘋果
banana: 香蕉
orange: 橙子
grape: 葡萄

6. 轉(zhuǎn)換為數(shù)組:
[ [ 'apple', '蘋果' ], [ 'banana', '香蕉' ], [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]

7. 使用擴展運算符轉(zhuǎn)換為數(shù)組:
[ [ 'apple', '蘋果' ], [ 'banana', '香蕉' ], [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]

8. 數(shù)組解構(gòu):
第一個元素: [ 'apple', '蘋果' ]
第二個元素: [ 'banana', '香蕉' ]
剩余元素: [ [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]

二、WeakMap集合類型

2.1 什么是WeakMap?

WeakMap是Map的特殊版本,它具有以下特點:

  • 弱引用鍵:WeakMap的鍵只能是對象,并且是弱引用。當(dāng)鍵對象沒有其他引用時,會被垃圾回收機制回收,對應(yīng)的鍵值對也會從WeakMap中自動移除。
  • 不可枚舉:WeakMap沒有size屬性,也不支持迭代方法(如keys()、values()、entries()),無法遍歷其內(nèi)容。
  • 有限的方法:只支持get()、set()、has()、delete()四個方法。

弱引用概念

  • 強引用:普通的對象引用,會阻止垃圾回收
  • 弱引用:不會阻止垃圾回收的引用

2.2 WeakMap的基本操作

// 創(chuàng)建WeakMap
const weakMap = new WeakMap();
// 創(chuàng)建幾個對象作為鍵
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 3 };
// 添加鍵值對 - set()
weakMap.set(obj1, '對象1的數(shù)據(jù)');
weakMap.set(obj2, '對象2的數(shù)據(jù)');
weakMap.set(obj3, '對象3的數(shù)據(jù)');
// 獲取值 - get()
console.log('獲取obj1對應(yīng)的值:', weakMap.get(obj1)); // 輸出: 獲取obj1對應(yīng)的值: 對象1的數(shù)據(jù)
console.log('獲取obj2對應(yīng)的值:', weakMap.get(obj2)); // 輸出: 獲取obj2對應(yīng)的值: 對象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會報錯
try {
  for (const item of weakMap) {
    console.log(item);
  }
} catch (e) {
  console.log('迭代WeakMap報錯:', e.message); // 輸出: 迭代WeakMap報錯: weakMap is not iterable
}

運行結(jié)果

獲取obj1對應(yīng)的值: 對象1的數(shù)據(jù)
獲取obj2對應(yīng)的值: 對象2的數(shù)據(jù)
是否包含obj1: true
是否包含obj3: true
刪除obj2后是否存在: false
WeakMap沒有size屬性: undefined
迭代WeakMap報錯: weakMap is not iterable

2.3 WeakMap的弱引用特性演示

弱引用是WeakMap最核心的特性,理解這一點對于正確使用WeakMap至關(guān)重要。

// 創(chuàng)建WeakMap
const weakMap = new WeakMap();
// 創(chuàng)建對象并添加到WeakMap
let obj = { data: '需要存儲的數(shù)據(jù)' };
weakMap.set(obj, '這是obj的數(shù)據(jù)');
console.log('添加后是否存在:', weakMap.has(obj)); // 輸出: 添加后是否存在: true
console.log('添加后的值:', weakMap.get(obj));     // 輸出: 添加后的值: 這是obj的數(shù)據(jù)
// 移除對象的引用
obj = null;
// 手動觸發(fā)垃圾回收(注意:在實際環(huán)境中無法保證立即執(zhí)行)
// 以下代碼僅在支持手動垃圾回收的環(huán)境中有效(如Chrome開發(fā)者工具)
if (typeof gc === 'function') {
  console.log('\n手動觸發(fā)垃圾回收...');
  gc();
  // 垃圾回收后檢查
  console.log('垃圾回收后是否存在:', weakMap.has(obj)); // 輸出: 垃圾回收后是否存在: false
  console.log('垃圾回收后的值:', weakMap.get(obj));     // 輸出: 垃圾回收后的值: undefined
} else {
  console.log('\n請在支持手動垃圾回收的環(huán)境中運行此示例(如Chrome開發(fā)者工具)');
  console.log('提示:在Chrome中可勾選Settings > Experiments > Memory > Enable manual garbage collection');
}
// 另一個演示:臨時對象自動回收
const tempObj = { temp: '臨時數(shù)據(jù)' };
weakMap.set(tempObj, '臨時對象的數(shù)據(jù)');
console.log('\n臨時對象是否存在:', weakMap.has(tempObj)); // 輸出: 臨時對象是否存在: true
// 函數(shù)執(zhí)行完畢后,tempObj將沒有引用,會被垃圾回收

運行結(jié)果

添加后是否存在: true
添加后的值: 這是obj的數(shù)據(jù)

手動觸發(fā)垃圾回收...
垃圾回收后是否存在: false
垃圾回收后的值: undefined

臨時對象是否存在: true

注意:要看到垃圾回收效果,需要在支持手動垃圾回收的環(huán)境中運行(如Chrome開發(fā)者工具的Console中,并勾選"Enable manual garbage collection"選項)。

2.4 Map與WeakMap詳細(xì)對比

特性MapWeakMap
鍵類型任意類型只能是對象
引用類型強引用弱引用
垃圾回收鍵不會被自動回收當(dāng)鍵沒有其他引用時會被自動回收
可枚舉性可枚舉,支持迭代方法不可枚舉,不支持迭代
size屬性有size屬性無size屬性
可用方法set, get, has, delete, clear, keys, values, entries, forEachset, get, has, delete
內(nèi)存泄漏風(fēng)險有(鍵是對象時)
使用場景需要遍歷、需要存儲基本類型鍵、需要知道大小臨時數(shù)據(jù)存儲、私有數(shù)據(jù)、緩存

三、Map的實際應(yīng)用場景

3.1 存儲復(fù)雜數(shù)據(jù)結(jié)構(gòu)

Map適合存儲需要保持插入順序、鍵類型多樣的復(fù)雜數(shù)據(jù)結(jié)構(gòu)。

// 使用Map存儲用戶信息,鍵可以是用戶ID對象
const user1 = { id: 1 };
const user2 = { id: 2 };
const userData = new Map();
// 存儲用戶信息
userData.set(user1, {
  name: '張三',
  age: 30,
  hobbies: ['閱讀', '運動']
});
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

運行結(jié)果

用戶1信息: { name: '張三', age: 30, hobbies: [ '閱讀', '運動' ] }
用戶2名稱: 李四

所有用戶信息:
用戶ID: 1, 姓名: 張三, 年齡: 30
用戶ID: 2, 姓名: 李四, 年齡: 25

用戶3是否存在: false

3.2 緩存計算結(jié)果

Map可以用于緩存函數(shù)計算結(jié)果,提高性能。

// 創(chuàng)建一個緩存Map
const calculationCache = new Map();
// 模擬一個耗時的計算函數(shù)
function expensiveCalculation(num) {
  console.log(`執(zhí)行耗時計算: ${num}`);
  // 模擬計算耗時
  let result = 0;
  for (let i = 0; i < num * 1000000; i++) {
    result += i;
  }
  return result;
}
// 使用緩存的計算函數(shù)
function calculateWithCache(num) {
  // 如果緩存中存在,直接返回緩存結(jié)果
  if (calculationCache.has(num)) {
    console.log(`使用緩存結(jié)果: ${num}`);
    return calculationCache.get(num);
  }
  // 否則執(zhí)行計算并緩存結(jié)果
  const result = expensiveCalculation(num);
  calculationCache.set(num, result);
  return result;
}
// 第一次計算(無緩存)
console.log('結(jié)果1:', calculateWithCache(10));
// 第二次計算相同的值(使用緩存)
console.log('結(jié)果2:', calculateWithCache(10));
// 計算另一個值(無緩存)
console.log('結(jié)果3:', calculateWithCache(15));
// 再次計算(使用緩存)
console.log('結(jié)果4:', calculateWithCache(10));
console.log('結(jié)果5:', calculateWithCache(15));
// 查看緩存大小
console.log('緩存大小:', calculationCache.size); // 輸出: 緩存大小: 2

運行結(jié)果

執(zhí)行耗時計算: 10
結(jié)果1: 499999500000
使用緩存結(jié)果: 10
結(jié)果2: 499999500000
執(zhí)行耗時計算: 15
結(jié)果3: 1124999250000
使用緩存結(jié)果: 10
結(jié)果4: 499999500000
使用緩存結(jié)果: 15
結(jié)果5: 1124999250000
緩存大小: 2

3.3 實現(xiàn)多鍵映射

Map支持任意類型的鍵,可以實現(xiàn)多鍵映射的復(fù)雜邏輯。

// 創(chuàng)建一個多鍵映射的Map
const multiKeyMap = new Map();
// 定義幾個對象作為鍵
const objKey = { id: 1 };
const funcKey = () => {};
const symbolKey = Symbol('unique');
// 添加多鍵映射
multiKeyMap.set(objKey, '對象鍵對應(yīng)的值');
multiKeyMap.set(funcKey, '函數(shù)鍵對應(yīng)的值');
multiKeyMap.set(symbolKey, 'Symbol鍵對應(yīng)的值');
multiKeyMap.set(123, '數(shù)字鍵對應(yīng)的值');
multiKeyMap.set('string', '字符串鍵對應(yīng)的值');
// 獲取值
console.log('對象鍵:', multiKeyMap.get(objKey));      // 輸出: 對象鍵: 對象鍵對應(yīng)的值
console.log('函數(shù)鍵:', multiKeyMap.get(funcKey));    // 輸出: 函數(shù)鍵: 函數(shù)鍵對應(yīng)的值
console.log('Symbol鍵:', multiKeyMap.get(symbolKey)); // 輸出: Symbol鍵: Symbol鍵對應(yīng)的值
// 演示對象鍵的引用特性
const anotherObj = { id: 1 }; // 與objKey內(nèi)容相同但不是同一個對象
console.log('不同對象相同內(nèi)容:', multiKeyMap.get(anotherObj)); // 輸出: 不同對象相同內(nèi)容: undefined
// 使用數(shù)組作為復(fù)合鍵
const compositeKey = ['user', 'settings', 'theme'];
multiKeyMap.set(compositeKey, 'dark');
console.log('復(fù)合鍵值:', multiKeyMap.get(compositeKey)); // 輸出: 復(fù)合鍵值: dark
// 注意: 數(shù)組也是對象,必須使用同一個數(shù)組引用才能獲取值
console.log('不同數(shù)組實例:', multiKeyMap.get(['user', 'settings', 'theme'])); // 輸出: 不同數(shù)組實例: undefined

運行結(jié)果

對象鍵: 對象鍵對應(yīng)的值
函數(shù)鍵: 函數(shù)鍵對應(yīng)的值
Symbol鍵: Symbol鍵對應(yīng)的值
不同對象相同內(nèi)容: undefined
復(fù)合鍵值: dark
不同數(shù)組實例: undefined

四、WeakMap的實際應(yīng)用場景

4.1 存儲對象的私有數(shù)據(jù)

WeakMap可以安全地存儲對象的私有數(shù)據(jù),不會干擾垃圾回收。

// 創(chuàng)建一個WeakMap用于存儲對象的私有數(shù)據(jù)
const privateData = new WeakMap();
// 定義一個類
class User {
  constructor(name, age) {
    // 公共屬性
    this.name = name;
    // 使用WeakMap存儲私有數(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)建實例
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實例被銷毀時,privateData中的對應(yīng)條目會自動被垃圾回收

運行結(jié)果

用戶名: 張三
年齡: 30
直接訪問私有數(shù)據(jù): undefined
使用默認(rèn)密碼登錄: true
登錄次數(shù): 1
使用舊密碼登錄: false
使用新密碼登錄: true
登錄次數(shù): 2

4.2 臨時緩存對象數(shù)據(jù)

WeakMap適合存儲臨時緩存,當(dāng)對象被回收時,緩存自動失效。

// 創(chuàng)建WeakMap作為緩存
const objectCache = new WeakMap();
// 獲取對象數(shù)據(jù)的函數(shù),帶緩存功能
function getObjectData(obj) {
  // 如果緩存中存在,直接返回
  if (objectCache.has(obj)) {
    console.log('使用緩存數(shù)據(jù)');
    return objectCache.get(obj);
  }
  // 否則獲取數(shù)據(jù)(模擬API請求或復(fù)雜計算)
  console.log('獲取新數(shù)據(jù)');
  const data = {
    timestamp: new Date().toISOString(),
    randomValue: Math.random()
  };
  // 存入緩存
  objectCache.set(obj, data);
  return data;
}
// 創(chuàng)建測試對象
const obj1 = { id: 1 };
const obj2 = { id: 2 };
// 第一次獲?。o緩存)
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;
// 手動觸發(fā)垃圾回收(在支持的環(huán)境中)
if (typeof gc === 'function') {
  console.log('\n觸發(fā)垃圾回收...');
  gc();
  // 嘗試獲取已被回收的對象數(shù)據(jù)
  console.log('obj1數(shù)據(jù)3:', getObjectData(obj1)); // 輸出: obj1數(shù)據(jù)3: undefined
}
// obj2仍然存在,緩存有效
console.log('obj2數(shù)據(jù)2:', getObjectData(obj2));

運行結(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ù)存儲

WeakMap非常適合存儲DOM元素的元數(shù)據(jù),當(dāng)DOM元素被移除時,相關(guān)數(shù)據(jù)會自動清理。

// 創(chuàng)建WeakMap存儲DOM元素元數(shù)據(jù)
const elementMetadata = new WeakMap();
// 獲取DOM元素
const button = document.createElement('button');
button.textContent = '點擊我';
// 為DOM元素存儲元數(shù)據(jù)
elementMetadata.set(button, {
  clicks: 0,
  created: new Date(),
  lastClick: null
});
// 添加點擊事件處理
button.addEventListener('click', function() {
  // 獲取元數(shù)據(jù)
  const metadata = elementMetadata.get(this);
  // 更新元數(shù)據(jù)
  metadata.clicks++;
  metadata.lastClick = new Date();
  console.log(`按鈕被點擊了${metadata.clicks}次`);
  console.log('最后點擊時間:', metadata.lastClick.toLocaleTimeString());
});
// 添加到文檔
document.body.appendChild(button);
// 模擬點擊
console.log('模擬第一次點擊:');
button.click();
console.log('\n模擬第二次點擊:');
button.click();
// 一段時間后移除元素
setTimeout(() => {
  console.log('\n移除按鈕元素');
  document.body.removeChild(button);
  // 此時button元素沒有引用了,元數(shù)據(jù)會隨著垃圾回收自動清理
  // 不需要手動從elementMetadata中刪除
}, 2000);

運行結(jié)果

模擬第一次點擊:
按鈕被點擊了1次
最后點擊時間: 08:30:00

模擬第二次點擊:
按鈕被點擊了2次
最后點擊時間: 08:30:00

移除按鈕元素

五、Map與WeakMap使用最佳實踐

5.1 何時使用Map

  1. 需要迭代鍵值對時:當(dāng)你需要遍歷集合中的所有鍵或值時
  2. 需要知道集合大小時:當(dāng)你需要使用size屬性獲取元素數(shù)量時
  3. 鍵不是對象類型時:當(dāng)你需要使用字符串、數(shù)字等基本類型作為鍵時
  4. 需要長期存儲數(shù)據(jù)時:當(dāng)你不希望數(shù)據(jù)被自動刪除時
  5. 需要清除所有元素時:當(dāng)你需要使用clear()方法清空集合時

5.2 何時使用WeakMap

  1. 鍵是對象且需要自動回收時:當(dāng)鍵對象不再使用時希望自動從集合中移除
  2. 存儲對象的附加信息時:如存儲對象的私有數(shù)據(jù)或元數(shù)據(jù)
  3. 實現(xiàn)臨時緩存時:當(dāng)對象被回收時,緩存自動失效
  4. 避免內(nèi)存泄漏時:特別是在處理DOM元素或大型對象時
  5. 不需要迭代集合時:當(dāng)你只需要通過鍵獲取值,不需要遍歷所有元素時

5.3 性能考量

  • 內(nèi)存占用
    • Map會保持所有鍵的強引用,可能導(dǎo)致內(nèi)存占用增加
    • WeakMap不會阻止垃圾回收,內(nèi)存占用更優(yōu)
  • 訪問速度
    • Map和WeakMap的get/set操作性能相近
    • Map的迭代操作在大數(shù)據(jù)量時可能影響性能
  • 垃圾回收
    • WeakMap有助于垃圾回收,適合臨時數(shù)據(jù)
    • Map需要手動管理內(nèi)存,避免內(nèi)存泄漏

5.4 常見錯誤和注意事項

將基本類型用作WeakMap的鍵

const weakMap = new WeakMap();
weakMap.set('key', 'value'); // TypeError: Invalid value used as weak map key

期望WeakMap自動清理后能立即反映

const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'data');
obj = null;
console.log(weakMap.has(obj)); // 可能仍然返回true,因為垃圾回收可能尚未執(zhí)行

嘗試迭代WeakMap

const weakMap = new WeakMap();
for (const item of weakMap) { // TypeError: weakMap is not iterable
  console.log(item);
}

混淆Map和對象的使用場景

// 適合用對象的場景
const config = {
  width: 100,
  height: 200,
  color: 'red'
};
// 適合用Map的場景
const userScores = new Map();
userScores.set(user1, 90);
userScores.set(user2, 85);

六、總結(jié)

6.1 Map和WeakMap核心特性總結(jié)

特性MapWeakMap
鍵類型任意類型僅對象
引用方式強引用弱引用
自動清理是(鍵對象無引用時)
可迭代性
size屬性
主要方法set, get, has, delete, clear, keys, values, entries, forEachset, get, has, delete
內(nèi)存泄漏風(fēng)險

6.2 選擇指南

  • 優(yōu)先使用對象的場景
    • 鍵是已知的字符串且數(shù)量有限
    • 需要JSON序列化
    • 需要使用對象字面量語法
    • 需要繼承原型鏈方法
  • 優(yōu)先使用Map的場景
    • 鍵類型多樣(不只是字符串)
    • 需要保持插入順序
    • 需要頻繁添加/刪除鍵值對
    • 需要迭代或獲取大小
  • 優(yōu)先使用WeakMap的場景
    • 鍵是對象且可能被回收
    • 存儲對象的私有數(shù)據(jù)
    • 實現(xiàn)臨時緩存
    • 避免內(nèi)存泄漏

6.3 現(xiàn)代JavaScript開發(fā)中的應(yīng)用趨勢

隨著JavaScript的發(fā)展,Map和WeakMap在現(xiàn)代開發(fā)中的應(yīng)用越來越廣泛:

  1. 框架內(nèi)部實現(xiàn):React、Vue等框架大量使用WeakMap存儲組件元數(shù)據(jù)
  2. 狀態(tài)管理:復(fù)雜狀態(tài)管理中使用Map存儲動態(tài)鍵值對
  3. 工具庫開發(fā):許多實用工具庫使用WeakMap實現(xiàn)無侵入式擴展
  4. 性能優(yōu)化:通過WeakMap實現(xiàn)高效的緩存機制
  5. 私有屬性模擬:在ES私有字段提案之前,WeakMap是模擬私有屬性的主要方式

Map和WeakMap是JavaScript中強大的集合類型,理解它們的特性和適用場景對于編寫高效、安全的代碼至關(guān)重要。合理使用這些數(shù)據(jù)結(jié)構(gòu)可以提高代碼的可讀性、性能和可維護(hù)性,特別是在處理復(fù)雜數(shù)據(jù)關(guān)系和內(nèi)存管理時。

到此這篇關(guān)于JavaScript集合Map與WeakMap使用最佳實踐的文章就介紹到這了,更多相關(guān)js map與weakmap內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論