دليلك الشامل لاستخدام مجموعات JavaScript المتقدمة: Map و Set

دقائق القراءة: 12

مقدمة إلى مجموعات البيانات في JavaScript: Map و Set

في عالم JavaScript، تُعد objects (الكائنات) و arrays (المصفوفات) من هياكل البيانات الأساسية التي تُستخدم لتخزين مجموعات من القيم. يُنشأ الكائن باستخدام الأقواس المعقوفة {…} ويحتوي على قائمة من الخصائص، حيث تكون كل خاصية عبارة عن زوج من key-value (مفتاح-قيمة). يجب أن يكون key (المفتاح) في الكائنات من نوع string (نص)، بينما يمكن أن يكون value (القيمة) من أي نوع.

من ناحية أخرى، تُعد المصفوفات مجموعات مرتبة يمكنها الاحتفاظ ببيانات من أي نوع، وتُنشأ باستخدام الأقواس المربعة [...]، وتسمح بالعناصر المكررة. حتى إصدار ES6 (ECMAScript 2015)، كانت الكائنات والمصفوفات هي هياكل البيانات الأكثر أهمية للتعامل مع مجموعات البيانات في JavaScript، ولم يكن لدى المطورين خيارات كثيرة خارجها، ومع ذلك، كان مزيج من الكائنات والمصفوفات قادرًا على التعامل مع البيانات في العديد من السيناريوهات.

لكن هذه الهياكل كانت تعاني من بعض أوجه القصور:

  • مفاتيح الكائنات (Object keys) يمكن أن تكون فقط من نوع string.
  • الكائنات لا تحافظ على ترتيب العناصر التي أُضيفت إليها.
  • تفتقر الكائنات إلى بعض الوسائل المفيدة، مما يجعل استخدامها صعبًا في بعض المواقف. على سبيل المثال، لا يمكنك حساب حجم (length) الكائن بسهولة، كما أن تعداد عناصر الكائن ليس مباشرًا.
  • المصفوفات هي مجموعات من العناصر التي تسمح بالتكرار، ودعم المصفوفات التي تحتوي على عناصر مميزة فقط يتطلب منطقًا ورمزًا إضافيًا.

مع تقديم ES6، حصلنا على هيكلي بيانات جديدين يعالجان أوجه القصور المذكورة أعلاه: Map و Set. في هذه المقالة، سننظر في كليهما عن كثب ونفهم كيفية استخدامهما في مواقف مختلفة.

فهم Map في JavaScript: مجموعة مفتاح-قيمة متقدمة

Map هي مجموعة من أزواج key-value (مفتاح-قيمة) حيث يمكن أن يكون key (المفتاح) من أي نوع. تتذكر Map الترتيب الأصلي الذي أُضيفت به العناصر إليها، مما يعني أنه يمكن استرداد البيانات بنفس الترتيب الذي أُدخلت به. بعبارة أخرى، تتمتع Map بخصائص كل من Object و Array:

  • مثل الكائن (Object)، تدعم بنية زوج المفتاح-القيمة.
  • مثل المصفوفة (Array)، تتذكر ترتيب الإدخال.

إنشاء وتهيئة Map في JavaScript

يمكن إنشاء Map جديدة على النحو التالي:

const map = new Map();

والتي تُرجع Map فارغة:

Map(0) {}

هناك طريقة أخرى لإنشاء Map وهي بقيم أولية. إليك كيفية إنشاء Map بثلاثة أزواج من المفتاح-القيمة:

const freeCodeCampBlog = new Map([
  [ 'name' , 'freeCodeCamp' ],
  [ 'type' , 'blog' ],
  [ 'writer' , 'Tapas Adhikary' ],
]);

والتي تُرجع Map بثلاثة عناصر:

Map(3) {"name" => "freeCodeCamp", "type" => "blog", "writer" => "Tapas Adhikary"}

إضافة قيم إلى Map في JavaScript

