لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 08/04/22 في كل الموقع
-
<?php session_start(); include'inc/con.php'; $username=$_SESSION['name']; $sql="select username from users where username='$username'"; $ret=mysqli_query($connection,$sql); while($res=mysqli_fetch_assoc($ret)){ $fullname=$res['fullname']; } ?> <!DOCTYPE html> <html> <head> <title>موقع شخصي</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="css/bootstrap.css" /> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" /> <link rel="stylesheet" type="text/css" href="css/" /> </head> <style> body{ background-color: #f0f0f0f0; } .col-lg-4 img { width: 90%; height: 30%; } .col-lg-4 { margin-top: 11px; background-color: #f0f0f0; height: 600px; } .thunbnil{ background-color:#FFFFFF; width: 98%; margin:10px; height: 98%; } footer{ width: 100%; height: 350px; background-color:#100F0F; } label{ color: #ffffff; } .black{ color:#000000; } </style> <body dir="rtl" > <nav class="navbar navbar-expand-lg bg-dark"> <div class="container-fluid bg-dark"> <a class="navbar-brand" style="color: white;" href="#"> متجر الاجهزه الاكترونيه</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link active" style="color: white;" aria-current="page" href="#">الرئيسيه</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" style="color: white;" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> انواع الاجهزه </a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="#">سوني</a></li> <li><a class="dropdown-item" href="#">سامسوج</a></li> <li><a class="dropdown-item" href="#">هواوي</a></li> <li> <hr class="dropdown-divider"> </li> <li><a class="dropdown-item" href="#">جميع الاجهزه</a></li> </ul> </li> <li class="nav-item"> <a class="nav-link disabled" style="color: white;">اتصل بنا</a> </li> </ul> <form class="d-flex" role="search"> <input class="form-control me-2" type="search" placeholder="اكتب اسم الجهاز" aria-label="Search"> <div style="margin-right: 5px;"> <button class="btn btn-outline-success" type="submit">البحث</button> </div> </form> </div> </div> </nav> <div class="container"> <div class="row"> <!----> <div class="col-lg-12 col-md-12"> <center><p>مرحبا <?php echo $fullname; ?></p></center> </div> </div> </div> <br> <footer class="footer" > <div class="col-lg-6 col-ms-6"> <br> <form> <div class="form-group"> <label for="exampleInputEmail1" >البريد الاكتروني</label> <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email"> <small id="emailHelp" class="form-text text-muted " >لن نشارك معلوماتك. </small> </div> <div class="form-group"> <label for="exampleInputPassword1">كلمة المرور</label> <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password"> </div> <div class="form-group form-check"> <input type="checkbox" class="form-check-input" id="exampleCheck1"> <label class="form-check-label" for="exampleCheck1">Check me out</label> </div> <button type="submit" class="btn btn-primary">ادخل </button> </form> </div> <br> <div class="col-lg-6 col-ms-6"> <iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d477499.4240782038!2d34.11789950630533!3d28.052289469435703!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14533bca3624d2e3%3A0xdd987e9c1945fd9c!2z2LTYsdmFINin2YTYtNmK2K7YjCDZgtiz2YUg2LTYsdmFINin2YTYtNmK2K7YjCDYrNmG2YjYqCDYs9mK2YbYp9ih2Iwg2YXYtdix!5e0!3m2!1sar!2ssa!4v1658691742415!5m2!1sar!2ssa" width="500" height="300" style="border:0;border:0;margin-right: 850px; margin-top: -250px;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe> </div> </footer> <!-- <script src="js/test.min.js"></script>--> <script src="js/bootstrap.bundle.js"></script> <!-- --> <!-- <script src="js/test.min.js"></script>--> <!-- <script src="js/bootstrap.bundle.js"></script>--> </body> </html>2 نقاط
-
myBrithday = datetime.datetime(2000,4,4) dateNow=datetime.datetime.now() print(myBrithday.day)1 نقطة
-
لدي العديد من الملفات التي تحتوي على توابع واريد استدعائها لكن المشكلة انني قمت باستخدام اسماء طويلة للملفات. **هل يمكن اختصار اسم الملف عند الاستخدام دون تغيره في الملف الاصلي ؟ import information_personal information_personal.Your_age(50) information_personal.Your_age(30) information_personal.Your_age(32) information_personal.Name("Zeina") information_personal.Name("Mary") information_personal.Name("elias")1 نقطة
-
يدعى الاسم المختصر للوحدة المستدعاة ب alias ويمكننا من إعادة تسمية الوحدة ضمن الملف الحالي نستخدم الكلمة المفتاحية as ثم الاسم المستعار import information_personal as inf_p1 inf_p1.Your_age(50) inf_p1.Your_age(30) inf_p1.Your_age(32) inf_p1.Name("Zeina") inf_p1.Name("Mary") inf_p1.Name("elias") أرجو مراجعة درس الوحدات1 نقطة
-
إن تضمين المكتبات وأول عملية طباعة لايوجد بها مشكلة، أي المشكلة في آخر سطر، حيث قمت بتمرير معاملين للدالة random وهي لا تستقبل وسطاء. إن الدالة random بنفسها تقوم بتشكيل قيمة عشوائية بين 0-1 ومن نمط float الدالة randint هي التي تقبل مجال من الأعداد start-end وتعيد قيمة عدد صحيح من ضمن المجال >>> type(random()) <class 'float'> >>> type(randint(1,3)) <class 'int'> >>> randint(1,3) 2 أي عليك تغيير الدالة المستدعاة الدرس التالي فيه مثال عن random:1 نقطة
-
لدي جدولين مع علاقة one to many. الجدول الأول يسمى "categories" والآخر هو "users". يمكن أن يكون لكل تصنيف categoryواحد العديد من المستخدمين. لذلك إذا أردت الحصول على جميع التصنيفات categories مع جميع المستخدمين ، فسأفعل هذا فقط: category::with("users")->get(); لكني أريد أيضًا معرفة عدد المستخدمين لكل تصنيف، وحاولت القيام بذلك من خلال إستخدام التابع count مع الكود السابق: Location::with("users")->count("users")->get(); لكن هذا الأمر لم ينجح. كيف يمكنني معرفة المستخدمين لكل تصنيف في لارافيل Laravel؟1 نقطة
-
هل توجد طريقة للوصول إلى متغيرات البيئة الموجودة في الملف env. داخل كود JavaScript في مشروع لارافيل Laravel؟ فكرت في أكثر من طريقة للقيام بهذا الأمر بالفعل لكن لم أتمكن من النجاح في حال هذه المشكلة. كيف استخدم متغيرات البيئة في كود JavaScript في مشروع لارافيل Laravel مع العلم أن كود JavaScript في ملف منفصل ضمن المجلد resources1 نقطة
-
عندما أقوم بعمل إستعلام لجلب بيانات منشور ما يتم جلب المعلومات بالشكل التالي: { id: 1 title: "Post 1" body: "Some text content" // ... } ولكن أريد أن يتم جلب بيانات المستخدم (كاتب المنشور) كذلك بالشكل التالي: { id: 1 title: "Post 1" body: "Some text content" // ... user:{ id: 1, name: "Emad Saif", email: "emad.saif@gmail.com" // ... } } كيف يمكنني جعل هذه العملية تتم بشكل تلقائي ويتم جلب معلومات الكاتب في كل مرة أطلب منشور معين من قاعدة البيانات؟1 نقطة
-
أريد أن أقوم بتوليد قالب Blade صغير وبسيط، لدي نص string بالفعل: "<h2>Hello, {{ $name }}</h2>" هل يمكن تحويل النص السابق إلى قالب Blade وتغير قيمة name$ إلى قيمة مدخله مباشرة داخل المتحكم controller بدلًا من عمل ملف منفصل وإستدعائه؟ إصدار لارافيل في المشروع هو 9.01 نقطة
-
يمكن الاستعانة بالواجهة Blade والاستفادة من التابع render ضمنها لترجمة نص من صيغة blade، ويمكن تمرير النص كمعامل أول ثم مصفوفة المتغيرات المستخدمة ضمن النص كمعامل ثاني، ليصبح المثال لديك كالتالي: use Illuminate\Support\Facades\Blade; echo Blade::render('<h2>Hello, {{ $name }}</h2>', ['name' => 'Emad Saif']); // <h2>Hello, Emad Saif</h2>1 نقطة
-
أريد أن أقوم بكتابة log وتنفيذ بعض الأكواد قبل أن يتم حفظ أي منشور جديد Post في قاعدة البيانات، حاولت البحث عن دوال مثل beforeSave أو afterSave في لارافيل Laravel ولكن لم أجد شيء. class Post extends Model { public static function before_save() { // ... } } هل توجد طريقة لتنفيذ كود معين قبل أن يتم حفظ منشور جديد في لارافيل Laravel؟1 نقطة
-
يمكنك الاستعانة بمفهوم المراقبة او المراقبين Observers لتحقيق هذا الغرض. وهي مثل عمليات مراقبة الاحداث التي تختص بنموذج ما. تحتوي أصناف Observer في لارافيل على توابع يمثل كل منها حدثا من أحداث Eloquent التي ترغب في الاستماع إليها. <?php namespace App\Observers; use App\Models\User; class UserObserver { /** * Handle the User "created" event. * * @param \App\Models\User $user * @return void */ public function created(User $user) { // } /** * Handle the User "updated" event. * * @param \App\Models\User $user * @return void */ public function updated(User $user) { // } /** * Handle the User "deleted" event. * * @param \App\Models\User $user * @return void */ public function deleted(User $user) { // } /** * Handle the User "restored" event. * * @param \App\Models\User $user * @return void */ public function restored(User $user) { // } /** * Handle the User "forceDeleted" event. * * @param \App\Models\User $user * @return void */ public function forceDeleted(User $user) { // } } لاحظ ان كل تابع يستقبل النموذج المستهدف. سيمكنك معاملته وفق اي منطق وتطبيق اي ما تريد عليه قبل حفظه الى قاعدة البيانات بشكل تام. نستعمل الأمر: php artisan make:observer UserObserver --model=User لإنشاء مراقب جديد. ونقوم بتسجيله في توابع boot لأحد موفرات الخدمة، يقترح ان يكون موفر خدمة التطبيق AppServiceProvider: public function boot() { User::observe(UserObserver::class); }1 نقطة
-
لدي مجلد mainModels في المجلد app في مشروع مبني بإستخدام لارافيل Laravel، وأريد أن أقوم بتوليد نموذج model بإستخدام php artisan في هذا المجلد مباشرة، بدلًا من توليد نموذج في المجلد models ثم نقله والتعديل عليه يدويًا. كيف يمكن القيام بهذا الأمر؟ وهل يمكن توليد نموذج model خارج المجلد app بالكامل؟1 نقطة
-
ازاي اجيب خاصيف في كائن Object في mongodb مثلا { _id: ObjectId('62eb860a88bcb5fc1ca6b850'), name: 'tea', creator: 'mohamed', validation: 2, src: "https://images.unsplash.com/photo-1594631252845-29fc4cc8cde9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80", __v: 0 } ازاي اجيب ال src1 نقطة
-
1 نقطة
-
@Mohamed Montaser3 يمكنك فعل ذلك باستخدام ال query التالية : db.products.find({}, {src:1, _id:0}) وهذه الـ query ستحضر الـ src فقط بدون الـ id، بينما التالية ستحضر الـ src والـ id فقط db.products.find({}, {src:1}) وتستطيع كتابتها في الـ mongodbCompass كما في الصور : إذا تريدها العمود المعين مع الـ id : إذا كنت تريده من دون الـ id :1 نقطة
-
1 نقطة
-
هذه mongodb يا أخي، لم افهم ما تقصده. هل تريد كويري تقوم بإحضار الـ src فقط ؟1 نقطة
-
تكتب اسمه ثم "." ثم اسم الخاصية. const myobject = { _id: ObjectId('62eb860a88bcb5fc1ca6b850'), name: 'tea', creator: 'mohamed', validation: 2, src: "https://images.unsplash.com/photo-1594631252845-29fc4cc8cde9?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80", __v: 0 }; console.log(myobject.src) في حال لم يحل ذلك المشكلة، قم بإيضاح سؤالك اكثر .1 نقطة
-
رسالة الخطأ تُخبرك أنه لا يوجد الفهرس fullname في المصفوفة res، لقد حددت في الإستعلام أن يجلب لك فقط username لاحظ بعد Select ماذا كتبت! مثلا هذه الأخطاء نقوم بتتبعها بطباعة النتائج إستخدم الدالة print_r او الدالة var_dump لطباعة الناتج res$ قبل كل شيء و ستتمكن من تجاوز المشكلة.1 نقطة
-
حاول طباعة كل من: قبل الحلقة لمرة واحدة print(UserSimplePreferences.getNotficationAppID()); ثم ضمن الحلقة: print(responsebody[i]['id']); على كل حال، لم أفهم الفائدة من الحلقة، سوف تختبر أكثر من id من response وفي حال كان نفسه تعمل break وإن كان غيره تعمل على تعديله؟ وماذا يحوي responsebody هل يمكنك طباعته بشكل منسق لفهم بنيته؟ وما هي برمجة الدالة getNotficationAppID؟ ربما الاختلاف أن هنالك نمط مختلف بين البيانات (نصية و رقمية) و عملية المقارنة لا تتم بشكل صحيح. responsebody[i]['id'].toString() == UserSimplePreferences.getNotficationAppID() ^^^^^^^^^^^^1 نقطة
-
لمعرفة أين يقع الخطأ فى جزء ال orderBy علينا معرفة كيف يقوم laravel بتحويل هذا الكود الى امر sql. إن laravel يقوم بتنفيذ امرين , للحصول اولا على UserProducts ثم بعد ذلك يقوم بتنفيذ امر اخر للحصول على ال products . لنرى كيف يقوم بتنفيذ ذلك SELECT * FROM `user_products` WHERE `user_id` = 1; SELECT * FROM `products` WHERE `products`.`id` in (1) هنا اولا يقوم بتنفيذ السطر الاول للحصول على userProducts ثم بعد ذلك يأخذ ال id الخاص بال product ثم يقوم بتمريرها للسطر الثانى . ولذلك فإن orderBy ستكون خطأ لانه سيقوم بترتيب السطر الاول عن طريق products.type اللتى لا توجد فى هذه العملية . ولحل تلك المشكلة هناك اكثر من حل وقد اخترت لك اسهل طريقتين . إن كنت تريد ان تقوم بترتيب النتيجة دائما ب product.type فالحل الافضل ان تقوم بالترتيب فى ملف ال model public function products() { return $this->hasMany(Product::class)->orderBy('type'); } وبذلك ستتاكد ان فى حالة استدعاء ال products ستكون مرتبة ب ال type . والطريقة الثانية عن طريق الدمج join UserProducts::join('products',function($join) { $join->on('products.id', '=', 'user_product.product_id') ->where('user_product.user_id', '=', $user->id) ->where('user_product.count', '>', 0); })->orderBy('products.type')->get(); والافضل هنا ان تقوم باستخدام select لتحديد الاعمده التى تريدها حتى لا يحدث خطأ إن كان هناك اعمده لها نفس الاسم فى الجدولين1 نقطة
-
يمكن تنفيذ تعليمات خلال مراحل معينة من دورة حياة النموذج Model، يمكنك تسجيلها ضمن التابع booted ضمن صنف النموذج، التوابع التي ستستخدمها لحالتك هي: saving يتم تنفيذه قبل حفظ النموذج saved يتم تنفيذه بعد حفظ النموذج ويمكنك داخلها الوصول لكائن النموذج الذي تتم عملية الحفظ عليه من خلال المعامل post$ الممرر للتابع الذي تسجله لكل حدث من تلك الأحداث بالشكل التالي: class Post extends Model { protected static function booted() { static::saving(function ($post) { // قبل الحفظ }); static::saved(function ($post) { // بعد الحفظ }); } }1 نقطة
-
ابدأ بكتاب الإكسير فهو يعتبر أفضل مرجع عربي للغة ++C ، حيث أن من يقرؤه سيصبح له أساس ممتاز في لغة ++C ويستطيع أن يعتمد على هذا الأساس ليصبح مبرمج محترف في لغة السي بلس بلس ، حيث بدأ مؤلف هذا الكتاب بأكود بسيطة لسهولة تعلمها بالنسبة للمبتدئ . الكتاب عملي أكثر من كونه نظري فهذا هو الجانب المشرق في الكتاب حيث في بدايته يجعلك تكتب كود ويشرح لك تفاصيله بكل صراحة الكتاب يفوق كل التوقعات مقارنة بالجهود العربية في هذا المجال. مؤلف هذا الكتاب هو المهندس سلطان محمد الثبيتي، ويبدأ بالحديث عن أساسيات لغة ال++c والتي لابد من معرفتها قبل البدء، ثم ينتقل للحديث عن بنى التحكم والمتغيرات وحالات if ثم يتناول المصفوفات والسلاسل، والمؤشرات، والتوابع . ويتناول كذلك البرمجة الكائنية وصناعة أنواع البيانات بأشكالها المختلفة والقوائم المترابطة والتعامل مع الاستثناءات، والملفات، ويستعرض لنا مكتبة القوالب القياسية، والخوارزميات. وهذا هو الكتاب الإكسير.pdf1 نقطة
-
تقدّم المصفوفات توابِع عديدة تُسهِّل التعامل معها. ولتبسيطها سنقسّمها إلى مجموعات بحسب الوظيفة في هذا الفصل ونشرح كل منها على حدة. إضافة العناصر وإزالتها عرفنا من الفصل الماضي بالتوابِع التي تُضيف العناصر وتُزيلها من بداية أو نهاية المصفوفة: arr.push(...items) -- يُضيف العناصر إلى النهاية، arr.pop() -- يستخرج عنصرًا من النهاية، arr.shift() -- يستخرج عنصرًا من البداية، arr.unshift(...items) -- يُضيف العناصر إلى البداية. وهذه أخرى غيرها. الوصل splice يا ترى كيف نحذف أحد عناصر المصفوفة؟ المصفوفات كائنات، يمكننا تجربة delete وربما تنجح: let arr = ["I", "go", "home"]; delete arr[1]; // أزِل "go" alert( arr[1] ); // undefined // صارت المصفوفة الآن arr = ["I", , "home"]; alert( arr.length ); // 3 أُزيل العنصر صحيح، ولكنّ ما زال في المصفوفة ثلاثة عناصر، كما نرى في arr.length == 3. هذا طبيعي، إذ يُزيل delete obj.key القيمة بمفتاحها key… وهذا فقط. ينفع للكائنات ربّما، لكنّا نريدها للمصفوفات أن تنتقل كل العناصر على اليمين وتأخذ الفراغ الجديد. أي أننا نتوقع أن تصغر المصفوفة الآن. لهذا السبب علينا استعمال توابِع خاصّة لذلك. يمكننا تشبيه التابِع arr.splice(start) بالتابِع «بتاع كُلّو» للمصفوفات (كما يُقال بالعامية). يمكنه أن يُجري ما تريد للعناصر: إدراج، إزالة، استبدال. هذه صياغته: arr.splice(index[, deleteCount, elem1, ..., elemN]) يبدأ التابِع من عند العنصر ذي الفهرس index، فيُزيل deleteCount من العناصر ويُدرج العناصر elem1, ..., elemN المُمرّرة إليه مكانها. أخيرًا يُعيد المصفوفة بالعناصر المُزالة. فهم هذا التابِع بالأمثلة أبسط. فلنبدأ أولًا بالحذف: let arr = ["I", "study", "JavaScript"]; // أزِل من العنصر ذا الفهرس 1 عنصرًا واحدًا (1) arr.splice(1, 1); alert( arr ); // ["I", "JavaScript"] رأيت؟ سهلة. نبدأ من العنصر ذي الفهرس 1 ونُزيل عنصرًا واحدًا (1). الآن، نُزيل ثلاثة عناصر ونستبدلها بعنصرين آخرين: let arr = ["I", "study", "JavaScript", "right", "now"]; // أزِل الثلاث عناصر الأولى وعوّضها بتلك الأخرى arr.splice(0, 3, "Let's", "dance"); alert( arr ) // ["Let's", "dance", "right", "now"] أمّا هنا فكيف يُعيد splice مصفوفةً بالعناصر المُزالة. let arr = ["I", "study", "JavaScript", "right", "now"]; // أزِل أوّل عنصرين let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- قائمة بالعناصر المُزالة يمكن أن يُدرج تابِع splice العناصر دون إزالة أيّ شيء أيضًا. كيف؟ نضع deleteCount يساوي الصفر 0: let arr = ["I", "study", "JavaScript"]; arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript" الفهارس السالبة ممكنة أيضًا يمكننا هنا وفي توابِع المصفوفات الأخرى استعمال الفهارس السالبة. وظيفتها تحديد المكان بدءًا من نهاية المصفوفة، هكذا: let arr = [1, 2, 5]; arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 القطع slice التابِع arr.slice أبسط بكثير من شبيهه arr.splice. صياغته هي: arr.slice([start], [end]) وهو يُعيد مصفوفة جديدةً بنسخ العناصر من الفهرس start إلى end (باستثناء end). يمكن أن تكون start وحتّى end سالبتان، بهذا يُعدّ المحرّك القيمتان أماكن بدءًا من نهاية المصفوفة. هذا التابِع يشبه تابِع السلاسل النصية str.slice، ولكن بدل السلاسل النصية الفرعية، يُعيد المصفوفات الفرعية. إليك المثال الآتي: let arr = ["t", "e", "s", "t"]; // (نسخة تبدأ من 1 وتنتهي عند 3) alert( arr.slice(1, 3) ); // e,s // (نسخة تبدأ من -2 وتنتهي في النهاية) alert( arr.slice(-2) ); // s,t يمكننا أيضًا استدعائها بلا وُسطاء: يُنشئ arr.slice() نسخة عن arr. نستعمل هذا غالبًا لأخذ نسخة وإجراء تعديلات عليها دون تعديل المصفوفة الأصلية، وتركها كما هي. الربط concat يُنشئ التابِع arr.concat مصفوفةً جديدة فيها القيم الموجودة في المصفوفات والعناصر الأخرى. صياغته هي: arr.concat(arg1, arg2...) وهو يقبل أيّ عدد من الوُسطاء، أكانت مصفوفات أو قيم. أمّا ناتجه هو مصفوفة جديدة تحوي العناصر من arr، ثم arg1 فَـ arg2 وهكذا دواليك. لو كان الوسيط argN نفسه مصفوفة، فستُنسخ كل عناصره، وإلّا فسيُنسخ الوسيط نفسه. لاحِظ هذا المثال: let arr = [1, 2]; // اصنع مصفوفة فيها العنصرين: arr و [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 // اصنع مصفوفة فيها العناصر: arr و[3,4] و[5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // اصنع مصفوفة فيها العنصرين: arr و[3,4]، بعدها أضِف القيمتين 5 و 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 عادةً تنسخ المصفوفة عناصر المصفوفات الأخرى. بينما الكائنات الأخرى (حتّى لو كانت مثل المصفوفات) فستُضاف كتلة كاملة. let arr = [1, 2]; let arrayLike = { 0: "something", length: 1 }; alert( arr.concat(arrayLike) ); // 1,2,[object Object] … ولكن لو كان للكائن الشبيه بالمصفوفات خاصية Symbol.isConcatSpreadable، فستتعامل معه concat مثلما تتعامل مع المصفوفات: ستُضاف عناصره بدل كيانه: let arr = [1, 2]; let arrayLike = { 0: "something", 1: "else", [Symbol.isConcatSpreadable]: true, length: 2 }; alert( arr.concat(arrayLike) ); // 1,2,something,else التكرار: لكلّ forEach يتيح لنا التابِع arr.forEach تشغيل إحدى الدوال على كلّ عنصر من عناصر المصفوفة. الصياغة: arr.forEach(function(item, index, array) { // ... استعملهما فيما تريد }); مثال على عرض كلّ عنصر من عناصر المصفوفة: // لكلّ عنصر، استدعِ دالة التنبيه alert ["Bilbo", "Gandalf", "Nazgul"].forEach(alert); بينما هذه الشيفرة تحبّ الكلام الزائد ومكانها في المصفوفة المحدّدة: ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { alert(`${item} is at index ${index} in ${array}`); }); ناتج التابِع (لو أعادَ شيئًا أصلًا) يُهمل ويُرمى. البحث في المصفوفات أمّا الآن لنرى التوابع التي تبحث في المصفوفة. التوابِع indexOf و lastIndexOf و includes للتوابِع arr.indexOf و arr.lastIndexOf و arr.includes نفس الصياغة ووظيفتها هي ذات وظيفة تلك بنسخة النصوص النصية، الفرق أنها هنا تتعامل مع العناصر بدل المحارف: arr.indexOf(item, from) -- يبحث عن العنصر item بدءًا من الفهرس from، ويُعيد فهرسه حيث وجده. ولو لم يجده، يُعيد -1. arr.lastIndexOf(item, from) -- نفسه، ولكن البحث يبدأ من اليمين وينتهي في اليسار. arr.includes(item, from) -- يبحث عن العنصر item بدءًا من الفهرس from، ويُعيد true إن وجدته. مثال: let arr = [1, 0, false]; alert( arr.indexOf(0) ); // 1 alert( arr.indexOf(false) ); // 2 alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true لاحظ أنّ التوابِع تستعمل الموازنة بِـ ===. لذا لو كنّا نبحث عن false، فستبحث هي عن false نفسها وليس الصفر. لو أردت معرفة فيما كانت تحتوي المصفوفة على عنصر معيّن، ولا تريد معرفة فهرسه، فدالة arr.includes مناسبة لك. وهناك أيضًا أمر، تختلف includes عن سابقاتها indexOf/lastIndexOf بأنّها تتعامل مع NaN كما ينبغي: const arr = [NaN]; alert( arr.indexOf(NaN) ); // يُعيد -1 (الصحيح هو 0 إلّا أنّ الموازنة === لا تعمل مع NaN) alert( arr.includes(NaN) );// true (الآن صحيح) البحث عبر find و findIndex لنقل أنّ لدينا مصفوفة من الكائنات، كيف نجد الكائن حسب شرط معيّن؟ هنا يمكننا استغلال التابِع arr.find(fn). صياغته هي: let result = arr.find(function(item, index, array) { // لو أُعيدت القيمة true، فيُعاد العنصر ويتوقّف التعداد // لو لم نجد ما نريد نُعيد undefined }); تُستدعى الدالة على كل عنصر من عناصر المصفوفة، واحدًا بعد الآخر: item: العنصر. index: الفهرس. array: المصفوفة نفسها. لو أعادت true، يتوقّف البحث ويُعاد العنصر item. إن لم يوجد شيء فيُعاد undefined. نرى في هذا المثال مصفوفة من المستخدمين، لكلّ مستخدم حقلان id وname. نريد الذي يتوافق مع الشرط id == 1: let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; let user = users.find(item => item.id == 1); alert(user.name); // John في الحياة العملية، يكثُر استعمال الكائنات في المصفوفات، ولهذا فالتابِع find مفيد جدًا لنا. يمكنك ملاحظة بأنّا في المثال مرّرنا للتابِع find الدالة item => item.id == 1 وفيها وسيط واحد. هذا طبيعي فنادرًا ما نستعمل الوُسطاء البقية في هذه الدالة. يتشابه التابِع arr.findIndex كثيرًا مع هذا، عدا على أنّه يُعيد فهرس العنصر الذي وجده بدل العنصر نفسه، ويُعيد -1 لو لم يجد شيئًا. الترشيح filter يبحث التابِع find عن أوّل عنصر (واحد فقط) يُحقّق للدالة شرطها فتُعيد true. لو أردت إعادة أكثر من واحد فيمكن استعمال arr.filter(fn). تشبه صياغة filter التابِع find، الفرق هو إعادته لمصفوفة بكلّ العناصر المتطابقة: let results = arr.filter(function(item, index, array) { // لو كانت true فتُضاف القائمة إلى مصفوفة النتائج ويتواصل التكرار // يُعيد مصفوفة فارغة إن لم يجد شيئًا }); مثال: let users = [ {id: 1, name: "John"}, {id: 2, name: "Pete"}, {id: 3, name: "Mary"} ]; // يُعيد مصفوفة تحتوي على أوّل مستخدمَين اثنين let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2 التعديل على عناصر المصفوفات لنرى الآن التوابِع التي تُعدّل المصفوفة وتُعيد ترتيبها. الخارطة map يُعدّ التابِع arr.map أكثرها استخدامًا وفائدةً أيضًا. ما يفعله هو استدعاء الدالة على كلّ عنصر من المصفوفة وإعادة مصفوفة بالنتائج. صياغته هي: let result = arr.map(function(item, index, array) { // يُعيد القيمة الجديدة عوض العنصر }); مثلًا، هنا نعدّل كل عنصر فنحوّله إلى طوله: let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6 sort(fn) نُرتّب باستدعاء arr.sort() المصفوفة كما هي دون نسخها فنغيّر ترتيب عناصرها. هي الأخرى تُعيد المصفوفة المُرتّبة، ولكن غالبًا ما نُهمل القيمة المُعادة فالمصفوفة arr هي التي تتغيّر. مثال: let arr = [ 1, 2, 15 ]; // يعيد التابع ترتيب محتوى المصفوفة arr.sort(); alert( arr ); // 1, 15, 2 هل لاحظت بأنّ الناتج غريب؟ صار 1, 15, 2. ليس هذا ما نريد. ولكن، لماذا؟ مبدئيًا، تُرتّب العناصر وكأنها سلاسل نصية. بالمعنى الحرفي للكلمة: تُحوّل كل العناصر إلى سلاسل نصية عند الموازنة. والترتيب المعجماتي هو المتّبع لترتيب السلاسل النصية، "2" > "15" صحيحة حقًا. علينا لاستعمال الترتيب الذي نريده تمريرَ دالة تكون وسيطًا للتابِع arr.sort(). على الدالة موازنة قيمتين اثنتين (أيًا كانتا) وإعادة الناتج: function compare(a, b) { // لو كانت القيمة الأولى أكبر من الثانية if (a > b) return 1; // لو تساوت القيمتين if (a == b) return 0; // لو كانت القيمة الأولى أصغر من الثانية if (a < b) return -1; } مثال عن الترتيب لو كانت القيم أعدادًا: function compareNumeric(a, b) { if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; } let arr = [ 1, 2, 15 ]; arr.sort(compareNumeric); alert(arr); // 1, 2, 15 الآن صارت تعمل كما نريد. لنتوقف لحظة ونفكّر فيما يحدث تمامًا. أنتّفق بأنّ المصفوفة arr يمكن أن تحتوي أيّ شيء؟ أيّ شيء من الأعداد أو السلاسل النصية أو الكائنات أو غيرها. كلّ ما لدينا هو مجموعة من العناصر. لترتيبها نحتاج دالة ترتيب تعرف طرقة مقارنة عناصر المصفوفة. مبدئيًا، الترتيب يكون بالسلاسل النصية. يُنفِّذ التابع arr.sort(fn)في طيّاته خوارزمية فرز عامّة. لسنا نكترث كيف تعمل هذه الخوارزمية خلف الكواليس (وهي غالبًا quicksort محسّنة)، بل نكترث بأنّها ستمرّ على المصفوفة، تُوازن عناصرها باستعمال الدالة المقدّمة أعلاه وتُعيد ترتيبها. نكترث بأن نقدّم دالة fn التي ستؤدّي الموازنة. بالمناسبة، لو أردت معرفة العناصر التي تُوازنها الدالة حاليًا، فلا بأس. لن يقتلك أحد لو عرضتها: [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); }); يمكن أن تقارن الخوارزمية العنصر مع غيره من العناصر، ولكنّها تحاول قدر الإمكان تقليص عدد الموازنات. يمكن أن تُعيد دالة الموازنة أيّ عدد في الواقع، ليس على دالة الموازنة إلّا إعادة عدد موجب بدلالة «هذا أكبر من ذاك» وسالب بدلالة «هذا أصغر من ذاك». يمكننا هكذا كتابة الدوال بأسطر أقل: That allows to write shorter functions: let arr = [ 1, 2, 15 ]; arr.sort(function(a, b) { return a - b; }); alert(arr); // 1, 2, 15 تحيا الدوال السهمية أتذكر الدوال السهمية من فصل تعابير الدوال والدوال السهمية؟ يمكننا استعمالها أيضًا لتبسيط كود الفرز: arr.sort( (a, b) => a - b ); لا تفرق هذه عن تلك الطويلة بشيء، البتة. العكس reverse يعكس التابِع arr.reverse ترتيب العناصر في المصفوفة arr. مثال: let arr = [1, 2, 3, 4, 5]; arr.reverse(); alert( arr ); // 5,4,3,2,1 كما ويُعيد المصفوفة arr بعد عكسها. التقسيم split والدمج join إليك موقفًا من الحياة العملية. تحاول الآن برمجة تطبيق مراسلة، ويُدخل المستخدم قائمة المستلمين بفاصلة بين كلّ واحد: John, Pete, Mary. ولكن لنا نحن المبرمجين، فالمصفوفة التي تحتوي الأسماء أسهل بكثير من السلسلة النصية. كيف السبيل إذًا؟ هذا ما يفعله التابِع str.split(delim). يأخذ السلسلة النصية ويقسمها إلى مصفوفة حسب محرف القاسِم delim المقدّم. في المثال أعلاه نقسم حسب «فاصلة بعدها مسافة»: let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { alert( `A message to ${name}.` ); // A message to Bilbo (والبقية) } للتابِع split وسيطًا عدديًا اختياريًا أيضًا، وهو يحدّ طول المصفوفة. لو قدّمته فستُهمل العناصر الأخرى. ولكن في الواقع العملي، نادرًا ما ستفيدك هذا: let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); alert(arr); // Bilbo, Gandalf التقسيم إلى أحرف لو ناديت split(s) وتركت s فارغًا فستُسقم السلسلة النصية إلى مصفوفة من الأحرف: let str = "test"; alert( str.split('') ); // t,e,s,t على العكس من split فنداء arr.join(glue) يُنشئ سلسلة نصية من عناصر arr مجموعةً معًا «باللاصق». مثال: let arr = ['Bilbo', 'Gandalf', 'Nazgul']; let str = arr.join(';'); alert( str ); // Bilbo;Gandalf;Nazgul التابِعان reduce و reduceRight متى ما أردنا أن نمرّ على عناصر المصفوفة، استعملنا forEach أو for أو for..of. ومتى ما أردنا أن نمرّ ونُعيد بيانات كلّ عنصر، استعملنا map. نفس الحال مع التابعين arr.reduce وarr.reduceRight، إلّا أنهما ليسا بالسهولة نفسها. يُستعمل هذان التابعان لحساب قيمة واحدة حسب عناصر المصفوفة. هذه الصياغة: let value = arr.reduce(function(previousValue, item, index, array) { // ... }, [initial]); تُطبّق الدالة على كل عناصر المصفوفة واحدًا بعد الآخر، و«تنقل» النتيجة إلى النداء التالي لها: وُسطاء الدالة: previousValue - نتيجة النداء السابق للدالة. يُساوي قيمة initial في أوّل نداء (لو قُدّمت أصلًا). item -- العنصر الحالي في المصفوفة. index -- مكان العنصر. array -- المصفوفة نفسها. حين تُطبّق الدالة، تُمرّر إليها نتيجة النداء السابق في أوّل وسيط. أجل، معقّد قليلًا، لكن ليس كما تتخيّل لو قلنا أنّ الوسيط الأول بمثابة «ذاكرة» تخزّن النتيجة النهائية من إجراءات التنفيذ التي سبقتها. وفي آخر نداء تصير نتيجة التابِع reduce. ربّما نقدّم مثالًا لتسهيل المسألة. هنا نعرف مجموعة عناصر المصفوفة في سطر برمجي واحد: let arr = [1, 2, 3, 4, 5]; let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 الدالة المُمرّرة إلى reduce تستعمل وسيطين اثنين فقط، وهذا كافٍ عادةً. لنرى تفاصيل النداءات. في أوّل مرّة، قيمة sum هي قيمة initial (آخر وسيط في reduce) وتساوي 0، وcurrent هي أوّل عنصر في المصفوفة وتساوي 1. إذًا فناتج الدالة هو 1. في النداء التالي، sum = 1 ونُضيف العنصر الثاني في المصفوفة (2) ونُعيد القيمة. في النداء الثالث، sum = 3، ونُضيف العنصر التالي في المصفوفة، وهكذا دواليك إلى آخر نداء… هذا سير العملية الحسابية: وهكذا نمثّلها في جدول (كلّ صف يساوي نداء واحد للدالة على العنصر التالي في المصفوفة): sum current الناتج أوّل نداء 0 1 1 ثاني نداء 1 2 3 ثالث نداء 3 3 6 رابع نداء 6 4 10 خامس نداء 10 5 15 table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } هكذا نرى بوضوح شديد كيف يصير ناتج النداء السابق أوّل وسيط في النداء الذي يلحقه. يمكننا أيضًا حذف القيمة الأولية: let arr = [1, 2, 3, 4, 5]; // أزلنا القيمة الأولية من التابِع reduce (اختفت القيمة 0) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 وستكون النتيجة متطابقة، إذ أنّ reduce تأخذ أول عنصر من المصفوفة على أنّه قيمة أولية (لو لم نقدّم نحن قيمة أولية) وتبدأ العملية من العنصر الثاني. جدول العملية الحسابية مُطابق للجدول أعلاه، لو حذفنا أول سطر فيه. ولكن عليك أن تحترس حين لا تقدّم تلك القيمة. لو كانت المصفوفة فارغة فنداء reduce بدون القيمة الأولية سيعطيك خطأً. مثال على ذلك: let arr = []; arr.reduce((sum, current) => sum + current); الشيفرة السابقة ستطلق خطأ، إذ لا يمكن استدعاء reduce مع مصفوفة فارغة دون قيمة أولية، وتحل المشكلة بتوفير قيمة أولية، وستعاد آنذاك. لذا خُذ هذه النصيحة وحدّد قيمة أولية دومًا. لا يختلف التابِع arr.reduceRight عن هذا أعلاه إلا بأنّه يبدأ من اليمين وينتهي على اليسار. Array.isArray المصفوفات ليست نوعًا منفصلًا في اللغة، بل هي مبنيّة على الكائنات. لذا typeof لن تفيدك في التفريق بين الكائن العادي والمصفوفة: alert(typeof {}); // كائن object alert(typeof []); // كائن أيضًا …ولكن، المصفوفات تستعمل كثيرًا جدًا لدرجة تقديم تابِع خاص لهذا الغرض: Array.isArray(value). يُعيد هذا التابِع true لو كانت value مصفوفة حقًا، وfalse لو لم تكن. alert(Array.isArray({})); // false alert(Array.isArray([])); // true تدعم أغلب التوابِع thisArg تقبل أغلب توابِع المصفوفات تقريبًا، التوابع التي تستدعي دوالًا (مثل find وfilter وmap، عدا sort) - تقبل المُعامل الاختياري thisArg. لم نشرح هذا المُعامل في الأقسام أعلاه إذ أنّه نادرًا ما يُستعمل. ولكن علينا الحديث عنه لألا يكون الشرح ناقصًا. هذه الصياغة الكاملة لهذه التوابِع: arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... // الوسيط thisArg هو آخر وسيط اختياري تكون قيمة المُعامل thisArg للدالة func تساوي this. هنا مثلًا نستعمل تابِع كائن army على أنّه مرشّح، والوسيط thisArg يمرّر سياق التنفيذ وذلك لإيجاد المستخدمين الذين يعيد التابع army.canJoin القيمة true: let army = { minAge: 18, maxAge: 27, canJoin(user) { return user.age >= this.minAge && user.age < this.maxAge; } }; let users = [ {age: 16}, {age: 20}, {age: 23}, {age: 30} ]; *!* let soldiers = users.filter(army.canJoin, army); */!* alert(soldiers.length); // 2 alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23 لو استعملنا في المثال أعلاه users.filter(army.canJoin) فسيُستدعى التابِع army.canJoin كدالة مستقلة بذاتها حيث this=undefined، ما سيؤدي إلى خطأ. يمكن استبدال استدعاء users.filter(army.canJoin, army) بالتعليمة التي تُؤدّي ذات الغرض users.filter(user => army.canJoin(user)). نستعمل الأولى أكثر من الثانية إذ أنّ الناس تفهمها أكثر من تلك. ملخص ورقة فيها كل توابِع الدوال (غُشّ منها): لإضافة العناصر وإزالتها: push(...items) -- تُضيف العناصر items إلى النهاية، pop() -- تستخرج عنصرًا من النهاية، shift() -- تستخرج عنصرًا من البداية، unshift(...items) -- تُضيف العناصر items إلى البداية. splice(pos, deleteCount, ...items) -- بدءًا من العنصر ذي الفهرس pos، احذف deleteCount من العناصر وأدرِج مكانه العناصر items. slice(start, end) -- أنشِئ مصفوفة جديدة وانسخ عناصرها بدءًا من start وحتّىend(ولكن دونend). concat(...items) -- أعِد مصفوفة جديدة: انسخ كل عناصر المصفوفة الحالية وأضَِف إليها العناصر items. لو كانت واحدة من عناصر items مصفوفة أيضًا، فستُنسخ عناصرها بدل. لتبحث عن العناصر: indexOf/lastIndexOf(item, pos) -- ابحث عن العنصر item بدءًا من العنصر ذي الفهرس pos وأعِد فهرسه أو أعِد -1 لو لم تجده. includes(value) -- أعِد القيمة true لو كان العنصر value في المصفوفة، وإلا أعِد false. find/filter(func) -- رشّح العناصر عبر دالة وأعِد أوّل قيمة (أو كل القيم) التي تُعيد الدالة قيمة true لو مُرّر ذلك العنصر لها. findIndex يشبه find، ولكن يُعيد الفهرس بدل القيمة. للمرور على عناصر المصفوفة: forEach(func) -- يستدعي func لكلّ عنصر ولا يُعيد أيّ شيء. لتعديل عناصر المصفوفة: map(func) -- أنشِئ مصفوفة جديدة من نتائج استدعاء func لكلّ من عناصر المصفوفة. sort(func) -- افرز المصفوفة كما هي وأعِد ناتج الفرز. reverse() -- اعكس عناصر المصفوفة كما هي وأعِد ناتج العكس. split/join -- حوّل المصفوفة إلى سلسلة نصية، والعكس أيضًا. reduce(func, initial) -- احسب قيمة من المصفوفة باستدعاء func على كلّ عنصر فيها وتمرير الناتج بين كلّ استدعاء وآخر. وأيضًا: Array.isArray(arr) يفحص لو كانت arr مصفوفة أم لا. لاحظ أنّ التوابِع sort وreverse وsplice تُعدّل المصفوفة نفسها. هذه التوابِع أعلاه هي أغلب ما تحتاج وما تريد أغلب الوقت (99.99%). ولكن هناك طبعًا غيرها: arr.some(fn)/arr.every(fn) تفحص المصفوفة. تُنادى الدالة fn على كلّ عنصر من المصفوفة (مثل map). لو كانت أيًا من (أو كل) النتائج true، فيُعيد true، وإلًا يُعيد false. arr.fill(value, start, end) -- يملأ المصفوفة بالقيمة المتكرّرة value من الفهرس start إلى الفهرس end. arr.copyWithin(target, start, end) -- ينسخ العناصر من العنصر ذا الفهرس start إلى ذا الفهرس end ويلصقها داخلها عند الفهرس target (تعوّض ما هو موجود مكانها في المصفوفة). طالِع الكتيّب لقائمة فيها كل شيء. من أول وهلة سترى بأنّ عدد التوابِع لا ينتهي ومهمة حفظها مستحيلة، ولكن الواقع هي أنّها بسيطة جدًا. طالِع «ورقة الغشّ» لتعرف ما تفعل كلًا منها، ثمّ حُلّ مهام هذا الفصل لتتدرّب عليها وتكون خبيرًا كفاية بتوابِع الدوال. بعدها، لو احتجت التعامل مع المصفوفات ولا تدري ما تفعل، تعال هنا وابحث في ورقة الغشّ عن التابِع المناسب لحالتك. الأمثلة الموجودة ستفيدك فتكتبها كما ينبغي. وسريعًا ما ستتذكّر كل التوابِع من تلقاء نفسك ودون بذل أيّ جهد. تمارين حوّل «border-left-width» إلى «borderLeftWidth» الأهمية: 5 اكتب دالة camelize(str) تغيّر الكلمات المقسومة بِشَرطات مثل «my-short-string» إلى عبارات بتنسيق «سنام الجمل»: «myShortString». بعبارة أخرى: أزِل كلّ الشرطات وحوّل أوّل حرف من كلّ كلمة بعدها إلى الحالة الكبيرة. أمثلة: camelize("background-color") == 'backgroundColor'; camelize("list-style-image") == 'listStyleImage'; camelize("-webkit-transition") == 'WebkitTransition'; تلميح: استعمل split لتقسيم السلسلة النصية إلى مصفوفة، ثمّ عدّل عناصرها وأعِد ربطها بتابِع join. الحل function camelize(str) { return str .split('-') // splits 'my-long-word' into array ['my', 'long', 'word'] .map( // كبر الحروف الأولى لجميع عناصر المصفوفة باستثناء أول عنصر // ['my', 'long', 'word'] --> ['my', 'Long', 'Word'] (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) ) .join(''); // joins ['my', 'Long', 'Word'] into 'myLongWord' } نطاق ترشيح الأهمية: 4 اكتب دالة filterRange(arr, a, b) تأخذ المصفوفة arr، وتبحث في عناصرها بين a وb وتُعيد مصفوفة بها. يجب ألّا تُعدّل الدالة المصفوفة، بل إعادة مصفوفة جديدة. مثال: let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); alert( filtered ); // 3,1 alert( arr ); // 5,3,8,1 الحل function filterRange(arr, a, b) { // أضفنا الأقواس حول التعبير لتسهيل القراءة return arr.filter(item => (a <= item && item <= b)); } let arr = [5, 3, 8, 1]; let filtered = filterRange(arr, 1, 4); alert( filtered ); // 3,1 alert( arr ); // 5,3,8,1 نطاق ترشيح «كما هو» الأهمية: 4 اكتب دالة filterRangeInPlace(arr, a, b) تأخذ المصفوفة arr وتُزيل منها كل القيم عدا تلك بين a وb. الشرط هو: a ≤ arr ≤ b. يجب أن تُعدّل الدالة المصفوفة، ولا تُعيد شيئًا. مثال: let arr = [5, 3, 8, 1]; // حذف جميع الأعداد باستثناء الواقعة بين 1 و 4 filterRangeInPlace(arr, 1, 4); alert( arr ); // [3, 1] الحل function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { let val = arr[i]; // إزالة إن كانت خارج النطاق if (val < a || val > b) { arr.splice(i, 1); i--; } } } let arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // removed the numbers except from 1 to 4 alert( arr ); // [3, 1] الفرز بالترتيب التنازلي الأهمية: 4 let arr = [5, 2, 1, -10, 8]; // ... شيفرة ترتيب العناصر تنازليًا alert( arr ); // 8, 5, 2, 1, -10 الحل let arr = [5, 2, 1, -10, 8]; arr.sort((a, b) => b - a); alert( arr ); نسخ المصفوفة وفرزها الأهمية: 5 في يدنا مصفوفة من السلاسل النصية arr. نريد نسخة مرتّبة عنها وترك arr بلا تعديل. أنشِئ دالة copySorted(arr) تُعيد هذه النسخة. let arr = ["HTML", "JavaScript", "CSS"]; let sorted = copySorted(arr); alert( sorted ); // CSS, HTML, JavaScript alert( arr ); // HTML, JavaScript, CSS الحل يمكن أن نستعمل slice() لأخذ نسخة ونفرز المصفوفة: function copySorted(arr) { return arr.slice().sort(); } let arr = ["HTML", "JavaScript", "CSS"]; *!* let sorted = copySorted(arr); */!* alert( sorted ); alert( arr ); خارطة بالأسماء الأهمية: 5 لدينا مصفوفة من كائنات user، لكلّ منها صفة user.name. اكتب كودًا يحوّلها إلى مصفوفة من الأسماء. مثال: let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let users = [ john, pete, mary ]; let names = /* شيفرتك هنا */ alert( names ); // John, Pete, Mary الحل let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let users = [ john, pete, mary ]; let names = users.map(item => item.name); alert( names ); // John, Pete, Mary أنشِئ آلة حاسبة يمكن توسعتها لاحقًا الأهمية: 5 أنشِئ دالة إنشاء باني «constructor» Calculator تُنشئ كائنات من نوع «آلة حاسبة» يمكن لنا «توسعتها». تنقسم هذه المهمة إلى جزئين اثنين: أولًا، نفّذ تابِع calculate(str) يأخذ سلسلة نصية (مثل "1 + 2") بالتنسيق «عدد مُعامل عدد» (أي مقسومة بمسافات) ويُعيد الناتج. يجب أن يفهم التابِع الجمع + والطرح -. مثال عن الاستعمال: let calc = new Calculator; alert( calc.calculate("3 + 7") ); // 10 بعدها أضِف تابِع addMethod(name, func) يُعلّم الآلة الحاسبة عمليّة جديدة. يأخذ التابِع المُعامل name ودالة func(a,b) بوسيطين تُنفّذ هذه العملية. كمثال على ذلك سنُضيف عمليات الضرب * والقسمة / والأُسّ **: let powerCalc = new Calculator; powerCalc.addMethod("*", (a, b) => a * b); powerCalc.addMethod("/", (a, b) => a / b); powerCalc.addMethod("**", (a, b) => a ** b); let result = powerCalc.calculate("2 ** 3"); alert( result ); // 8 في هذه المهمة ليس هناك أقواس رياضية أو تعابير معقّدة. تفصل الأعداد والمُعامل مسافة واحدة فقط. يمكنك التعامل مع الأخطاء لو أردت. الحل لاحظ طريقة تخزين التوابِع، حيث تُضاف إلى صفة this.methods فقط. كلّ الشروط والتحويلات العددية موجودة في التابِع calculate. يمكننا في المستقبل توسيعه ليدعم تعابير أكثر تعقيدًا. function Calculator() { this.methods = { "-": (a, b) => a - b, "+": (a, b) => a + b }; this.calculate = function(str) { let split = str.split(' '), a = +split[0], op = split[1], b = +split[2] if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } return this.methods[op](a, b); } this.addMethod = function(name, func) { this.methods[name] = func; }; } خارطة بالكائنات فرز المستخدمين حسب أعمارهم الأهمية: 5 اكتب دالة sortByAge(users) تأخذ مصفوفة من الكائنات بالصفة age وتُرتبّها حسب أعمارهم age. مثال: let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let arr = [ pete, john, mary ]; sortByAge(arr); // [john, mary, pete] alert(arr[0].name); // John alert(arr[1].name); // Mary alert(arr[2].name); // Pete الحل function sortByAge(arr) { arr.sort((a, b) => a.age > b.age ? 1 : -1); } let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; let arr = [ pete, john, mary ]; sortByAge(arr); // [john, mary, pete] alert(arr[0].name); // John alert(arr[1].name); // Mary alert(arr[2].name); // Pete خلط المصفوفات الأهمية: 3 اكتب دالة shuffle(array) تخلط عناصر المصفوفة (أي ترتّبها عشوائيًا). يمكن بتكرار نداء shuffle إعادة العناصر بترتيب مختلف. مثال: let arr = [1, 2, 3]; shuffle(arr); // arr = [3, 2, 1] shuffle(arr); // arr = [2, 1, 3] shuffle(arr); // arr = [3, 1, 2] // ... يجب أن تكون جميع احتمالات ترتيب العناصر متساوية. فمثلًا يمكن إعادة ترتيب [1,2,3] لتكون [1,2,3] أو [1,3,2] أو [3,1,2] أو أو أو، واحتمال حدوث كلّ حالة متساوٍ. الحل هذا هو الحل البسيط: function shuffle(array) { array.sort(() => Math.random() - 0.5); } let arr = [1, 2, 3]; shuffle(arr); alert(arr); تعمل هذه الشيفرة (نوعًا ما) إذ أنّ Math.random() - 0.5 عددٌ عشوائي ويمكن أن يكون موجبًا أم سالبًا، بذلك تُعيد دالة الفرز ترتيب العناصر عشوائيًا. ولكن ليس هذه الطريقة التي تعمل فيها دوال الفرز، إذ ليس لكلّ حالات التبديل الاحتمال نفسه. فمثلًا في الشيفرة أعلاه، تُنفّذ shuffle 1000000 مرّة وتعدّ مرّات ظهور النتائج الممكنة كلّها: function shuffle(array) { array.sort(() => Math.random() - 0.5); } // نعدّ مرّات ظهور كلّ عمليات التبديل الممكنة let count = { '123': 0, '132': 0, '213': 0, '231': 0, '321': 0, '312': 0 }; for (let i = 0; i < 1000000; i++) { let array = [1, 2, 3]; shuffle(array); count[array.join('')]++; } // نعرض عدد عمليات التبديل الممكنة for (let key in count) { alert(`${key}: ${count[key]}`); } إليك عيّنة عن الناتج (إذ يعتمد على محرّك جافاسكربت): 123: 250706 132: 124425 213: 249618 231: 124880 312: 125148 321: 125223 نرى تحيّز الشيفرة بوضوح شديد، إذ تظهر 123 و213 أكثر بكثير من البقية. يختلف ناتج هذه الشيفرة حسب محرّكات جافاسكربت ولكن هذا يكفي لنقول بأنّ هذه الطريقة ليست موثوقة. ولكن لمَ لا تعمل الشيفرة؟ بشكل عام فتابِع sort أشبه ”بالصندوق الأسود“: نرمي فيه مصفوفة ودالة موازنة وننتظر أن تفرز لنا المصفوفة. ولكن بسبب عشوائية الموازنة يختلّ ذكاء الصندوق الأسود، وهذا الاختلال يعتمد على طريقة كتابة كلّ محرّك للشيفرة الخاصة به. ثمّة طرق أخرى أفضل لهذه المهمّة، مثل الخوارزمية خلّاط فِشر ييتس الرائعة. فكرتها هي المرور على عناصر المصفوفة بالعكس وتبديل كلّ واحد بآخر قبله عشوائيًا: function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // رقم عشوائي من 0 إلى i // let t = array[i]; array[i] = array[j]; array[j] = t [array[i], array[j]] = [array[j], array[i]]; } } بدلنا بالمثال هذا العنصرين array و array[j] وذلك نستعمل صياغة ”الإسناد بالتفكيك" وستجد تفاصيل أكثر عن هذه الصياغة في فصول لاحقة. لنختبر الطريقة هذه بنفس ما اختبرنا تلك: function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } // نعدّ مرّات ظهور كلّ عمليات التبديل الممكنة let count = { '123': 0, '132': 0, '213': 0, '231': 0, '321': 0, '312': 0 }; for (let i = 0; i < 1000000; i++) { let array = [1, 2, 3]; shuffle(array); count[array.join('')]++; } // نعرض عدد عمليات التبديل الممكنة for (let key in count) { alert(`${key}: ${count[key]}`); } عيّنة عن الناتج: 123: 166693 132: 166647 213: 166628 231: 167517 312: 166199 321: 166316 الآن كل شيء سليم: لكلّ عمليات التبديل ذات الاحتمال. كما أنّ خوارزمية ”فِشر ييتس“ أفضل من ناحية الأداء إذ ليس علينا تخصيص الموارد لعملية ”الفرز“. ما متوسّط الأعمار؟ الأهمية: 4 اكتب دالة getAverageAge(users) تأخذ مصفوفة من كائنات لها الصفة age وتُعيد متوسّط الأعمار. معادلة المتوسّط: (age1 + age2 + ... + ageN) / N. مثال: let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 29 }; let arr = [ john, pete, mary ]; alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28 الحل function getAverageAge(users) { return users.reduce((prev, user) => prev + user.age, 0) / users.length; } let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 29 }; let arr = [ john, pete, mary ]; alert( getAverageAge(arr) ); // 28 ترشيح العناصر الفريدة في المصفوفة الأهمية: 4 لمّا أنّ arr مصفوفة، أنشِئ دالة unique(arr) تُعيد مصفوفة فيها عناصر arr غير مكرّرة. مثال: function unique(arr) { /* شيفرة هنا */ } let strings = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; alert( unique(strings) ); // Hare, Krishna, :-O الحل ما سنفعل هو المرور على عناصر المصفوفة: سنفحص كلّ عنصر ونرى إن كان في المصفوفة الناتجة. إن كان كذلك… نُهمله، وإن لم يكن، نُضيفه إلى المصفوفة. function unique(arr) { let result = []; for (let str of arr) { if (!result.includes(str)) { result.push(str); } } return result; } let strings = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O" ]; alert( unique(strings) ); // Hare, Krishna, :-O صحيح أنّ الكود يعمل، إلّا أنّ فيه مشكلة أداء محتملة. خلف الكواليس، يمرّ التابِع result.includes(str) على المصفوفة result ويقارن كلّ عنصر مع str ليجد المطابقة المنشودة. لذا لو كان في result مئة 100 عنصر وما من أيّ مطابقة مع str، فعليها المرور على جُلّ result وإجراء 100 حالة مقارنة كاملة. ولو كانت result كبيرة مثل 10000 فيعني ذلك 10000 حالة مقارنة. إلى هنا لا مشكلة، لأنّ محرّكات جافاسكربت سريعة جدًا، والمرور على 1000 عنصر في المصفوفة يحدث في بضعة ميكروثوان. ولكنّا هنا في حلقة for نُجري هذه الشروط لكلّ عنصر من arr. فإن كانت arr.length تساوي 10000 فيعني أنّا سنُجري 10000*10000 = مئة مليون حالة مقارنة. كثير جدًا. إذًا، فهذا الحل ينفع للمصفوفات الصغيرة فقط. سنرى لاحقًا في الفصل كيف نحسّن هذا الكود. ترجمة -وبتصرف- للفصل Array methods من كتاب The JavaScript language .task__importance { color: #999; margin-left: 30px; } .task__answer { border: 3px solid #f7f6ea; margin: 20px 0 14px; position: relative; display: block; padding: 25px 30px; } اقرأ أيضًا المقال التالي: الكائنات المكرَّرة (Iterables) المقال السابق: المصفوفات (arrays)1 نقطة