لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 09/22/21 في كل الموقع
-
2 نقاط
-
أحاول إنشاء عمليات ترحيل migrations داخل تطبيق جانغو Django موجود باستخدام الأمر makemigrations ولكنه يظهر الرسالة No changes detected عادةً ما أقوم بإنشاء تطبيقات جديدة في المشروع باستخدام الأمر startapp ولكني لم أستخدمه لهذا التطبيق عندما قمت بإنشائه. بعد محاولة أكتشاف الأخطاء، اكتشفت أنه لا يتم إنشاء ملفات ترحيل البيانات migrations لأن المجلد migrations غير موجود في أحد التطبيقات. هل سيكون من الأفضل إنشاء المجلد إذا لم يكن موجودًا أم سوف يسبب خطأ بعد ذلك أو يؤدي لمشكلة في قاعدة البيانات؟2 نقاط
-
<head> <meta charset="UTF-8" /> <title>Learn JavaScript</title> <script> let ahref = document.quer("open"); console.log(ahref[1].classList) </script> </head> <body> <a class="open" href="https://google.com">Google</a> <a class="open" href="https://wikipedia.org">wikipedia</a> <a class="not" href="https://facebook.com">Facebook</a> <a class="linked" href="https://wikipedia.org">wikipedia</a> </body> </html> لماذا classList لا تعمل إذا وضعت كود جافا سكريبت في HTML ؟ يظهر لي الخطأ التالي : Uncaught TypeError: Cannot set properties of null (setting 'onload') تعديل : تم حل المشكلة بال window.onload2 نقاط
-
ما وظيفة الدالة compile وماهي الوسطاء الخاصة بهذه الدالة؟2 نقاط
-
احاول تشغيل redux devtools عند استخدام next js ولكن لا استطيع, حاولت كثيرا وفي كل مرة ياتيني خطأ مختلف2 نقاط
-
1 نقطة
-
1 نقطة
-
انا في الحقيقة لم اثبت احد البرامج مثل xampp أو wampp فقط قمت بتثيت mysql workbench وربطه مع mysql 81 نقطة
-
بدي اسأل انا كشخص سوري بدي اشتري الكورسات او كورس معين كيف فيني اشتري اذا مافي اي وسيلة دفع مدعومة عنا؟؟؟؟1 نقطة
-
يمكن الدفع من أي بطاقة بنكية أوحساب بايبال ولا يشترط نفس الشخص أن يدفع، أي إن كان لك أقارب في الخارج يمكنهم مساعدتك والدفع للدورة، وذلك لأن الشهادة الخاصة بالدورة تعطى لمن يجتاز الامتحان بعد التأكد من الثبوتيات، أي لا يهم من دفع سعر الدورة. يمكنك التواصل مع مركز مساعدة حسوب وشرح جميع التفاصيل ليتم إرشادك لطريقة الدفع المناسبة في حالتك.1 نقطة
-
إن كان لديك مشكلة مادية في شراء دورتين، يمكنك الاعتماد على دورة جافاسكربت، فهذه اللغة تستطيع العمل في كل من Front End أي واجهات المستخدم، مثلا باستخدام مكتبة React js وأيضا العمل كمخدم للموقع Back End أيضا من خلال بيئة العمل node وأيضا إطار العمل Express وأيضا قواعد البيانات.. وهذا كله يتم تعليمه في دورة واحدة. مع العلم بعد شراء أي دورة يتم فتح المسارات الأساسية من باقي الدورات ومنهم دورة تطوير واجهات المستخدم والتي تحوي في مسارها الأول على أساسيات كل من HTML - CSS - JS والتي تفيدك في دورة جافاسكربت عند تنسيق واجهات الموقع. تعتبرة دورة تطوير الواجهات ممتازة بالنسبة لعمل تفاصيل كثيرة في الموقع وإخراجه بأفضل شكل ممكن ولكنها تنحصر بالواجهات الأمامية، أي بدوت قواعد بيانات ومعالجة وتوثيق المستخدمين وإلا ماهنالك.. وبالاعتماد على دروس الأساسيات منها ودورة جافاسكربت سيكون لك قدرة على برمجة مواقع كاملة Full Stack.1 نقطة
-
تطوير المواقع بشكل عام ينقسم إلى جزئين: واجهة المستخدم Frontend والواجهة الخلفية Backend وتحتوي الأكاديمية على دورات لتعلم كلا الجزئين من الصفر، وهذه الدورات كالتالي: دورة تطوير واجهات المستخدم Frontend: تحتوي هذه الدورة على كل الأساسيات اللازمة للبدء في عمل واجهات المستخدم بأحدث التقنيات، وتبدأ الدورة من شرح أساسيات اللغات المستخدم في تطوير واجهات المستخدم مثل HTML و CSS و JavaScript مع شرح لأشهر المكتبات مثل Bootstrap و jQuery ثم تنتقل لعمل أكثر من مشروع مثل بناء واجهة لموقع يشبه YouTube، وتطوير واجهة استخدام حقيقة لمتجر الكتروني كامل من الصفر، وتطوير موقع لشركة مع مدونة خاصة خطوة بخطوة، وبناء 5 صفحات هبوط مختلفة، وبناء لوحة تحكم لتطبيق ويب، كما ستتعلم كيفية التعامل مع تقنيات أخرى مثل SCSS و Gulp و Git و GitHub وكيفية نشر المشاريع على الإنترنت وغيرها من التقنيات. يمكنك معرفة المزيد من التفاصيل من خلال صفحة الدورة من هنا (دورة تطوير واجهات المستخدم). أما بالنسبة لتطوير الواجهات الخلفية Backend فيوجد ثلاث دورات مختلفة بإستخدام ثلاث لغات، ويمكنك الإشتراك في واحدة منهم فقط لتعلم تطوير الواجهات الخلفية backend، وهم كالتالي: دورة تطوير تطبيقات الويب باستخدام لغة PHP: تستخدم في هذه الدورة لغة PHP وتبدأ الدورة في شرح الأساسيات البرمجية بإستخدام PHP ثم تنتقل لشرح كيفية التخاطب مع الخادم وكيف يتم إرسال الطلبات، ثم يتم عمل تطبيق ويب لإدارة المهام، وشرح أساسيات إطار العمل Laravel، بناء تطبيق إدارة المهام باستخدام إطار العمل Laravel، تطوير واجهة برمجية API بالإعتماد علي نمط RESTful، تطوير موقع إعلانات مبوبة، شبكة اجتماعية تشبه Instagram، تطوير نظام إدارة محتوى CMS كامل من الصفر، وفي النهاية يتم شرح كيفية تطوير قوالب WordPress، يمكنك معرفة المزيد من التفاصيل من خلال صفحة الدورة من هنا (دورة تطوير تطبيقات الويب باستخدام لغة PHP). دورة تطوير التطبيقات باستخدام لغة JavaScript: وهي دورة عامة يتم خلالها شرح الأساسيات البرمجية بإستخدام JavaScript، ثم شرح تطوير تطبيقات الخادم Backend باستخدام بيئة Node.js وإطار العمل Express.js، ثم تنتقل لأساسيات مكتبة React.js وبناء تطبيق ملاحظات باستخدامها من نوع SPA، ثم ستتعرف على أساسيات مكتبة React Native و Expo لعمل تطبيقات الهاتف الجوال، وستبدأ في تطوير تطبيق جوال للوصل بين الأطباء والمرضى، وبناء بناء تطبيق محادثة يشبه تطبيق WhatsApp، بعد ذلك ستدرس أساسيات إنشاء تطبيقات لسطح المكتب من خلال إطار العمل Electron.js وكيفية إنشاء مشاريع من خلال إطار العمل Next.js، يمكنك معرفة المزيد من التفاصيل من خلال صفحة الدورة من هنا (دورة تطوير التطبيقات باستخدام لغة JavaScript). دورة تطوير تطبيقات الويب باستخدام لغة Ruby: تبدأ الدورة في شرح الأساسيات البرمجية بإستخدام Ruby، ستبدأ في عمل مشاريع من خلال بناء لعبة بسيطة باستخدام لغة Ruby، ثم ستنتقل لإنشاء تطبيقات الويب من خلال التعرف على أساسيات إطار العمل Ruby on Rails، وفي النهاية سوف تقوم بالتطبيق من خلال بناء تطبيق إدارة محتوى، وتطوير شبكة إجتماعية تشبه تويتر، يمكنك معرفة المزيد من التفاصيل من خلال صفحة الدورة من هنا (دورة تطوير تطبيقات الويب باستخدام لغة Ruby). لكي تكون مبرمج Full-Stack يجب أن تدرس دورة تطوير الواجهات Frontend وتختار أحد دورات تطوير الواجهات الخلفية Backend.1 نقطة
-
في الوقت الحالي تقبل الأكاديمية الدفع عبر PayPal أو البطاقات الإئتمانية أو إستخدام كوبون لشحن الحساب أولًا ثم إستخدام الرصيد من الحساب، يمكنك التواصل مع فريق الدعم الفني لمعرفة المزيد عن تفاصيل عملية الدفع وكل الوسائل المتاحية وكيفية إستخدامها. للتواصل مع فريق الدعم الفني يمكنك إستخدام مركز المساعدة الخاص بالأكاديمية من هنا.1 نقطة
-
سلام عليكم ما المقصود ب globally ؟ وماهى فائدة استخدماها ؟ كيف تثبتها ؟ وشكرا1 نقطة
-
لدي استفسار لو تكرمتو اواجه مشكله في بعض الصفحات الصفحات التي تنتقل وفيها اجراء على سبيل المثال لدي صفحه A انتقل منها الى صفحه B ثم الى صفحه C وهيا صفحه تعديل البيانات اقوم بتعديل البيانات بداخلها ثم اضغط حفظ ف يقوم زر الحفظ بحفظ البيانات وايضا يوجد امر انتقال وعوده الى صفحه B الان المستخدم في الصفحه B تحدث المشكله هنا لو قمت بضغط على زر row back في شريط appbar يعود بي الى صفحه C وهذا غلط من المفترض ان يعود الى صفحه A كيف يمكن حل هذا نوع من مشاكل الانتقال في Flutter لا اعلم حقيقه اذا كانت المشكله واضحه حسب ما قمت بشرحها1 نقطة
-
تحيه طيبه للجميع لدي شريط bottomNavigationBar يعرض صفحات مختلفه من خلال احد الصفحات فيه اقوم بالانتقال الى صفحه مختلفه بواسطة الامر التالي: Navigator.push( context, MaterialPageRoute( builder: (context) => Page2()), ); ولكن مشكلتي لو قمت الان من خلال الصفحة page2 بالعوده الى الصفحة الاولى التي سبق وقمت بالانتقال منها يتم عرض الصفحه الاولى ولكن بدون شريط bottomNavigationBar ايضا انا اقوم بالعوده لها من خلال الامر التالي: Navigator.push( context, MaterialPageRoute( builder: (context) => Page1()), ); هل يوجد حل لهذا المشكله ام طريقة مختلفه لتجنب هذ المشكلة؟ كيف يمكن الوصول الى الصفحه الاولى في شريط bottomNavigationBar1 نقطة
-
لدي جهازي يعمل بنظام تشغيل اندرويد وارغب بعمل نسخه من برنامج واتساب اعمال كيف الطريقه لفعل ذلك؟انا اشوف الكثير يعمل له نسخه من الواتساب ويقوم باضافات وتغير الايقونه والاسم وغيره هل توجد طريقة محدده لفعل ذلك؟ مع العلم ان نظام المراسل المزدوج لا يمكن من خلال نسخ تطبيق واتساب اعمال1 نقطة
-
تختلف عملية استنساخ التطبيق حسب مصنع هاتف أندرويد، توجه للضبط setting ثم ابحث عن أحد هذه التسيمات Dual apps, App Clone, App twin, or Parallel Apps وهذهمدعومة من نظام التشغيل مباشرة، وسوف تعمل على استنساخ التطبيق وتنصيبه بدون مشاكل.1 نقطة
-
لعمل نسخة مختلفة من تطيق ما، عليك تحميل التطبيق ثم استخدام مفاهيم و أدوات الهندسة العكسية لاسرتاج الشيفرة المصدرية الأصلية له، وهنا بعد عمل تلك الخطوة، سيصبح لديك نسخة المطور من المشروع، حيث يمكنك تعديل اسم الحزمة و عمل التغييرات التي تريدها، "لكن هذا أمر غير قانوني" هل تقصد تشغيل نسختين من التطبيق مثل dual messenger على نفس الجهاز.. هذا سببه عدم دعم التطبيق لهذا الأمر على عكس تطبيق facebook messenger وغيره، ربما تستطيع توضيح هذه الجزئية من فضلك؟1 نقطة
-
هذا رابط ال github https://github.com/moath-bahasan/sfrny1 نقطة
-
طيب اريد شرح لهذه الجمل : CommonJS, every file is module (by default) Modules - Encapsulated Code (only share minimum)1 نقطة
-
انت تقول بعد النقر على root سيتم عرض المجلدات الموجودة مباشرة في المستودع ثم اختار المجلد الصحيح. انا بعد ما انقر على root لم يعرض لي اي شيء علشان اختار المجلد الصحيح ايش الحل؟1 نقطة
-
هلا بيك اخي ساقوم بتجربه باذن الله حقيقه انني اشاهد هذا الطريقة منذ فتره ولكن لم يسبق لي تجربتها ماذا تفرق هذا الطريقه اخوي عن طريقه المعتاده؟ كل الشكر لك اخي الكريم1 نقطة
-
يجب عليك وضع زر عودة في leading أو actions في appbar ثم تعطيه أمر عند الضغط بأن يعود إلى صفحة A بهذا الشكل appBar: AppBar( actions: [ IconButton( icon: Icon(Icons.back), onPressed: () { Navigator.pushNamed(context, '/a'); }, ), ], ),1 نقطة
-
سبب هذا الخطأ هو أن مجموعة أدوات تطوير ريدكس(redux dev-tools) تعمل على المتصفح بينما الnext js تعمل على الخادم مما يسبب مشاكل, فعندما نقوم بإستخدام React. js كنا نقوم بكتابة شفرة برمجية كتلك const store = createStore(reducer , window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) ولكن عند كتابة تلك الشفرة في Next.js سيتسبب ذلك بمشكلة لأن كما أخبرتك أن الnext js تعمل على الخادم أولاً حتى تحقق ال static-site-generating أو ال server-side rendering و الكائن window هو أحد الكائنات الخاصة بالمتصفح وبما أن الشفرة البرمجية تعمل عند الخادم أولاً فبالتالي لن يمكنها الوصول إلى الكائنات الخاصة بالمتصفح. الحل: أن نقوم بالتأكد أولاً من كون الشفرة البرمجية يتم تنفيذها على المتصفح وليس على الخادم حتى ﻻ تتسبب بأخطاء, ويمكننا التحقق من ذلك عن طريق التحقق من توفر الكائن window , ويمكن كتابة الشفرة البرمجية على النحو التالي: const enhancer = (typeof window !== 'undefined' && window.devToolsExtension) ? window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() : f => f const store = createStore(reducer ,enhancer); هكذا نقوم بالتأكد أولاً أن الشفرة البرمجية تعمل بالمتصفح وأن مجموعة الأدوات تعمل بنجاح1 نقطة
-
هل يمكن ارسال البيانات by reference في لغة جافا سكريبت1 نقطة
-
عملية الدفع تتم على مرحلتين الاولى ادخال بيانات حساب paypal في webview التطبيق من اجل عملية الدفع وتكون المعلومات المرجعة هي nonce و الايميل كما في الصورة سؤالي ماهو nonce ولماذا يستخدم شاهدت اكثر من فيديو يتم ارسالة nonce الى سيرفر firebase لأتمام الدفع !!1 نقطة
-
لمعرفة كيف تقوم أي شركة بالمكسب، عليك أن تبحث عن مخطط نموذج العمل التجاري"business model canvas" الخاص بها. شركة حالا تعتمد على مخطط نموذج العمل التجاري مشابة تماما لشركة أوبر، الصورة التالية توضح مخطط نموذج العمل التجاري الخاصة بشركة أوبر: اذا نظرت الي قسم "Revenue stream" أي العوائد، والذي يوضح من أين تكسب الشركة المال،تجد أنها تعتمد على اقتطاع نسبة من تكلفة الرحلة وكذلك بيع تراخيصها او استخدام علامتها التجارية والاعلانات. ونظرا لكثرة عدد الرحلات التي تقوم بها يوميا، فان اقتطاع نسبة صغيرة في مثل هذا العدد الكبير يحقق دخل كبير وهو ما تعتمد عليه الشركة بشكل اساسي بجانب بعض المصادر الثانوية.1 نقطة
-
1 نقطة
-
لتبديل عنصرين في مصوفة ما، نقوم بتحزين الفهرس index الخاص بكل عنصر من العنصرين في متغير وليكن باسم position_1 و position_2، ثم نقوم بتخزين قيمة كل من العنصرين في متغير آخر وليكن باسم value_1 و value_2 ثم نقوم بحذف هذين العنصرين من المصفوفة الأصلية، بعد ذلك نقوم بإضافة كل عنصر من العنصرين (بإستخدام value_1 و value_2 ) في الموقعين position_2 و position_1، وبهذا نكون قد قمنا بحذف العنصرين القديمين وقمنا بإعادة إضافتهما في عكس أماكنهما (كل عنصر مكان الآخر). يمكن أن يكون هناك دوال تقوم بهذا الأمر بشكل تلقائي لكن يجب معرفة اللغة البرمجة التي تستعملها أولًا حتى يمكننا مساعدتك. لضرب مصفوفتين تتكون من صف واحد يجب أن تحتوي كل مصفوفة على نفس عدد العناصر الموجودة في المصفوفة الآخرى، نقوم بضرب العنصر الأول في المصفوفة الأولى بالعنصر الأول في المصفوفة الثانية، ونضرب العنصر الثاني في المصفوفة الأولى في العنصر الثاني في المصفوفة الثانية وهكذا ثم يمكنك أن تقوم بجمع نتيجة ضرب كل هذه العناصر معًا بهذا الشكل: a = [1, 2, 3, 4] b = [5, 6, 7, 8] result = (1*5) + (2*6) + (3*7) + (4*8) = 701 نقطة
-
يمكن القيام بذلك من خلال المقارنة بين أعمالك وأعمال المحترفين في كل جزئية مثلًا توزيع وتناسق الألوان في العمل، تنظيم ملفات المشروع في مجلد واحد، وتوزيع العناصر في العمل إلخ. بالإضافة إلى عمل مشاريع بشكل مستمر والرجوع إليها لعد فترة من الزمن بمتابعة مستوى تقدمك، حينها سيمكنك معرفة العيوب الموجودة في كل عمل قديم من أعمالك.1 نقطة
-
خدمة bitbucket تستعمل لحفظ ملفات المشاريع من الضياع وليست بالضرورة تستعمل لعرض هذه المشاريع، لكنها ستسهل عملية بناء portfolio، حيث يمكن الحصول على إستضافة مجانية من أحد المواقع التي تقدم إستضافة مجانية محدودة أو يمكن شراء إستضافة بإمكانيات صغيرة ووضع عيلها المشاريع بطريقة عرض جذابةـ ثم ربط هذه الإستضافة بنطاق خاص domain ليسهل الوصول إلى الموقع وليكون له طابع إحترافي. ويمكن أن يكون الـ portfolio مشروع في حد ذاته مفتوح المصدر، ويمكنك إضافة إلى قائمة مشاريعكِ التي قمتِ بها. يجب أيضًا وضع وصف للمشروع والتقنيات المستخدمه فيه ورابط المستودع الخاص بكل مشروع لكي يمكن للعملاء والشركات التي ترغب بتوظيفك من تصفح الكود الخاص بهذه المشاريع ودراسة وتقييم هذا الكود. بعد عمل الـ portfolio الخاص بكِ، حاولي نشره على مواقع التواصل وفي مواقع التوظيف، ليسهل الوصول إليه.1 نقطة
-
يوجد الكثير من الأماكن التي يتجمع فيها المبدعين والمصممين المتميزين، ومنها المواقع المتخصصة في ذلك مثل behance و dribbble و deviantart وغيرها الكثير. وفي كل موقع من هذه المواقع يوجد العديد من التصنيفات (موسن جرافيك - web design - UI/UX - fan arts - إلخ). بالإضافة إلى المواقع السابقة يوجد أيضًا مجموعات الفيسبوك المتخصصة والتي يكون بها تفاعل كبير (حسب المجموعة وعدد الأعضاء المتفاعلين). ولكي تزيدي من مستوى إحترافك يجب أن تقارني كل عمل من أعمالك لأعمال المحترفين من وجهة نظرك وتبحثين عن مناطق القوة والضعف وتحاولي تحسينها أكثر فأكثر حتى تكوني راضية عن المستوى الذي وصلتي له. كما أن متابعة أحداث المجال الذي تتخصصين فيه سيكون له أثر إجابي كبير على طريقة تعلمك والمصادر التي تتعليم منها. بالتوفيق.1 نقطة
-
1- علوم الحاسب Computer Science يهتم هذا التخصص في كيفية بناء أنظمة تتناسب مع مختلف المجالات في الحياة وأيضاً يهتم بكيفية التراسل داخل الشبكات وحماية المعلومات وحماية التراسل بين الشبكات. تخصص علوم الحاسب دائما يركز على الأنظمة Software و البيانات Data والأمن Security سواء للشبكات أو للمعلومات و كيفية إنشاء Algorithms لحل مشاكل معينة. يركز هذا التخصص على الرياضيات بشكل كبير فطلاب علوم الحاسب غالبا يدرسون 6 إلى 8 كورسات في الرياضيات. علوم الحاسب يقوم بحل المشاكل المعقدة بوضع حلول رائعة مناسبة باستخدام التحليل الرياضي والخوارزميات باستخدام لغات برمجة وأيضا يركّز على كيفية حماية برنامج معين أو شبكه معينة. علوم الحاسب يشكل القاعدة الأساسية لهندسة البرامج، كما الفيزياء تشكل القاعدة الأساسية للهندسة الكهربائية وهو تخصص لا غنى عنه في جميع المجالات فهو غير مقتصر على بناء البرامج البسيطة بل له دخول في جميع العلوم الموجودة وله تواجد في جميع التخصصات مثل الإدارة والطب والأحياء والهندسة وعلم الفلك والجيولوجيا واللغة وجميع التخصصات ويوجد مسارات tracks |or| Concentrations داخل تخصص علوم الحاسب بمعنى يتخصص الطالب بأحد المسارات في دراسته وعادة تكون في مرحلة الماجستير اكثر من البكالوريوس أمثله على المسارات Artificial Intelligence Communication Networks Computer Architecture and Compilers Cryptography and Security web development 2- هندسة الحاسب Computer Engineering يهتم هذا التخصص في تجميع الأجهزة ومكونات البرامج لنصب استعمال الحاسبات بأفضل خصائص يركز هذا التخصص على تكوين الهاردوير Hardware وكيفية عملة داخل الجهاز وكيفية التعامل مع المعالج Processor و الذاكرة Memory و الدائرة الكهربائية Electric circuit طلاب هندسة الحاسب يأخذون بعض الكورسات من تخصص Computer Science مثل البرمجة و قواعد البيانات وغيرها في بعض الجامعات هندسة الحاسب تدخل ضمن أنظمة كلية الهندسة ويوجد مسارات tracks |or| Concentrations داخل تخصص هندسة الحاسب بمعنى يتخصص الطالب بأحد المسارات في دراستة وعادة تكون في مرحلة الماجستير اكثر من البكالوريوس أمثله على المسارات Computer Architecture & Embedded Systems Computer Aided Design Computer Networks & Communication Systems ويوجد مسارات أخرى كثيرة الآن نأتي إلى الفرق: الفرق بين تخصص الحاسب التربوي والهندسي؟ - التربوي يركز على كيفية استخدام الحاسب : استخدام ويندوز - اوفيس Word Excel Access - فوتوشوب - وتعريف طابعات وغيرها من كيفية استخدام البرامج الجاهزة - الهندسي يركز على كيفية بناء الحاسب وبناء انظمة له : من برمجة وتحليل وبناء شبكات وأمن برامج وغيرها. علوم الحاسب يهتم في دراسة أساسيات علم الحاسب والبرمجة لذلك يركز هذا التخصص على كورسات البرمجة والخورازميات والتحليل الرقمي والنظريات في علم الحاسب - هندسة الحاسب تعمل على بناء الهاردوير. لكن لا تقتصر على الهاردوير فقط بل تشارك في بناء الأنظمة الأساسية داخل معظم الأجهزة مثل أنظمة الحاسب الداخلة في السيارات والطيارات والإلكترونيات والهواتف والشبكات وغيرها . وفي كثير من الأحيان يعمل مهندس الحاسب كمبرمج. أما القسم الذي يهتم بالبرمجة بشكل كبير هو علوم الحاسب ننصحك بالرجوع إلى مقال المدخل الشامل لتعلم علوم الحاسوب تحديدًا قسم ما الفرق بين علوم الحاسوب وهندسة الحاسوب؟ ففيه تفاصيل أوسع ويتحدث المقال بعمق عم مجال علوم الحاسوب. وبالتوفيــــــــــــــــــــــق1 نقطة
-
يمكن القول إن TypeScript تشمل ES6 وتزيد عليها الأنواع، في النّهاية تبقى TypeScript لغة غير معياريّة على الرّغم من كونها مفتوحة المصدر وتطويرها مُتعلّق بمصالح الشّركة المُطوّرة لها (Microsoft)، أمّا ES6 فهي اللغة المعيارية الّتي نضمن أنّها ستكون متاحة دومًا في المستقبل.1 نقطة
-
التفكيك Destructuringذكرنا في الجزء السابق أن اهتمامًا كبيرًا أُوليَ لتسهيل كتابة الشفرة وقراءتها في ECMAScript 6، والإسناد بالتفكيك (Destructuring assignment) لا يخرج عن هذا السياق، وهو ليس بالمفهوم الجديد في عالم البرمجة، فهو معروف في Python وفي Ruby. بعيدًا عن تعقيدات المصطلحات، إليك هذا المثال: var [a, b, c] = [1, 2, 3]; a == 1 // true b == 2 // true c == 3 // trueما الذي يحدث هنا؟ بكل بساطة تسمح ECMAScript 6 بصياغة جديدة للتعريف عن المتغيرات أو إسناد قيم جديدة إليها جُملةً واحدة من خلال جمعها ضمن قوسي مصفوفة (Array) وسيقوم مُفسّر اللغة بإسناد قيمة مقابلة لكل متغيّر من المصفوفة الواقعة على يمين مُعامل الإسناد (=). الأمر لا يقتصر على إسناد المصفوفات، بل يمكن أيضًا إسناد خصائص العناصر: let person = { firstName: "John", lastName: "Smith", Age: 42, Country: "UK" }; let { firstName, lastName } = person; console.log(`Hello ${ firstName } ${ lastName }!`); // Hello John Smith!في هذا المثال لدينا متغيّرات تتبع للنطاق العامّ firstName وlastName، وقد أسندنا لها قيمًا من خصائص الكائن person، حيث يبحث مفسّر اللّغة عن خصائص في الكائن person يماثل اسمها اسم المتغيّر المفروض ويُسندها إلى المُتغيّرات. يمكن توضيح المقصود بصورة أفضل إذا أعدنا كتابة الشفرة لتتوافق مع الإصدار الحالي من JavaScript: var person = { firstName: "John", lastName: "Smith", Age: 42, Country: "UK" }; var firstName = person.firstName; var lastName = person.lastName; console.log("Hello " + firstName + " " + lastName + "!"); // Hello John Smith! يشيع استخدام التفكيك في CoffeeScript (وهي لغة أقر Brendan Eich مُخترع JavaScript بأنّ الإصدار الأخير من JavaScript استوحى الكثير منها)، وخصوصًا عندما تُنظّم البرامج في وحدات كما في Node.js ويكون اهتمامًا مُقتصرًا على استيراد جزء مُحدّد من الوحدة المعنيّة: { EventEmitter } = require 'events' { EditorView } = require 'atom' { compile } = require 'coffee-script' compile('# coffeescript code here');عند تحويل هذا النص إلى JavaScript الحالية، سنحصل على: var EventEmitter = require('events').EventEmitter; var EditorView = require('atom').EditorView; var compile = require('events').compile; compile('# coffeescript code here');من الاستخدامات المفيدة للإسناد بالتفكيك التبديل بين قيمتي متغيّرين بصورة سهلة، سنقتبس المثال من توثيق CoffeeScript ونُعيد كتابته بـJavaScript: var theBait = 1000; var theSwitch = 0; [theBait, theSwitch] = [theSwitch, theBait];قبل ES6 كنا لنحتاج لكتابة مُتغيّر مؤقّت نخزن فيه قيمة إحدى المتغيّرين للاحتفاظ بها قبل التبديل بين القيمتين، وهو ما يفعله محوّل CoffeeScript بالفعل ليعطينا شفرة JavaScript متوافقة مع الإصدار الحالي (مع أنه يقوم بتخزين كلا القيمتين في مصفوفة، إلا أنّ الفكرة تبقى ذاتها): var theBait, theSwitch, _ref; theBait = 1000; theSwitch = 0; _ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1];المُكرِّرات (Iterators) وحلقة for... ofما من لغة برمجة تخلو من وسيلة للمرور على عدد من القيم وتكرار تنفيذ عمليّة معيّنة على هذه القيم، من أبسط هذه الوسائل حلقة for التقليديّة الشّهيرة، وفي JavaScript يشيع استخدام حلقة for... in إلى جانبها للمرور على أسماء خصائص العناصر، إذ يمكننا معرفة كل خصائص العنصر document بسطرين فقط: for (var propertyName in document) { console.log(propertyName); } // "body" // "addEventListener" // "getElementById" // ...لاحظ أن حلقة for... in تُعيد أسماء خصائص العنصر (كسلسة نصيّة String)، والأمر لا يستثني المصفوفات، فهي ليست سوى كائنات بأسماء خصائص توافق رقم الفهرس (Index): for (var i in [1, 2, 3]) { console.log(i); } // "0" // "1" // "2" من عيوب حلقة for... in أن لا شيء في تعريف اللغة يُجبر مُفسّر اللّغة على إخراج العناصر بترتيب ثابت بالضّرورة، وهذا يعني أنها تصبح مباشرة غير صالحة للمرور على المصفوفات - التي تستخدم لحفظ عناصر مُرتّبة - بطريقة بديهيّة، ويحلّ محلّها حلقة for التقليديّة عندئذٍ، وأمّا عند استخدامها للمرور على الكائنات، فإنّها لا تُعيد إلّا الخصائص الّتي تُعرّف على أنها قابلة للتعداد (enumerable)، وهو شيء يُحدّد عند تعريف الخاصّة، كما أنّها تُعيد الخصائص القابلة للتعداد التي ورثها الكائن عن "آباءه" ضمن سلسلة الوراثة، وهو تصرّف قد لا يكون مرغوبًا دومًا، وغالبًا سترى المطوّرين يُجرون فحصًا للخاصّة قبل متابعة تنفيذ الشفرة لمعرفة ما إذا كانت تخصّ العنصر ذاته أمّ أنّه ورثها: var obj = { a: 1, b: 2, c: 3 }; // كائن جديد لا يرث سوى النموذج Object for (var prop in obj) { console.log("o." + prop + " = " + obj[prop]); } // "o.a = 1" // "o.b = 2" // "o.c = 3"في هذا المثال (المنقول عن شبكة مطوّري موزيلا) فرضنا كائنًا جديدًا بثلاث خصائص، وعند المرور عليه بحلقة for... in فإنّنا حصلنا على النتيجة المتوقّعة، ولم نحصل على خصائص إضافيّة لأنّ الكائن الذي فرضناه لا يرث أي كائن آخر سوى Object (الذي ترثه كل الكائنات افتراضًا). أما في المثال التالي، فقد احتجنا لإجراء اختبار hasOwnProperty على العنصر الوارث لكي لا تظهر سوى الخاصة color التي يملكها بذاته ولم يرثها: var triangle = {a: 1, b: 2, c: 3}; function ColoredTriangle() { this.color = "red"; } ColoredTriangle.prototype = triangle; var obj = new ColoredTriangle(); for (var prop in obj) { if (obj.hasOwnProperty(prop)) { console.log("o." + prop + " = " + obj[prop]); } } // Output: // "o.color = red"حسنًا، لقد أطلنا الحديث عن حلقة for... in وهي ليست بالجديدة؛ لكنّنا أصبحنا نرى الحاجة لشيء جديد أكثر بساطة ومرونة، فهذا ما تبتغيه ES6 في النهاية، ولهذا نشأت فكرة المُكرّرات؛ التي تسمح لأي عنصر بأن يختار لنفسه الطّريقة التي يتصرّف بها عند المرور به في حلقة، ومع المُكرّرات لا بدّ من نوع جديد من الحلقات لتلبية هذه الحاجة والمحافظة على حلقة for... in للتوافق مع الإصدارات القديمة. من هنا نشأت حلقة for... of الجديدة. for (var num of [1, 2, 3]) { console.log(num); } // 1 // 2 // 3 for (var node of document.querySelectorAll('a')) { console.log(node); } // <a class="title" href="/"> // <a class="contact" href="/contact/">حصلنا في المثالين السابقين على قيمة الخاصّة وليس اسم الخاصّة، لكنّ هذا لا يعني أنّ حلقة for... of تُعيد قيم الخصائص دومًا، بل إنّها تستدعي مُكرّر الكائن (Iterator) وتطلب منه في كلّ دورة للحلقة تزويدها بشيء ما، وتترك للمُكرّر الحُريّة بإعادة أي قيمة يرغب بها، ولكن ولأنّنا نستدعي في مثالنا مصفوفة أولاً، وعنصر من نوع NodeList ثانيًا، وكلا النّوعين يُعيد مُكرُّرهما قيمَ العناصر في المصفوفة، فإنّنا نحصل على تلك النتيجة البديهيّة. بإمكاننا إنشاء أصناف بمُكرّرات خاصّة نُنشئها بأنفسنا، ولنفترض أن لدينا نوعًا لصفّ ضمن مدرسة ابتدائية، ونريد أن نحصل على تفاصيل الطلّاب على هيئة نص مُنسّق عند المرور على الصّفّ في حلقة for... of: function SchoolClass(students) { this.students = students; } SchoolClass.prototype[Symbol.iterator] = function*() { for (let i = 0; i < this.students.length; i++) { let student = this.students[i]; yield `#${i+1} ${student.name} (${student.age} years old)`; } } var ourClass = new SchoolClass([ { name: "Ahmed", age: 10 }, { name: "Alaa", age: 9 }/*, ...*/ ]); for (student of ourClass) { console.log(student); } // "#1 Ahmed (10 years old)" // "#2 Alaa (9 years old)" // "#3 ..." ...استخدمنا الرمز الخاص Symbol.iterator لإسناد دالّة مُولّد (Generator function) التي تُعطينا عند استدعائها نسخة من مُكرّر الصنف المُخصّص الذي أنشأناه. سنتعرف بعد قليل على المُولّدات (Generators) وكذلك على الرموز (Symbols) في وقت لاحق. لاحظ أنّنا استخدمنا حلقة for... of للمرور على محتويات ourClass. تذكّر أنّنا استخدمنا هذه الحلقة في الجزء السابق مع Array Comprehension، كما في المثال: let people = ["Samer", "Ahmed", "Khalid"]; console.log([`Hello ${person}` for (person of people)]); إن كانت الفقرة الأخيرة غامضة بعض الشيء فلا تقلق، سنتوسّع بشرح المولّدات بعد قليل. لكن دعونا نتوقّف قليلاً ولننتقل إلى الجانب الفلسفي لهذه الإضافات في JavaScript، قد تبدو للوهلة الأولى تعقيدات بلا طائل، خصوصًا وأنّ كثيرًا منها لا يهدف سوى للتسهيل، ولا يقدّم شيئًا يستحيل إنجازه بالإصدارات السابقة من اللغة؛ هنا يمكن الرّدّ بأنّ تطوّر اللغة متعلّق بكيفيّة استخدامها والخبرة التي تُكتسب مع مرور السّنين، حيث تظهر للمطوّرين حاجات جديدة وأفكار تطبّق مرارًا لدرجة أنها ترتقي لتصبح ضمن أساسات اللغة. سهولة كتابة الشفرة لم تعد رفاهية، بل هي ضرورة لإنجاز المشاريع الكبيرة لأنّها تتيح اختصار الوقت الذي كان سيضيع في كتابة متكرّرة ومُملّة، كما أنّها تُلبّي ما يتوقّعه المطوّرون من لغة أصبحت تؤخذ على محمل الجدّ وتُستخدم في تطوير تطبيقات ضخمة ومُعقّدة بعد أن كان جُلّ استخدامها تنفيذ بعض المهام البسيطة. المُولِّدات (Generators)المولدات (Generators) ببساطة هي دوال يمكن إيقافها والعودة إليها في وقت لاحق مع الاحتفاظ بسياقها دون تغيير، صياغة دوال المولدات لا تختلف كثيرًا عن صياغة الدوال التقليدية، كل ما عليك هو إضافة إشارة * بعد function واستخدام yield بدل return، المثال التالي سيوضح فكرة المولدات أكثر: function* getName() { let names = ['Muhammad', 'Salem', 'Abdullah']; for (name of names) { yield name; } } let nameGenerator = getName(); nameGenerator.next().value; // 'Muhammad' nameGenerator.next().value; // 'Salem' nameGenerator.next().value; // 'Abdullah' nameGenerator.next().value; // undefined } ما الذي يحدث هنا؟ فرضنا دالّة مولّد (Generator function) (والتي تُميّز بإشارة النجمة *) سمّيناها getName، وفيها صرحنا عن مصفوفة فيها أسماء، وظيفة هذه الدالة أن تعطينا عند استدعائها نسخة من مُكرّر (Iterator) (الذي شرحناه لتوّنا)، يزوّدنا بالأسماء بالترتيب في كل مرة نستدعيه فيها ليعطينا النتيجة التالية (next())، أولاً يجب حفظ نسخة المُكرّر ضمن متغير لكي نسمح له بحفظ حالته، ودون ذلك سيعطينا استدعاء دالّة المولد مباشرةً getName().next() دوماً النتيجة الأولى لأننا عملياً نُنشئ نسخة جديدة عنه في كل مرة نستدعيه، أما استدعاء نسخة عنه وحفظها في متغير مثل myGenerator فيسمح لنا باستدعاء .next() عليها كما هو متوقع. لا ترجع الدالة .next() القيمة التي نرسلها عبر yield فقط، بل ترجع كائناً يحوي القيمة المطلوبة ضمن الخاصة value، وخاصة أخرى done تسمح لنا بمعرفة ما إذا كان المولد قد أعطانا كل شيء. لنُعِدْ ترتيب أفكارنا: المولّدات تسمح بتوقّف تنفيذها مع الاحتفاظ بحالة التنفيذ (يحدث توقّف التنفيذ عند كلّ كلمة yield). فلو أنّنا كتبنا دالّة تقليديّة في المثال أعلاه مع return بدل yield لحصلنا في كلّ مرّة على الاسم الأول (Muhammad). وهذه الميزة في المولّدات يمكن استغلالها لإنشاء حلقات لا نهائية دون إعاقة متابعة البرنامج: function* numberGenerator() { for (let i = 0; true; i++) { yield i; } } let numGen = numberGenerator(); numGen.next(); // { value: 0, done: false } numGen.next(); // { value: 1, done: false } numGen.next(); // { value: 2, done: false } numGen.next(); // { value: 3, done: false } // ...دوالّ المولّدات تُعطي عند استدعاءها مُكرّرات، وهذا يعني إمكانيّة استخدامها في حلقة for... of (احذر من تطبيق مثال كهذا على مولّد غير منتهٍ كما في المثال السابق!): for (let name of getName()) { console.log(name); } // "Muhammad" // "Salem" // "Abdullah"لكلّ مُكرّر وظيفة .next() مهمّتها بدء تنفيذ الدّالّة أو متابعة تنفيذها ثم إيقافها مؤقّتًا عند كلّ كلمة yield. استدعاء next() على المُكرّر يعيد لنا في كلّ مرة كائنًا ذا خاصّتين: الأولى value وهي أيّ شيء نُعيده بكلمة yield، والثّانية done وهي قيمة منطقيّة (Boolean) تشير إلى حالة انتهاء تنفيذ الدّالة.تقبل الوظيفة .next() للمُكرّرات مُعاملاً اختياريًّا تستقبله وتُرسله لدالّة المولّد بعد متابعة التنفيذ، ويمكن استخدامها لإرسال رسائل لدالّة المولّد بحيث نؤثّر في تنفيذه: function* numberGenerator() { for (let i = 0; true; i++) { var reset = yield i; if (reset) i = -1; } } let numGen = numberGenerator(); numGen.next(); // { value: 0, done: false } numGen.next(); // { value: 1, done: false } numGen.next(); // { value: 2, done: false } numGen.next(); // { value: 3, done: false } numGen.next(true); // { value: 0, done: false }في هذا المثال مرّرنا القيمة true إلى الوظيفة .next() على المُكرّر، والذي بدوره يُرسلها لدالّة المولّد كنتيجة yeild i في الدّورة الموافقة للحلقة، لنقومَ بحفظها في متغيّر reset ونُجريَ فحصًا عند متابعة التنفيذ لإعادة تعيين قيمة i، التي ستزداد بمقدار واحد مع بدء الدورة التالية لحلقة for جاعلةً قيمة i مساوية للصّفر. خصائص المولّدات تجعلها مناسبة جدًا لكتابة شيفرة غير متزامنة بصورة أسهل تكاد تبدو فيها وكأنها شيفرة متزامنة خالية من الاستدعاءات الراجعة المتداخلة (Nested callbacks)؛ هذه الفكرة تحتاج إلى تركيز لأنها أساس لعدد من المكتبات مثل co وsuspend التي ظهرت مؤخّرًا وتصاعدت شعبيّتها بسرعة لأنّها تحلّ مشكلة جوهرية في استخدام JavaScript، ألا وهي التعامل مع الدوال غير المتزامنة (asynchronous functions) وذلك بالاعتماد كُليًّا على المُولّدات. لنفترض أنّ لدينا موقعًا لقراءة الكتب يعرض ملفّ المستخدم الشّخصيّ مع عدد الكتب التي قرأها وعنوان آخر كتاب مع تقييم المستخدم له: var list = document.querySelector("#book-list"); getJSON("http://reading-website.com/users/fwz.json", function(err, user) { if (err) return; // افعل شيئًا بما بخصوص الخطأ var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json", function(err, user_rating) { getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json", function(err, book) { var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.textContent = user.full_name; var h3 = document.createElement("h3"); h3.textContent = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.textContent = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); }); }); })في المثال السابق احتجنا إلى إرسال 3 طلبات AJAX يعتمد أحدها على الآخر، ولأنّنا لا نستطيع إرسال طلب بتقييم المستخدم للكتاب قبل معرفة مُعرّف الكتاب، فلا بدّ من أن يرسل الطلب الخاصّ بتقييم الكتاب ضمن الاستدعاء الرّاجع لطلب معلومات المستخدم، ثمّ يمكن جلب عنوان الكتاب ضمن الاستدعاء الرّاجع للطلب السّابق، وهذا يعني زيادة تعقيد الشفرة مع تداخل الاستدعاءات الرّاجعة لتبدو أشبه بسباغيتي لا تُعرف بدايته من نهايته. تخيّلوا -لغرض التّخيّل- لو أمكننا كتابة هذه الشفرة (وهي غير متزامنة) لتبدو لقارئها وكأنها نص برمجي يسير بترتيب متزامن وبديهيّ... ألن يكون هذا أعظم شيء منذ اختراع JavaScript؟ var list = document.querySelector("#book-list"); try { var user = getJSON("http://reading-website.com/users/fwz.json"); var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; var user_rating = getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json"); var book = getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json"); var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.innerText = user.full_name; var h3 = document.createElement("h3"); h3.innerText = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.innerText = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); } catch (e) { // افعل شيئًا بما بخصوص الخطأ // Error } نحن نعلم أن الأمور لا يمكن أن تكون بهذه الروعة، وأنّ الشفرة أعلاه لن تعمل... نحن نعلم أن شيفرتنا تحتاج تفاصيل المستخدم للحصول على الكتب، وأننا نحتاج للكتاب لجلب عنوانه وتقييمه، وتنفيذ هذه المهمّات بشكل غير متزامن لا يعني أنّه ليس علينا انتظار المهمّة الأولى قبل إطلاق الثانية - بل يعني فقط أن المتصفح يمكنه تنفيذ رسم العناصر الأخرى وعرض الصفحات وإرسال طلبات أخرى في هذا الوقت. حسنًا، لدي خبر جيّد وآخر سيئ: أمّا الجيّد فهو أنّنا كتابة شيفرة شبيه بهذه أصبحت قريبة المنال مع الدّوالّ غير المتزامنة (Async Functions)، وأمّا الخبر السيّئ فهو أنّ علينا الانتظار إلى الإصدار 7 من ECMAScript لنستطيع كتابتها! (مع العلم أن المتصفّحات لم تنتهِ من تطبيق ES6!). لكن هذا لا يعني أن نقف مكتوفي الأيدي إلى أن تصدر ES7، بل بإمكاننا إيجاد حلّ وسط لهذه المشكلة؛ لماذا نضطّر إلى تعقيد الأمور بالاستدعاءات الرّاجعة المتداخلة؟ ألا يتوفّر في اللّغة بنية برمجيّة تسمح بإيقاف شيفرتنا ريثما يتمّ أمر ما غير متزامن (الانتظار لإكمال طلب AJAX) ثمّ المتابعة بعد انتهاءه؟ يبدو هذا الحديث مألوفًا! نعلم حتى الآن أننا بحاجة لاستخدام مولّد، ولذلك سنحيط شيفرتنا بدالّة مولّد كخطوة أولى: var list = document.querySelector("#book-list"); function* displayUserProfile() { // شيفرتنا هنا } الآن نحتاج لتنفيذ طلب AJAX الأوّل والانتظار إلى انتهاءه قبل الانتقال إلى الطّلب الثّاني، نعلم أنّ yield توقف تنفيذ المولّد: var list = document.querySelector("#book-list"); function* displayUserProfile() { yield getJSON("http://reading-website.com/users/fwz.json"); // ... } عظيم! لكن كيف نُخبر المولّد بأنّ عليه متابعة التنفيذ؟ var list = document.querySelector("#book-list"); function* displayUserProfile() { yield getJSON("http://reading-website.com/users/fwz.json", resume); // ... } سنمرر دالة اسمها resume للدّالة getJSON، وهذه الدالة ستُستدعى عند انتهاء جلب جواب الطّلب الذي أرسلناه، وهي فرصتنا لإخبار المولّد بمتابعة التنفيذ... فكيف سيكون محتواها؟ var list = document.querySelector("#book-list"); var resume = function(err, response) { displayIterator.next(response); } function* displayUserProfile() { var user = yield getJSON("http://reading-website.com/users/fwz.json", resume); var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; var user_rating = yield getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json", resume); var book = yield getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json", resume); var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.innerText = user.full_name; var h3 = document.createElement("h3"); h3.innerText = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.innerText = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); } var displayIterator = displayUserProfile(); displayIterator.next(); حفظنا نسخة عن المكرّر في متغيّر ثم استدعينا وظيفته next() في دالّة المتابعة، ممرّرين لها جواب الطّلب ليمكننا تخزينه ضمن المتغيّر user. الدّالة resume تستطيع الوصول إلى displayIterator لأنّه يكون معرّفًا قبل استدعاءها حتمًا، ولا ننسَ أن تعريف المتغيّرات في JavaScript يخضع لعملية الرّفع إلى أعلى النّطاق (variable hoisting) ممّا يجعل المتغيّر displayIterator موجودًا (وإن كان بلا قيمة) منذ بداية تنفيذ الشيفرة. للتأكّد من فهم هذه الشيفرة، سنعيد تحليلها خطوة بخطوة: في طلب AJAX الأوّل تستدعى الدالة resume ويمرّر إليها جواب الطّلب (response)، الذي يمرّر بدوره إلى المُكرّر ليُحفظ في المتغيّر user الذي سيُستخدم في الخطوة التّالية للمولّد لإرسال الطّلب الثّاني. تُكرّر العمليّة ذاتها للطلبين الآخرين ثمّ تُعرض النتائج في الصّفحة. الفائدة التي جنيناها من استخدام المولّدات هي التخلّص من تعقيد الاستدعاءات الرّاجعة نهائيًّا وتحويل شيفرة غير متزامنة وجعلها تبدو وكأنّها متزامنة. ذكرنا القليل عن مكتبات مثل co وsuspend، لكنّها باختصار تعمل بطريقة مماثلة جدًا لمثالنا الأخير: var suspend = require('suspend'), resume = suspend.resume; suspend(function*() { var data = yield fs.readFile(__filename, 'utf8', resume()); console.log(data); })();هذه المكتبات خطوة نحو مستقبل JavaScript، الذي بدأ يتشكل مع مشروع الدّوال غير المتزامنة باستخدام الكلمتين المفتاحيتين الجديدتين async وawait اللّتان ستتوفّران في الإصدار السّابع وتستندان في عملهما إلى أرضيّة الوعود (Promises) الّتي تتوفّر اليوم في ES6. سيكون بإمكاننا كتابة هذه الشيفرة بدل الاعتماد على المولّدات: async function displayUserProfile() { var user = await getJSON("http://reading-website.com/users/fwz.json"); var num_books = user.books.length; var most_recent_book_id = user.books[num_books - 1]; var user_rating = await getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json"); var book = await getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json"); var fragment = document.createDocumentFragment(); var h2 = document.createElement("h2"); h2.innerText = user.full_name; var h3 = document.createElement("h2"); h2.innerText = "الكتب التي قرأها"; for (let book of books) { let li = document.createElement("li"); li.innerText = book.title + (book.id == most_recent_book_id ? " " + user_rating : ""); fragment.appendChild(li); } list.appendChild(fragment); }في هذا المثال يجب على getJSON أن تُعيد وعدًا Promise ليستطيع مُفسّر اللّغة انتظاره إلى أن يُحقّق (resolve) أو يُرفض (reject)، والقيمة الّتي تُحقّق تُحفظ ضمن المُتغيّر user، وأما عند رفض الوعد يُرمى خطأ (throw) يمكن تلقّيه (catch) كما في الشيفرة غير المتزامنة. مُعامِل البقيّة (Rest parameter) والناشرة (Spread)بعد كلّ هذا الكلام المُعقّد عن الأشياء غير المتزامنة التي نريد جعلها تبدو متزامنة وما إلى ذلك، سنختم الجزء الثّاني بفكرتين بسيطتين أُضيفتا إلى ECMAScript في الإصدار السّادس وتحلّان مشكلتين شائعتين في كثير من اللّغات البرمجيّة: أمّا الأولى فهي الحاجة إلى تنفيذ نصّ برمجيّ ضمن دالة على عدد غير معروف من المُعاملات، فلنفترض أنّ لدينا دالة تجمع عددين: function add(n1, n2) { return n1 + n2; } add(1) // 1 add(1, 2) // 3ونظرًا لكوننا مبرمجين أذكياء فقد قرّرنا جعل الدّالة تقبل أي عددين أو ثلاثة أو أكثر... لنجعلها تقبل عددًا لا نهائيًّا من الأعداد؛ في الإصدار الحالي سنلجأ إلى استخدام الكائن الخاصّ arguments المتوفّر ضمن نطاق كلّ دالّة تلقائيًا: function add() { return [].reduce.call(arguments, function(memo, n) { return memo + n; }); } add(1) // 1 add(1, 2) // 3 add(1, 2, 3) // 6 حسنًا لقد اضطررنا إلى "استعارة" دالة الاختزال من مصفوفة فارغة لتطبيقها على الكائن الخاص arguments الذي يُعتبر "شبيه مصفوفة" ولا يملك ما تمتلكه المصفوفة من دوالّ، لماذا لا يمكننا كتابة هذا فحسب: function add(...numbers) { return numbers.reduce(function(memo, n) { return memo + n; }); } add(1) // 1 add(1, 2) // 3 add(1, 2, 3) // 6 وأمّا الفكرة الثّانية فهي تكاد تكون عكس السّابقة، فإذا كانت الأولى تجمع بقيّة المعاملات في كائن مُفرَد، فإنّ هذه "تَنشر" محتويات المصفوفة إلى عناصرها المكوّنة لها، ماذا لو لم نكن أذكياء وعجزنا عن الإتيان بدالة تجمع عددًا غير منتهٍ من الأرقام: function addThreeNumbers(n1, n2, n3) { return n1 + n2 + n3; } var myNumbers = [1, 2, 3]; addThreeNumbers(...myNumbers); // 6 لاحظ أن صياغة النشر (Spread) تطابق تمامًا صياغة البقيّة (Rest)، والاختلاف في السّياق فقط. لاحظ أيضًا أنّ معامل البقيّة، وكما يوحي اسمه، يمكن استخدامه لتجميع ما تبقى من مُعاملات الدّالة فقط: function addThreeOrMoreNumbers(n1, n2, ...numbers) { return n1 + n2 + numbers.reduce(function(memo, n) { return memo + n; }); } addThreeOrMoreNumbers(1, 2, 3); // 6في الجزء القادم سنتعرف بمشيئة الله على الوحدات (Modules) التي تُعتبر طريقة جديدة لتنظيم الشفرة اُستلهمَت من عالم Node.js وrequire.js، وسُنلقي نظرة على الأصناف (Classes)، المكوّن البرمجيّ الذي وجد طريقه أخيرًا إلى JavaScriptّ! المصادرشبكة مطوّري موزيلّاGoing Async With ES6 GeneratorsReplacing callbacks with ES6 Generators Iterators gonna iterate1 نقطة
-
هناك اعتقاد خاطئ بين العديد من مطوّري الويب أن CSS هي الطريقة الوحيدة للقيام بالتحريك Animations. برزت CSS بقوة كأكثر نظام مُدلّل خلال سنوات وحتى الآن، وتحدّث المطوّرون باستمرار حول متانتها وتوافقها مع الهواتف. CSS جيدة لكنها ليست أفضل من جافاسكربت. هناك بعض الأساطير حول CSS قادت المطوّرين بشكل استباقي لتبنّي هذا النظام (أي CSS) والتخلي عن كلٍّ من جافاسكربت والتحريك الخاصّ بها. ما يحيّر المطوّرين هو الاستخدام الفعّال لـ CSS، فبينما يقومون بإدارة التفاعل بين العناصر من داخل CSS، يُجبرون أنفسهم أيضًا على جعل مشروعهم متوافقًا مع Internet Explorer 8 و 9، وأخيرًا، يتجنّبون التحريك الجاهز الذي لا يتوفر إلا عن طريق جافاسكربت. يهدف هذا المقال لدحض بعض الأساطير حول CSS وكشف جميع المشكلات الأساسية التي بالكاد يتحدث الناس عنها بسبب إعجابهم بـ CSS. يهدف المقال إلى رفع معرفتك ووعيك بفوائد استخدام جافاسكربت، حتى يمكنك بسهولة التخلص من المواقف التي تثير غضبك. لذا، دون مزيد من الإطالة دعونا نتناقش حول كل منهم بالتفصيل. 1. jQueryدعونا نبدأ بالأساسيات، يتم الربط بين جافاسكربت ومكتبة jQuery بشكل خاطئ . فالتحريك المُصمَّم بجافاسكربت سريع ومرن، بينما المُصمَّم بـ jQuery بطيء. السبب أنه -وبرغم مواصفات jQuery القوية- إلّا أنها لم تهدف بشكل رئيسي لتنفيذ التحريك. هناك العديد من الأسباب لدعم هذا: لا يمكن لمكتبة jQuery ببساطة أن تتوقف عن تحطيم التنسيق (layout thrashing) نظرًا لهيكلتها البرمجية التي تخدم عددًا من الأغراض غير التحريك. هذا بشكل عام يسبب تقطّعًا في المراحل الأولى من التحريك.الذاكرة التي تستهلكها jQuery دومًا تُحفّز جمع القمامة (Garbage Collection) مما يؤدي إلى تجمّد التحريك. ونظرًا لـ جمع القمامة، يمكن للمرء أن يواجه تقطّعًا أثناء التحريك.تم تصميم jQuery لتستخدم set_interval() وليس طلب إطار الحركة (Request Animation Frame - RAF). عندما لا يكون طلب إطار الحركة موجودًا فيمكن أن ينتج عن ذلك تحريكات بعدد إطارات منخفض (جودة ودقة الحركة تعتمد على عدد الإطارات التي تظهر في الثانية، العدد المثالي هو 60 إطار في الثانية).2. فقر التحكم بالتدوير وتحديد الموقعمن الميزات الأساسية الضرورية أثناء تنفيذ التحريك استخدام المُتحكّمات لـ: التحريك animation، التدوير rotation، وتحديد المواقع positioning. في CSS، تم تكديس جميع هذه الدوالّ في خاصية معروفة هي tranform. هذه الخاصية تُسبب مشاكل عند تحريك شيء ما بطريقة فريدة عن طريق عنصر مشترك. على سبيل المثال، إن أردت تحريك “التدوير” rotation و”التكبير” scaling بشكل منفصل، وباستخدام أزمنة مختلفة، فهذا مُمكن فقط عن طريق جافاسكربت ﻷنها تُمكّنك من استخدام حِيل متنوعة، وهذه غير مسموحة في CSS. هذه إحدى مساوئ CSS. هي جيدة من أجل مشاريع تتطلب تحريكاتٍ بسيطة وليس من أجل المشاريع التي تتطلب اندماج تصميمٍ بتحريكاتٍ كبيرة. 3. الأداء مع مكتبتيّ Velocity وGSAPVelocity وGSAP هما المكتبتان الرائدتان والأكثر شعبية في جافاسكربت. كلاهما يعمل مع jQuery أو بدونها. عندما يتم استخدام أيّ من هاتين المكتبتين جنبًا إلى جنب مع jQuery فلا يحدث أي تدهورأو تباطؤ في الأداء لأنهما تعملان بشكل منفصل عن تحريكات jQuery الأساسية. ويمكن أيضًا استخدام هاتين المكتبتين بسهولة عندما لا تكون مكتبة jQuery موجودة في الصفحة. وهذا يبين بوضوح أنه بدلًا من ربط جميع أنواع استدعاءات التحريك في عنصر jQuery مشترك يمكنك بشكل مباشر تمرير العنصر المراد إلى استدعاء الحركة كما هو مبين أدناه. /* Working without jQuery */ Velocity(element, { opacity: 0.4 }, 900); // Velocity TweenMax.to(element, 1, { opacity: 0.4 }); // GSAP في المثال السابق، يمكنك ملاحظة أن Velocity تستخدم نفس الصيغة حتى عندما لا يتم استخدام jQuery. قامت فقط بإزاحة جميع العناصر تجاه اليمين لترك مساحة للعناصر المستهدفة. بالمقابل، في GSAP تم استخدام تصميم كائني التوجه (عن طريق استدعاء كائن يمثّل صنف Class) جنبًا إلى جنب مع دالّة بسيطة ثابتة. بهذه الطريقة المستخدم يملك تحكم كامل بعملية الحركة. 4. معامل وحدة معالجة الرسوم (GPU)عندما تكون وحدة معالجة رسوم GPU مُحسّنة بشكل كامل، عندها ستكون عظيمة لأداء مهام متنوعة كالتعامل مع مصفوفات التحويل transform والشفافية opacity، لهذا السبب أول ما تفعله جميع المتصفحات المتطورة هو تفريغ هذه المهام من وحدة المعالجة المركزية CPU إلى وحدة معالجة الرسوم GPU. الهدف الأساسي هو فصل جميع العناصر المتحركة إلى طبقات خاصة بها في وحدة معالجة الرسوم ﻷنه فور انتهاء النظام من إنشاء طبقات وحدة معالجة الرسوم لا يُظهر النظام أي اهتمام بتحريك البكسلات وجمعهم معًا. لهذا لا يوجد حاجة بتاتًا لحساب كل بكسل بشكل مستمر. بدلًا من ذلك يمكن تحريك بكسل واحد فوق الآخر وتوفير كثير من الوقت. ملاحظة: لا حاجة لإعطاء كل عنصر طبقته الخاصة نظرًا لذاكرة الفيديو المحدودة في وحدة معالجة الرسوم. فإذا نفدت الذاكرة سيفسد كل شيء. من ناحية أخرى، إذا قمتَ بتعريف التحريك في CSS عندئذ ستكون مهمة المتصفح أن يحدد طبقة وحدة معالجة الرسوم لكل عنصر وسيحصل انقسام بسبب ذلك. مع ذلك، يمكنك عمل هذا الأمر عن طريق جافاسكربت أيضًا. كل ما تحتاجه هو تحديد التحويل transform مع خاصية 3D (تمامًا مثل translate3d() و matrix3d()) لجعل المُتصفح يعلم عن عملية إنشاء طبقات وحدة معالجة الرسوم للعنصر المستهدف. لذا، فمن الواضح أن وحدة معالجة الرسوم لا توفّر زيادة السرعة لـ CSS فقط بل لجافاسكربت أيضًا. 5. قوة تحريك جافاسكربتلدى جافاسكربت كل الإمكانيات للفوز بمقارنة الأداء مع CSS. جافاسكربت هي أسرع. لكن إلى أي حدّ يمكن أن تكون أسرع؟ بدايةً هي مرنة بما فيه الكفاية لإنشاء عرض تحريك ثلاثيّ الأبعاد 3D ملفت للنظر، الذي يمكن أن تراه عبر WebGL. وجافاسكربت سريعة بشكلٍ كافٍ لبناء دعاية وسائط متعددة، والتي كان من الممكن تطويرها باستخدام Flash أو After Effects. وبالتأكيد بناء عالم افتراضي باستخدام جافاسكربت بمساعدة Canvas. بقدر ما تكون مكتبات التحريك هي المعنية في هذا المقال، فإن الحديث الآن عن توثيق مكتبتيّ Transit و Velocity غير مبرر على الإطلاق. لمزيد من التوضيح لهذه النقطة، قمنا بطرح قائمة تحسينات يمكن فقط للتحريك المبنيّ بجافاسكربت أن يؤديها: مزامنة DOMالتخزين المؤقت للخصائص عبر سلسلة من الاستدعاءات، للتخفيف من استعلامات DOMالتخزين المؤقت لنسب تحويل الواحداتخلاصةنجد بوضوح كيف أن النقاط المذكورة أعلاه تسلط الضوء على براعة التحريك المبنيّ على جافاسكربت بالمقارنة مع المبنيّ على CSS. ولكن هل هذا يعني أن تحريك CSS “سيّء”؟ بالتأكيد لا. هو جيّد لأداء تحريك بسيط. لكن لمرونة وقدرات فريدة أعلى، يجب أن تأخذ جافاسكربت بعين الاعتبار. ترجمة -وبتصرفّ- للمقال CSS Vs JavaScript Myths Fall to Pieces1 نقطة
-
مررنا جميعًا بموقف كهذا: بضع سطور JavaScript كُتبت لتؤدّي وظيفة صغيرة ثمّ تضخمت مع الوقت لتبلغ بضع عشرات من السطور، ثم مئات السطور... أصبحت تقبل معاملات أكثر، وتختبر شروطًا أكثر، ثم أتى ذلك اليوم: تقرير بعلّة، التطبيق لا يعمل كما ينبغي! وحينئذ يكون فكّ غموض هذه السطور المتشابكة مسؤوليّتنا. مع تزايد الأعباء الّتي نُلقيها على مشروعنا من جهة المتصفّح، يتّضح أمامنا أمران: أوّلهما: تجربة التطبيق بالنقر على الأزرار والروابط سريعًا ليس حلًّا عمليًّا، بل لا بدّ من الاختبارات المُؤتمتة حتّى نثق باستقرار مشروعنا؛ وثانيهما: علينا (غالبًا) أن نُغيّر كيفيّة كتابة برامجنا بما يُتيح لنا كتابة الاختبارات. هل أنا جادّة؟ نعم! لأنّنا وإن علمنا فائدة الاختبارات المؤتمتة، فإنّ أكثرنا اليوم لا يكتبون سوى اختبارات التكامل (integration tests). لا يُقلّل هذا من أهمّيّة اختبارات التكامل، فهي تُركّز على كيفيّة عمل أجزاء التّطبيق سويّةً، إلّا أنّها لا تستطيع إخبارنا إن كانت الوحدات المنفصلة لتطبيقنا تعمل كما ينبغي لها. هنا تبرز ضرورة اختبارات الوحدات (unit tests)، وستكون كتابة اختبارات الوحدات صعبة جدًّا إن لم يكن نصّ JavaScript الّذي كتبناه ملائمًا للاختبار. ما الفرق بين اختبارات الوحدات واختبارات التكامل؟كتابة اختبارات التكامل أمر بسيط عادةً: كل ما نفعله كتابة برنامج بسيط يصف كيف يتفاعل المُستخدم مع تطبيقنا، وما يجب أن يتوقّعه المستخدم عندئذٍ. إحدى برامج أتمتة المُتصفّحات الشّهيرة Selenium. وفي Ruby يُسهل Capybara التّعامل مع Selenium، وتتوفّر أدوات مشابهة كثيرة للغات الأخرى. فيما يلي اختبار تكامل لجزء من تطبيق بحث: def test_search fill_in('q', :with => 'cat') find('.btn').click assert( find('#results li').has_content?('cat'), 'Search results are shown' ) assert( page.has_no_selector?('#results li.no-results'), 'No results is not shown' ) end إذن فاختبار التكامل يهتمّ بتفاعل المستخدم مع التّطبيق، أمّا اختبار الوحدات فتركيزه ضيّق ويقتصر على جزء صغير من البرنامج: عندما أستدعي دالّةً (function) بمُدخلات معيّنة، فما المُخرجات المُتوقّعة؟ يصبح إخضاع التّطبيقات المكتوبة بأسلوب إجرائي (procedural) تقليديّ لاختبار الوحدات أمرًا شديد الصعوبة، وكذلك صيانتها وتنقيحها والبناء عليها. ولكن عندما نكتب نصوص برامجنا وفي ذهننا الحاجة لإجراء اختبارات الوحدات في المستقبل، فسنجد أنّ كتابة هذه الاختبارات أمر يسير، وسينعكس ذلك بالإيجاب على نصوصنا البرمجية كذلك. لنفهم ما أتحدّث عنه، دعنا نُلقِ نظرةً على تطبيق بحث بسيط: عندما يكتب المستخدم عبارة للبحث عنها، يُرسل التّطبيق طلب XMLHttpRequest للخادوم يطلب منه البيانات المُوافقة. يُجيبه الخادوم بالبيانات، مُصاغاة بـJSON، ليتلقّاها التطبيق ويعرضها في الصّفحة مُستخدمًا أدوات القولبة المُتوفّرة للمتصفّحات. يستطيع المستخدم أن ينقر على إحدى نتائج البحث ليُشير "بإعجابه" بها؛ وعندما يفعل ذلك، يُضاف اسم الشّخص الّذي أعجبه إلى قائمة "الإعجابات" على يمين الصّفحة. يمكن كتابة هذا البرنامج بأسلوب تقليدي كما يلي: var tmplCache = {}; function loadTemplate (name) { if (!tmplCache[name]) { tmplCache[name] = $.get('/templates/' + name); } return tmplCache[name]; } $(function () { var resultsList = $('#results'); var liked = $('#liked'); var pending = false; $('#searchForm').on('submit', function (e) { e.preventDefault(); if (pending) { return; } var form = $(this); var query = $.trim( form.find('input[name="q"]').val() ); if (!query) { return; } pending = true; $.ajax('/data/search.json', { data : { q: query }, dataType : 'json', success : function (data) { loadTemplate('people-detailed.tmpl').then(function (t) { var tmpl = _.template(t); resultsList.html( tmpl({ people : data.results }) ); pending = false; }); } }); $('<li>', { 'class' : 'pending', html : 'Searching …' }).appendTo( resultsList.empty() ); }); resultsList.on('click', '.like', function (e) { e.preventDefault(); var name = $(this).closest('li').find('h2').text(); liked.find('.no-results').remove(); $('<li>', { text: name }).appendTo(liked); }); }); يحلو لصديقنا Adam Sontag أن يُسمّي هذا الأسلوب "اختر مغامرتك الخاصّة"، ويحقّ له ذلك! ففي أيّ سطر ممّا سبق، قد نتعامل مع كيفيّة عرض البيانات، أو مع البيانات ذاتها، أو مع تفاعل المستخدم مع الصّفحة، أو مع حالة التّطبيق في لحظة معيّنة! من يدري!؟ هذا الأسلوب لا يجعل كتابة اختبارات التكامل صعبًا، ولكنّ اختبار المهامّ المختلفة للبرنامج تصبح معه غايةً في التعقيد. كيف ذلك؟ لنرَ: غياب كامل لبنية واضحة؛ فكلّ شيء يحدث ضمن $(document).ready() ثمّ في دوالّ مجهولة لا يمكن اختبارها لأنّها مُغلّفة ضمن الدّالة السّابقة ولا يمكن الوصول إليها.دوالّ معقدة؛ فعندما تتجاوز دالّة مُفردة 10 سطور، كما في دالّة إرسال النّموذج، فهذا يعني غالبًا أنّها تفعل أشياء أكثر مما ينبغي لها أن تفعل!حالة (state) مخفيّة أو مُشتركة؛ كالمتغيّر pending المُعرّف ضمن دالّة مُغلقة (closure)، لا سبيل لاختبار قيمته والتأكّد من كونها عُينت إلى قيمة صحيحة في لحظة ما.اعتماد شديد للأجزاء بعضها على بعض؛ فمثلاً: لا تحتاج دالّة نجاح طلب $.ajax وصولًا مباشرًا إلى عناصر الصّفحة.تنظيم النّصّ البرمجيّالخطوة الأولى لحلّ المشكلة السابقة هي إزالة هذا التّشابك، وفصل المهامّ السابقة إلى عدّة أجزاء: العرض والتفاعلإدارة البيانات وتخزينهاحالة التطبيق العامّةربط الأجزاء السّابقة مع بعضها لتعمل سويّةً!في الأسلوب التّقليديّ الّذي بيّناه سابقًا، تتشابك هذه المهمّات الأربع، ففي سطرٍ نتعامل مع العرض، وبعده ببضع سطور نتواصل مع الخادوم. يمكننا كتابة اختبار تكامل للبرنامج السابق بلا شكّ (بل ينبغي أن نفعل!)، ولكن كتابة اختبارات الوحدات أمرٌ صعب. في اختبارات التكامل، يمكننا افتراض عبارات من نحو "عندما يبحث المستخدم عن شيءٍ ما، يُتوقّع أن يرى النّتائج المُناسبة"، ولكن لا يمكننا التعمّق في التّفاصيل، وإن حدثت مشكلةٌ ما، ينبغي علينا تحديد موقع المشكلة بالضّبط، ولن تساعدنا اختبارات التكامل في ذلك. ولكنْ، إن أعدنا التفكير في كيفيّة كتابة نصّ البرنامج يمكننا كتابة اختبارات للوحدات تُسهِّل لنا تحديد موقع المشكلة، وتسمح بتسهيل صيانة النّصّ النهائي للبرنامج وتحسينه واستخدامه ثانيةً. سيتَّبع برنامجنا الجديد المبادئ التّالية: مثّل كلّ مهمّة ضمن كائن مُنفصل يقع تنصيفه في واحدة من المناطق الأربعة الّتي سبق ذكرها، دون أن يكون هذا الكائن في حاجة لمعرفة وجود الكائنات الأخرى. هذا يُجنّنبا تشابك النّص البرمجيّ.ادعم إمكانية الضّبط، بدلًا من كتابة قيم ثابتة. هذا سيُجنّبنا الحاجة لمحاكاة كامل بيئة HTML عند إجراء الاختبارات.أبقِ وظائف الكائنات بسيطة ومُوجزة. سيؤدّي هذا إلى نصّ برمجيّ أبسط وأسهل قراءةً.استخدم دوالّ مُشيّدة (constructors) لإنشاء نسخ من الكائنات، هذا يُتيح إنشاء نُسخ "نظيفة" من كلّ جزء من البرنامج لغرض الاختبار.بدايةً، علينا أن نكتشف كيف سنفصل تطبيقنا لعدّة أجزاء. سيكون لدينا 3 أجزاء مُخصّصة للعرض والتفاعل: نموذج البحث ونتائج البحث ومساحة الإعجابات. سيكون لدينا أيضًا جزء مُخصّص لجلب البيانات من الخادوم، وجزء آخر يربط بين تلك الأجزاء كلها. لنبدأ بالنّظر في واحد من أبسط أجزاء التطبيق: مساحة الإعجابات. في الإصدار السّابق من برنامجنا، كان النّصّ التّالي مسؤولًا عن تحديث هذه المساحة: var liked = $('#liked'); var resultsList = $('#results'); // ... resultsList.on('click', '.like', function (e) { e.preventDefault(); var name = $(this).closest('li').find('h2').text(); liked.find( '.no-results' ).remove(); $('<li>', { text: name }).appendTo(liked); });لاحظ كيف يتداخل جزء نتائج البحث في جزء مساحة الإعجابات وكيف يحتاج أن يعلم الكثير عن عناصره في الصّفحة. إن أردنا كتابة هذا الجزء بأسلوب أسهل اختبارًا، فعلينا إنشاء كائن لمساحة الإعجابات مسؤولٍ عن تعديل عناصر الصّفحة المُتعلّقة بقائمة الإعجابات: var Likes = function (el) { this.el = $(el); return this; }; Likes.prototype.add = function (name) { this.el.find('.no-results').remove(); $('<li>', { text: name }).appendTo(this.el); }; يوفّر هذا النّصّ دالّة تشييد (constructor function) تُعطي نسخة جديدة من مساحة الإعجابات. للنسخة المُنشأة وظيفة .add() يمكن استخدامها لإضافة نتائج جديدة. ولإثبات صحّة عملها، يمكننا كتابة بضعة اختبارات: var ul; setup(function(){ ul = $('<ul><li class="no-results"></li></ul>'); }); test('constructor', function () { var l = new Likes(ul); assert(l); }); test('adding a name', function () { var l = new Likes(ul); l.add('Brendan Eich'); assert.equal(ul.find('li').length, 1); assert.equal(ul.find('li').first().html(), 'Brendan Eich'); assert.equal(ul.find('li.no-results').length, 0); });ليس الأمر صعبًا كما ترى! استخدمنا Mocha كإطار عمل للاختبار وChai كمكتبة لإنشاء الافتراضات (assesrtions). Mocha توفّر test وsetup، وChai توفّر assert. تتوفّر أطر عمل ومكتبات للافتراضات كثيرة، ولكنّ السابقتين ملائمتان تمامًا للمُبتدئين. عليك اختيار ما يناسبك ويناسب مشروعك. اطّلع على QUnit وIntern (الأخيرة تبدو واعدة). يبدأ برنامج الاختبا بإنشاء عنصر في الصّفحة سنستخدمه لاحتواء مساحة الإعجابات، ثمّ ينطلق اختباران: الأول للتأكّد من إمكانيّة إنشاء مساحة الإعجابات، والآخر للتأكد من أن وظيفة .add() تعطينا النتيجة المرغوبة. بعد إنشاء هذين الاختبارين، يمكننا إعادة صياغة النصّ البرمجي لمساحة الإعجابات، مطمئنين إلى أنّ الأخطاء الّتي نرتكبها عندئذٍ ستُكتشف فورًا في الاختبار. يبدو تطبيقنا الآن كما يلي: var liked = new Likes('#liked'); var resultsList = $('#results'); // ... resultsList.on('click', '.like', function (e) { e.preventDefault(); var name = $(this).closest('li').find('h2').text(); liked.add(name); });جزء نتائج البحث أشدّ تعقيدًا من مساحة الإعجابات، لكن لنجرّب إعادة صياغته أيضًا. نريد أن نُنشئ وظائف للتفاعل مع نتائج البحث كما أنشأنا الوظيفة .add() لمساحة الإعجابات. نريد طريقةً لإضافة نتائج جديدة، وكذلك طريقة "لبثّ" التّغيرات الّتي تطرأ على النّتائج لبقيّة أجزا التّطبيق (كأن يُعجب المستخدم بنتيجة مثلاً). var SearchResults = function (el) { this.el = $(el); this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) ); }; SearchResults.prototype.setResults = function (results) { var templateRequest = $.get('people-detailed.tmpl'); templateRequest.then( _.bind(this._populate, this, results) ); }; SearchResults.prototype._handleClick = function (evt) { var name = $(evt.target).closest('li.result').attr('data-name'); $(document).trigger('like', [ name ]); }; SearchResults.prototype._populate = function (results, tmpl) { var html = _.template(tmpl, { people: results }); this.el.html(html); };الآن يمكن لنصّنا البرمجيّ القديم المسؤول عن إدارة التفاعلات بين نتائج البحث ومساحة الإعجابات أن يصبح كما يلي: var liked = new Likes('#liked'); var resultsList = new SearchResults('#results'); // ... $(document).on('like', function (evt, name) { liked.add(name); })وهذا أبسط وأقل تشابكًا، لأنّنا نستخدم document كممرّ عامّ للرّسائل المُتبادلة بين الأجزاء بحيث لا يضطرّ أحدها إلى أن يعلم وجود الآخر. (ملاحظة: في تطبيق حقيقيّ، ربّما يُستخدم شيء مثل Backbone أو مكتبة RSVP لإدارة الأحداث، استخدمنا document للتبسيط.) كذلك أخفينا كل التّفاصيل المُعفّدة (كإيجاد اسم الشّخص الّذي أُعجب المستخدم به) داخل كائن نتائج البحث، بدل أن نترك هذه التّفاصيل تُلوّث نصّ تطبيقنا. أفضل ما في الأمر هو أنّ بإمكاننا الآن كتابة اختبارات للتأكّد من عمل كائن نتائج البحث كما يجب: var ul; var data = [ /* fake data here */ ]; setup(function () { ul = $('<ul><li class="no-results"></li></ul>'); }); test('constructor', function () { var sr = new SearchResults(ul); assert(sr); }); test('display received results', function () { var sr = new SearchResults(ul); sr.setResults(data); assert.equal(ul.find('.no-results').length, 0); assert.equal(ul.find('li.result').length, data.length); assert.equal( ul.find('li.result').first().attr('data-name'), data[0].name ); }); test('announce likes', function() { var sr = new SearchResults(ul); var flag; var spy = function () { flag = [].slice.call(arguments); }; sr.setResults(data); $(document).on('like', spy); ul.find('li').first().find('.like.btn').click(); assert(flag, 'event handler called'); assert.equal(flag[1], data[0].name, 'event handler receives data' ); });التفاعل مع الخادم جزء آخر يجب إنشاؤه، وقد احتوى النّص البرمجيّ القديم على طلب $.ajax() فيه استدعاء راجع (callback) يتعامل مباشرةً مع عناصر الصّفحة: $.ajax('/data/search.json', { data : { q: query }, dataType : 'json', success : function( data ) { loadTemplate('people-detailed.tmpl').then(function(t) { var tmpl = _.template( t ); resultsList.html( tmpl({ people : data.results }) ); pending = false; }); } }); مُجدّدًا أقول: من الصعب كتابة اختبار وحدات للنّصّ السابق لأن أشياء كثيرةً تحدث في بضع سطور. يمكننا إعادة صياغة القسم المسؤول عن البيانات في صورة كائن مُنفصل: var SearchData = function () { }; SearchData.prototype.fetch = function (query) { var dfd; if (!query) { dfd = $.Deferred(); dfd.resolve([]); return dfd.promise(); } return $.ajax( '/data/search.json', { data : { q: query }, dataType : 'json' }).pipe(function( resp ) { return resp.results; }); };وعندها يمكن تعديل النّصّ البرمجيّ لجلب النّتائج إلى الصّفحة: var resultsList = new SearchResults('#results'); var searchData = new SearchData(); // ... searchData.fetch(query).then(resultsList.setResults); لاحظ كم بسّطنا نصّ تطبيقنا، وكيف عزلنا التّعقيدات ضمن كائن بيانات البحث، بدلًا من تركه حُرًّا ضمن نصّ تطبيقنا العامّ. قمنا أيضًا بجعل واجهة البحث قابلةً للاختبار. ولكن هناك بضعة أمور يجب النّظر فيها عندما نختبر جزءًا من برنامج يتفاعل مع الخواديم. أوّل هذه الأمور: لا نريد أنّ نتفاعل مع الخادوم حقيقةً، ففعل ذلك يعني العودة إلى عالم اختبارات التّكامل، ولأنّنا مُطوّرون أكفاء، فقد انتهينا من كتابة تلك، صحيح؟ ما نريده الآن هو "محاكاة" التّفاعل مع الخادوم، الأمر الّذي يمكن إنجازه بمكتبة Sinon. ثانيًا: يجب أيضًا اختبار المسارات غير المُتوقّعة للتطبيق، كعبارة بحث فارغة مثلًا. <code class="lang-javascript"> </code>test('constructor', function () { var sd = new SearchData(); assert(sd); }); suite('fetch', function () { var xhr, requests; setup(function () { requests = []; xhr = sinon.useFakeXMLHttpRequest(); xhr.onCreate = function (req) { requests.push(req); }; }); teardown(function () { xhr.restore(); }); test('fetches from correct URL', function () { var sd = new SearchData(); sd.fetch('cat'); assert.equal(requests[0].url, '/data/search.json?q=cat'); }); test('returns a promise', function () { var sd = new SearchData(); var req = sd.fetch('cat'); assert.isFunction(req.then); }); test('no request if no query', function () { var sd = new SearchData(); var req = sd.fetch(); assert.equal(requests.length, 0); }); test('return a promise even if no query', function () { var sd = new SearchData(); var req = sd.fetch(); assert.isFunction( req.then ); }); test('no query promise resolves with empty array', function () { var sd = new SearchData(); var req = sd.fetch(); var spy = sinon.spy(); req.then(spy); assert.deepEqual(spy.args[0][0], []); }); test('returns contents of results property of the response', function () { var sd = new SearchData(); var req = sd.fetch('cat'); var spy = sinon.spy(); requests[0].respond( 200, { 'Content-type': 'text/json' }, JSON.stringify({ results: [ 1, 2, 3 ] }) ); req.then(spy); assert.deepEqual(spy.args[0][0], [ 1, 2, 3 ]); }); });سأترك ما تبقّى من إعادة صياغة نموذج البحث لغرض الاختصار، وقد قمت بتبسيط بعض الاختبارات والنّصوص أعلاه مُجدّدًا، ويمكنك الاطّلاع على الإصدار النّهائي للتطبيق هنا إن كنت مُهتمًّا. عندما ننتهي من كتابة تطبيقنا بأسلوب يسمح بإجراء الاختبارات، سيكون مشروعنًا أكثر نظافةً ممّا بدأنا به: $(function() { var pending = false; var searchForm = new SearchForm('#searchForm'); var searchResults = new SearchResults('#results'); var likes = new Likes('#liked'); var searchData = new SearchData(); $(document).on('search', function (event, query) { if (pending) { return; } pending = true; searchData.fetch(query).then(function (results) { searchResults.setResults(results); pending = false; }); searchResults.pending(); }); $(document).on('like', function (evt, name) { likes.add(name); }); }); والأهمّ من ذلك أنّنا ننتهي ولدينا مشروعٌ أُخضع لاختبارات دقيقة، وهذا يعني أنّ بإمكاننا إعادة كتابة الأجزاء ونحن مطمئنون إلى أنّ ذلك لن يسيئ إلى عمل التطبيق. بإمكاننا أيضًا إضافة اختبارات جديدةً عندما نكتشف مشكلات جديدة، ثمّ كتابة النّصّ الّذي يجعل هذه الاختبار تنجح. اختبار البرامج يُسهّل حياتك على المدى البعيدمن السهولة أن تنظر إلى ما سبق وتقول لنفسك: "مهلًا! أتريدين منّي كتابة برامج أطول لأداء المهمّة ذاتها؟" عندما يتعلّق الأمر ببناء المشاريع على الإنترنت، فإنّه لا مفرّ من هذه الحقيقة: لا بدّ من وقتٍ كافٍ لتصميم منهجٍ لحلّ مشكلة. لا بدّ من تجربة الحلّ، سواءٌ كانت التّجربة يدويّة تقوم بها بنفسك في المتصفّح، أو مؤتمتة، أو أن تترك مستخدميك يجرّبوا مشروعك بعد نشره (فكرة مرعبة!). ستُغيّر نصوصك البرمجيّة، ثمّ سيستخدمها آخرون، وفي النّهاية ستبقى العلل موجودة مهما بلغ عدد الاختبارات الّتي تكتبها. صحيحٌ أنّ الاختبارات تتطلّب وقتًا عند كتابتها أوّل مرّة، ولكنّها ستوفّر وقتك في المدى البعيد، سترضى عن نفسك عندما يفشل أحد الاختبارات الّتي كتبتها كاشفًا عن علّة قبل أن يصل النّص البرمجيّ إلى مرحلة الإنتاج، وكذلك عندما يكون لديك نظامٌ يُثبت أنّ العلل قد حلّت بالفعل قبل أن تجد طريقها إلى المُستخدمين! مصادر إضافيّةكلّ ما كتبته أعلاه ليس إلّا أقلّ القليل من موضوع اختبارات JavaScript، فإن أردت المزيد: شاهد العرض الذي قدّمته في مؤتمر Full Frontal عام 2012 في برايتون في المملكة المتحدة.أو جرّب Grunt، وهي أداة تُساعدك في أتمتة عمليّة الاختبارات وأشياء أخرى كثيرة.أو اقرأ كتاب Test-Driven JavaScript Development لمؤلّفه Christian Johansen، صاحب مكتبة Sinon. هذا الكتاب مرجع ثمينٌ وضخم في موضوع اختبارات JavaScript ترجمة -وبتصرّف- للمقال Writing Testable JavaScript لصاحبته Rebecca Murphey1 نقطة
-
التّصميم الجيّد للمُنتجات يعني الاعتناء بالنواحي المهمّة للمنتج وإيلاءها اهتمامًا أكبر لتكونَ النتيجة واجهة استخدام جميلة ومُفيدة ومفهومة، لكن إياك أن تظنّ أنّ التصميم يقع على عاتق المُصمّمين فقط! التصميم مطلوب في النصوص البرمجية، ليس فقط في النصوص البرمجية المكتوبة لبناء الواجهات المرئيّة للمستخدم — بل المقصود تصميم النّصوص البرمجيّة ذاتها. النّصوص المُصمّمة بإتقان أسهل صيانةً وإمكانيّة تطويرها والبناء عليها أكبر، ممّا يجعل المُطوّرين أكثر كفاءة، لأنّها تسمح بتوجيه الاهتمام والجهد نحو بناء مُنتجات أفضل، والنتيجة شعورٌ عامّ بالراحة - راحة المطوّرين والمستخدمين وكلّ من يهمّه الأمر! لتصميم النّصوص البرمجيّة نواحٍ ثلاث عامّة غير مرتبطة بلغة برمجة مُعيّنة: بنية النظام: المُخطّط العامّ لمجموعة ملفّات المشروع، والقواعد الّتي تُحدد كيف تتواصل مكوّناته المختلفة فيما بينها، سواء كانت نماذج البيانات أم واجهات العرض أم المُتحكّمات.إمكانية الصيانة: إلى أي حدّ يمكن تحسين النصّ البرمجيّ والبناء عليه؟إعادة الاستخدام: هل يمكن إعادة استخدام مكوّنات المشروع؟ هل يسهُل تخصيص أحد هذه المكوّنات لإعادة استخدامه؟التصميم المُتقن للنّصوص البرمجية في اللّغات المرنة مثل JavaScript يفرض على المطوّر إلزام نفسه بمجموعة من القواعد، لأنّ بيئة JavaScript المُتسامحة قد تسمح للمشروع أن يعمل وإن كانت أجزاؤه مبعثرة هنا وهناك. لذلك فإن تأسيس بنية المشروع والالتزام بها منذ البداية يضمنان انسجامه حتّى تمامه. نمط الوحدات (module pattern) هو إحدى الطّرق المُجرّبة والّتي أثبتت فائدتها في تصميم البرمجيّات، إذ تتلاءم بنيتها القابلة للتوسيع مع ما يتطلّبه بناء مشروع برمجيّ متماسك وسهل الصّيانة. أعتمدُ هذا النّمط في بناء إضافات jQuery لأنّه يسمح بإعادة استخدام الوحدات وضبطها من خلال مجموعة من الخيارات عبر واجهة برمجيّة مُتقنة التّصميم. سنستعرض فيما يلي كيف يُكتبُ نصّ برمجيّ موزّع في مكوّنات يمكن استخدامها في مشاريع أخرى لاحقة. نمط الوحداتتكثر أنماط التّصميم، وتكثر معها مصادر تعلّمها. كتب Addy Osmani كتابًا ممتازًا (ومجّانيًّا!) عن أنماط التّصميم في JavaScript، أنصح كلّ المطوّرين من كلّ المستويات بالاطّلاع عليه. يٌسهّل نمط الوحدات تنظيم المشروع ويضمن بقاءه نظيفًا ومرتّبًا، والوحدة التي نقصدها ليست سوى كائن JavaScript حرفيّ تقليديّ (object literal) يتضمّن وظائف (methods) وخصائص (properties)، وهذه البنية البسيطة هي أفضل ما في نمط الوحدات لأنّها تسمح حتى لغير المتمرّسين ممّن يطّلعون على النّصّ البرمجيّ أن يقرؤوه ويفهموا بسرعة كيف يعمل. في التّطبيقات التي تتبنّى هذا النّمط، يكون لكلّ مكوّن فيه وحدة منفصلة تتبع له؛ فمثلاً، يمكن لحقل نصّ في الواجهة المرئيّة يسمح بالإكمال التلقائيّ للكلمات أن يُبنى من وحدتين، الأولى للحقل ذاته، والأخرى لقائمة الكلمات المُقترحَة. تتواصل الوحدتان فيما بينهما دون أن يتداخل نصّاهما البرمجيّان معًا. هذا العزل بين المكوّنات هو ما يجعلها مناسبة لإقامة بنية متماسكة للمشاريع، حيث تكون العلاقات بين أجزاء التطبيق محدّدة بدقّة؛ ويتولّى حقل النّصّ ما يرتبط به ضمن وحدته، ولا تُترك هذه الأمور مبعثرة بين الملفّات — لنحصل بالنّتيجة على نصّ برمجيّ واضح. توفّر البنية القائمة على الوحدات فائدة أخرى، إذ تسمح طبيعتها بتسهيل صيانة المشروع، وذلك بتطوير كلّ وحدة بصورة منفصلة دون أن تتأثّر بقيّة أجزاء التّطبيق. استخدمت نمط الوحدات لإنشاء إضافة jPanelMenu، وهي إضافة لـjQuery تسمح بإنشاء قائمة للموقع خارج الشاشة (off-canvas menu). سأستخدم هذه الإضافة كمثال عن بناء وحدة وفق هذا النمط. بناء الوحدةبدايةً سأفرض ثلاث وظائف وخاصّة واحدة تُستخدم جميعها للتفاعل مع القائمة. var jpm = { animated: true, openMenu: function( ) { … this.setMenuStyle( ); }, closeMenu: function( ) { … this.setMenuStyle( ); }, setMenuStyle: function( ) { … } };الفكرة هنا هي تجزئة النصّ إلى أصغر أجزاء مُفيدة ويمكن إعادة استخدامها. كان بإمكاننا كتابة وظيفة واحدة toggleMenu( ) لتبديل وضع القائمة بين الظهور والخفاء، ولكن إنشاء وظيفتين منفصلتين openMenu( ) وcloseMenu( ) يعطينا تحكّمًا أكبر ويسمح بإعادة استخدامهما ضمن الوحدة. لاحظ أنّ استدعاء وظائف الوحدة وخواصّها من ضمن الوحدة ذاتها (مثل استدعاء setMenuStyle( )) يُسبق بالكلمة المفتاحية this، فهذه هي طريقة وصول الوحدات إلى أعضائها. هذه هي البنية الأساسيّة لكلّ وحدة. يمكنك إضافة خواص ووظائف أخرى عند الحاجة إليها، ولكنّ الوحدة ستبقى بالبساطة ذاتها. بعد إقامة الأساسات السابقة، يمكن بناء طبقة فوقها لإعادة استخدام الوحدة، وذلك من خلال إتاحة ضبط خيارات الوحدة والتحكم بها. إضافات jQueryقد يكون الجانب الثالث للتصميم المُتقن هو الجانب الأهمّ: إعادة الاستخدام. هذه الفقرة يرافقها تحذير؛ فعلى الرغم من وجود طرق لكتابة مكوّنات يمكن إعادة استخدامها بـJavaScript "ساده"، إلّا أنّني أفضّل بناء الوحدة على صورة إضافة jQuery عندما تكون الأمور مُعقّدة، ولهذا أسباب عدّة. أهمّ هذه الأسباب: بناء الوحدة بصورة إضافة jQuery يوصل للمُطوّرين رسالة ضمنيّة بأنّ وحدتنا تعتمد على وجود jQuery، فهذا يجب أن يكون واضحًا منذ البداية لمن يُريد استخدامها. يُضاف إلى السّبب السّابق أنّ النّص البرمجيّ الّذي سنكتبه سيكون مُنسجمًا مع بقيّة أجزاء المشروع المُقامة على jQuery، وهذا جيّد لاعتبارات جماليّة أولًا، ولأنّه يسمح للمطوّرين بفهم استخدام الإضافة إلى حدٍّ ما دون كثير عناء، فهي إذًا طريقة أخرى لتحسين الواجهة البرمجيّة الّتي سيتخدمها المطوّرون. قبل البدء ببناء إضافة jQuery، تأكّد من أنّها لا تتعارض مع مكتبات JavaScript أخرى تستخدم الرّمز $. لا تقلق فالأمر أبسط ممّا يبدو، كلّ ما عليك هو إحاطة نصّ الإضافة بما يلي: (function($) { // نص الإضافة البرمجي هنا })(jQuery);بعد ذلك سنُعدُّ الإضافة ونُلقي بالوحدة الّتي كتبناها سابقًا ضمن الإضافة. إضافة jQuery ليس إلا وظيفة مُعرِّفة على الكائن $ (كائن jQuery). (function($) { $.jPanelMenu = function( ) { var jpm = { animated: true, openMenu: function( ) { … this.setMenuStyle( ); }, closeMenu: function( ) { … this.setMenuStyle( ); }, setMenuStyle: function( ) { … } }; }; })(jQuery);كل ما علينا لاستخدام الإضافة استدعاء الدّالة الّتي أنشأناها للتّوّ. var jpm = $.jPanelMenu( );الخياراتلا تكون الإضافة صالحةً لإعادة الاستخدام إن لم تُتِح تخصيصها وفق متطلّبات المشروع، فكلّ مشروع له أسلوبه الخاصّ وبنيته الخاصّة، وإتاحة ضبط الوحدة بالخيارات يسمح للإضافة بالتكيّف مع حاجات المشاريع المختلفة. تزويد الوحدة بقيم مبدئية للخيارات أمرٌ مفضَّل. أسهل طريقة لفعل ذلك استخدام وظيفة jQuery $.extend() الّتي تستقبل على الأقل مُعاملين اثنين (arguments). اجعل المُعامل الأول لهذه الوظيفة كائنًا يوفّر كل الخيارات المتاحة وقيهما المبدئيّة، واجعل المعامل الثاني الخيارات الّتي يُقدّمها المُطور الّذي يستخدم الإضافة. ستقوم الوظيفة بدمج الكائنين مع إحلال الخيارات الّتي قدّمها المّطوّر مكان مقابلاتها المبدئيّة. (function($) { $.jPanelMenu = function(options) { var jpm = { options: $.extend({ 'animated': true, 'duration': 500, 'direction': 'left' }, options), openMenu: function( ) { … this.setMenuStyle( ); }, closeMenu: function( ) { … this.setMenuStyle( ); }, setMenuStyle: function( ) { … } }; }; })(jQuery);إضافةً إلى توفير القيم المبدئيّة، تشرح الخيارات نفسها بنفسها، أي يستطيع من يقرأ النّصّ البرمجيّ رؤية كلّ الخيارات المُتاحة مباشرةً. أتِح أكبر عددٍ ممكن من الخيارات، فهذا قد يكون مُفيدًا في المستقبل، والمرونة لن تضرّ! الواجهة البرمجيّةالخيارات الّتي تتيحها الإضافة وسيلة رائعة لتخصيص عملها، وأمّا الواجهة البرمجيّة، فهي تتيح توسيع وظيفة الإضافة بكشف محتوى الإضافة من خصائص ووظائف ليستطيع المشروع الاستفادة منها. مع أنّ إتاحة أكبر ما يمكن من الوحدة عبر الواجهة البرمجيّة أمرٌ حسن، إلا أنّه ليس على من يستخدمها من الخارج الوصول إلى كلّ المكوّنات الدّاخليّة. أتِح في الواجهة البرمجيّة العناصر الّتي ستُستخدم فقط. في مثالنا، يجب على الإضافة أن تُتيح الوصول إلى وظائف فتح القائمة وإغلاقها فقط، أمّا الوظيفة الداخليّة setMenuStyle( ) فيجب أن تُستدعى عندما تُفتح القائمة أو تغلق، ولكن ليس على الأجزاء الخارجيّة من المشروع أن تصل إليها. لكشف الواجهة البرمجيّة، أعِد كائنًا يحوي الوظائف والخصائص المرغوب كشفها في نهاية نصّ الإضافة، بإمكانك أيضًا ربط الكائنات المُعادة مع تلك المُحتواة ضمن الوحدة؛ وفي هذا التنظيم يكمن جمال نمط الوحدات. (function($) { $.jPanelMenu = function(options) { var jpm = { options: $.extend({ 'animated': true, 'duration': 500, 'direction': 'left' }, options), openMenu: function( ) { … this.setMenuStyle( ); }, closeMenu: function( ) { … this.setMenuStyle( ); }, setMenuStyle: function( ) { … } }; return { open: jpm.openMenu, close: jpm.closeMenu, someComplexMethod: function( ) { … } }; }; })(jQuery);ستكون خصائص الواجهة ووظائفها متاحةً عبر هذا الكائن الّذي أُعيد بعد تهيئة الإضافة. var jpm = $.jPanelMenu({ duration: 1000, … }); jpm.open( );"صقل" واجهة المُطوّرينباتّباع الخطوط العامّة البسيطة والقليل من المفاهيم، أنشأنا لأنفسنا إضافة صالحةً لإعادة الاستخدام والتّوسعة، وهذا سيُسهّل عملنا كثيرًا، وككلّ ما نُنجزه تبقى التّجربة العامل الحاسم قبل اعتماد هذا النّمط على مستوى فريقك، وبما يُناسب سياق عملك. كلّ ما وجدت نفسي أكتب شيئًا يمكن إعادة استخدامه لاحقًا، بادرتُ لفصله في إضافة jQuery مبنيّة كوحدة. أفضل ما في هذا الأسلوب أنّه يجبرك على استخدام النّصّ الّذي تكتبه وتجربته، وعندما تستخدم شيئًا وأنت تُنشئه، يكون تحديد مواضع القوّة ومواضع الضّعف أسرع، الأمر الّذي يسمح لك بتغيير خطّة العمل باكرًا. هذه العمليّة تُعطي في النّهاية نصًّا برمجيًّا مُجرّبًا وجاهزًا لإتاحته كمشروع مفتوح المصدر يستقبل المساهمات، وقد قمت بالفعل بنشر إضافاتي (الّتي أعتبرها شبه ممتازة) على GitHub. وحتّى إن لم ترغب بنشر ما تكتبه للعموم، فإنّ تصميم النّصّ البرمجيّ أمر شديد الأهمّيّة، وستشكر نفسك بسببه في المستقبل! ترجمة -وبتصرّف- للمقال The Design of Code: Organizing JavaScript لصاحبه Anthony Colangelo1 نقطة
-
كما هو عليه الحال كل بضع عقود، تطفو إلى السطح لغة برمجة ما ويعدنا المتعصبون لها بأنها ستفعل لنا كل شيء، بدءًا من تطبيقات الحاسوب مرورًا بالهواتف الذكية وليس انتهاءً بالتعامل مع الجمادات من حولنا بطرق رائعة كالتحكم بطائرة بلا طيّار باستخدام قبضة Xbox 360! لم يكن هذا الحماس يومًا أشدّ منه مع JavaScript، والأمر يعود لعدّة أسباب: كونها تعمل في المتصفح جعلها عابرة للمنصات، فكل حاسوب وكلّ هاتف ذكي (أو حتى متوسّط الذكاء!) اليوم يأتي مزوّدًا بمتصفّح قادر على تشغيل JavaScript،وبما أنّها لغة الويب الوحيدة التي يمكن استعمالها لبرمجة المواقع من جهة المتصفّح، فلك أن تتخيّل عدد مطوّري الويب الذي يتقنونها حول العالم!كذلك كون JavaScript بطبيعتها لغة فائقة المرونة لدرجة أن كل شيء فيها هو في الحقيقة كائن Object حتى الدّوال (functions)! ولا يوجد شيء اسمه أصناف (classes) بالمعنى التّقليديّ، وإنما توجد وراثة أنموذجية (Prototypal inheritance) فكل كائن يستطيع أن يرث أي كائن آخر، ولا أنواع محدّدة للكائنات، فما تفرضه في البداية كسلسلة نصيّة يمكنك أن تغيّره فيما بعد ليصبح رقمًا، وبإمكانك توسعة الأنماط البدئية. هذه المرونة التي يألفها من يبتدئ البرمجة بـJavaScript تجعله يُصاب بصدمة عندما ينتقل إلى لغة أخرى تفرض عليه قيودًا في التصريح عن الأنواع ووراثة الأصناف...الأمر الثالث الذي يجعل JavaScript متفوّقة هو التحسّن الممتاز في أدائها الذي لا يبدو أنه سيتوقّف عند حدّ ما قريبًا، منذ بضع سنوات عندما ظهر Google Chrome مع محرّك JavaScript الجديد V8 والذي اعتمد على JIT compilation بدأت ثورة في عالم التطوير للويب جعلت JavaScript موضع اهتمام وأخذ المطوّرون ينظرون في إمكانيّة استعمالها في تطوير "تطبيقات ويب" بدل "مواقع ويب"، ثم توسّع الأمر مع ظهور Node.js التي قامت على محرّك V8 ذاته لتصبح JavaScript مواطنًا من الدّرجة الأولى على الخواديم مثلها مثل Ruby on Rails وPHP. الأمور ليست ورديّة تمامًا لكنك تستطيع استيعاب سرعة التطوّر الذي تشهده JavaScript وخاصّة أنّه أصبح لدينا أنظمة تشغيل ليست سوى متصفّح ويب في حقيقتها (Chrome OS وFirefox OS). بإمكانك استضافة تطبيقات Node.js مجّانًا على منصّة Heroku أو OpenShift من RedHat مثلها مثل تطبيقات Java وPython وRuby.باختصار، تحوّلت JavaScript إلى لغة عامّة الأغراض (general-purpose) بعد أن كانت تستخدم بشكل بسيط لإضفاء القليل من التأثيرات السخيفة (Blink! Blink!) على صفحات الويب. الآن أريد أن أوضّح شيئًا، في الحقيقة أنا لست مُبرمجًا مُختصًّا، وJavaScript هي اللغة الوحيدة التي أزعم أنّني متوسّط إلى خبير بها، ومنذ عام أو أكثر لم أكتب تقريبًا أي شيء بلغة أخرى (بالطّبع CoffeeScript لا تُعتبر لغة مستقلّة، هي فقط لغة تُحوّل إلى JavaScript ومهمّتها تبسيط الكتابة)، مع أنّني بدأت تعلّم البرمجة مع PHP، إلا أنّني كرهت كل إشارات الدولار تلك ($variable) وعدم انسجام الواجهات البرمجيّة فيها. لا شيء يمنعك من استخدام PHP، وفي الحقيقة إطار العمل Laravel ممتاز ومنسّق بشكل جيّد، لكنّ ما يعيبها هو أنها تحاول أن تفعل كلّ شيء من الوصول لنظام الملفّات إلى دوال للتعامل مع مُعاملات طلبات HTTP إلخ... وهذا بالضّبط ما تحاول بيئات البرمجة الجديدة أن تتجنّبه، ففي Node.js، وعلى الرّغم من أنّه باستطاعتك أن تصل إلى نظام الملفّات وأن تُنشئ خادمًا يستمع إلى الطلبات على أحد المنافذ؛ إلّا أنّ كلّ شيء مُنظّم في وحدات (modules) مستقلّة وعليك أن تُصرح علانيًّة برغبتك باستعمال وحدة نظام الملفّات مثلاً، وكذلك الأمر بالنسبة للوحدات التي يكتبها مبرمجون آخرون. نظام الوحدات هذا والتصريح عنها ضمن ملفّ وعدم تلويث نطاق الأسماء العامّ (Global scope) هي بعضٌ من الأشياء التي نفّذها مُطوّرو Node.js على وجه صحيح، وأزعم أنه واحد من الأشياء التي جعلت JavaScript تُؤخذ على محمل الجدّ. هناك الكثير من المحاولات لتقليد هذا النظام بعد أن أثبت تفوّقه، انظر مثلاً إلى Composer بالنسبة لـPHP، وPip مع virtualenv في Python؛ لكنّ محاولة إدخال هذه الأنظمة على لغات ناضجة لا تبدو موفّقة كثيرًا، وأما في لغة Go فتعتبر الحُزم شيئًا من أساس اللغة. أيًّا يكن، لقد وصلنا إلى مرحلة يمكن بها إنجاز أيّة تطبيق بأيّة لغة، ويبقى الفارق هو التنظيم والسرعة والأمان وأنماط التّصميم المُتّبعة، وأهمّ من ذلك كلّه المجتمع الّذي يوفّر الدّعم والمساعدة للمبتدئين (في النّقطة الأخيرة لا شكّ أن JavaScript متفوّقة على كلّ اللّغات). لكن دعونا لا نُهين قدرتنا العقلية ونتجاهل وجود لغات برمجة أخرى فقط لأنّنا ألِفنا لغة برمجة واحدة، مهما كانت محبوبة! هناك أشياء في JavaScript لا يمكن التّغاضي عنها ولا يُمكن في أحسن الأحوال أن نعتبرها مزايا: فهي أوّلاً بطيئة رغم تحسّن أدائها بأضعاف ما كانت عليه منذ سنوات، وما تزال أبطأ بكثير من لغات أخرى. هناك من يجادل (وأنا أؤيّد هذا الرأي) أن السّرعة ليست كلّ شيء، فيكفي لتطبيقات الويب أن تكون سريعة بما يكفي، وليس عليها أن تكون خارقة السرعة، الفارق بين كتابة تطبيق بسيط يؤدي مهمّة محدّدة بـJavaScript وتوزيعه ليعمل على كلّ منصّات الهواتف الذكية أمر يستحق التضحية بالقليل (والقليل فقط، أي إلى حدّ معقول) من السّرعة في مقابل كتابة تطبيق منفصل بـJava (لن تتخيّل عدد السّطور المُرعب الذي تحتاجه!) وآخر بـObjective C (أو Swift) وآخر بلغة ما لـWindows Phone (إن كان هناك من يُطوّر لهذا النظام! ونعم أنا جاهل به لدرجة أنّني لا أعرف شيئًا عن اللّغة التي تُستخدم لتطوير تطبيقاته!). لكن بعد تجاوز هذا الحد المعقول من التضحية، يجدر بك أن تُعيد النظر في صلاحية هذه اللّغة إذا ما أردت تطبيقًا ينفّذ مهمّة تتطلّب سرعة فائقة، دعك من أنّ هناك تطبيقات تحتاج إلى التعامل مع النظام بطريقة لا توفّرها بيئة تطبيقات الويب على هذه الأجهزة.وثانيًا JavaScript هي عالم من الفوضى إن تغاضينا عن التنظيم الممتاز في Node.js ونظرنا إلى بيئة المتصفّحات... كم مرّة عانى مطوّرو الويب من عدم التوافق... المتصفّح الفلاني يوفّر الميزة الفلانية... رائع! هذا بالضبط ما أحتاجه! لكن للأسف المتصفح الآخر لا يوفّرها، وستحتاج إلى مكتبة بديلة (polyfill) لسدّ هذا الفراغ، حسنًا سأضيف هذا الـpolyfill وسيمكنني التطوير لكل المتصفّحات... نعم، باستثناء أنّ هذا الـpolyfill يسدّ الفراغ بنسخة قديمة من معيار هذه الميزة التي تحتاجها - فكما تعرف أعضاء منظّمة W3C لا يتوقّفون عن تغيير الواجهات البرمجيّة للأشياء التجريبية في المتصفّحات... ألم نُحذّرك من استخدام هذه الميزة غير المُستقرّة؟ كان يجدر بك أن تبحث عن حلّ بديل!... وهذا هو بالضّبط السّبب الذي يجعلني أكره التطوير للواجهات (front-end development) ولهذا قرّرت الاعتزال في عالم Node.js والتطوير للنهاية الخلفيّة (backend)! لا شيء من أحلامك الورديّة يتحقّق بسهولة في عالم المتصفّحات! أعرف أن الأمور في تحسّن دائم، وهناك الكثير من الأشياء الرائعة القادمة... مثل applicationCache وindexedDb وObject.observe وWeb Components وHTML Imports... لكنّ المشكلة أنها جميعها ليست مستقرّة أو غير مُتبنّاة في كلّ المتصفّحات بعد؛ ويبدو أن هذه الفوضى لن تنتهي يومًا، ولا حلّ لها سوى المزيد من فوضى المكتبات البديلة، هل قلت يومًا إنّ الويب هو مستقبل التطوير الموحّد لكلّ المنصّات؟ لقد كنت ساذجًا!هناك الكثير من عيوب التصميم في JavaScript، بعضها يعود لكونها لغة صُمّمت لإنجاز مهام بسيطة وعلى عجَلَ (في الحقيقة Brendan Eich صمّمها خلال 10 أيام لمتصفّح Netscape)، فمثلاً لا يُمكنك إنشاء خيوط (threads) لأنّها تعمل ضمن خيط واحد (single-threaded)، هناك الكثير من الحلول الالتفافيّة (workarounds) في Node.js والمتصفّحات تعدنا بحل محدود الفعاليّة (Web Workers) لكنّ إيّاك أن تُخبر مُبرمج Java بأنّه لا يُمكنك إنشاء threads في JavaScript ثمّ تدعي أنّها لغة قويّة! أيضًا لا تستغرب إن قضيت ساعات تشرح لمطوّري اللّغات الأخرى عن حلقة الأحداث (event loop) وكيف تعمل ولماذا عليك تمرير استدعاءات راجعة (callbacks) عندما تُنفّذ طلبات XMLHttpRequest وما هو جحيم الاستدعاءات (callback hell) ولماذا ظهرت بدائل عنها مثل الوعود (Promise) التي تحوّل جحيم الاستدعاءات إلى جحيم then!asynctask1(function(err1, data1) { if (err1) { throw err1; return; } asynctask2(data1, function(err2, data2) { if (err2) { throw err2; return; } asynctask3(data2, function(err3, data3) { // ... WELCOME TO JAVASCRIPT! }) }) });أخيرًا أرجوك لا تنسى بديهيّة أن JavaScript بحدّ ذاتها لغة تُفسّر بمحرّك مكتوب بـC++ أو لغة أخرى أعرق وأقوى أداءً، النّواة Linux مكتوبة بخليط من C وC++ وبرامج تشغيل الأجهزة (drivers) غالبًا تُكتب بـAssembly. نعم، صدّقني JavaScript ليست اللّغة الوحيدة ضمن المجموعة الشّمسيّة!ضحكت كثيرًا عندما شاهدت هذه الصورة منذ بضعة أسابيع، التي تُعبّر بالضّبط عن موضوع هذه التدوينة: تسخر هذه الصّورة بشدّة من تعصّب بعض مبرمجي JavaScript الذي يدعوهم إلى الظنّ بأنّ على كل تطبيق جديد في الكرة الأرضية أن يستخدم JavaScript من اليوم فصاعدًا، وأنّ لغات أخرى ستصبح طيّ النسيان ولا يستخدمها إلّا المبرمجون القدامى الذين عفى عليهم الدهر؛ الأمر ليس مقتصرًا على متعصّبي JavaScript وحدهم، فلكلّ لغة برمجة أنصارها ومتعصّبوها، لكن JavaScript بالذّات هي أكثر اللغات التي تترافق بهذه الظاهرة، للأسباب التي ذكرتها سابقًا. ما خُلاصة هذا الحديث؟ما أريد قوله من هذه التدوينة السّريعة هو أن أنصح المتعصّبين للغة برمجية أيًّا كانت أن يتوقّفوا عن إهانة قدراتهم العقليّة على التّعلّم وتقبّل الكتابة بلغة أخرى يرون أنّها للفاشلين فقط أو للقادمين من العصر الحجري... لم أتعلّم هذا إلا بالطّريقة الصّعبة، وأعتقد أنّ السّبب الّذي جعلني أتقبّل هذه الحقيقة هو كوني غير مختصّ، تعلّمي للبرمجة غير مرهون بعملٍ أو بربح مادّيٍّ، أنا فقط أُبرمج على سبيل التّسلية، وكلّ مشاريعي التي كتبتها (هذه المدوّنة بنيتها من الصّفر، وتطبيق aQuran، وتطبيق جديد أكتبه لتوليد خلاصات RSS...) كلّها كانت مجرّد تجربة ومحاولة لاستكشاف أنماط التصميم (design patterns) ومفاهيم برمجيّة أخرى. نعم تعلّمت الكثير عن البرمجة عمومًا من خلال JavaScript، لكنّها لن تكون اللّغة الوحيدة التي أكتب بها لبقيّة حياتي بالطّبع! يبدو أن هدفي التالي سيكون لغة Go حديثة العهد. البرمجة أوسع من صياغة اللّغة (syntax) لذا فلا يعتبر انتقالي لتعلّم لغة أخرى خسارة، والكثير من المفاهيم البرمجية كالبرمجة المُقادة بالاختبارات (test-driven development) والتّعامل مع الاستثناءات (exception handling) ومكوّنات اللغة كالأصناف (classes) والواجهات (interfaces) والدّوالّ (functions) هي أشياء توجد بعضها أو كلّها في كلّ اللّغات.1 نقطة