استخدام تفكيك المصفوفات والكائنات (Destructuring) في JavaScript: دليل شامل

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

منذ ظهور ES6 (ECMAScript 2015)، أصبحت لغة JavaScript أكثر قوة ومرونة بفضل الميزات الجديدة التي قدمتها. إحدى هذه الميزات الرائعة هي “تفكيك التعيين” (Destructuring Assignment)، وهي تعبير برمجي يتيح لنا استخراج القيم من المصفوفات أو الخصائص من الكائنات وتعيينها لمتغيرات مستقلة بسهولة وفعالية. بعبارة أخرى، يمكننا استخلاص البيانات من الهياكل المعقدة وتخصيصها لمتغيرات بطريقة نظيفة ومباشرة.

لماذا نحتاج إلى هذه الميزة؟ تخيل أنك تريد استخراج بيانات محددة من مصفوفة أو كائن. في السابق، كانت هذه العملية تتطلب كتابة أكواد متكررة ومطولة. لنلقِ نظرة على كيفية القيام بذلك قبل ES6، ثم نكتشف كيف يغير التفكيك قواعد اللعبة.

تفكيك المصفوفات (Array Destructuring)

يعد تفكيك المصفوفات طريقة بسيطة وفعالة لاستخراج العناصر من المصفوفات وتعيينها لمتغيرات. لنأخذ مثالاً تقليدياً قبل ES6:

 let introduction = [ "Hello" , "I" , "am" , "Sarah" ];
 let greeting = introduction[ 0 ];
 let name = introduction[ 3 ];
 console .log(greeting); //"Hello"
 console .log(name); //"Sarah"

كما نرى، لاستخراج البيانات من المصفوفة، كان علينا تكرار نفس النمط مراراً وتكراراً باستخدام الفهرسة. مع تفكيك المصفوفات في ES6، تصبح هذه العملية أسهل بكثير.

تفكيك المصفوفات الأساسي

باستخدام تفكيك المصفوفات، يمكننا تحقيق نفس النتيجة بكود أقل وأكثر وضوحاً:

 let introduction = [ "Hello" , "I" , "am" , "Sarah" ];
 let [greeting, pronoun] = introduction;
 console .log(greeting); //"Hello"
 console .log(pronoun); //"I"

هنا، تم تعيين العنصر الأول من المصفوفة introduction إلى المتغير greeting، والعنصر الثاني إلى المتغير pronoun. يمكننا أيضاً تعيين القيم مباشرة دون الحاجة إلى مصفوفة موجودة مسبقاً:

 let [greeting, pronoun] = [ "Hello" , "I" , "am" , "Sarah" ];
 console .log(greeting); //"Hello"
 console .log(pronoun); //"I"

الإعلان عن المتغيرات قبل التعيين

يمكن الإعلان عن المتغيرات قبل تعيينها باستخدام التفكيك، كما هو موضح أدناه:

 let greeting, pronoun;
 [greeting, pronoun] = [ "Hello" , "I" , "am" , "Sarah" ];
 console .log(greeting); //"Hello"
 console .log(pronoun); //"I"

لاحظ أن المتغيرات يتم تعيينها من اليسار إلى اليمين؛ فالمتغير الأول يحصل على العنصر الأول في المصفوفة، والمتغير الثاني يحصل على العنصر الثاني، وهكذا.

تجاوز عناصر في المصفوفة

ماذا لو أردنا الحصول على العنصر الأول والأخير فقط من المصفوفة، وتجاهل العناصر الوسطى؟ يمكن تحقيق ذلك باستخدام الفواصل (commas) لتجاوز القيم:

 let [greeting,,,name] = [ "Hello" , "I" , "am" , "Sarah" ];
 console .log(greeting); //"Hello"
 console .log(name); //"Sarah"

في هذا المثال، تشير الفواصل الثلاثة المتتالية إلى أننا نتجاوز ثلاثة عناصر بين greeting و name. يمكننا أيضاً تجاوز عناصر أخرى، مثل العنصر الأول والثالث:

 let [,pronoun,,name] = [ "Hello" , "I" , "am" , "Sarah" ];
 console .log(pronoun); //"I"
 console .log(name); //"Sarah"