لإضافة قيمة إلى Map، استخدم الوسيلة set(key, value). تأخذ الوسيلة set(key, value) معاملين، key و value، حيث يمكن أن يكون المفتاح والقيمة من أي نوع، سواء كان نوعًا بدائيًا (boolean، string، number، إلخ) أو كائنًا:

// إنشاء Map
const map = new Map();

// إضافة قيم إلى Map
map.set( 'name' , 'freeCodeCamp' );
map.set( 'type' , 'blog' );
map.set( 'writer' , 'Tapas Adhikary' );

الناتج:

Map(3) {"name" => "freeCodeCamp", "type" => "blog", "writer" => "Tapas Adhikary"}

يرجى ملاحظة أنه إذا استخدمت نفس المفتاح لإضافة قيمة إلى Map عدة مرات، فستحل دائمًا محل القيمة السابقة:

// إضافة كاتب مختلف
map.set( 'writer' , 'Someone else!' );

لذا سيكون الناتج:

Map(3) {"name" => "freeCodeCamp", "type" => "blog", "writer" => "Someone else!"}

استرجاع القيم من Map في JavaScript

لاسترجاع قيمة من Map، استخدم الوسيلة get(key):

map.get( 'name' ); // تُرجع freeCodeCamp

مفاتيح Map في JavaScript: المرونة المطلقة

يمكن أن تكون مفاتيح Map من أي نوع، سواء كان نوعًا بدائيًا أو كائنًا. هذا هو أحد الاختلافات الرئيسية بين Map وكائنات JavaScript العادية حيث يمكن أن يكون المفتاح فقط من نوع string:

// إنشاء Map
const funMap = new Map();

funMap.set( 360 , 'My House Number' ); // رقم كمفتاح
funMap.set( true , 'I write blogs!' ); // قيمة منطقية كمفتاح
let obj = { 'name' : 'tapas' }
funMap.set(obj, true ); // كائن كمفتاح
console .log(funMap);

إليك الناتج:

Map(3) {
  360 => "My House Number",
  true => "I write blogs!",
  {…} => true
}

يتعامل كائن JavaScript العادي دائمًا مع المفتاح على أنه string. حتى عندما تمرر له نوعًا بدائيًا أو كائنًا، فإنه يحول المفتاح داخليًا إلى string:

// إنشاء كائن فارغ
const funObj = {};

// إضافة خاصية. لاحظ، تمرير المفتاح كرقم.
funObj[ 360 ] = 'My House Number' ;

// تُرجع true لأن الرقم 360 تحول داخليًا إلى السلسلة '360'!
console .log(funObj[ 360 ] === funObj[ '360' ]);

خصائص ووسائل Map المدمجة في JavaScript

تتمتع Map في JavaScript بخصائص ووسائل مدمجة تجعل استخدامها سهلاً. إليك بعض الشائعة منها:

  • استخدم الخاصية size لمعرفة عدد العناصر في Map:
console .log( 'size of the map is' , map.size);
  • ابحث عن عنصر باستخدام الوسيلة has(key):
// تُرجع true، إذا كانت Map تحتوي على عنصر بالمفتاح 'John'
console .log(map.has( 'John' ));
// تُرجع false، إذا كانت Map لا تحتوي على عنصر بالمفتاح 'Tapas'
console .log(map.has( 'Tapas' ));
  • احذف عنصرًا باستخدام الوسيلة delete(key):
map.delete( 'Sam' ); // تحذف العنصر بالمفتاح 'Sam'.
  • استخدم الوسيلة clear() لإزالة جميع العناصر من Map دفعة واحدة:
// مسح Map بإزالة جميع العناصر
map.clear();
map.size // ستُرجع 0

المكررات في Map: keys(), values(), و entries()

تُرجع الوسائل keys() و values() و entries() كائن MapIterator، وهو أمر ممتاز لأنه يمكنك استخدام حلقة for-of أو forEach مباشرة عليه.

أولاً، أنشئ Map بسيطة:

const ageMap = new Map ([
  [ 'Jack' , 20 ],
  [ 'Alan' , 34 ],
  [ 'Bill' , 10 ],
  [ 'Sam' , 9 ]
]);
  • احصل على جميع المفاتيح:
