كائن الـ`Array`
تشرح هذه المقالة عن كائن الـArray۔
سأشرح الاستخدامات العملية للمصفوفات خطوة بخطوة بطريقة سهلة الفهم۔
YouTube Video
كائن الـArray
كائن الـArray في جافاسكريبت هو أحد أهم الهياكل التي تشكل أساس جميع أنواع معالجة البيانات۔ بدءًا من العمليات الأساسية على المصفوفات إلى الدوال عالية المستوى المفيدة لتحويل البيانات بشكل فعال، هناك العديد من الميزات التي ينبغي معرفتها۔
أساسيات المصفوفات
في جافاسكريبت، تُعد المصفوفات هيكل بيانات أساسي لمعالجة عدة قيم معًا۔ هنا، نقدم كيفية إنشاء المصفوفات وكيفية قراءة وكتابة عناصرها باستخدام أمثلة بسيطة۔
1// Create arrays in different ways
2const arr1 = [1, 2, 3]; // array literal
3const arr2 = new Array(4, 5, 6); // Array constructor
4const arr3 = Array.of(7, 8, 9); // Array.of
5
6console.log("arr1 created with literal. :", arr1);
7console.log("arr2 created with constructor:", arr2);
8console.log("arr3 created with Array.of. :", arr3);
9// arr1 created with literal. : [ 1, 2, 3 ]
10// arr2 created with constructor: [ 4, 5, 6 ]
11// arr3 created with Array.of. : [ 7, 8, 9 ]
12
13// Access and modify elements
14let first = arr1[0]; // read element
15console.log("First element of arr1:", first);
16// First element of arr1: 1
17
18arr1[1] = 20; // modify element
19console.log("arr1 after modifying index 1:", arr1);
20// arr1 after modifying index 1: [ 1, 20, 3 ]
21
22const len = arr1.length; // get length
23console.log("Length of arr1:", len);
24// Length of arr1: 3
- يوضح هذا الكود ثلاث طرق لإنشاء المصفوفات، كيفية قراءة وتحديث العناصر باستخدام الفهارس، وكيفية الحصول على الطول باستخدام الخاصية
length۔ - الحرفية للمصفوفات هي الأكثر شيوعاً وأسهل في القراءة، وتُستخدم في أغلب الأحيان في الحالات اليومية۔
إضافة وحذف العناصر (في النهاية أو البداية)
تتيح لك المصفوفات بسهولة إضافة أو إزالة العناصر في النهاية أو البداية۔ هذه العمليات مفيدة أيضًا عند تنفيذ هياكل مثل المكدسات (Stack) أو الطوابير (Queue)۔
1// Push and pop (stack-like)
2const stack = [];
3console.log("Initial stack:", stack);
4
5stack.push(1); // push 1
6console.log("After push(1):", stack);
7
8stack.push(2); // push 2
9console.log("After push(2):", stack);
10
11const last = stack.pop(); // pop -> 2
12console.log("Popped value:", last);
13console.log("Stack after pop():", stack);
14
15// Unshift and shift (queue-like)
16const queue = [];
17console.log("Initial queue:", queue);
18
19queue.push('a'); // add to end
20console.log("After push('a'):", queue);
21
22queue.unshift('start'); // add to front
23console.log("After unshift('start'):", queue);
24
25const firstItem = queue.shift(); // remove from front
26console.log("Shifted value:", firstItem);
27console.log("Queue after shift():", queue);
28
29// Initial stack: []
30// After push(1): [ 1 ]
31// After push(2): [ 1, 2 ]
32// Popped value: 2
33// Stack after pop(): [ 1 ]
34
35// Initial queue: []
36// After push('a'): [ 'a' ]
37// After unshift('start'): [ 'start', 'a' ]
38// Shifted value: start
39// Queue after shift(): [ 'a' ]
pushوpopتعملان على نهاية المصفوفة۔ إنها مثالية لتنفيذ هياكل المكدسات۔unshiftوshiftتعملان على بداية المصفوفة۔ ومع ذلك، يجب الانتباه إلى أن العمل على البداية يتطلب تغيير جميع فهارس العناصر داخليًا، مما يجعله عملية مكلفة۔
معالجة العناصر في الوسط (splice وslice)
عند التعامل مع عناصر في وسط المصفوفة، اختر بين splice وslice حسب ما إذا كنت تريد تعديل المصفوفة الأصلية أم لا۔ إذا كنت تريد فقط استخراج جزء من المصفوفة، استخدم slice؛ أما إذا أردت تعديل المصفوفة نفسها مثل إدراج أو حذف عناصر، فاستخدم splice۔
1// slice (non-destructive)
2const nums = [0, 1, 2, 3, 4];
3console.log("Original nums:", nums);
4
5const part = nums.slice(1, 4); // returns [1, 2, 3]
6console.log("Result of nums.slice(1, 4):", part);
7console.log("nums after slice (unchanged):", nums);
8
9// splice (destructive)
10const arr = [10, 20, 30, 40];
11console.log("\nOriginal arr:", arr);
12
13// remove 1 item at index 2, insert 25 and 27
14arr.splice(2, 1, 25, 27);
15console.log("After arr.splice(2, 1, 25, 27):", arr);
16
17// Original nums: [ 0, 1, 2, 3, 4 ]
18// Result of nums.slice(1, 4): [ 1, 2, 3 ]
19// nums after slice (unchanged): [ 0, 1, 2, 3, 4 ]
20
21// Original arr: [ 10, 20, 30, 40 ]
22// After arr.splice(2, 1, 25, 27): [ 10, 20, 25, 27, 40 ]
- دالة
sliceتقوم فقط باستخراج العناصر ولا تعدل المصفوفة الأصلية۔ - دالة
spliceتضيف أو تزيل عناصر وتعدل المصفوفة نفسها، لذا يجب الانتباه بشكل خاص لتأثيرها على السلوك۔
التكرار (for / for...of / forEach)
هناك عدة طرق لمعالجة المصفوفات بالتسلسل، ويمكنك الاختيار حسب الغرض وأسلوب البرمجة المفضل لديك۔ فيما يلي ثلاثة تراكيب شائعة لحلقات التكرار۔
1const items = ['apple', 'banana', 'cherry'];
2console.log("Items:", items);
3
4// classic for
5console.log("\n--- Classic for loop ---");
6for (let i = 0; i < items.length; i++) {
7 console.log(`Index: ${i}, Value: ${items[i]}`);
8}
9
10// for...of (recommended for values)
11console.log("\n--- for...of loop ---");
12for (const item of items) {
13 console.log(`Value: ${item}`);
14}
15
16// forEach (functional style)
17console.log("\n--- forEach loop ---");
18items.forEach((item, index) => {
19 console.log(`Index: ${index}, Value: ${item}`);
20});- حَلْقَة التكرار
forهي الأكثر مرونة، حيث تسمح بالتحكم في الفهارس والتحكم الدقيق بالتكرار باستخدامbreakوعبارات أخرى۔ - حَلْقَة التكرار
for...ofتوفر طريقة مختصرة للتعامل مع قيم العناصر، وهي الأكثر توازنًا من حيث قابلية القراءة۔ - تسمح دالة
forEachبأسلوب البرمجة الدالية (الوظيفية)، وتناسب العمليات ذات التأثير الجانبي مثل تسجيل السجلات أو تحديث البيانات لكل عنصر۔ لكن يجب ملاحظة أنه لا يمكنك استخدامbreakأوcontinueمعها، وهي غير مناسبة لمعالجة غير متزامنة باستخدامawait۔
map / filter / reduce — دوال عالية المستوى
تُعتبر الدوال العليا مثل map و filter و reduce شائعة الاستخدام عند تحويل أو تصفية أو تجميع المصفوفات.۔ نظرًا لإمكانية التعبير بوضوح عن العمليات المتكررة، يصبح الكود الخاص بك بسيطًا وسهل الفهم.۔
1const numbers = [1, 2, 3, 4, 5];
2console.log("Original numbers:", numbers);
3
4// map: transform each item
5const doubled = numbers.map(n => n * 2);
6console.log("\nResult of map (n * 2):", doubled);
7
8// filter: select items
9const evens = numbers.filter(n => n % 2 === 0);
10console.log("Result of filter (even numbers):", evens);
11
12// reduce: accumulate to single value
13const sum = numbers.reduce((acc, n) => acc + n, 0);
14console.log("Result of reduce (sum):", sum);
15
16// Original numbers: [ 1, 2, 3, 4, 5 ]
17// Result of map (n * 2): [ 2, 4, 6, 8, 10 ]
18// Result of filter (even numbers): [ 2, 4 ]
19// Result of reduce (sum): 15
- تتيح لك هذه الطرق التركيز على ما تريد فعله بأسلوب تصريحي، مما يحسن من قابلية القراءة ويساعدك على تجنب الآثار الجانبية غير المرغوب بها۔
find / findIndex / some / every
فيما يلي نظرة عامة على طرق البحث والتحقق من الشروط۔ هذه الطرق مفيدة في العثور على عناصر تحقق شروطًا معينة أو لإجراء فحوصات منطقية على المجموعة۔
1const users = [
2 { id: 1, name: 'Alice' },
3 { id: 2, name: 'Bob' },
4 { id: 3, name: 'Carol' }
5];
6
7console.log("Users:", users);
8
9// Find the first user whose name is 'Bob'
10const bob = users.find(user => user.name === 'Bob');
11console.log("\nResult of find (name === 'Bob'):", bob);
12
13// Find index of the user whose id is 3
14const indexOfId3 = users.findIndex(user => user.id === 3);
15console.log("Result of findIndex (id === 3):", indexOfId3);
16
17// Check if there exists a user with id = 2
18const hasId2 = users.some(user => user.id === 2);
19console.log("Result of some (id === 2):", hasId2);
20
21// Check if all users have a numeric id
22const allHaveNumericId = users.every(user => typeof user.id === 'number');
23console.log("Result of every (id is number):", allHaveNumericId);
24// Result of find (name === 'Bob'): { id: 2, name: 'Bob' }
25// Result of findIndex (id === 3): 2
26// Result of some (id === 2): true
27// Result of every (id is number): true
findتُعيد أول عنصر يطابق الشرط۔findIndexتُعيد فهرس العنصر الذي يطابق الشرط۔someتُعيدtrueإذا كان هناك على الأقل عنصر واحد يحقق الشرط۔everyتُعيدtrueإذا كانت جميع العناصر تحقق الشرط۔
جميع هذه الطرق مفيدة جدًا في معالجة المصفوفات، لذا فإن استخدامها بالشكل المناسب سيجعل كودك مختصرًا وواضحًا۔
الترتيب ودوال المقارنة
يتم ترتيب المصفوفات باستخدام sort، لكن بشكل افتراضي تقارن العناصر كسلاسل نصية، ما قد يؤدي إلى نتائج غير متوقعة عند ترتيب الأرقام۔
1const nums = [10, 2, 33, 4];
2console.log("Original nums:", nums);
3
4// Default sort: compares elements as strings (not suitable for numbers)
5nums.sort();
6console.log("\nAfter default sort (string comparison):", nums);
7
8// Numeric ascending sort using a compare function
9nums.sort((a, b) => a - b);
10console.log("After numeric sort (a - b):", nums);
11
12// Sort objects by a property
13const people = [{ age: 30 }, { age: 20 }, { age: 25 }];
14console.log("\nOriginal people:", people);
15
16people.sort((a, b) => a.age - b.age);
17console.log("After sorting people by age:", people);
18
19// After default sort (string comparison): [ 10, 2, 33, 4 ]
20// After numeric sort (a - b): [ 2, 4, 10, 33 ]
21// Original people: [ { age: 30 }, { age: 20 }, { age: 25 } ]
22// After sorting people by age: [ { age: 20 }, { age: 25 }, { age: 30 } ]
- عند ترتيب الأرقام أو الكائنات، يجب دائمًا تحديد دالة مقارنة للحصول على الترتيب الصحيح۔
- في دالة المقارنة، فإن القيمة السالبة تضع
aقبلb، والقيمة الموجبة تضعbقبلa، والصفر يبقي ترتيبهما كما هو۔
نسخ المصفوفات والثباتية
عند نسخ المصفوفات، من المهم فهم الفرق بين 'النسخ بالإشارة' و'النسخ السطحي'.۔ وبالأخص، إذا كان هناك كائنات داخل المصفوفة، فإن النسخ السطحي سيؤدي إلى مشاركة الكائنات الداخلية بين النسختين۔
النسخ بالإشارة
عندما تقوم بإسناد مصفوفة إلى متغير آخر، لا يتم تكرار المصفوفة نفسها؛ بل يتم نسخ 'الإشارة' التي تشير إلى نفس المصفوفة.۔
1const a = [1, 2, 3];
2console.log("Original a:", a);
3
4// Reference copy; modifying b also affects a
5const b = a;
6console.log("\nReference copy b = a:", b);
7// Reference copy b = a: [ 1, 2, 3 ]
8
9b[0] = 100;
10console.log("After modifying b[0] = 100:");
11console.log("a:", a); // a: [ 100, 2, 3 ] (affected)
12console.log("b:", b); // b: [ 100, 2, 3 ]
- عند النسخ بالإشارة، إذا قمت بتعديل محتويات المصفوفة باستخدام المتغير المنسوخ، فسيتم أيضًا عكس تلك التغييرات على المتغير الأصلي الذي يشير إلى نفس المصفوفة.۔
النسخ السطحي
عند استخدام slice() أو طريقة النشر (spread syntax)، يتم إنشاء 'نسخة سطحية'، حيث يتم تكرار القيم فقط وتُعامل المصفوفة الأصلية والمنسوخة كوحدتين منفصلتين.۔
1const a = [1, 2, 3];
2console.log("Original a:", a);
3
4// Shallow copy (new array)
5const b = a.slice();
6console.log("\nShallow copy using slice():", b);
7// Shallow copy using slice(): [ 100, 2, 3 ]
8
9const c = [...a];
10console.log("Shallow copy using spread [...a]:", c);
11// Shallow copy using spread [...a]: [ 100, 2, 3 ]
12
13// Modifying c or d does NOT affect a
14b[1] = 200;
15c[2] = 300;
16console.log("\nAfter modifying b[1] = 200 and c[2] = 300:");
17console.log("a:", a); // [ 100, 2, 3 ]
18console.log("b:", b); // [ 100, 200, 3 ]
19console.log("c:", c); // [ 100, 2, 300 ]
- يوضح هذا الكود أن النسخة السطحية لمصفوفة تم إنشاؤها بواسطة
slice()أو طريقة النشر لا تؤثر على المصفوفة الأصلية.۔
النسخ السطحي وعدم القابلية للتغيير
حتى إذا قمت بنسخ المصفوفة باستخدام 'نسخة سطحية'، قد يحدث مشاركة غير مقصودة إذا كانت المصفوفة تحتوي على كائنات بداخلها.۔
1// Shallow copy doesn't clone inner objects
2const nested = [{ x: 1 }, { x: 2 }];
3console.log("\nOriginal nested:", nested);
4// Original nested: [ { x: 1 }, { x: 2 } ]
5
6const shallow = nested.slice();
7console.log("Shallow copy of nested:", shallow);
8// Shallow copy of nested: [ { x: 1 }, { x: 2 } ]
9
10// Changing inner object affects both arrays
11shallow[0].x = 99;
12console.log("\nAfter shallow[0].x = 99:");
13console.log("nested:", nested);
14console.log("shallow:", shallow);
15// nested: [ { x: 99 }, { x: 2 } ]
16// shallow: [ { x: 99 }, { x: 2 } ]
- يوضح هذا الكود أنه عند النسخ السطحي تتم مشاركة الكائنات الداخلية، وبالتالي فإن تعديل هذه الكائنات يؤثر على كل من المصفوفة الأصلية والنسخة.۔
- إذا كنت بحاجة إلى بيانات مستقلة تمامًا، يجب عليك إجراء 'نسخ عميق' مثل استخدام
structuredClone()أو التحويل إلى JSON۔
طرق مساعدة مفيدة
فيما يلي طرق مساعدة تُستخدم بشكل متكرر عند العمل مع المصفوفات۔ من خلال استخدامها بالشكل المناسب لهدفك، يمكنك كتابة كود قصير وسهل القراءة۔
includes
تتحقق طريقة includes مما إذا كانت قيمة معينة موجودة في المصفوفة.۔
1// includes: check if array contains a value
2const letters = ['a', 'b', 'c'];
3const hasB = letters.includes('b');
4console.log("letters:", letters); // [ 'a', 'b', 'c' ]
5console.log("letters.includes('b'):", hasB); // true
- في هذا الكود، يتم استخدام طريقة
includesلتحديد ما إذا كانت قيمة محددة موجودة في المصفوفة بشكل موجز.۔
concat
تعيد طريقة concat مصفوفة جديدة تُضيف المصفوفة أو القيم المحددة إلى النهاية، مع الحفاظ على المصفوفة الأصلية دون تغيير.۔
1// concat: merge arrays without mutation
2const a1 = [1, 2];
3const a2 = [3, 4];
4const combined = a1.concat(a2);
5console.log("a1.concat(a2):", combined); // [ 1, 2, 3, 4 ]
6console.log("a1(unchanged):", a1); // [ 1, 2 ]
- يوضح هذا الكود أن
concatغير مدمرة، حيث يمكنك إنشاء مصفوفة جديدة مع الحفاظ على الأصلية.۔
flat
باستخدام طريقة flat، يمكنك تسطيح المصفوفات المتداخلة.۔
1// flat and flatMap: flatten arrays or map + flatten in one step
2const nested = [1, [2, [3]]];
3console.log("nested:", nested); // nested: [ 1, [ 2, [ 3 ] ] ]
4console.log("nested.flat():", nested.flat()); // default depth = 1
5// nested.flat(): [ 1, 2, [ 3 ] ]
- يوضح هذا الكود نتيجة تسطيح المصفوفة بمستوى واحد.۔
- نظرًا لأن
flatيسمح لك بتحديد العمق، يمكنك معالجة التداخلات بمرونة حسب الحاجة.۔
flatMap
تطبق طريقة flatMap تحويلًا على كل عنصر ثم تقوم بتسطيح النتائج تلقائيًا في مصفوفة أحادية البعد.۔
1// flat and flatMap: flatten arrays or map + flatten in one step
2const words = ['hello world', 'hi'];
3console.log("words:", words); // [ 'hello world', 'hi' ]
4
5const splitWords = words.flatMap(w => w.split(' '));
6console.log("words.flatMap(w => w.split(' ')):", splitWords);
7// words.flatMap(w => w.split(' ')): [ 'hello', 'world', 'hi' ]
- يوضح هذا الكود مثالاً حيث يتم تقسيم كل سلسلة نصية في المصفوفة بواسطة الفراغات، وتُجمع النتائج وتُسطح في مصفوفة واحدة.۔
join
تنشئ طريقة join سلسلة نصية عن طريق ضم عناصر المصفوفة باستخدام فاصل محدد.۔
1// join: combine elements into a string with a separator
2const csv = ['a', 'b', 'c'].join(',');
3console.log("['a', 'b', 'c'].join(','):", csv); // a,b,c
- في هذا الكود، يتم استخدام طريقة
joinلتحويل مصفوفة إلى سلسلة نصية مفصولة بفواصل.۔
الأخطاء الشائعة
قد تبدو عمليات المصفوفة بسيطة للوهلة الأولى، لكن هناك عدة نقاط قد تؤدي بسهولة إلى تصرفات غير مقصودة۔ هناك العديد من الأخطاء التي يسهل إغفالها أثناء العمل اليومي مع المصفوفات، لذا فإن مراعاة النقاط التالية سيرفع من موثوقية الكود بشكل كبير۔
- دالة
sort()في كائن الـArrayتقوم بالمقارنة كسلسلة نصية افتراضيًا۔ عند ترتيب الأرقام بشكل صحيح، يجب عليك دائمًا تزويد دالة مقارنة۔ - نسخ المصفوفات (عن طريق
sliceأو بناء النشر، إلخ) ينشئ نسخًا سطحيًا۔ إذا احتوت مصفوفاتك على كائنات، يجب الانتباه لأن البيانات الأصلية قد تتغير دون قصد۔ spliceهي دالة مدمّرة تغير المصفوفة مباشرة، بينماsliceهي دالة غير مدمّرة لا تعدل المصفوفة الأصلية۔ من المهم استخدامها بالشكل المناسب لاحتياجاتك۔forEachغير مناسبة لحلقات التكرار التي تتضمن معالجة غير متزامنة باستخدامawait۔ إذا كنت تريد تنفيذ المعالجة غير المتزامنة بشكل مضمون ومرتب، يُنصح باستخدامfor...of۔
مثال عملي
أدناه مثال يجمع بين طرق المصفوفة من أجل 'الحصول على مجموع الأعمار من بيانات المستخدم، واستخراج من هم أكبر أو يساوون 30 عامًا، وإنشاء قائمة بالأسماء.'۔
1const users = [
2 { name: 'Alice', age: 28 },
3 { name: 'Bob', age: 34 },
4 { name: 'Carol', age: 41 },
5 { name: 'Dave', age: 19 }
6];
7
8console.log("Users:", users);
9
10// sum ages
11const totalAge = users.reduce((acc, u) => acc + u.age, 0);
12console.log("\nTotal age of all users:", totalAge);
13// Total age of all users: 122
14
15// filter and map names of users 30 and older
16const namesOver30 = users
17 .filter(u => u.age >= 30)
18 .map(u => u.name);
19
20console.log("Users aged 30 or older (names):", namesOver30);
21// Users aged 30 or older (names): [ 'Bob', 'Carol' ]
- عن طريق ربط
reduceوfilterوmap، يمكنك كتابة عمليات التجميع والاستخلاص والتحويل بكل بساطة۔ - مثل هذا 'خط معالجة البيانات' يكون عالي القابلية للقراءة، وغالبًا ما يُستخدم كأسلوب قليل التأثيرات الجانبية في التطبيقات العملية۔
الملخص
مع المصفوفات في جافاسكريبت، حتى العمليات الأساسية قابلة للتطبيق بشكل واسع، وباستخدام الدوال عالية المستوى يصبح كودك أكثر إيجازًا وتعبيرًا۔ هناك الكثير من النقاط لفهمها، ولكن بمجرد إتقان كيفية استخدام كل واحدة بالشكل المناسب، ستصبح معالجة البيانات أكثر سلاسة بكثير۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