إذا أردنا تجاوز جميع العناصر، يمكننا ببساطة استخدام عدد كافٍ من الفواصل:

 let [,,,,] = [ "Hello" , "I" , "am" , "Sarah" ]; // لا يتم تعيين أي متغيرات هنا

تعيين بقية عناصر المصفوفة (Rest Syntax)

في بعض الأحيان، قد نرغب في تعيين جزء من المصفوفة لمتغيرات محددة، وتجميع بقية العناصر في مصفوفة جديدة. هنا يأتي دور عامل التفكيك “البقية” (Rest Syntax)، الذي يمثله ثلاث نقاط (...):

 let [greeting,...intro] = [ "Hello" , "I" , "am" , "Sarah" ];
 console .log(greeting); //"Hello"
 console .log(intro); //["I", "am", "Sarah"]

باستخدام هذا النمط، يمكنك تفكيك وتعيين الجزء المتبقي من المصفوفة إلى متغير واحد، والذي سيكون مصفوفة بحد ذاته.

تفكيك التعيين مع الدوال (Functions)

يمكننا أيضاً استخراج البيانات من مصفوفة تُرجعها دالة. لنفترض أن لدينا دالة تُرجع مصفوفة:

 function getArray ( ) {
  return [ "Hello" , "I" , "am" , "Sarah" ];
 }
 let [greeting,pronoun] = getArray();
 console .log(greeting); //"Hello"
 console .log(pronoun); //"I"

نحصل على نفس النتائج، مما يوضح مرونة التفكيك.

استخدام القيم الافتراضية (Default Values)

يمكن تعيين قيم افتراضية للمتغيرات في حال كانت القيمة المستخرجة من المصفوفة undefined:

 let [greeting = "hi" ,name = "Sarah" ] = [ "hello" ];
 console .log(greeting); //"hello"
 console .log(name); //"Sarah"

في هذا المثال، تم تعيين greeting إلى "hello" لأنها متوفرة في المصفوفة. أما name، فلم يتم تعريفها في المصفوفة، لذلك عادت إلى قيمتها الافتراضية "Sarah".

تبديل القيم باستخدام تفكيك التعيين

ميزة أخرى مفيدة لتفكيك المصفوفات هي القدرة على تبديل قيم متغيرين بسهولة دون الحاجة إلى متغير مؤقت:

 let a = 3 ;
 let b = 6 ;
 [a,b] = [b,a];
 console .log(a); //6
 console .log(b); //3

هذا يجعل عملية تبديل القيم أكثر إيجازاً ووضوحاً.

تفكيك الكائنات (Object Destructuring)

تماماً مثل المصفوفات، يوفر تفكيك الكائنات طريقة أنيقة لاستخراج الخصائص من الكائنات وتعيينها لمتغيرات. لنرى أولاً لماذا نحتاج إلى تفكيك الكائنات. تخيل أننا نريد استخراج بيانات من كائن وتعيينها لمتغيرات جديدة. قبل ES6، كان يتم ذلك على النحو التالي:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let name = person.name;
 let country = person.country;
 let job = person.job;
 console .log(name); //"Sarah"
 console .log(country); //"Nigeria"
 console .log(job); //Developer"

لاحظ مدى تكرار هذه العملية لاستخراج جميع البيانات. يوفر تفكيك الكائنات حلاً فعالاً لهذه المشكلة.

تفكيك الكائنات الأساسي

باستخدام ES6، يمكننا تكرار المثال أعلاه بطريقة أكثر اختصاراً. بدلاً من تعيين القيم واحدة تلو الأخرى، يمكننا استخدام الكائن على الجانب الأيسر لاستخراج البيانات:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let {name, country, job} = person;
 console .log(name); //"Sarah"
 console .log(country); //"Nigeria"
 console .log(job); //Developer"