console .log(ageMap.keys());
// الناتج:
// MapIterator {"Jack", "Alan", "Bill", "Sam"}
  • احصل على جميع القيم:
console .log(ageMap.values());
// الناتج:
// MapIterator {20, 34, 10, 9}
  • احصل على جميع الإدخالات (أزواج المفتاح-القيمة):
console .log(ageMap.entries());
// الناتج:
// MapIterator {"Jack" => 20, "Alan" => 34, "Bill" => 10, "Sam" => 9}

التكرار على Map في JavaScript

يمكنك استخدام حلقة forEach أو for-of للتكرار على Map:

// باستخدام forEach
ageMap.forEach( ( value, key ) => {
  console .log( ` ${key} is ${value} years old!` );
});

// باستخدام for-of
for ( const [key, value] of ageMap) {
  console .log( ` ${key} is ${value} years old!` );
}

سيكون الناتج هو نفسه في كلتا الحالتين:

Jack is 20 years old!
Alan is 34 years old!
Bill is 10 years old!
Sam is 9 years old!

تحويل كائن (Object) إلى Map في JavaScript

قد تواجه موقفًا تحتاج فيه إلى تحويل object إلى بنية تشبه Map. يمكنك استخدام الوسيلة entries من Object للقيام بذلك:

const address = {
  'Tapas' : 'Bangalore' ,
  'James' : 'Huston' ,
  'Selva' : 'Srilanka'
};
const addressMap = new Map ( Object .entries(address));

تحويل Map إلى كائن (Object) في JavaScript

إذا كنت ترغب في القيام بالعكس، يمكنك استخدام الوسيلة fromEntries:

Object .fromEntries(map)

تحويل Map إلى مصفوفة (Array) في JavaScript

هناك طريقتان لتحويل Map إلى مصفوفة:

  • استخدام Array.from(map):
const map = new Map();
map.set( 'milk' , 200 );
map.set( "tea" , 300 );
map.set( 'coffee' , 500 );
console .log( Array .from(map));
  • استخدام عامل الانتشار (spread operator):
console .log([...map]);

متى تستخدم Map ومتى تستخدم Object؟

تتمتع Map بخصائص كل من object و array. ومع ذلك، فإن Map تشبه object أكثر من array بسبب طبيعة تخزين البيانات بتنسيق key-value. لكن التشابه مع الكائنات ينتهي هنا. كما رأيت، تختلف Map بطرق عديدة. إذن، أي منهما يجب أن تستخدم، ومتى؟ كيف تقرر؟

استخدم Map عندما:

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

استخدم object عندما:

  • ليس لديك أي من الاحتياجات المذكورة أعلاه.
  • تعتمد على JSON.parse() حيث لا يمكن تحليل Map بواسطته.

فهم Set في JavaScript: مجموعة العناصر الفريدة

Set هي مجموعة من العناصر الفريدة التي يمكن أن تكون من أي نوع. Set هي أيضًا مجموعة مرتبة من العناصر، مما يعني أنه سيتم استرداد العناصر بنفس الترتيب الذي أُدخلت به. تتصرف Set في JavaScript بنفس طريقة المجموعة الرياضية.

إنشاء وتهيئة Set في JavaScript

يمكن إنشاء Set جديدة على النحو التالي:

const set = new Set();
console .log(set);

وسيكون الناتج Set فارغة:

Set(0) {}

إليك كيفية إنشاء Set ببعض القيم الأولية:

const fruteSet = new Set ([ '🍉' , '🍎' , '🍈' , '🍏' ]);
console .log(fruteSet);

الناتج:

Set(4) {"🍉", "🍎", "🍈", "🍏"}

خصائص ووسائل Set المدمجة في JavaScript

تحتوي Set على وسائل لإضافة عنصر إليها، وحذف عناصر منها، والتحقق مما إذا كان العنصر موجودًا فيها، ومسحها بالكامل:

  • استخدم الخاصية size لمعرفة حجم Set. تُرجع عدد العناصر فيها:
