JavaScript المتقدم (ES6+)

Maps - أزواج المفتاح والقيمة بأي نوع مفتاح

13 دقيقة الدرس 22 من 40

Maps - أزواج المفتاح والقيمة بأي نوع مفتاح

قدمت ES6 كائن Map، وهو هيكل بيانات قوي للمفتاح والقيمة يسمح بمفاتيح من أي نوع. على عكس الكائنات العادية، يمكن لـ Maps استخدام الكائنات أو الدوال أو أي أولي كمفاتيح، وتحافظ على ترتيب الإدراج مع توفير أداء أفضل للإضافات والحذف المتكررة.

ما هي Map؟

Map هي مجموعة من أزواج المفتاح والقيمة حيث يمكن أن تكون المفاتيح من أي نوع. الاختلافات الرئيسية عن الكائنات:

  • يمكن أن تكون المفاتيح من أي نوع (كائنات، دوال، أوليات)
  • تحافظ Maps على ترتيب الإدراج
  • لدى Maps خاصية size للوصول السريع للطول
  • Maps محسّنة للإضافات والحذف المتكررة
  • Maps قابلة للتكرار مباشرة
حقيقة مهمة: على عكس الكائنات حيث المفاتيح دائماً نصوص أو رموز، يمكن أن تكون مفاتيح Map حرفياً أي شيء - حتى كائنات أخرى أو دوال.

إنشاء Maps

يمكنك إنشاء Maps بعدة طرق:

// Map فارغة const emptyMap = new Map(); // Map من مصفوفة أزواج [key, value] const users = new Map([ [1, 'Alice'], [2, 'Bob'], [3, 'Charlie'] ]); // Map بأنواع مفاتيح مختلفة const mixedMap = new Map([ ['string', 'String key'], [42, 'Number key'], [true, 'Boolean key'], [{id: 1}, 'Object key'], [function() {}, 'Function key'] ]); // تحويل كائن إلى Map const obj = {name: 'John', age: 30}; const mapFromObj = new Map(Object.entries(obj)); console.log(mapFromObj); // Map {'name' => 'John', 'age' => 30}

طرق Map

توفر Maps طرقاً بديهية للعمل مع أزواج المفتاح والقيمة:

const map = new Map(); // set(key, value) - إضافة أو تحديث زوج مفتاح-قيمة map.set('name', 'Alice'); map.set('age', 25); map.set('city', 'New York'); // تسلسل الطرق مع set map.set('country', 'USA').set('email', 'alice@example.com'); // get(key) - استرجاع قيمة console.log(map.get('name')); // 'Alice' console.log(map.get('missing')); // undefined // has(key) - التحقق من وجود المفتاح console.log(map.has('age')); // true console.log(map.has('phone')); // false // delete(key) - إزالة زوج مفتاح-قيمة map.delete('city'); console.log(map.has('city')); // false // size - الحصول على عدد الإدخالات console.log(map.size); // 4 // clear() - إزالة جميع الإدخالات map.clear(); console.log(map.size); // 0
نصيحة: ترجع طريقة set() الـ Map نفسها، مما يتيح تسلسل الطرق: map.set('a', 1).set('b', 2).set('c', 3)

استخدام الكائنات كمفاتيح

إحدى أقوى ميزات Map هي القدرة على استخدام الكائنات كمفاتيح:

// استخدام الكائنات كمفاتيح const user1 = {id: 1, name: 'Alice'}; const user2 = {id: 2, name: 'Bob'}; const userRoles = new Map(); userRoles.set(user1, 'admin'); userRoles.set(user2, 'editor'); console.log(userRoles.get(user1)); // 'admin' console.log(userRoles.get(user2)); // 'editor' // عناصر DOM كمفاتيح const button1 = document.createElement('button'); const button2 = document.createElement('button'); const clickCounts = new Map(); clickCounts.set(button1, 0); clickCounts.set(button2, 0); button1.addEventListener('click', () => { clickCounts.set(button1, clickCounts.get(button1) + 1); }); // الدوال كمفاتيح const fn1 = () => console.log('First'); const fn2 = () => console.log('Second'); const fnMetadata = new Map(); fnMetadata.set(fn1, {name: 'First Function', calls: 0}); fnMetadata.set(fn2, {name: 'Second Function', calls: 0});

التكرار على Maps

Maps قابلة للتكرار وتحافظ على ترتيب الإدراج:

const fruits = new Map([ ['apple', 2], ['banana', 5], ['orange', 3] ]); // for...of مع التفكيك for (const [key, value] of fruits) { console.log(`${key}: ${value}`); } // طريقة forEach fruits.forEach((value, key, map) => { console.log(`${key} => ${value}`); }); // الحصول على جميع المفاتيح for (const key of fruits.keys()) { console.log(key); // apple, banana, orange } // الحصول على جميع القيم for (const value of fruits.values()) { console.log(value); // 2, 5, 3 } // الحصول على جميع الإدخالات for (const [key, value] of fruits.entries()) { console.log(`${key}: ${value}`); } // التحويل إلى مصفوفة const entriesArray = [...fruits]; // [['apple', 2], ['banana', 5], ...] const keysArray = [...fruits.keys()]; // ['apple', 'banana', 'orange'] const valuesArray = [...fruits.values()]; // [2, 5, 3]

Maps مقابل Objects

فهم متى تستخدم Maps مقابل Objects أمر بالغ الأهمية:

استخدم Map عندما: - تكون المفاتيح غير معروفة حتى وقت التشغيل - تكون المفاتيح من أنواع مختلفة (خاصة غير النصوص) - تحتاج إلى إضافة/حذف إدخالات بشكل متكرر - تحتاج إلى التكرار بترتيب الإدراج - تحتاج إلى حجم المجموعة استخدم Object عندما: - تكون المفاتيح معروفة وثابتة - تكون جميع المفاتيح نصوصاً أو رموزاً - تحتاج إلى تسلسل JSON - تحتاج إلى العمل مع بناء الوصول للخاصية (obj.prop) - تحتاج إلى استخدام طرق الكائن والنماذج الأولية
// مزايا Map const map = new Map(); const objKey = {id: 1}; map.set(objKey, 'value'); // كائن كمفتاح map.set(1, 'number'); // رقم كمفتاح map.set('1', 'string'); // مختلف عن الرقم 1 console.log(map.size); // حجم مدمج // قيود Object const obj = {}; obj[objKey] = 'value'; // يتحول إلى نص '[object Object]' obj[1] = 'number'; // يتحول إلى نص '1' obj['1'] = 'string'; // نفس الرقم 1 console.log(Object.keys(obj).length); // حساب الحجم يدوياً
مهم: يتم مقارنة مفاتيح Map باستخدام خوارزمية SameValueZero. مفتاحان كائن متساويان فقط إذا كانا يشيران إلى نفس الكائن، وليس إذا كان لديهما نفس الخصائص.

WeakMap

WeakMap هو متغير من Map بخصائص إدارة ذاكرة خاصة:

// WeakMap يقبل الكائنات فقط كمفاتيح const weakMap = new WeakMap(); let user = {name: 'Alice'}; weakMap.set(user, {loginCount: 5}); console.log(weakMap.has(user)); // true console.log(weakMap.get(user)); // {loginCount: 5} // عندما يتم جمع user من الذاكرة، يتم إزالة إدخال WeakMap تلقائياً user = null; // بعد جمع الذاكرة، يختفي الإدخال خصائص WeakMap: - يجب أن تكون المفاتيح كائنات (ليست أوليات) - يتم الاحتفاظ بالمفاتيح بشكل ضعيف (لا تمنع جمع الذاكرة) - غير قابلة للتكرار (لا keys() أو values() أو entries() أو forEach()) - لا خاصية size - مثالية للبيانات الخاصة والتخزين المؤقت الحساس للذاكرة

أمثلة عملية

// المثال 1: التخزين المؤقت لنتائج الدالة const cache = new Map(); function expensiveOperation(n) { if (cache.has(n)) { console.log('Cache hit!'); return cache.get(n); } console.log('Computing...'); const result = n * n; // محاكاة حساب مكلف cache.set(n, result); return result; } console.log(expensiveOperation(5)); // Computing... 25 console.log(expensiveOperation(5)); // Cache hit! 25 // المثال 2: عد التكرارات function countOccurrences(arr) { const counts = new Map(); for (const item of arr) { counts.set(item, (counts.get(item) || 0) + 1); } return counts; } const numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]; const counts = countOccurrences(numbers); console.log(counts); // Map {1 => 1, 2 => 2, 3 => 3, 4 => 4} // المثال 3: التجميع حسب الخاصية function groupBy(arr, keyFn) { const groups = new Map(); for (const item of arr) { const key = keyFn(item); if (!groups.has(key)) { groups.set(key, []); } groups.get(key).push(item); } return groups; } const people = [ {name: 'Alice', age: 25}, {name: 'Bob', age: 30}, {name: 'Charlie', age: 25} ]; const byAge = groupBy(people, person => person.age); console.log(byAge); // Map {25 => [{name: 'Alice', age: 25}, {name: 'Charlie', age: 25}], // 30 => [{name: 'Bob', age: 30}]}

التحويل بين Maps و Objects

// Object إلى Map const obj = {a: 1, b: 2, c: 3}; const mapFromObj = new Map(Object.entries(obj)); // Map إلى Object const map = new Map([['x', 10], ['y', 20]]); const objFromMap = Object.fromEntries(map); // Map إلى JSON const jsonStr = JSON.stringify([...map]); // JSON إلى Map const parsed = new Map(JSON.parse(jsonStr)); // تحويل Map بمفاتيح كائن إلى مصفوفة لـ JSON const complexMap = new Map([[{id: 1}, 'value']]); const serializable = [...complexMap].map(([key, value]) => ({ key: JSON.stringify(key), value: value }));

تمرين عملي:

التحدي: أنشئ دالة تعيد أول حرف غير متكرر في نص.

function firstNonRepeating(str) { const charCount = new Map(); // عد التكرارات for (const char of str) { charCount.set(char, (charCount.get(char) || 0) + 1); } // العثور على الأول بعدد 1 for (const char of str) { if (charCount.get(char) === 1) { return char; } } return null; } console.log(firstNonRepeating('aabbcdde')); // 'c' console.log(firstNonRepeating('aabbcc')); // null

جربه بنفسك: عدّل هذا لإرجاع جميع الأحرف غير المتكررة.

الملخص

في هذا الدرس، تعلمت:

  • تخزن Maps أزواج المفتاح والقيمة مع مفاتيح من أي نوع
  • طرق Map: set() و get() و has() و delete() و clear() وخاصية size
  • تحافظ Maps على ترتيب الإدراج وقابلة للتكرار مباشرة
  • يمكن استخدام الكائنات كمفاتيح Map، مما يتيح أنماطاً قوية
  • Maps مقابل Objects: Maps للمفاتيح الديناميكية، Objects للخصائص الثابتة
  • WeakMap للتخزين الفعال للذاكرة بمفاتيح الكائن
  • الاستخدامات العملية: التخزين المؤقت، العد، التجميع، وتخزين البيانات الوصفية
التالي: في الدرس التالي، سنستكشف Symbols - معرفات فريدة وثابتة!