ستحصل على نفس النتائج تماماً. من الصالح أيضاً تعيين المتغيرات مباشرة من كائن لم يتم الإعلان عنه مسبقاً:

 let {name, country, job} = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 console .log(name); //"Sarah"
 console .log(country); //"Nigeria"
 console .log(job); //Developer"

الإعلان عن المتغيرات قبل التعيين (مع ملاحظة هامة)

يمكن الإعلان عن المتغيرات في الكائنات قبل تعيينها باستخدام التفكيك. دعنا نجرب ذلك:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let name, country, job;
 {name, country, job} = person; // هذا سيؤدي إلى خطأ!
 console .log(name); // Error : "Unexpected token ="

ما الذي حدث هنا؟ لقد نسينا إضافة الأقواس () حول عبارة التعيين. الأقواس حول عبارة التعيين مطلوبة عند استخدام تفكيك حرفي الكائن (object literal destructuring assignment) بدون إعلان، وذلك لأن الأقواس المتعرجة {} على الجانب الأيسر تُعتبر كتلة (block) وليست حرفية كائن. إليك الطريقة الصحيحة للقيام بذلك:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let name, country, job;
 ({name, country, job} = person);
 console .log(name); //"Sarah"
 console .log(job); //"Developer"

من المهم أيضاً ملاحظة أنه عند استخدام هذا النحو، يجب أن يسبق الأقواس () فاصلة منقوطة (semicolon) لتجنب تفسيرها كاستدعاء دالة من السطر السابق. أيضاً، يجب أن تكون أسماء المتغيرات في الكائن على الجانب الأيسر مطابقة لأسماء مفاتيح الخصائص في الكائن person. إذا كانت الأسماء مختلفة، فسنحصل على undefined:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let {name, friends, job} = person;
 console .log(name); //"Sarah"
 console .log(friends); //undefined

ولكن ماذا لو أردنا استخدام اسم متغير جديد؟ يمكننا ذلك.

استخدام اسم متغير جديد

إذا أردنا تعيين قيم كائن لمتغير جديد بدلاً من استخدام اسم الخاصية نفسه، يمكننا القيام بذلك:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let { name : foo, job : bar} = person;
 console .log(foo); //"Sarah"
 console .log(bar); //"Developer"

هنا، تم تمرير القيم المستخرجة إلى المتغيرين الجديدين foo و bar.

استخدام القيم الافتراضية

يمكن أيضاً استخدام القيم الافتراضية في تفكيك الكائنات، وذلك في حال كانت الخاصية غير موجودة أو قيمتها undefined في الكائن الذي نحاول استخراج البيانات منه:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let {name = "myName" , friend = "Annie" } = person;
 console .log(name); //"Sarah"
 console .log(friend); //"Annie"

في هذا المثال، إذا كانت القيمة غير undefined، فإن المتغير يخزن القيمة المستخرجة من الكائن، كما في حالة name. وإلا، فإنه يستخدم القيمة الافتراضية، كما حدث مع friend. يمكننا أيضاً تعيين قيم افتراضية عند تعيين القيم لمتغير جديد:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 let { name :foo = "myName" , friend : bar = "Annie" } = person;
 console .log(foo); //"Sarah"
 console .log(bar); //"Annie"

هنا، تم استخراج name من الكائن person وتعيينها لمتغير مختلف (foo). أما friend، فلم تكن موجودة في person، لذلك تم تعيين المتغير الجديد bar بالقيمة الافتراضية.

أسماء الخصائص المحسوبة (Computed Property Names)

تعد أسماء الخصائص المحسوبة ميزة أخرى لحرفيات الكائنات تعمل أيضاً مع التفكيك. يمكنك تحديد اسم الخاصية عبر تعبير إذا وضعته بين أقواس مربعة []:

 let prop = "name" ;
 let {[prop] : foo} = { name : "Sarah" , country : "Nigeria" , job : "Developer" };
 console .log(foo); //"Sarah"

دمج المصفوفات مع الكائنات