set.size
  • استخدم الوسيلة add(element) لإضافة عنصر إلى Set:
// إنشاء Set - saladSet
const saladSet = new Set();

// إضافة بعض الخضروات إليها
saladSet.add( '🍅' ); // طماطم
saladSet.add( '🥑' ); // أفوكادو
saladSet.add( '🥕' ); // جزر
saladSet.add( '🥒' ); // خيار
console .log(saladSet);
// الناتج
// Set(4) {"🍅", "🥑", "🥕", "🥒"}

أنا أحب الخيار! ماذا عن إضافة واحد آخر؟ أوه لا، لا أستطيع – Set هي مجموعة من العناصر الفريدة:

saladSet.add( '🥒' );
console .log(saladSet);

الناتج هو نفسه كما كان من قبل – لم يُضف أي شيء إلى saladSet.

  • استخدم الوسيلة has(element) للبحث عما إذا كان لدينا جزر (🥕) أو بروكلي (🥦) في Set:
// تحتوي السلطة على 🥕، لذا تُرجع true
console .log( 'Does the salad have a carrot?' , saladSet.has( '🥕' ));
// لا تحتوي السلطة على 🥦، لذا تُرجع false
console .log( 'Does the salad have broccoli?' , saladSet.has( '🥦' ));
  • استخدم الوسيلة delete(element) لإزالة الأفوكادو (🥑) من Set:
saladSet.delete( '🥑' );
console .log( 'I do not like 🥑, remove from the salad:' , saladSet);

الآن Set سلطتنا هي كما يلي:

Set(3) {"🍅", "🥕", "🥒"}
  • استخدم الوسيلة clear() لإزالة جميع العناصر من Set:
saladSet.clear();

التكرار على Set في JavaScript

تحتوي Set على وسيلة تسمى values() والتي تُرجع SetIterator للحصول على جميع قيمها:

// إنشاء Set
const houseNos = new Set ([ 360 , 567 , 101 ]);
// الحصول على SetIterator باستخدام الوسيلة `values()`
console .log(houseNos.values());

الناتج:

SetIterator { 360 , 567 , 101 }

يمكننا استخدام حلقة forEach أو for-of عليها لاسترداد القيم. من المثير للاهتمام أن JavaScript تحاول جعل Set متوافقة مع Map. لهذا السبب نجد اثنتين من نفس الوسائل مثل Map، وهما keys() و entries(). نظرًا لأن Set لا تحتوي على مفاتيح، فإن الوسيلة keys() تُرجع SetIterator لاسترداد قيمها:

console .log(houseNos.keys());
// الناتج
// SetIterator { 360 , 567 , 101 }

مع Map، تُرجع الوسيلة entries() مكررًا لاسترداد أزواج المفتاح-القيمة. مرة أخرى، لا توجد مفاتيح في Set، لذا تُرجع entries() كائن SetIterator لاسترداد أزواج القيمة-القيمة:

console .log(houseNos.entries());
// الناتج
// SetIterator {360 => 360, 567 => 567, 101 => 101}

يمكننا التعداد على Set باستخدام حلقتي forEach و for-of:

// باستخدام forEach
houseNos.forEach( ( value ) => {
  console .log(value);
});

// باستخدام for-of
for ( const value of houseNos) {
  console .log(value);
}

الناتج لكلاهما هو:

360
567
101

العلاقة بين Set والمصفوفات (Arrays) في JavaScript

تسمح لك المصفوفة، مثل Set، بإضافة وإزالة العناصر. لكن Set مختلفة تمامًا، وليست مخصصة لتحل محل المصفوفات. الفرق الرئيسي بين المصفوفة و Set هو أن المصفوفات تسمح لك بوجود عناصر مكررة. أيضًا، بعض عمليات Set مثل delete() أسرع من عمليات المصفوفة مثل shift() أو splice(). فكر في Set كامتداد لمصفوفة عادية، ولكن مع مزيد من القوة. هيكل بيانات Set ليس بديلاً للمصفوفة. كلاهما يمكن أن يحل مشاكل مثيرة للاهتمام.