يمكن أيضاً استخدام المصفوفات داخل الكائنات عند التفكيك:

 let person = { name : "Sarah" , country : "Nigeria" , friends : [ "Annie" , "Becky" ]};
 let { name :foo, friends : bar} = person;
 console .log(foo); //"Sarah"
 console .log(bar); //["Annie", "Becky"]

التداخل في تفكيك الكائنات (Nesting)

يمكن أن تكون الكائنات متداخلة (nested) عند التفكيك، مما يسمح لنا باستخراج الخصائص من الكائنات الفرعية:

 let person = {
  name : "Sarah" ,
  place : { country : "Nigeria" , city : "Lagos" },
  friends : [ "Annie" , "Becky" ]
 };
 let { name :foo, place : { country : bar, city : x} } = person;
 console .log(foo); //"Sarah"
 console .log(bar); //"Nigeria"
 console .log(x); //"Lagos"

بقية الخصائص في تفكيك الكائنات (Rest Syntax)

يمكن أيضاً استخدام عامل التفكيك “البقية” (Rest Syntax) في تفكيك الكائنات لالتقاط مفاتيح الخصائص التي لم يتم التقاطها بالفعل بواسطة نمط التفكيك. يتم نسخ هذه المفاتيح وقيمها إلى كائن جديد:

 let person = { name : "Sarah" , country : "Nigeria" , job : "Developer" , friends: [ "Annie" , "Becky" ]};
 let {name, friends, ...others} = person;
 console .log(name); //"Sarah"
 console .log(friends); //["Annie", "Becky"]
 console .log(others); // {country: "Nigeria", job: "Developer"}

هنا، تم تعيين الخصائص المتبقية التي لم تكن مفاتيحها جزءاً من أسماء المتغيرات المدرجة إلى المتغير others. يمكن إعادة تسمية others إلى أي متغير تريده.

تفكيك الكائنات والدوال

يمكن استخدام تفكيك الكائنات لتعيين المعلمات للدوال، مما يجعل توقيع الدالة أكثر وضوحاً عند التعامل مع كائنات الإعدادات:

 function person ( {name: x, job: y} = {} ) {
  console .log(x);
 }
 person({ name : "Michelle" }); //"Michelle"
 person(); //undefined
 // person(friend); // هذا سيؤدي إلى خطأ: friend is not defined

لاحظ الأقواس المتعرجة الفارغة {} على الجانب الأيمن من كائن المعلمات. إنها تجعل من الممكن استدعاء الدالة دون تمرير أي وسيطات، وهذا هو السبب في أننا حصلنا على undefined. إذا أزلناها، فسنحصل على رسالة خطأ. يمكننا أيضاً تعيين قيم افتراضية للمعلمات:

 function person ( {name: x = "Sarah" , job: y = "Developer" } = {} ) {
  console .log(x);
 }
 person({name: "Ahmed"}); //"Ahmed"
 person(); //"Sarah"

كما رأينا في الأمثلة أعلاه، يمكننا القيام بالكثير من الأشياء باستخدام تفكيك المصفوفات والكائنات في JavaScript، مما يعزز من كفاءة الكود وقراءته.

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

يمثل تفكيك المصفوفات والكائنات في JavaScript (Array and Object Destructuring) قفزة نوعية في كتابة الكود النظيف والفعال. هذه الميزة، التي قدمتها ES6، لا تقتصر على تبسيط استخراج البيانات من الهياكل المعقدة فحسب، بل تساهم أيضاً في تحسين قراءة الكود وتقليل الأسطر البرمجية اللازمة لإنجاز المهام المتكررة. من تبديل قيم المتغيرات إلى التعامل مع المعلمات الافتراضية في الدوال، يوفر التفكيك أدوات قوية للمطورين لإنشاء تطبيقات JavaScript أكثر قوة ومرونة. إن إتقان هذه التقنية أمر بالغ الأهمية لأي مطور يسعى لكتابة كود حديث وعالي الجودة.

اترك تعليقاً

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