تحويل Set إلى مصفوفة (Array) في JavaScript

تحويل Set إلى مصفوفة أمر بسيط:

const arr = [...houseNos];
console .log(arr);

استخراج القيم الفريدة من مصفوفة باستخدام Set في JavaScript

يُعد إنشاء Set طريقة سهلة جدًا لإزالة القيم المكررة من مصفوفة:

// إنشاء مصفوفة mixedFruit مع بعض الفواكه المكررة
const mixedFruit = [ '🍉' , '🍎' , '🍉' , '🍈' , '🍏' , '🍎' , '🍈' ];

// تمرير المصفوفة لإنشاء مجموعة من الفواكه الفريدة
const mixedFruitSet = new Set (mixedFruit);
console .log(mixedFruitSet);

الناتج:

Set(4) {"🍉", "🍎", "🍈", "🍏"}

Set والكائنات (Objects) في JavaScript

يمكن أن تحتوي Set على عناصر من أي نوع، حتى الكائنات:

// إنشاء كائن شخص
const person = { 'name' : 'Alex' , 'age' : 32 };

// إنشاء Set وإضافة الكائن إليها
const pSet = new Set ();
pSet.add(person);
console .log(pSet);

الناتج:

مجموعة Set تحتوي على كائن واحد

لا مفاجأة هنا – تحتوي Set على عنصر واحد وهو كائن. دعنا نغير خاصية من الكائن ونضيفه إلى المجموعة مرة أخرى:

// تغيير اسم الشخص
person.name = 'Bob' ;
// إضافة كائن الشخص إلى Set مرة أخرى
pSet.add(person);
console .log(pSet);

ماذا تعتقد سيكون الناتج؟ كائنان person أم واحد فقط؟ إليك الناتج:

مجموعة Set لا تسمح بإضافة نفس الكائن بعد تعديل خاصية

Set هي مجموعة من العناصر الفريدة. بتغيير خاصية الكائن، لم نغير الكائن نفسه. وبالتالي، لن تسمح Set بالعناصر المكررة. Set هي هيكل بيانات رائع للاستخدام بالإضافة إلى مصفوفات JavaScript. ومع ذلك، ليس لديها ميزة كبيرة على المصفوفات العادية. استخدم Set عندما تحتاج إلى الحفاظ على مجموعة مميزة من البيانات لإجراء عمليات المجموعة عليها مثل union (الاتحاد)، intersection (التقاطع)، difference (الفرق)، وما إلى ذلك.

الخلاصة التقنية

تمثل مجموعات Map و Set في JavaScript إضافة قيمة هائلة لمجموعة أدوات المطورين، خاصة بعد ظهور ES6. لقد عالجت هذه المجموعات بفعالية القيود الجوهرية في الكائنات والمصفوفات التقليدية، مما فتح آفاقًا جديدة لإدارة البيانات بمرونة وكفاءة أعلى. فـ Map، بقدرتها على استخدام أي نوع بيانات كمفاتيح مع الحفاظ على ترتيب الإدخال، تقدم حلاً مثاليًا للسيناريوهات التي تتطلب مفاتيح معقدة أو ترتيبًا محددًا للعناصر، متفوقة بذلك على الكائنات العادية التي تقتصر مفاتيحها على السلاسل النصية ولا تضمن الترتيب. بينما توفر Set حلاً أنيقًا وفعالًا للتعامل مع مجموعات من العناصر الفريدة، مما يبسط عمليات إزالة التكرار ويجعل تنفيذ العمليات الرياضية على المجموعات أكثر سهولة. لا تُعد Map و Set بديلًا شاملًا للكائنات والمصفوفات، بل هي أدوات تكميلية قوية، يتيح فهمها واختيار الأداة المناسبة للمهمة المحددة للمطورين بناء تطبيقات JavaScript أكثر قوة وتنظيمًا وأداءً.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *