Emq Mohammed

الأعضاء
  • المساهمات

    21
  • تاريخ الانضمام

  • تاريخ آخر زيارة

السُّمعة بالموقع

2 Neutral
  1. نُنشِئ الكائنات باستخدام الصيغة الاعتيادية المختصرة {...}. لكننا نحتاج لإنشاء العديد من الكائنات المتشابهة غالبًا، مثل العديد من المستخدمين، أو عناصر لقائمة وهكذا. يمكن القيام بذلك باستخدام الدوال البانية (constructor functions) لكائن والمُعامِل "new". الدالة البانية تقنيًا، الدوال البانية هي دوال عادية، لكن يوجد فكرتين متفق عليها: أنها تبدأ بأحرف كبيرة. يجب تنفيذها مع المُعامِل "new" فقط. إليك المثال التالي: function User(name) { this.name = name; this.isAdmin = false; } let user = new User("Jack"); alert(user.name); // Jack alert(user.isAdmin); // false عند تنفيذ دالة مع الُعامِل new، تُنَفَّذ الخطوات التالية: يُنشَأ كائن فارغ ويُسنَد إلى this. يُنَفَّذ محتوى الدالة. تقوم غالبًا بتعديل this، وإضافة خاصيات إليه. تُرجَع قيمة this. بمعنى آخر، تقوم new User(...)‎ بشيء يشبه ما يلي: function User(name) { // this = {}; (implicitly) // this إضافة خاصيات إلى this.name = name; this.isAdmin = false; // this; (implicitly) إرجاع } إذًا، تُعطي let user = new User("Jack")‎ النتيجة التالية ذاتها: let user = { name: "Jack", isAdmin: false }; الآن، إن أردنا إنشاء مستخدمين آخرين، يمكننا استدعاء new User("Ann")‎، و new User("Alice‎")‎ وهكذا. تعدُّ هذه الطريقة في بناء الكائنات أقصر من الطريقة الاعتيادية عبر الأقواس فقط، وأيضًا أسهل للقراءة. هذا هو الغرض الرئيسي للبانيات، وهي تطبيق شيفرة قابلة لإعادة الاستخدام لإنشاء الكائنات. لاحظ أنَّه يمكن استخدام أي دالة لتكون دالة بانية تقنيًا. يعني أنه يمكن تنفيذ أي دالة مع new، وستُنَفَّذ باستخدام الخوارزمية أعلاه. استخدام الأحرف الكبيرة في البداية هو اتفاق شائع لتمييز الدالة البانية من غيرها وأنَّه يجب استدعاؤها مع new. new function() { … }‎ إن كان لدينا العديد من الأسطر البرمجية، وجميعها عن إنشاء كائن واحد مُعَقَّد، فبإمكاننا تضمينها في دالة بانية، هكذا: let user = new function() { this.name = "John"; this.isAdmin = false; // ...شيفرة إضافية لإنشاء مستخدم // ربما منطق معقد أو أي جمل // متغيرات محلية وهكذا.. }; لا يمكن استدعاء المُنشِئ مجددًا، لأنه غير محفوظ في أي مكان، يُنشَأ ويُستدعى فقط. لذا فإن الخدعة تهدف لتضمين الشيفرة التي تُنشِئ كائنًا واحدًا، دون إعادة الاستخدام وتكرار العملية مستقبلًا. وضع اختبار الباني: new.target ميزة متقدمة: تُستخدم الصيغة في هذا الجزء نادرًا، ويمكنك تخطيها إلا إن كنت تُريد الإلمام بكل شيء. يمكننا فحص ما إن كانت الدالة قد استدعيت باستخدام new أو دونه من داخل الدالة، وذلك باستخدام الخاصية الخاصة new.target. تكون الخاصية فارغة في الاستدعاءات العادية، وتساوي الدالة البانية إذا استُدعِيَت باستخدام new: function User() { alert(new.target); } // "new" بدون: User(); // undefined // باستخدام "new": new User(); // function User { ... } يمكن استخدام ذلك بداخل الدالة لمعرفة إن استُدعِيَت مع new، "في وضع بناء كائن"، أو بدونه "في الوضع العادي". يمكننا أيضًا جعل كلًا من الاستدعاء العادي و new ينفِّذان الأمر ذاته -بناء كائن- هكذا: function User(name) { if (!new.target) { // new إن كنت تعمل بدون return new User(name); // new ...سأضيف } this.name = name; } let john = User("John"); // new User تُوَجِّه الاستدعاء إلى alert(john.name); // John يستخدم هذا الاسلوب في بعض المكتبات أحيانًا لجعل الصيغة أكثر مرونة حتى يتمكن الأشخاص من استدعاء الدالة مع new أو بدونه، وتظل تعمل. ربما ليس من الجيد استخدام ذلك في كل مكان، لأن حذف new يجعل ما يحدث أقل وضوحًا. لكن مع new، يعلم الجميع أن كائنًا جديدًا قد أُنشِئ. ما تُرجِعه الدوال البانية لا تملك الدوال البانية عادةً التعليمة return. فَمُهِمَتُهَا هي كتابة الأمور المهمة إلى this، وتصبح تلقائيًا هي النتيجة. لكن إن كان هناك التعليمة return فإن القاعدة بسيطة: إن استُدعِيَت return مع كائن، يُرجَع الكائن بدلًا من this. إن استُدعِيَت return مع متغير أولي، يُتَجاهَل. بمعنىً آخر، return مع كائن يُرجَع الكائن، وفي الحالات الأخرى تُرجَع this. مثلًا، يعاد في المثال التالي الكائن المرفق بعد return ويهمل الكائن المسنَد إلى this: function BigUser() { this.name = "John"; return { name: "Godzilla" }; // <-- تُرجِع هذا الكائن } alert( new BigUser().name ); // Godzilla, حصلنا على الكائن وهنا مثال على استعمال return فارغة (أو يمكننا وضع متغير أولي بعدها، لا فرق): function SmallUser() { this.name = "John"; return; // ← this تُرجِع } alert( new SmallUser().name ); // John لا تحوي الدوال البانية غالبًا على تعليمة الإعادة return. نذكر هنا هذا التصرف الخاص عند إرجاع الكائنات بغرض شمول جميع النواحي. حذف الاقواس بالمناسبة، يمكننا حذف أقواس new في حال غياب المعاملات مُعامِلات: let user = new User; // <-- لا يوجد أقوس // الغرض ذاته let user = new User(); لا يُعد حذف الأقواس أسلوبًا جيدَا، لكن الصيغة مسموح بها من خلال المواصفات. الدوال في الباني استخدام الدوال البانية لإنشاء الكائنات يُعطي مرونة كبيرة. قد تحوي الدالة البانية على مُعامِلات ترشد في بناء الكائن ووضعه، إذ يمكننا إضافة خاصيات ودوال إلى this بالطبع. مثلًا، تُنشِئ new User(name)‎ في الأسفل كائنًا بالاسم المُعطَى name والدالة sayHi: function User(name) { this.name = name; this.sayHi = function() { alert( "My name is: " + this.name ); }; } let john = new User("John"); john.sayHi(); // My name is: John /* john = { name: "John", sayHi: function() { ... } } */ لإنشاء كائنات أكثر تعقيدًا، يوجد صيغة أكثر تقدمًا، الفئات، والتي سنغطيها لاحقًا. الخلاصة الدوال البانية، أو باختصار البانيات، هي دوال عادية، لكن يوجد اتفاق متعارف عليه ببدء اسمها بحرف كبير. يجب استدعاء الدوال البانية باستخدام new فقط. يتضمن هذا الاستدعاء إنشاء كائن فارغ وإسناده إلى this وبدء العملية ثم إرجاع هذا الكائن في نهاية المطاف. يمكننا استخدام الدوال البانية لإنشاء كائنات متعددة متشابهة. تزود JavaScript دوالًا بانية للعديد من الأنواع (الكائنات) المدمجة في اللغة: مثل النوع Date للتواريخ، و Set للمجموعات وغيرها من الكائنات التي نخطط لدراستها. عودة قريبة غطينا الأساسيات فقط عن الكائنات وبانياتها في هذا الفصل. هذه الأساسيات مهمة تمهيدًا لتعلم المزيد عن أنواع البيانات والدوال في الفصل التالي. بعد تعلم ذلك، سنعود للكائنات ونغطيها بعمق في فصل الخصائص، والوراثة، والأصناف. تمارين دالتين - كائن واحد الأهمية: 2 هل يمكن إنشاء الدالة A و B هكذا new A()==new B()‎؟ function A() { ... } function B() { ... } let a = new A; let b = new B; alert( a == b ); // true إن كان ممكنًا، وضح ذلك بمثال برمجي. الحل نعم يمكن ذلك. إن كان هناك دالة تُرجِع كائنًا فإن new تُرجِعه بدلًا من this. لذا فمن الممكن، مثلًا، إرجاع الكائن المعرف خارجيًا obj: let obj = {}; function A() { return obj; } function B() { return obj; } alert( new A() == new B() ); // true إنشاء حاسبة جديدة الأهمية: 5 إنشِئ دالة بانية باسم Calculator تنشئ كائنًا بثلاث دوال: read()‎ تطلب قيمتين باستخدام سطر الأوامر وتحفظها في خاصيات الكائن. sum()‎ تُرجِع مجموع الخاصيتين. mul()‎ تُرجِع حاصل ضرب الخاصيتين. مثلًا: let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() ); الحل function Calculator() { this.read = function() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); }; this.sum = function() { return this.a + this.b; }; this.mul = function() { return this.a * this.b; }; } let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() ); إنشاء مجمِّع الأهمية: 5 انشِئ دالة بانية باسم Accumulator(startingValue)‎، إذ يجب أن يتصف هذا الكائن بأنَّه: يخزن القيمة الحالية في الخاصية value. تُعَيَّن قيمة البدء عبر المعامل startingValue المعطى من الدالة البانية. يجب أن تستخدم الدالة read() الدالة prompt لقراءة رقم جديد وإضافته إلى value. بمعنى آخر، الخاصية value هي مجموع القيم المدخلة بواسطة المستخدم بالإضافة إلى القيمة الأولية startingValue. هنا مثال على ما يجب أن يُنَفَّذ: let accumulator = new Accumulator(1); // القيمة الأولية 1 accumulator.read(); // يضيف قيمة مدخلة بواسطة المستخدم accumulator.read(); // يضيف قيمة مدخلة بواسطة المستخدم alert(accumulator.value); // يعرض مجموع القيم الحل function Accumulator(startingValue) { this.value = startingValue; this.read = function() { this.value += +prompt('How much to add?', 0); }; } let accumulator = new Accumulator(1); accumulator.read(); accumulator.read(); alert(accumulator.value); ترجمة -وبتصرف- للفصل Constructor, operator "new" من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: توابع الأنواع الأولية المقال السابق: التحويل من نوع كائن إلى نوع أولي
  2. ماذا يحدث عند جمع كائنين بالشكل obj1 + obj2، أو طرح كائنين obj1 - obj2 أو طباعة الكائنات باستخدام alert(obj)‎؟ تحوَّل الكائنات في مثل هذه الحالات إلى أنواع أولية (primitives) ثم تُنَفَّذ العملية. رأينا في فصل تحويل الأنواع قواعد التحويل بين الأنواع الأساسية مثل الارقام، النصوص أو القيم المنطقية، لكن بقيت فجوة متعلقة بالكائنات. الآن بعد أن تعرفنا على الرموز والدوال، يمكن ملء هذه الفجوة. 1- في السياق المنطقي (boolean)، جميع الكائنات تساوي القيمة true، إذ يوجد تحوَّل الكائنات إلى أرقام ونصوص فقط. 2- يحدث التحويل العددي (numeric) عند طرح الكائنات أو تنفيذ العمليات الرياضية عليها. مثلًا، يمكن طرح كائنات Date (سيتم شرحها في فصل التاريخ والوقت) ويكون ناتج طرح date1 - date2 هو الفارق الزمني بين التاريخين. 3- بالنسبة للتحويل إلى نص (string)، يحدث ذلك عادةً عند إخراج كائن بالطريقة alert(obj)‎ وأي سياق مشابه. التحويل إلى أنواع أساسية عبر ToPrimitive يمكننا ضبط التحويل إلى نص أو عدد باستخدام دوال خاصة بالكائنات. يوجد ثلاثة أنواع للتحويل بين الأنواع (يطلق عليها «النوع المخمَّن» [hint] الذي سيجري تحويل الكائن إليه)، مشروحة في هذه المواصفات: إلى سلسلة نصية "string" يُجرَى التحويل من كائن لنص عند القيام بعمليات على كائن تتوقع أن يكون الكائن نصًا، مثل alert: // المخرجات alert(obj); // استخدام كائن كمفتاح للخاصية anotherObj[obj] = 123; إلى عدد "number" يجري تحويل كائن لعدد عند القيام بعمليات حسابية: // تحويل صريح let num = Number(obj); // ( عمليات رياضية (عدى عملية الجمع الأحادي let n = +obj; // عملية جمع أحادية let delta = date1 - date2; // عملية موازنة أصغر/أكبر let greater = user1 > user2; إلى نوع افتراضي "default" يحدث في حالات نادرة عندما لا يكون المُشَغِّل متأكدًا من نوع البيانات المُتوَقَّع. فمثلًا، يمكن لِمُعامِل الجمع الثنائي + أن يتعامل مع كلًا من النصوص (دمج السلسلتين) ومع الأرقم (بجمعها)، لذا فإن كلًا من النصوص والأرقام تعمل بشكل صحيح. أيضًا، عند موازنة كائن بنص، أو رقم أو رمز، فلا يكون واضحًا أي نوع من التحويلات هو المطلوب. // الجمع الثنائي let total = car1 + car2; // obj == string/number/symbol if (user == 1) { ... }; يمكن لِمعاملات الموازنة أكبر/أصغر <> التعامل أيضًا مع كلً من النصوص والأعداد لكنها ترجح التحويل إلى عدد دون النظر إلى التحويل الافتراضي المرجح في هذه الحالة وذلك لأسباب تاريخية. عمليًا، تُطبِّق جميع الكائنات المُدمجة في اللغة التحويل الافتراضي "default" بنفس طريقة التحويل إلى عدد "number". وربما يجب أن نقوم بذلك أيضًا. (عدا الكائن Date، والذي سَنتعلمه لاحقَا.) لاحظ أن هناك ثلاثة أنواع للتحويل فقط، إذ الأمر بهذه البساطة. لا يوجد تحويل للنوع المنطقي (boolean، فجميع الكائنات تحمل القيمة true في السياق المنطقي) أو أي شيء آخر. وإن عاملنا كلًا من التحويل الافتراضي "default" والتحويل العددي "number" بالطريقة ذاتها كما تقوم أغلب الدوال المدمجة، فسيكون هنالك تحويلين فقط. تحاول JavaScript العثور على ثلاث دوال للكائن واستدعائها عند القيام بالتحويل استدعاء obj[Symbol.toPrimitive](hint)‎ - الدالة ذات المفتاح الرمزي Symbol.toPrimitive (رمز نظام)، إن كانت هذه الدالة موجودة. أو إن كان النوع المخمَّن هو نص "string" جرب obj.toString()‎ و obj.valueOf()‎، أيًا كانت متواجدة. إن كان hint هو عدد "number" أو "default" جرب obj.valueOf()‎ و obj.toString()‎ أيًا كانت متواجدة. تابع التحويل Symbol.toPrimitive لنبدأ من التابع الأول؛ يوجد رمز مُضَمَّن في JavaScript مُسَمَّى Symbol.toPrimitive والذي يجب استخدامه لتسمية تابع التحويل، هكذا: obj[Symbol.toPrimitive] = function(hint) { // يجب أن تُرجِع قيمة أولية // النوع المخمَّن هو إما نص أو عدد أو النوع الافتراضي }; مثلا، الكائن user هنا يتضمنها: let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // تجربة للتحويل: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 كما يمكننا أن نرى في الشيفرة، أصبح user نصًا يصف نفسه أو كمية من المال وفقًا للتحويل. تقوم الدالة user[Symbol.toPrimitive]‎ بجميع حالات التحويل. التحويل إلى نص عبر toString أو valueOf ظهر التابعان toString و valueOf منذ وقت طويل، ولا يتبعان إلى نوع الرمز (symbol، إذ لم تكن الرموز موجودة في ذلك الحين)، لكنهما تابعين مسميان بأسماء توحي بارتباطهما بالنوع النصي، إذ كانت توفر آلية لعملية التحويل أصبحت قديمة النمط الآن. إن لم يكن هناك تنفيذًا للتابع Symbol.toPrimitive فتحاول JavaScript إيجاد هذه الدوال بالترتيب التالي: toString ->‏ valueOf للنصوص. valueOf ->‏ toString لباقي الأنواع. مثلا، يقوم الكائن user بنفس الغرض السابق باستخدام toString و valueOf: let user = { name: "John", money: 1000, // في حال النوع المخمَّن سلسلة نصية toString() { return `{name: "${this.name}"}`; }, // في حال النوع المخمَّن عدد أو النوع الافتراضي valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 كما رأينا، يتنفذ السلوك ذاته كما في المثال السابق الذي استخدم Symbol.toPrimitive. نحتاج غالبًا إلى مكان واحد يقوم بجميع التحويلات للأنواع الأولية (نص أو عدد). ففي هذه الحالة، يمكننا استخدام التابع toString فقط لينفِّذ جميع عمليات التحويل، كما يلي: let user = { name: "John", toString() { return this.name; } }; alert(user); // toString -> John alert(user + 500); // toString -> John500 سينفِّذ التابع toString بجميع التحويلات الأولية في غياب كلًا من Symbol.toPrimitive و valueOf. الأنواع المعادة الأمر المهم الذي يجب معرفته عن جميع دوال التحويل بين الأنواع الأولية هو أنها لا تُرجِع بالضرورة النوع المخمَّن (hint) الأولي ذاته. لا يوجد تحكم في ما إن كانت toString()‎ ترجع نصًا، أو أن Symbol.toPrimitive تُرجِع عددًا عند تحويل عدد. الأمر الوحيد المعروف والثابت هو أنَّ هذه الدوال تُرجِع نوعًا اوليًا وليس كائنًا. ملاحظة تاريخية لا يوجد خطأ إن أعاد التابع toString أو valueOf كائنًا، وذلك لأسباب تاريخية، لكن يتم تجاهل مثل هذه القيم (وكأن الدالة ليست موجودة). وذلك لعدم وجود مبدأ الخطأ الجيد (good error) في JavaScript حينها. بالمقابل، يجب أن يعيد التابع Symbol.toPrimitive قيمة أولية، وإلا فسيكون هناك خطأ. عمليات تحويل إضافية عرفنا مسبقًا أن جميع العوامل (operator) والدوال تجري عمليات تحويل على الأنواع التي تتعامل معها مثل معامل الضرب * يحول جميع المُعامَلات (operands) إلى أعداد. فإن مرَّرنا كائنًا عبر وسيط إلى إحدى الدوال أو العمليات، فستمر عملية التحويل على مرحلة أو مرحلتين هما: يحوَّل الكائن إلى نوع أولي (باستعمال القواعد التي تحدثنا عنها آنفًا) إن لم يكن النوع الأولي الناتج مطابقًا للنوع المطلوب، فيحوَّل إلى النوع المطلوب وفق مبدأ التحويل بين الأنواع الأولية إليك المثال التالي: let obj = { // جميع التحويلات في غياب باقي الدوال toString يجري التابع toString() { return "2"; } }; // حُوِّل الكائن إلى القيمةا لأولية "2", ثم جعلته عملية الضرب عددًا alert(obj * 2); // 4 حوَّلت عملية الضرب obj * 2 الكائن إلى نوع أولي، وكان النوع المعاد من عملية التحويل سلسلةً نصية، هي "2". جرى بعدئذٍ تحويل تلك السلسلة في العملية ‎"2" * 2، إلى عدد ليصبح 2 * 2. في المثال التالي، يقوم الجمع الثنائي بدمج النصوص في هذه الحالة والاكتفاء بعملية تحويل الكائن إلى سلسلة نصية: let obj = { toString() { return "2"; } }; // (أعادت عملية التحويل إلى نوع أولي نصًا => دمج) alert(obj + 2); // 22 الخلاصة تجرَى عملية التحويل من كائن لنوع أولي تلقائيًا بواسطة العديد من الدوال المدمجة في اللغة والمُعاملات التي تتوقع أن تتعامل مع قيم أولية. يخمَّن النوع الأولي المراد تحويل الكائن إليه إلى: سلسلة نصية "string" (للدالة alert والعمليات الأخرى التي تتعامل مع نصوص) عدد "number" (للعمليات الرياضية) -النوع الافتراضي "default" (لبعض العوامل) تُحدِّد المواصفات النوع المخمَّن (hint) الذي يستخدمه كل مُعامل بوضوح. يوجد القليل من العوامل التي لا تعلم ما النوع المتوقع وتستخدم النوع الافتراضي "default". يُعامل النوع الافتراضي "default" معامل العدد "number" في الكائنات أغلب الأحيان؛ لذا، يتم دمج النوعين عمليًا. خوارزمية التحويل كالتالي: استدعاء obj[Symbol.toPrimitive](hint)‎ إن وُجِدَت، أو إن كان النوع المخمَّن هو نص "string" جرب obj.toString()‎ و obj.valueOf()‎، أيًا كانت متواجدة. إن كان النوع المُخمَّن هو عدد "number" أو "default" جرب obj.valueOf()‎ و obj.toString()‎ أيًا كانت متواجدة. عمليًا، يكفي استخدام obj.toString()‎ فقط لإجراء جميع التحويلات، إذ تعيد "شيئًا مقروءًا" يمثِّل الكائن يستعمل هذا التمثيل لعملية التسجيل أو التنقيح. ترجمة -وبتصرف- للفصل Object to primitive conversion من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: الباني والعامل "new" المقال السابق: الدوال في الكائنات واستعمالها this
  3. تُنشّأ الكائنات عادة لتُمَثِّل أشياء من العالم الحقيقي مثل المستخدمين، والطلبات، وغيرها: let user = { name: "John", age: 30 }; يمكن للمستخدم في العالم الحقيقي أن يقوم بعدة تصرفات: مثل اختيار شيء من سلة التسوق، تسجيل الدخول، والخروج …إلخ. تُمَثَّل هذه التصرفات في لغة JavaScript بإسناد دالة إلى خاصية وتدعى الدالة آنذاك بالتابع (method، أي دالة تابعة لكائن). أمثلة على الدوال بدايةً، لنجعل المستخدم user يقول مرحبًا: let user = { name: "John", age: 30 }; user.sayHi = function() { alert("Hello!"); }; user.sayHi(); // Hello! استخدمنا هنا تعبير الدالة لإنشاء دالة تابع للكائن user وربطناها بالخاصية user.sayHi ثم استدعينا الدالة. هكذا أصبح بإمكان المستخدم التحدث! الآن أصبح لدى الكائن user الدالة sayHi. يمكننا أيضًا استخدام دالة معرفة مسبقًا بدلًا من ذلك كما يلي: let user = { // ... }; // أولا، نعرف دالة function sayHi() { alert("Hello!"); }; // أضِف الدالة للخاصية لإنشاء تابع user.sayHi = sayHi; user.sayHi(); // Hello! البرمجة الشيئية (Object-oriented programming) يسمى كتابة الشيفرة البرمجية باستخدام الكائنات للتعبير عن الاشياء «بالبرمجة الشيئية/كائنية» (object-oriented programming، تُختَصَر إلى "OOP"). OOP هو موضوع كبيرجدًا، فهو علم مشوق ومستقل بذاته. يعلمك كيف تختار الكائنات الصحيحة؟ كيف تنظم التفاعل فيما بينها؟ كما يعد علمًا للهيكلة ويوجد العديد من الكتب الأجنبية الجيدة عن هذا الموضوع مثل كتاب “Design Patterns: Elements of Reusable Object-Oriented Software” للمؤلفين E.Gamma، و R.Helm، و R.Johnson، و J.Vissides أو كتاب “Object-Oriented Analysis and Design with Applications” للمؤلف G.Booch، وغيرهما. اختصار الدالة يوجد طريقة أقصر لكتابة الدوال في الكائنات المعرفة تعريفًا مختصرًا باستعمال الأقواس تكون بالشكل التالي: // يتصرف الكائنان التاليان بالطريقة نفسها user = { sayHi: function() { alert("Hello"); } }; // يبدو شكل الدالة المختصر أفضل، أليس كذلك؟ user = { sayHi() { // مثل "sayHi: function()" alert("Hello"); } }; يمكننا حذف الكلمة المفتاحية "function" وكتابة sayHi()‎ كما هو موضح. حقيقةً، التعبيرين ليسا متطابقين تمامًا، يوجد اختلافات خفية متعلقة بالوراثة في الكائنات (سيتم شرحها لاحقًا)، لكن لا يوجد مشكلة الآن. يفضل استخدام الصياغة الأقصر في كل الحالات تقريبًا. الكلمة المفتاحية "this" في الدوال من المتعارف أن الدوال تحتاج للوصول إلى المعلومات المخزنة في الكائن لِتنفذ عملها. مثلًا، قد تحتاج الشيفرة التي بداخل user.sayHi()‎ لِاسم المستخدم user. هنا، يمكن للدالة استخدام الكلمة المفتاحية this للوصول إلى نسخة الكائن التي استدعتها. أي، قيمة this هي الكائن "قبل النقطة" الذي استُخدِم لاستدعاء الدالة. مثلًا: let user = { name: "John", age: 30, sayHi() { // هو الكائن الحالي "this" alert(this.name); } }; user.sayHi(); // John أثناء تنفيذ user.sayHi()‎ هنا، ستكون قيمة this هي الكائن user. عمليًا، يمكن الوصول إلى الكائن بدون استخدام this بالرجوع إليه باستخدام اسم المتغير الخارجي: let user = { name: "John", age: 30, sayHi() { alert(user.name); // "user" يدلًا من "this" } }; لكن، لا يمكن الاعتماد على الطريقة السابقة. فإذا قررنا نسخ الكائن user إلى متغير آخر، مثلا: admin = user وغيرنا محتوى user لشيء آخر، فسيتم الدخول إلى الكائن الخطأ كما هو موضح في المثال التالي: let user = { name: "John", age: 30, sayHi() { alert( user.name ); // يتسبب في خطأ } }; let admin = user; user = null; // تغيير المحتوى لتوضيح الأمر admin.sayHi(); // يُرجِع خطأ sayHi() استخدام الاسم القديم بِداخل إن استخدمنا this.name بدلًا من user.name بداخل alert، فستعمل الشيفرة عملًا صحيحًا. "this" غير محدودة النطاق الكلمة المفتاحية this في JavaScript تتصرف تصرفًا مختلفًا عن باقي اللغات البرمجية. فيمكن استخدامها في أي دالة. انظر إلى المثل التالي، إذ لا يوجد خطأ في الصياغة: function sayHi() { alert( this.name ); } تُقَيَّم قيمة this أثناء تنفيذ الشيفرة بالاعتماد على السياق. مثلًا، في المثال التالي، تم تعيين الدالة ذاتها إلى كائنين مختلفين فيصبح لكل منهما قيمة مختلفة لـ "this" أثناء الاستدعاء: let user = { name: "John" }; let admin = { name: "Admin" }; function sayHi() { alert( this.name ); } // استخدام الدالة ذاتها مع كائنين مختلفين user.f = sayHi; admin.f = sayHi; // this لدى الاستدعائين قيمة مختلفة لـ // التي بداخل الدالة تعني المتغير الذي قبل النقطة "this" user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (يمكن الوصول إلى الدالة عبر الصيغة النقطية أو الأقواس المربعة – لا يوجد مشكلة في ذلك) القاعدة ببساطة: إذا استُدعِيَت الدالة obj.f()‎، فإن this هي obj أثناء استدعاء f؛ أي إما user أو admin في المثال السابق. استدعاءٌ دون كائن: this == undefined يمكننا استدعاء الدالة دون كائن: function sayHi() { alert(this); } sayHi(); // undefined - غير معرَّف في هذه الحالة ستكون قيمة this هي undefined في الوضع الصارم. فإن حاولنا الوصول إلى this.name سيكون هناك خطأ. في الوضع غير الصارم، فإن قيمة this في هذه الحالة ستكون المتغير العام (في المتصفح window والتي سَنشرحها في فصل المتغيرات العامة). هذا السلوك زمني يستخدم إصلاحات الوضع الصارم "use strict". يُعد هذا الاستدعاء خطأً برمجيًا غالبًا. فإن وًجِدت this بداخل دالة، فمن المتوقع استدعاؤها من خلال كائن. الأمور المترتبة على this الغير محدودة النطاق إن أتيت من لغة برمجية أخرى، فمن المتوقع أنك معتاد على "this المحدودة" إذ يمكن لِلدوال المعرَّفة في الكائن استخدام this التي ترجع للكائن. تستخدم this بحرية في JavaScript، وتُقَيَّم قيمتها أثناء التنفيذ ولا تعتمد على المكان حيث عُرِّفت فيه، بل على الكائن الذي قبل النقطة التي استدعت الدالة. يوجد ايجابيات وسلبيات لمبدأ تقييم this أثناء وقت التشغيل. فمن ناحية، يمكن إعادة استخدام الدالة مع عدة كائنات، ومن الناحية الأخرى، المرونة الأكثر تعطي فرصًا أكثر للخطأ. لسنا بصدد الحكم على تصميم اللغة ونعته بالجيد أم سيء، بل نحاول فهم طريقة عملها وكيفية الاستفادة من ميزاتها وتجنب الأخطاء. ميزة داخلية: النوع المرجعي يُغطي هذا الجزء ميزة متقدمة -من ميزات اللغة-لفهم أفضل لحالة معينة. إن كنت على عجلة من أمرك، يمكنك تخطي أو تأجيل هذا الجزء. يمكن لاستدعاء الدالة المعقد أن يفقد this، فمثلًا: let user = { name: "John", hi() { alert(this.name); }, bye() { alert("Bye"); } }; user.hi(); // John (يعمل الاستدعاء البسيط) // user.hi أو وفقًا للاسم user.bye الآن، لنستدعي (user.name == "John" ? user.hi : user.bye)(); // خطأ! يوجد معامل شرطي في السطر الأخير والذي يختار إما user.hi أو user.bye. في هذه الحالة يتم اختيار user.hi ثم يتم استدعاء الدالة مع الأقواس (). لكنها لا تعمل! كما ترى، ينتج خطأ من الاستدعاء لأن قيمة "this" بداخل الاستدعاء أصبحت undefined. ستعمل بهذه الطريقة (الكائن.الدالة): user.hi(); هذه الصياغة لا تُعطي دالة: (user.name == "John" ? user.hi : user.bye)(); // خطأ! لماذا؟ إن أردنا فهم سبب حدوث ذلك، لِنكشف الغطاء عن كيفية عمل الاستدعاء obj.method()‎. عند النظر عن قرب، يمكننا ملاحظة عمليتين في التعليمة obj.method()‎: 1- أولا، النقطة '.' تسترجع الخاصية obj.method. 2- ثم الأقواس () تنفذ الدالة. إذًا، كيف تُمرَّر المعلومات عن this من الجزء الأول للثاني؟ إن وضعنا العمليتين في سطرين منفصلين، فَسنفقد this بالتأكيد: let user = { name: "John", hi() { alert(this.name); } } // فصل الحصول على الدالة واستدعائها في سطرين منفصلين let hi = user.hi; hi(); // غير مُعَرَّفَة this خطأ، لأن تُسنِد التعليمة hi = user.hi الدالة إلى المتغير، ثم، في السطر الأخير تصبح مستقلة، فلا يوجد this هنا ضمن النطاق. تستخدم JavaScript خدعة لجعل user.hi()‎ تعمل - صيغة النقطة '.' لا تُرجِع دالة، بل قيمة من النوع المرجعي الخاص النوع المرجعي هو "نوع للتخصيص". لا يمكننا استخدام هذا النوع بشكل واضح، بل يُسخدَم داخليًا بواسطة اللغة. تُشَكَّل قيمة النوع المرجعي من ثلاث قيم (base, name, strict)، إذ: base هي الكائن. name هو اسم الخاصية. strict تساوي "true" إن كان الوضع الصارم use strict مُفعلًا. النتيجة من الوصول إلى خاصية user.hi ليست دالة، إنما قيمة من النوع المرجعي. بالنسبة لـ user.hi في الوضع الصارم تكون: // قيمة من النوع المرجعي (user, "hi", true) عند استدعاء الأقواس () في النوع المرجعي فإنها تستقبل المعلومة كاملة عن الكائن والدلة، وتتمكن من تعيين this بطريقة صحيحة (في هذه الحالة user). النوع المرجعي هو نوع "وسيط" داخلي، وغرضه هو تمرير المعلومات من الصيغة النُقطية . إلى أقواس الاستدعاء (). أي عملية أخرى مثل الإسناد hi = user.hi تُلغي النوع المرجعي ككل، فهي تأخذ قيمة الدالة user.hi وتُمررها. فَتفقد العمليات التالية this. لذا، ونتيجة لذلك، تُمرَّر قيمة this بالطريقة الصحيحة إن كانت الدالة مُستدعاه مباشرة باستخدام صيغة النقطة obj.method()‎ أو الأقواس المربعة obj['method']()‎ (يؤديان العمل ذاته). سنتعلم طرائق أخرى لحل هذه المشكلة لاحقًا، مثل استخدام func.bind(). الدوال السهمية لا تحوي "this" الدوال السهمية (Arrow function) هي دوال خاصة: فهي لا تملك this مخصصة لها. إن وضعنا this في إحدى هذه الدوال فَستؤخذ قيمة this من الدالة الخارجية. مثلًا، تحصل الدالة arrow()‎ على قيمة this من الدالة الخارجية user.sayHi()‎: let user = { firstName: "Ilya", sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // Ilya يُعد ذلك إحدى ميزات دوال الدوال السهمية، وهي مفيدة عندما لا نريد استخدام this مستقلة، ونريد أخذها من السياق الخارجي بدلًا من ذلك. سَنتعمق في موضوع الدوال السهمية لاحقًا في فصل «إعادة النظر في الدوال السهمية». الخلاصة الدوال المخزنة في الكائنات تسمى «توابع» (methods). تسمح هذه الكائنات باستدعائها بالشكل object.doSomething()‎. يمكن للدوال الوصول إلى الكائن المعرفة فيه (أو النسخة التي استدعته المشتقة منه) باستخدام الكلمة المفتاحيةthis. تُعَرَّف قيمة this أثناء التنفيذ. قد نستخدم this عند تعريف دالة، لكنها لا تملك أي قيمة حتى استدعاء الدالة. يمكن نسخ دالة بين الكائنات. عند استدعاء دالة بالصيغة object.method()‎، فإن قيمة this أثناء الاستدعاء هي object. لاحظ أن الدوال السهمية مختلفة تتعامل تعاملًا مختلفًا مع this إذ لا تملك قيمة لها. عند الوصول إلى this بداخل دالة سهمية فإن قيمتها تؤخذ من النطاق الموجودة فيه. تمارين فحص الصياغة الأهمية: 2 ما نتيجة هذه الشيفرة؟ let user = { name: "John", go: function() { alert(this.name) } } (user.go)() ملاحظة: يوجد فخ الحل خطأ! جرب تشغيل الشيفرة: let user = { name: "John", go: function() { alert(this.name) } } (user.go)() // خطأ! لا تعطي مُعظم رسائل الخطأ في المتصفحات توضيح لسبب الخطأ. سبب الخطأ هو فاصلة منقوطة مفقودة بعد user = {...}‎. لا تقوم JavaScript بوضع فاصلة منقوطة قبل القوس ‎(user.go)()‎. لذا فإنها تقرأ الشيفرة كالتالي: let user = { go:... }(user.go)() يمكننا أيضًا رؤية أن هذا التعبير المتداخل هو استدعاء للكائن { go: ...‎ } كدالة بالمعامل (user.go). ويحدث ذلك أيضًا في السطر نفسه مع let user، لذا فإن الكائن user لم يُعَرَّف بعد، وهكذا يظهر الخطأ. إن وضعنا الفاصلة المنقوطة، سيصبح كل شيء صحيح: let user = { name: "John", go: function() { alert(this.name) } }; (user.go)() // John لاحظ أن الأقواس حول (user.go) لا تعمل شيئًا هنا. فهي ترتب العمليات غالبًا، لكن النقطة لها الأولوية على أي حال. لذا فليس هناك أي تأثير. فقط الفاصلة المنقوطة هي الخطأ. شرح قيمة "this" الأهمية: 3 استدعينا الدالة user.go()‎ في الشيفرة التي بالأسفل 4 مرات متتالية. لكن الاستدعاءان (1) و (2) يعملان عملًا مختلفًا عن الاستدعائين (3) و (4). لماذا؟ let obj, method; obj = { go: function() { alert(this); } }; obj.go(); // (1) [object Object] (obj.go)(); // (2) [object Object] (method = obj.go)(); // (3) غير معرف (obj.go || obj.stop)(); // (4) غير معرف الحل هنا التوضيح: 1- يُعد استدعاء دالة عادي. 2- مثل 1 تمامًا، لا تغير الأقواس ترتيب العمليات هنا، تعمل النقطة أولًا على أي حال. 3- هنا لدينا استدعاء أكثر تعقيدًا ‎(expression).method()‎. يعمل الاستدعاء كما لو تم فصله إلى سطرين: f = obj.go; // حساب التعبير f(); // الاستدعاء تُنَفَّذ f()‎ هنا كدالة، دون this. 4- مشابة ل (3)، لدينا تعبيرًا يسار النقطة .. لشرح سلوك الاستدعائين (3) و (4)، نحتاج لإعادة استدعاء معاملات الوصول لتلك الخاصية (النقطة أو الأقواس المربعة) التي ترجع قيمة من النوع المرجعي. أي عملية عليها عدا استدعاء الدالة (مثل التعيين = أو ||) تُرجِعُها إلى قيمة عادية، والتي لا تحمل المعلومات التي تسمح بتعيين this. استخدام this في الكائن معرَّف باختصار عبر الأقواس الأهمية: 5 تُرجِع الدالة makeUser كائنًا هنا. ما النتيجة من الدخول إلى ref الخاص بها؟ ولماذا؟ function makeUser() { return { name: "John", ref: this }; }; let user = makeUser(); alert( user.ref.name ); // ما النتيجة؟ الحل الإجابة: ظهور خطأ. جربها: function makeUser() { return { name: "John", ref: this }; }; let user = makeUser(); alert( user.ref.name ); // ِلِقيمة غير معرفة 'name' خطأ: لا يمكن قراءة الخاصية ذلك لأن القواعد التي تعين this لا تنظر إلى تعريف الكائن. ما يهم هو وقت الاستدعاء. قيمة this هنا بداخل makeUser()‎ هي undefined، لأنها استُدعيَت كدالة منفصلة، وليس كدالة بصياغة النقطة. قيمة this هي واحدة للدالة ككل، ولا تؤثر عليها أجزاء الشيفرة ولا حتى الكائنات. لذا فإن ref: this تأخذ this الحالي للدالة. هنا حالة معاكسة تمامًا: function makeUser() { return { name: "John", ref() { return this; } }; }; let user = makeUser(); alert( user.ref().name ); // John أصبحت تعمل هنا لأن user.ref()‎ هي دالة، وقيمة this تعَيَّن للكائن الذي قبل النقطة '.'. إنشاء آلة حاسِبة الأهمية: 5 أنشئ كائنًا باسم calculator يحوي الدوال الثلاث التالية: read()‎ تطلب قيمتين وتحفظها كخصائص الكائن. sum()‎ تُرجِع مجموع القيم المحفوظة. mul()‎ تضرب القيم المحفوظة وتُرجِع النتيجة. let calculator = { // ... ضع شيفرتك هنا... }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() ); الحل let calculator = { sum() { return this.a + this.b; }, mul() { return this.a * this.b; }, read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() ); التسلسل الأهمية: 2 لدينا الكائن ladder (سُلَّم) الذي يتيح الصعود والنزول: let ladder = { step: 0, up() { this.step++; }, down() { this.step--; }, showStep: function() { // يعرض الخطوة الحالية alert( this.step ); } }; الآن، إن أردنا القيام بعدة استدعاءات متتالية، يمكننا القيام بما يلي: ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 عَدِّل الشيفرة الخاصة بالدوال up، و down، و showStep لجعل الاستدعاءات متسلسلة كما يلي: ladder.up().up().down().showStep(); // 1 يُستخدم هذا النمط بنطاق واسع في مكتبات JavaScript. الحل الحل هو إرجاع الكائن نفسه من كل استدعاء. let ladder = { step: 0, up() { this.step++; return this; }, down() { this.step--; return this; }, showStep() { alert( this.step ); return this; } } ladder.up().up().down().up().down().showStep(); // 1 يمكننا أيضا كتابة استدعاء مستقل في كل سطر ليصبح سهل القراءة بالنسبة للسلاسل الأطول: ladder .up() .up() .down() .up() .down() .showStep(); // 1 ترجمة -وبتصرف- للفصل Object methods, "this" من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: التحويل من نوع كائن إلى نوع أولي المقال السابق: النوع الرمزي (Symbol)
  4. وفقًا للمواصفات، قد تكون مفاتيح خصائص الكائنات من نوع نصي (string) أو رمزي (Symbol)، ولا تكون أرقامًا ولا قيمًا منطقية، إما نصًا أو رمزًا فقط. حتى الآن، استخدمنا النوع النصي فقط. الآن، لنرى فوائد النوع الرمزي. الرموز يُمَثِّل الرمز مُعَرِّفًا فريدًا، ويمكن إنشاء قيمة من هذا النوع باستخدام Symbol()‎: // هو رمز جديد id let id = Symbol(); يمكننا إعطاء وصف للرمز أثناء الإنشاء (ما يُسمَّى أيضًا باسم الرمز)، ويكون مفيدًا لعملية تصحيح الأخطاء: // "id" هو رمز مع الوصف id let id = Symbol("id"); تتصف الرموز بكونها فريدة حتى إن أنشأنا أكثر من رمز بالوصف ذاته، تبقى الرموز قيمًا مختلفة. يُعد الوصف مجرد طابع ولا يؤثر على أي شيء. مثلًا، هنا رمزين بالوصف ذاته لكنهما غير متساويان: let id1 = Symbol("id"); let id2 = Symbol("id"); alert(id1 == id2); // false إن كانت لديك خلفية عن لغة Ruby أو أي لغة برمجية أخرى تستخدم الرموز فلا تخلط الأمور. فالرموز في JavaScript مختلفة. لا تتحول الرموز تلقائيًا إلى نص تتضمن أغلب القيم في JavaScript تحويلًا ضمنيًا إلى نص. مثلًا، يمكننا عرض أي قيمة تقريبا باستخدام alert وتعمل بشكل صحيح. لكن الرموز خاصة، فهي لا تتحول تلقائيًا. مثلا، أمر alert التالي سَيعرض خطأ: let id = Symbol("id"); alert(id); // TypeError: Cannot convert a Symbol value to a string يُعد هذا الأمر من حرَّاس اللغة لتجنب الخطأ، لأن النصوص والرموز مختلفة جذريًا ولا يجب أن يتحول نوع منهما للآخر عن طريق الخطأ. إن كنا نريد عرض رمز ما، يجب أن نستدعي الدالة .toString()‎. كما في المثال التالي: let id = Symbol("id"); alert(id.toString()); // Symbol(id), أصبحت تعمل الآن أو استخدم الخاصية symbol.description لعرض الوصف فقط: let id = Symbol("id"); alert(id.description); // id خاصيات "خفية" تتيح لنا الرموز إنشاء خاصيات "خفية" للكائن، والتي لا يمكن لأي شيفرة الوصول إليها وتغيير محتواها حتى عن طريق الخطأ. مثلا، إن كنا نتعامل مع كائنات تنتمي إلى شيفرة من طرف ثالث ونريد إضافة مُعرفات لها، نستخدم مفتاحًا رمزيًا لذلك: let user = { // ينتمي لِشيفرة أخرى name: "John" }; let id = Symbol("id"); user[id] = 1; alert( user[id] ); // يمكننا الوصول إلى البيانات باستخدام الرمز كمفتاح ما الفائدة من استخدام Symbol("id")‎ على النص "id"؟ بما أن كائنات user تنتمي لِشيفرة أخرى، وبما أن الشيفرة أعلاه تتعامل معها فليس آمنًا إضافة أي حقل لها. لكن لا يمكن الوصول إلى الرمز حتى عن طريق الخطأ، قد لا تراه شيفرة الطرف الثالث أيضًا، لذا فإنها الطريقة الصحيحة للقيام بذلك. تخيل أيضًا أن سكريبت آخر يريد إضافة معاملاته الخاصة بِداخل user لغرضه الخاص. قد يكون هذا السكريبت مكتبة JavaScript أخرى، لذا فإن كل سكريبت لا يعلم بتواجد الآخر بتاتًا، ثم يمكن لهذا السكريبت إنشاء رمزه الخاص Symbol("id")‎ هكذا: // ... let id = Symbol("id"); user[id] = "Their id value"; بهذه الطريقة، لن يوجد أي تعارض بين المُعاملات التي انشأناها ومُعاملات السكريبت الآخر لأن الرموز تختلف دومًا حتى إن كان لديها الاسم ذاته. لكن إن استخدمنا النص "id" بدلًا من الرمز للغرض ذاته، فَسيوجد تعارض: let user = { name: "John" }; // "id" يستخدم السكربت الذي أنشأناه الخاصية user.id = "Our id value"; // لغرض آخر "id" يريد سكريبت آخر استخدام الخاصية user.id = "Their id value" // !تعارض! تم تغيير المحتوى من قِبل السكريبت الآخر الرموز في التعريف المختصر لكائن إن أردنا استخدام الرموز عند تعريف كائن بالطريقة المختصرة عبر الأقواس {...} فَسنحتاج إلى تغليفها بأقواس مربعة هكذا: let id = Symbol("id"); let user = { name: "John", [id]: 123 // "id: 123" ليس }; ذلك لأننا نريد القيمة من المتغير id كمفتاح وليس النص "id". تتخطى for…in الرموز لا تشارك الخصائص الرمزية في الحلقة for..in. مثلًا: let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 }; for (let key in user) alert(key); // name, age (لا يوجد رموز) // يعمل الوصول المباشر للرمز alert( "Direct: " + user[id] ); يتجاهل Object.keys(user)‎ الرموز أيضًا. يُعد هذا من جزءًا من مبدأ "إخفاء الخاصيات الرمزية". إن حاول سكريبت آخر أو مكتبة JavaScript الدخول إلى كائن والتنقل فيه، فلن يصل إلى الخاصيات الرمزية بتاتًا. في المقابل، تنسخ Object.assign كلًا من النصوص والخاصيات الرمزية: let id = Symbol("id"); let user = { [id]: 123 }; let clone = Object.assign({}, user); alert( clone[id] ); // 123 لا يوجد لَبسٌ هنا، فهذا الأمر ضمن التصميم. الفكرة هي أنه عند استنساخ كائن أو دمج كائنين، فإننا نريد أن تُنسَخ جميع الخصائص (بما فيها الرموز مثل id). تُحوَّل مفاتيح الخاصيات التي من نوع آخر غير الرمز إلى نصوص جبريًا يمكننا استخدام النصوص أو الرموز فقط كمفاتيح في الكائنات، ويُحوَّل أي نوع آخر إلى نص. مثلًا، الرقم 0 يصبح النص "0" عندما يستخدم كمفتاح لخاصية: let obj = { 0: "test" // "0": "test" مثل }; // "إلى الخاصية ذاتها أي يحوَّل الرقم 0 إلى نص "0 alert تصل الدالة alert( obj["0"] ); // اختبار alert( obj[0] ); // (اختبار (الخاصية ذاتها الرموز العامة جميع الرموز تكون مختلفة دائمًا كما رأينا، حتى إن كانت تحمل الأسماء ذاتها. لكن قد نريد أحيانا أن تكون الرموز التي تحمل الاسم ذاته هي نفس الكائنات. مثلًا، تحتاج أجزاء متعددة في التطبيق الوصول إلى الرمز "id" ما يعني الخاصيات ذاتها. لنتمكن من القيام بذلك، يوجد سجل للرموز العامة. يمكننا إنشاء رموز فيه والوصول إليها لاحقًا، كما يضمن أن الوصول المتكرر إلى الاسم ذاته يُرجِع الرمز ذاته في كل مرة. لقراءة، أو إنشاء رمز في حال عدم تواجده في السجل، نستخدم Symbol.for(key)‎. يفحص هذا الاستدعاء السجل العام للرموز، إن كان هناك رمزًا بالوصف key، فإنه يُرجِعه، وإن لم يجده فإنه يُنشئ رمزًا جديدًا بالاستدعاء Symbol(key)‎ ويُخزِّنه في السجل بالوصف المُعطى key. إليك المثال التالي: // يقرأ من السجل العام let id = Symbol.for("id"); // إن لم يجد الرمز، ينشئه // يُقرأ مجددًا، ربما من جزء آخر في الشيفرة let idAgain = Symbol.for("id"); // الرمز ذاته alert( id === idAgain ); // true تُدعى الرموز داخل السجل العام رموزًا عامة، ونستعلمها في حال أردنا رمزًا على مستوى تطبيق كامل، وقابلًا للوصول في الشيفرة. هذا الأمر مشابه لما في لغة Ruby يوجد في بعض اللغات البرمجية مثل Ruby رمزًا واحدًا لكل اسم. كما رأينا، في JavaScript الأمر ذاته صحيح بالنسبة للرموز العامة. Symbol.keyFor بالنسبة للرموز العامة، ليس فقط الدالة Symbol.for(key)‎ تُرجِع الرمز وفقًا لاسمه، بل يوجد استدعاء عكسي: Symbol.keyFor(sym)‎، والتي تعكس ما تقوم به الأخرى: تُرجِع اسمًا بواسطة رمز عام. مثلًا: // نَسترجع الرمز بالاسم let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // نسترجع الاسم بالرمز alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id تستخدم Symbol.keyFor سجل الرموز العام داخليًا للبحث عن مفتاح الرمز. لذا فإنها لا تعمل مع الرموز الغير عامة. إن كان الرمز غير عام، فلن يتمكن من العثور عليه وسيُرجِع undefined. يمكن القول أن أي رمز لدية الخاصية description. مثلا: let globalSymbol = Symbol.for("name"); let localSymbol = Symbol("name"); alert( Symbol.keyFor(globalSymbol) ); // name, رمز عام alert( Symbol.keyFor(localSymbol) ); // undefined, غير عام alert( localSymbol.description ); // name رموز النظام يوجد العديد من رموز "النظام" التي تستخدمها JavaScript داخليًا، ويمكن استخدامها لضبط نواحي متعددة من الكائنات بدقة. هذه الرموز مُدرَجة في وصف جدول الرموز المتعارف عليها: Symbol.hasInstance Symbol.isConcatSpreadable Symbol.iterator Symbol.toPrimitive وهكذا… مثلًا، يتيح لنا الرمز Symbol.toPrimitive وصف كائن إلى تحويل أولي. سنرى استخدامها قريبًا. ستعتاد على باقي الرموز عند دراسة ميزات اللغة المُقابلة. الخلاصة النوع Symbol هو نوع أولي للمُعاملات الفريدة. يتم إنشاء الرموز عبر استدعاء الدالة Symbol()‎ مع وصف اختياري (اسم). الرموز تكون دائمًا مختلفة القيم حتى إن كان لها الاسم ذاته، أي تتصف بأنها فريدة. إن أردنا أن يكون الرمز بالاسم ذاته يجمل القيمة ذاتها، يجب أن نستخدم السجل العام: تُرجِع Symbol.for(key)‎ (وتُنشِئ في حال الحاجة) رمزًا عامًا بالمفتاح كاسم. تُرجِع الاستدعاءات العديدة لِلدالة Symbol.for باستخدام المفتاح ذاته الرمز نفسه. لدى الرموز حالتي استخدام: خاصيات الكائن "الخفية". إن أردنا إضافة خاصية إلى كائن ينتمي إلى سكريبت أو مكتبة أخرى، يمكننا إنشاء رمز واستخدامه كمفتاح خاصية. لا تظهر الخاصية الرمزية في for..in، حتى لا تتم معالجته عن طريق الخطأ مع باقي الخاصيات. لن يتم الوصول إليه مباشرةً أيضًا لأن السكريبت الخارجي لا يحوي على هذا الرمز. هكذا تكون الخاصية محمية من الاستخدام الخارجي. لذا، يمكننا إخفاء شيء ما بشكل تام في الكائنات التي نحتاجها، والتي لا يجب أن يراها الآخرون باستخدام الخاصيات الرمزية. يوجد العديد من رموز النظام المستخدمة بواسطة JavaScript التي يمكن الوصول إليها بواسطة Symbol.*‎. يمكننا استخدامها لتعديل سلوك مُدمَج، مثلا، سنستخدم Symbol.iterato لاحقَا في الشرح للحلقات، و Symbol.toPrimitive لإعداد التحويل من كائن لقيمة أولية وهكذا… عمليًا، الرموز ليست خفية 100%. يوجد دالة مدمجة Object.getOwnPropertySymbols(obj)‎ تتيح لنا الوصول إلى جميع الرموز. كما يوجد دالة تُدعى Reflect.ownKeys(obj)‎ والتي تُرجِع جميع مفاتيح الكائن بما فيها الرموز. لذا فإن الرموز ليست مخفية فعلًا، لكن أغلب المكاتب، والدوال المدمجة والهياكل لا تستخدم هذه الدوال. ترجمة -وبتصرف- للفصل Symbol type من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: الدوال في الكائنات واستعمالها this المقال السابق: كنس البيانات المهملة
  5. تُدَار الذاكرة في JavaScript تلقائيًا في الخفاء. نحن ننشئ المتغيرات الأولية، والكائنات، والدوال وجميعها تأخذ مكانًا في الذاكرة ولكن هل سألت نفسك ماذا يحدث عندما يصبح أحد هذه الأشياء مهملًا وغير مهم؟ كيف يكتشف ذلك مُحرِّك JavaScript ويتخلص منه؟ هذا ما سنعرفه في هذا الفصل. قابلية الوصول المبدأ الرئيسي لإدارة الذاكرة في JavaScript هو قابلية الوصول (reachability). ببساطة، القيم "القابلة للوصول" هي القيم التي يمكن الوصول إليها واستخدامها بطريقة ما وهذه القيم مخولة لتُخزَّن في الذاكرة. 1- يوجد مجموعة من القيم القابلة للوصول بطبيعة الحال والتي لا يمكن التخلص منها لأسباب وجيهة مثل: المتغيرات المحلية والمُعاملات للدالة الحاليِّة. المتغيرات والمُعاملات لدوال أخرى ضمن السلسلة الحالية في الاستدعاء المُتداخل. المتغيرات العامة. (بالإضافة إلى بعض المتغيرات الأخرى الداخلية) تُدعى هذه القيم «جذورًا» (roots). 2- تكون أي قيمة أخرى قابلة للوصول إن كان بالإمكان الوصول إليها من جذر باستخدام مرجع أو سلسلة من المراجع. مثلًا، إن كان هناك كائن في متغير محلي، ولدى هذا الكائن خاصية تشير لكائن آخر، فإنَّ هذا الكائن قابل للوصول. وهذه الكائنات التي يُشار إليها قابلة للوصول أيضًا. ستجد أمثلة توضيحية لاحقًا. يوجد عملية خلفيَّة في محرك JavaScript تُدعَى «كانس المهملات» (garbage collector) تعمل على مراقبة الكائنات وحذف التي لم تَعُد قابلة للوصول. مثال بسيط أبسط مثال هو: // user يرجِع لكائن let user = { name: "John" }; يُصوِّر السهم في الصورة مرجعًا لكائن. المتغير العام "user" يشير للكائن {name: "John"‎} (سنُسميه John اختصارًا). الخاصية "name" للكائن John تُخزن قيمة أولية، لذ رُسمَت بداخل الكائن. إن استُبدلَت قيمة المتغير user، فَسَنفقد المرجع الذي يشير إلى الكائن John: user = null; أصبح الكائن John غير قابل للوصول الآن أي لا يوجد طريقة للوصول إليه. سيعمل كانس المهملات على حذف البيانات المتمثلة في الكائن John وتحرير الذاكرة التي يحتلها. مرجعَان لكائن لنفترض أننا نسخنا المرجع من المتغير user إلى متغير آخر باسم admin: // user يرجِع لكائن let user = { name: "John" }; let admin = user; إن قمنا بالأمر السابق ذاته الآن: user = null; فسيكون الكائن قابلًا للوصول من خلال المتغير العام admin، لذا يبقى في الذاكرة. إن استبدلنا محتوى المتغير admin أيضًا فسيُحذَف الكائن. الكائنات المتداخلة الآن ننتقل لمثال أكثر تعقيدًا. ألق نظرة على الشيفرة التالية: function marry(man, woman) { woman.husband = man; man.wife = woman; return { father: man, mother: woman } } let family = marry({ name: "John" }, { name: "Ann" }); تربط الدالة marry كائنين بجعل كلاهما يشير إلى الآخر ثم ترجِع كائنًا جديدًا يحوي كلاهما. هيكل الذاكرة الناتج يكون كالتالي: تكون جميع البيانات قابلة للوصول حتى الآن. دعنا نجرب حذف مرجعين الآن: delete family.father; delete family.mother.husband; ليس من الكافِ حذف أحد المرجِعين فقط، لأنَّ جميع الكائنات ستظل قابلة للوصول لكن إن حذفنا كلا المرجعين، فسنرى عدم وجود أي مرجع إلى الكائن John: لا يهم وجود مرجع من الكائن، إذ ما يجعله قابلًا للوصول هو المراجع التي تشير إليه، لذا فإنَّ الكائن John أصبح غير قابل للوصول وسيُحذَف من الذاكرة مع جميع بياناته التي أصبحت غير قابلة للوصول أيضًا. بعد تجميع البيانات الغير مرغوب بها، يبقى لدينا: جزيرة غير قابلة للوصول يمكن أن تصبح جزيرة من الكائنات المترابطة غير قابلة للوصول وتُحذَف من الذاكرة. الكائن الرئيسي هو الكائن أعلاه ذاته: family = null; تُصبح الصورة في الذاكرة كما يلي: يوضح هذا المثال أهمية مبدأ قابلية الوصول. من الواضح أن الكائنين John و Ann ما زالا مرتبطين ولكل منهما مراجع لبعضهما، لكن ذلك غير كافٍ. الكائن السابق "family" أصبح غير مربوط بالجذر، أي لم يعد هناك أي مرجع إليه لذا فإن الجزيرة كاملةً تصبح غير قابلة للوصول وتُحذَف. الخوارزميات الداخلية تُدعى الخوارزمية الأساسية لتجميع البيانات المهملة «الاستهداف والتمشيط» (mark-and-sweep)؛ تُنفَّذ خطوات جمع البيانات المهملة دوريًا وفق الخطوات التالية: يأخذ كانس المهملات الجذور ويحفظها (يُحدِّدها هدفًا له). ثم يُمشِّط جميع الإشارات الخارجة منها (مراجع لكائنات أخرى) ويحفظها لاستهدافها أيضًا. ثم يُمشِّط جميع الكائنات التي استهدفها مسبقًا ويحفظ مراجعها لاستهدفها لاحقًا. يُمشِّط جميع الكائنات بتلك الطريقة ويتذكرها لكي لا يُمشِّط أي كائن مرةً ثانية مستقبلًا. تستمر العملية مرارًا وتكرارًا حتى يصبح هناك مراجع لم تُمشَّط (غير قابلة للوصول من أي جذر). تُحذَف جميع الكائنات باستثناء تلك استُهدفَت وعُلِّمَت بأنَّها غير مهملة. مثلًا، ليكن هيكل الكائنات لدينا كما يلي: يمكن رؤية جزيرة غير قابلة للوصول في اليمين. الآن لنرى كيف يتعامل معها كانس المهملات وفق خوارزمية الاستهداف والتمشيط. الخطوة الأولى هي تحديد الجذور: ثم تُحدَّد المراجع التي تشير إليها: تُحدَّد المراجع التي تشير لها هذه الكائنات أيضًا: تُعدُّ الكائنات التي لم تُمشَّط أثناء العملية غير قابلة للوصول ويجري حذفها: هذه الآلية التي يعمل بها كانس المهملات. تطبق محركات JavaScript العديد من التحسينات لتحسين هذه الخوارزمية وتسريع عملها بطريقة لا تؤثر على سرعة التنفيذ. بعض التحسينات: التجميع وفق الجيل: تُقَسَّم الكائنات إلى مجموعتين: "كائنات جديدة" و "كائنات قديمة". تُنشَأ العديد من الكائنات ثمَّ تؤدي عملها ثمَّ تموت بسرعة، ويمكن تنظيفها بقوة. وتلك التي تنجو تصبح قديمة وتُفحَص بوتيرة أقل. التجميع التدريجي: إن وُجِد العديد من الكائنات وأردنا المرور خلال جميع الكائنات وتحديدها دفعة واحدة، فقد يأخذ ذلك وقتًا ويظهِِر آنذاك تأخيرٌ ملحوظٌ في التنفيذ، لذا يحاول المُحرِّك تقسيم عملية كنس البيانات المهملة إلى أجزاء ثم تنفيذ الأجزاء واحدًا تلو الآخر بشكل منفصل. قد يتطلب ذلك إجراء حسابات إضافية لتتبع التغييرات، لكن يصبح لدينا الكثير من التأخيرات الغير ملحوظة بدلًا من تأخير واحد كبير. التجميع وقت الخمول: يحاول كانس المهملات العمل عندما يكون المعالج غير مشغول لتقليل أي تأثيرات محتملة على التنفيذ. يوجد تحسينات وإضافات أخرى على خوارزمية كنس المهملات لكن يجب على التوقف هنا لأنَّ المحركات المختلفة تُطبِّق تقنيات مختلفة. والأهم من ذلك، تتغير الأمور بتطور المحركات، لذا فإنَّ التعمق مسبقًا دون الحاجة لذلك لا يستحق العناء. إلا إن كان ذلك رغبة شخصية، فَسنضع بعض الروابط في الأسفل. الخلاصة الأشياء التي يجب معرفتها: تُكنس البيانات المهملة تلقائيًا، وهو أمر لا يمكن فرضه أو تجنبه. تبقى الكائنات في الذاكرة طالما يمكن الوصول إليها من أي جذر. وجود مرجِع للكائن ليس مثل أن يكون قابلًا للوصول من جذر، ويمكن لمجموعة من الكائنات المترابطة أن تصبح غير قابلة للوصول. تستخدم المحركات الحديثة خوارزميات متطورة لكنس البيانات المهملة. يغطي كتاب "The Garbage Collection Handbook: The Art of Automatic Memory Management" (لمؤلفه R. Jones وغيره) بعضًا منها. إن كنت معتادًا على البرمجة بلغات ذات مستوى منخفض، يوجد معلومات مفصَّلة عن كانس المهملات في المُحرِّك V8 في المقال رحلة إلى V8: كنس البيانات المهملة. تنشر مدونة V8 أيضًا مقالات عن التغييرات في إدارة الذاكرة من وقت لآخر. لتتعلم عن كنس البيانات المهملة، يجب أن تتجهز بتعلم أمور V8 الداخلية كما يُفضَّل أن تقرأ مدونة Vyacheslav Egorov الذي عمل كأحد مهندسي V8. أنا أقول V8 لوجود الكثير من المقالات عنه على الإنترنت. العديد من الجوانب متشابهة بالنسبة لباقي المحركات، لكن يختلف كنس البيانات المهملة من عدة نواحي بينها. المعرفة العميقة بالمحركات جيدة عندما تحتاج إلى إجراء تحسين منخفض المستوى. فكِّر في ذلك وليكن خطوتك التالية بعد أن تعتاد على اللغة. ترجمة -وبتصرف- للفصل Garbage collection من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: النوع الرمزي (Symbol) المقال السابق: الكائنات
  6. يوجد سبعة أنواع للبيانات في JavaScript كما رأينا في فصل أنواع البيانات. ستة من هذه الأنواع تُدعى "أساسية" (primitive) لأنها تحوي قيمة شيء واحد فقط (سواء كان نصًا أو رقمًا أو أي شيء آخر). في المُقابل، تُستخدم الكائنات لتخزين مجموعة من البيانات المتنوعة والوحدات المعقدة المُرَمَّزة بمفاتيح. تُضمَّن الكائنات في ما يقارب جميع نواحي JavaScript، لذا يجب علينا أن نفهمها قبل التعمق في أي شيء آخر. يمكن إنشاء أي كائن باستخدام الأقواس المعقوصة {…} مع قائمة اختيارية بالخاصيات. الخاصية هي زوج من "مفتاح: قيمة" (key: value) إذ يكون المفتاح عبارة عن نص (يُدعى "اسم الخاصية")، والقيمة يمكن أن تكون أي شيء. يمكننا تخيل الكائن كخزانة تحوي ملفات. يُخزن كل جزء من هذه البيانات في الملف الخاص به باستخدام المفتاح. يمكن إيجاد، أو إضافة، أو حذف ملف باستخدام اسمه. يمكن إنشاء كائن فارغ (خزانة فارغة) باستخدام إحدى الصيغتين التاليتين: let user = new Object(); // (object constructor) صياغة باني كائن let user = {}; // (object literal) صياغة مختصرة لكائن عبر الأقواس تُستخدم الأقواس المعقوصة {...} عادة، وهذا النوع من التصريح يُسمى «الصياغة المختصرة لتعريف كائن» (object literal). القيم المُجرَّدة والخاصيات يمكننا إضافة بعض الخاصيات (properties) إلى الكائن المعرَّف بالأقواس {...} مباشرة بشكل أزواج "مفتاح: قيمة": let user = { // كائن name: "John", // name عبر المفتاح John خزِّن القيمة age: 30 // age خزِّن القيمة 30 عبر المفتاح }; لدى كل خاصية مفتاح (يُدعى أيضًا "اسم " أو "مُعَرِّف") قبل النقطتين ":" وقيمة لهذه الخاصية بعد النقطتين. يوجد خاصيتين في الكائن user: اسم الخاصية الأولى هو "name" وقيمتها هي "John". اسم الخاصية الثانية هو "age" وقيمتها هي "30". يمكن تخيل الكائن السابق user كخزانة بملفين مُسَمَّيان "name" و "age". يمكننا إضافة، وحذف، وقراءة الملفات من الخزانة في أي وقت. يمكن الوصول إلى قيم الخاصيات باستخدام الصيغة النُقَطية (dot notation): // الحصول على قيم خاصيات الكائن: alert( user.name ); // John alert( user.age ); // 30 يمكن للقيمة أن تكون من أي نوع، لِنُضِف قيمة من نوع بيانات منطقية (boolean): user.isAdmin = true; يمكننا استخدام المُعامِل delete لحذف خاصية: delete user.age; يمكننا أيضا استخدام خاصيات بأسماء تحوي أكثر من كلمة، لكن يجب وضعها بين علامات الاقتباس "": let user = { name: "John", age: 30, "likes birds": true // يجب أن تكون الخاصية ذات الاسم المُحتوي على أكثر من كلمة بين علامتي اقتباس }; يمكن إضافة فاصلة بعد آخر خاصية في القائمة: let user = { name: "John", age: 30, } وتُسمى بفاصلة "مُعَلِّقَة" أو "زائدة" فهي تجعل إضافة أو حذف أو التنقل بين الخاصيات أسهل لأن جميع الأسطر تُصبِح متشابهة. الأقواس المعقوفة لا تعمل طريقة الوصول إلى الخاصيات ذات الأسماء المحتوية على أكثر من كلمة باستخدام الصيغة النُقَطية: // تعرض هذه التعليمة وجود خطأ في الصياغة user.likes birds = true ذلك لأن الصيغة النُقطية تحتاج لاسم متغير صحيح. لا يحوي مسافات أو حدود أخرى. يوجد بديل يعمل مع أي نص "صيغة الأقواس المعقوفة" []: let user = {}; // تخزين user["likes birds"] = true; // استرجاع alert(user["likes birds"]); // true // حذف delete user["likes birds"]; يعمل كل شيء وفق المطلوب الآن. يُرجى ملاحظة أنَّ النص بداخل الأقواس مُحاط بعلامتي اقتباس (تعمل علامات التنصيص الأخرى بطريقة صحيحة أيضًا). تتيح لنا الأقواس المعقوفة أيضًا جلب اسم خاصية ناتجة عن قيمة أي تعبير - بدلًا من اسم الخاصية الفعلي - مثل استعمال اسم من متغير كما يلي: let key = "likes birds"; // user["likes birds"] = true; يماثل قول user[key] = true; يمكن حساب قيمة المتغير key أثناء التنفيذ أو قد تعتمد قيمته على مدخلات المستخدمين ثم نستخدمه للوصول إلى الخاصية مما يعطي مرونة كبيرة في التعامل. إليك المثال التالي: let user = { name: "John", age: 30 }; let key = prompt("What do you want to know about the user?", "name"); // الوصول باستخدام متغير alert( user[key] ); // John (if enter "name") لا يمكن استخدام الصيغة النُقطية بالطريقة نفسها: let user = { name: "John", age: 30 }; let key = "name"; alert( user.key ) // غير معروف الخاصيات المحسوبة يمكن استخدام الأقواس المعقوفة في كائن معرَّف بالأقواس، وهذا ما يسمى بالخاصيات المحسوبة. إليك المثال التالي: let fruit = prompt("Which fruit to buy?", "apple"); let bag = { [fruit]: 5, // fruit يؤخذ اسم الخاصية من المتغير }; alert( bag.apple ); // fruit="apple" قيمتها 5 إذا كانت معنى الخاصية المحسوبة سهل: تعني [fruit] أنَّ اسم الخاصية يجب أن يُؤخذ من fruit؛ لذا، إن أدخل الزائر "apple"، ستصبح قيمة bag هي {apple: 5}. يعمل الأمر السابق بالطريقة التالية ذاتها: let fruit = prompt("Which fruit to buy?", "apple"); let bag = {}; // fruit خُذ اسم الخاصية من المتغير bag[fruit] = 5; لكن شكله يبدو أفضل، أليس كذلك؟! يمكن استخدام تعابير أكثر تعقيدًا داخل الأقواس المعقوفة: let fruit = 'apple'; let bag = { [fruit + 'Computers']: 5 // bag.appleComputers = 5 }; الأقواس المعقوفة أكثر قوة من الصيغة النُقطية. فهي تسمح باستخدام أي اسم للخاصيات والمتغيرات. لكنها أكثر تعقيدا في الكتابة. لذا، إن كانت أسماء المتغيرات سهلة ومعروفة، تُستخدَم الصيغة النُقَطية غالبًا. وإن أردنا بعض التعقيد، نستخدم إلى الأقواس المعقوفة. يمكن استخدام الأسماء المحجوزة مع أسماء الخاصيات لا يمكن لمتغير أن يحمل اسم إحدى الكلمات المحجوزة في اللغة مثل "for"، أو "let"، أو "return"، …الخ. لكن لا تُطبَّق هذه القاعدة على أسماء خاصيات الكائنات. فيمكن استخدام أي اسم معها: let obj = { for: 1, let: 2, return: 3 }; alert( obj.for + obj.let + obj.return ); // 6 عمومًا، يمكن استخدام أي اسم، لكن هناك استثناء: "__proto__" لهذا الاسم معاملة خاصة لأسباب تاريخية. مثلًا، لا يمكننا استخدام الاسم على أنَّه قيمة لغير كائن: let obj = {}; obj.__proto__ = 5; alert(obj.__proto__); // لا تعمل وفق المطلوب [object Object] كما نرى في الشيفرة، تم تجاهل تخزين القيمة الأولية 5. تخزين أزواج الخاصيات التحكمية في كائن وإتاحة تحديد مفاتيح هذه الأزواج للزائر قد يجعل الشيفرة مصدرًا للأخطاء ومليئًا بالثغرات. في تلك الحالة، قد يختار الزائر - الخبير والماكر - الاسم __proto__ ليكون مفتاحًا ويخرِّب البنية المنطقية للشيفرة (كما في المثال أعلاه). يوجد طريقة لجعل الكائنات تتعامل مع __proto__ بِعدِّها خاصية عادية وسنتطرق لها لاحقًا بعد فهم الكائنات بشكل أعمق. يوجد أيضا هيكل بيانات آخر يدعى Map، والذي ستتعلمه في القسم الذي يتحدث عن نوعي البيانات Map و Set، اللذين يدعمان استعمال أي نوع مع المفاتيح. اختزال قيم الخاصيات نستخدم غالبا قيم متغيرات موجودة مسبقًا لتكون قيمًا لأسماء الخاصيات في الشيفرات الحقيقية. اطلع مثلًا على الشيفرة التالية: function makeUser(name, age) { return { name: name, age: age // ... خاصيات أخرى }; } let user = makeUser("John", 30); alert(user.name); // John تحمل الخاصيات في المثال السابق نفس أسماء المتغيرات. يُعدُّ إنشاء خاصية من متغير موجود مسبقًا حالة استخدام شائعة. أي أنه يوجد اختزال خاص لقيمة الخاصية لاختصار الشيفرة. بدلا من كتابة name:name، يمكننا كتابة name فقط، كما يلي: function makeUser(name, age) { return { name, // name: name يماثل كتابة age // age: age يماثل كتابة // ... }; } يمكننا استخدام كلًا من الخاصيات الاعتيادية والاختزال في الكائن ذاته: let user = { name, // name:name يماثل age: 30 }; فحص الكينونة قابلية الوصول إلى أي خاصية في الكائن هي إحدى مميزات الكائنات، ولكن ألَا يوجد أي خطأ في حال لم تكن الخاصية موجودة؟! عند محاولة الوصول إلى خاصية غير موجودة، تُرجَع القيمة undefined. مما يُعطي طريقة متعارفة لفحص كينونة (وجود) خاصية ما من عدمه بموازنتها مع القيمة "undefined" ببساطة: let user = {}; alert( user.noSuchProperty === undefined ); // تحقق هذه الموازنة يشير إلى عدم وجود الخاصية يوجد أيضا مُعامل خاص "in" لفحص تواجد أي خاصية. طريقة استخدام هذا المعامل كالتالي: "key" in object مثلا: let user = { name: "John", age: 30 }; alert( "age" in user ); // true, user.age موجود alert( "blabla" in user ); // false, user.blabla غير موجود لاحظ أنه من الضروري وجود اسم الخاصية على يسار in. ويكون عادة نصًا بين علامتي تنصيص. إن لم نستخدم علامتي التنصيص فهذا يعني فحص متغير يحمل الاسم ذاته مثل: let user = { age: 30 }; let key = "age"; alert( key in user ); // key إذ تؤخذ قيمة المتغير true تطبع القيمة // user ويُتحقق من وجود خاصية بذلك الاسم في الكائن استخدام "in" مع الخاصيات التي تُخزن القيمة undefined تفحص عملية الموازنة الصارمة "=== undefined" غالبًا وجود الخاصية وفق المطلوب. لكن يوجد حالة خاصة تفشل فيها هذه العملية، بينما لا يفشل المعامل in إن استعمل مكانها. هذه الحالة هي عند وجود الخاصية في الكائن لكنها تُخزن القيمة undefined: let obj = { test: undefined }; alert( obj.test ); // ولكن هل تُعدُّ الخاصية موجودة أم لا؟ undefined يطبع القيمة alert( "test" in obj ); // وتَعدُّ الخاصية موجودة في الكائن true تُطبع القيمة الخاصية obj.test موجودة فعليًا في الشيفرة أعلاه، لذا يعمل المُعامل in بصحة. تحدث مثل هذه الحالات نادرًا فقط لأن القيمة undefined لا تُستخدَم بكثرة. نستخدم غالبا القيمة null للقيم الفارغة أو الغير معرفة، لذلك يُعد المُعامل in قليل الاستخدام في الشيفرات. الحلقة for…in يوجد شكل خاص للحلقة for..in للمرور خلال جميع مفاتيح كائنٍ ما. هذه الحلقة مختلفة تمامًا عما درسناه سابقًا، أي الحلقة for(;;)‎. صياغة الحلقة تكون بالشكل التالي: for (key in object) { // يتنفذ ما بداخل الحلقة لكل مفتاح ضمن خاصيات الكائن } مثلا، لنطبع جميع خاصيات الكائن user: let user = { name: "John", age: 30, isAdmin: true }; for (let key in user) { // المفاتيح alert( key ); // name, age, isAdmin // قيم المفاتيح alert( user[key] ); // John, 30, true } لاحظ أن جميع تراكيب "for" تتيح لنا تعريف متغير التكرار بِداخل الحلقة، مثل let key في المثال السابق. يمكننا أيضًا استخدام اسم متغير آخر بدلًا من key. إليك مثال يُستخدم بكثرة: for (let prop in obj) الترتيب في الكائنات هل الكائنات مرتبة؟ بمعنى آخر، إن تنقلنا في حلقة خلال كائن، هل نحصل على جميع الخاصيات بنفس الترتيب الذي أُضيفت به؟ وهل يمكننا الاعتماد على هذا؟ الإجابة باختصار هي: "مرتب بطريقة خاصة": الخاصيات الرقمية يُعاد ترتيبها، تظهر باقي الخاصيات بترتيب الإنشاء ذاته كما في التفاصيل التالية. لنرَ مثالًا لكائن بِرموز الهاتف: let codes = { "49": "Germany", "41": "Switzerland", "44": "Great Britain", // .., "1": "USA" }; for (let code in codes) { alert(code); // 1, 41, 44, 49 } قد تُستخدم الشيفرة لاقتراح قائمة من الخيارات للمستخدم. إن كنا نبني موقعًا لزوار من ألمانيا فقد نريد أن تظهر 49 أولا. لكن، عند تشغيل الشيفرة، نرى شيئا مختلفًا تماما: تظهر USA (1)‎ أولًا ثم Switzerland (41)‎ وهكذا. تُستخدم رموز الهواتف بترتيب تصاعدي لأنها أعدادٌ، لذا نرى 1, 41, 44, 49. خاصيات عددية؟ ما هذا؟ تعني "الخاصية العددية" (integer property) نصًا يمكن تحويله من وإلى عدد دون أن يتغير. لذا فإن 49 هو اسم خاصية عددي لأنه عند تحويله إلى عدد وإرجاعه لنص يبقى كما هو. لكن "1.2" و "‎+49" ليست كذلك: // هي دالة تحذف الجزء العشري Math.trunc alert( String(Math.trunc(Number("49"))) ); // "49", الخاصية العددية ذاتها alert( String(Math.trunc(Number("+49"))) ); // ‏ "49" مختلفة عن "49+" => إذًا ليست خاصية عددية alert( String(Math.trunc(Number("1.2"))) ); // ‏ "1" مختلفة عن "1.2" => إذًا ليست خاصية عددية في المقابل، إن كانت المفاتيح غير عددية، فتُعرَض بالترتيب الذي أُنشِئت به. إليك مثال على ذلك: let user = { name: "John", surname: "Smith" }; user.age = 25; // add one more // تُعرض الخاصيات الغير رقمية بترتيب الإنشاء for (let prop in user) { alert( prop ); // name, surname, age } لذا، لحل مشكلة رموز الهواتف يمكننا التحايل وجعلها غير عددية بإضافة "+" قبل كل رمز كما يلي: let codes = { "+49": "Germany", "+41": "Switzerland", "+44": "Great Britain", // .., "+1": "USA" }; for (let code in codes) { alert( +code ); // 49, 41, 44, 1 } الآن تعمل وفق المطلوب! النسخ بالمرجع إحدى الاختلافات الرئيسية بين الكائنات والمتغيرات الأولية هي أنَّ الكائنات تُخزن وتُنسخ "بالمرجع". بينما تُخزن/تُنسخ القيم الأولية: النصوص، والأرقام، والقيم المنطقية بِعدِّها قيمةً كاملةً. إليك المثال التوضيحي التالي: let message = "Hello!"; let phrase = message; لدينا في هذه الشيفرة متغيرين مستقلين كلاهما يُخزن النص "Hello!‎ ". الكائنات ليست كذلك. لا يُخزِّن المتغير الكائن نفسه، وإنما "عنوانه في الذاكرة". بمعنى آخر، "مرجع للكائن" هنا صورة للكائن: let user = { name: "John" }; كما نرى، يُخزَّن المتغير في مكان ما في الذاكرة ويُخزِّن المتغير user مرجعًا إليه. عند نسخ متغير من نوع كائن، يُنسَخ المرجع الذي يشير إلى ذلك الكائن ولا يُنسَخ الكائن نفسه ويُكرَّر. إذا تخيلنا الكائن كخزانة، فإن المتغير هو مفتاح الخزانة ونسخ المتغير يكرِّر المفتاح وليس الخزانة ذاتها. إليك مثال يوضح ما ذكرناه: let user = { name: "John" }; let admin = user; // يُنسَخ المرجع الآن، أصبح لدينا متغيرين، كلاهما يحمل مرجعًا للكائن ذاته: يمكننا استخدام كلا المتغيرين (المفتاحين) للوصول إلى الخزانة وتعديل محتواها: let user = { name: 'John' }; let admin = user; admin.name = 'Pete'; // "admin" عُدِّلت باستخدام المرجع alert(user.name); // "user" أي يمكن رؤية التعديلات من المرجع 'Pete' تُعرَض القيمة يوضح المثال أعلاه وجود كائن واحد فقط. كما لو كان لدينا خزامة بمفتاحين، واستخدمنا أحدهما (admin) للوصول إلى الخزانة. ثم إذا استخدمنا الآخر (user) لاحقًا، فسنرى إنعكاس التغييرات التي أجراها الأول. الموازنة بالمرجع يعمل مُعاملي المساواة == والمساواة الصارمة === بنفس الطريقة للكائنات. يكون الكائنان متساويان إذا كانَا الكائن نفسه فقط. أي، إذا كان متغيران يشيران للكائن ذاته، فهما متساويان: let a = {}; let b = a; // نفس المرجع alert( a == b ); // true, كلا المتغيرين يشيران للكائن نفسه alert( a === b ); // true وهنا متغيران مستقلان ليسا متساويين أي يشيران إلى كائنين منفصلين حتى وإن كانا متماثلين تمامًا: let a = {}; let b = {}; // متغيران منفصلان alert( a == b ); // خطأ يُحوَّل الكائن إلى قيمة أولية (أساسية) في الموازنات مثل obj1 > obj2 أو obj == 5. سندرس كيفية تحويل الكائنات قريبًا، لكن، في الحقيقة، مثل هذه الموازنات تكون نادرة الضرورة وتنتج غالبًا من خطأ في كتابة الشيفرة. الكائنات الثابتة يمكن تغيير الكائن المُعَرَّف على أنَّه ثابت (أي وسم المتغير الذي يحوي الكائن بالكلمة المفتاحية const) دون حصول أي أخطاء. إليك الشيفرة التالية مثلًا: const user = { name: "John" }; user.age = 25; // (*) alert(user.age); // 25 قد يبدو أن السطر (*) سيسبب خطأ، لكن لا يوجد أي مشكلة البتة. ذلك لأنَّ const تحافظ على قيمة user ذاتها وتمنع تغييرها. ويُحزِّن user المرجع للكائن نفسه دائمًا ولا يتغيِّر بتعديل الكائن نفسه. يدخل السطر (*) إلى الكائن ولا يعيد تعيين قيمة المتغير user. يمكن أن يعطي const خطأ إن حاولنا تغيير قيمة المتغير user مثل: const user = { name: "John" }; // (user خطأ (لا يمكن تغيير قيمة المتغير user = { name: "Pete" }; لكن، ماذا إن أردنا إنشاء خاصيات ثابتة ضمن الكائن؟ سيُعطي user.age = 25 آنذاك خطأ، وذلك ممكن أيضًا. سيتم شرحه في الفصل رايات الخاصيات وواصفاتها. الاستنساخ والدمج إذًا، نسخ كائن يُنشِئ مرجعًا إضافيًّا له. لكن ماذا إن كنا نريد تكرار كائن فعليًّا أي إنشاء نسخة مستقلة منه؟ ذلك ممكن أيضًا، لكنه أصعب قليلًا لعدم وجود دالة تقوم بذلك في JavaScript ذلك لأنَّه لا حاجة لذلك بكثرة. النسخ بالمرجع كافٍ غالبًا. لكن إن أردنا ذلك فعلًا، فإننا نحتاج لإنشاء كائن وتكرار هيكل الكائن الموجود عبر التنقل خلال خاصياته ونسخها على المستوى الأولي. وإليك مثال على ذلك: let user = { name: "John", age: 30 }; let clone = {}; // الكائن الجديد الفارغ // إليه user ننسخ جميع خاصيات المتغير for (let key in user) { clone[key] = user[key]; } // الآن أصبحت النسخة مستقلة تماما clone.name = "Pete"; // تغيير البيانات في النسخة alert( user.name ); // في الكائن الأصلي John تظل يمكننا استخدام الدالة Object.assign للغرض ذاته. الصياغة الدالة هي: Object.assign(dest, [src1, src2, src3...]) تُعد المُعاملات dest، و src1، وحتى srcN كائنات (يمكن أن تكون بالعدد المُراد). تنسخ الدالة خاصيات جميع الكائنات src1, ..., srcN إلى الكائن dest. بمعنى آخر، تُنسخ جميع الخاصيات لجميع المُعاملات بدءًا من المُعامل الثاني إلى المُعامل الأول. ثم يتم إرجاع dest. مثلا، يمكننا استخدام الدالة لدمج عدة كائنات إلى كائن واحد: let user = { name: "John" }; let permissions1 = { canView: true }; let permissions2 = { canEdit: true }; // user إلى permissions2 و permissions1 تنسخ جميع الخاصيات من Object.assign(user, permissions1, permissions2); // now user = { name: "John", canView: true, canEdit: true } إن كان الكائن user يحوي أحد أسماء الخاصيات مسبقًا، فسيتم إعادة كتابة محتواها: let user = { name: "John" }; // isAdmin وإضافة name إعادة كتابة Object.assign(user, { name: "Pete", isAdmin: true }); // now user = { name: "Pete", isAdmin: true } يمكننا أيضًا استخدام الدالة Object.assign بدلًا من الحلقة للاستنساخ البسيط: let user = { name: "John", age: 30 }; let clone = Object.assign({}, user); تنسخ الدالة جميع خاصيات الكائن user إلى الكائن الفارغ وتُرجِعه كما في الحلقة لكن بشكل أقصر. حتى الآن، عدَدْنا جميع خاصيات user أولية (أساسية)، لكن قد تكون بعض الخاصيات مرجعًا لكائن آخر مثل الشيفرة التالية فما العمل؟ let user = { name: "John", sizes: { height: 182, width: 50 } }; alert( user.sizes.height ); // 182 في هذه الحالة نسخ clone.sizes = user.sizes ليس كافيًا لأنَّ user.sizes عبارة عن كائن، فسيُنسَخ على أنَّه مرجعٌ. هكذا، سيصبح لدى clone و user الحجم ذاته: let user = { name: "John", sizes: { height: 182, width: 50 } }; let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // صحيح، الكائن ذاته // sizes الكائن الفرعي clone و user يتشارك الكائنان user.sizes.width++; // تغيير خاصية من مكان ما alert(clone.sizes.width); // 51, يعرض النتيجة من مكان آخر لإصلاح هذا، يجب استخدام حلقة الاستنساخ التي تفحص كل قيمة في user[key]‎، وإن كان كائنًا نستبدل الهيكل الخاص به أيضًا. هذه الطريقة تُسمى "استنساخ عميق" (deep cloning). يوجد خوارزمية عامة للاستنساخ العميق تنفِّذ الحالة السابقة بشكل صحيح، بالإضافة إلى حالات أكثر تعقيدًا. تُدعى هذه الخوارزمية خوارزمية الاستنساخ المُهيكلة. حتى لا نُعيد اختراع العجلة مجدَّدًا، يمكننا استخدام تنفيذ جاهز للحالة من مكتبة JavaScript lodash، تُدعى الدالة _.cloneDeep(obj). الخلاصة الكائنات عبارة عن مصفوفات ترابطية بميزات خاصة عديدة. تُخزن الكائنات خاصيات (أزواج مفتاح-قيمة)، بشرط أنه: يجب أن تكون مفاتيح الخاصيات نصوصًا أو رموزًا (غالبًا نصوص). يمكن أن تكون القيم من أي نوع. يمكننا استخدام ما يلي للوصول إلى خاصية: الصيغة النُقَطِيَّة: obj.property. صيغة الأقواس المعقوفة obj["property"]‎. تتيح لنا الأقواس المعقوفة أخذ مفتاح من متغير، مثل obj[varWithKey]‎. عمليات أخرى: لِحذف خاصية: delete obj.prop. لِفحص تواجد خاصية بمفتاح معين: "key" in obj. للتنقل خلال كائن: الحلقة for (let key in obj)‎. تُخَزَّن الكائنات وتُنسخ باستخدام المرجع. بمعنىً آخر، لا يُخزن المتغير قيمة الكائن (object value) لكنه يُخزن مرجعًا (reference) يمثِّل موقع قيمة الكائن في الذاكرة. لذا فإن نسخ هذا المتغير أو تمريره إلى دالة سَينسخ هذا المرجع وليس الكائن ككُل. جميع العمليات (مثل إضافة أو حذف خاصيات) المُنفَّذة على مرجع منسوخ تُنفَّذ على الكائن نفسه. لعمل نسخة حقيقية (الاستنساخ) يمكننا استخدام Object.assign أو _.cloneDeep(obj). يُسمى ما درسناه في هذا الفصل "كائن بسيط" أو كائن فقط. يوجد العديد من أنواع الكائنات الأخرى في JavaScript: الكائن Array (مصفوفة): لتخزين مجموعة البيانات المرتبة، الكائن Date (تاريخ): لتخزين معلومات عن الوقت والتاريخ، الكائن Error (خطأ): لتخزين معلومات عن خطأ ما. وغيرها من أنواع الكائنات. لدى هذه الأنواع ميزاتها الخاصة التي سيتم دراستها لاحقًا. يقول بعض الأشخاص أحيانًا شيئًا مثل "نوع مصفوفة" أو "نوع تاريخ" (الاسم الذي وضعته بين قوسين بجانب نوع الكائن)، لكن هذه الأنواع ليست أنواعًا مستقلة بحد ذاتها، إنما تنتمي إلى نوع البيانات Object (كائن) وتتفرع عنه بأشكال مختلفة. تُعد الكائنات في JavaScript قوية جدًا. درسنا في هذا الفصل جزءًا بسيطًا من موضوع هائل جدًا. سنتعامل مع الكائنات لاحقًا بصورة أقرب وسَنتعلم أكثر عنها في فصول أخرى. تمارين مرحبًا، بالكائن الأهمية: 5 اكتب الشيفرة البرمجية، سطر لكل متطلب: أنشئ كائنًا فارغًا باسم user. أضف الخاصية name بالقيمة John. أضف الخاصية surname بالقيمة Smith. غير قيمة الخاصية name إلى Pete. احذف الخاصية name من الكائن. الحل let user = {}; user.name = "John"; user.surname = "Smith"; user.name = "Pete"; delete user.name; التحقق من الفراغ اكتب الدالة isEmpty(obj)‎ التي تُرجع القيمة true إن كان الكائن فارغًا، وتُرجِع القيمة false في الحالات الأخرى. يجب أن تعمل كالتالي: let schedule = {}; alert( isEmpty(schedule) ); // true schedule["8:30"] = "get up"; alert( isEmpty(schedule) ); // false إليك تجربة حية للمثال. الحل قم بالمرور خلال الكائن ونفذ الأمر return false مباشرة إن عثرت على أي خاصية: function isEmpty(obj) { for (let key in obj) { // إن بدأت الحلقة بالعمل، فهناك خاصية في الكائن return false; } return true; } كائنات ثابتة؟ الأهمية: 5 هل من الممكن تغيير كائن صُرِّح عنه بالكلمة المفتاحية const؟ ما رأيك؟ const user = { name: "John" }; // هل تعمل؟ user.name = "Pete"; الحل بالفعل ستعمل بدون مشاكل. تحمي الكلمة المفتاحية const المتغير نفسه من التغيير فقط. بمعنى آخر، يخزن user مرجعًا للكائن ولا يمكن تغييره مع وجود التصريح عنه بالكلمة المفتاحية const لكن يمكن تغيير محتوى الكائن. const user = { name: "John" }; // تعمل user.name = "Pete"; // خطأ user = 123; جمع خاصيات الكائن لدينا كائن يُخزن رواتب الفريق: let salaries = { John: 100, Ann: 160, Pete: 130 } اكتب الشيفرة التي تجمع الرواتب وتُخزنها في المتغير sum. يجب أن يكون مجموع المثال أعلاه 390. إن كان salaries فارغًا، فإن الناتج سيكون 0. الحل let salaries = { John: 100, Ann: 160, Pete: 130 }; let sum = 0; for (let key in salaries) { sum += salaries[key]; } alert(sum); // 390 ضرب الخاصيات العددية بالقيمة 2 الأهمية: 3 أنشئ دالةً باسم multiplyNumeric(obj)‎ تضرب جميع الخاصيات العددية في الكائن obj في العدد 2. مثلا: // قبل الاستدعاء let menu = { width: 200, height: 300, title: "My menu" }; multiplyNumeric(menu); // بعد الاستدعاء menu = { width: 400, height: 600, title: "My menu" }; لاحظ أنَّ الدالة multiplyNumeric لا يجب أن تُرجِع أي شيء. يجب أن تُعدِّل القيم بداخل الكائن. ملاحظة: استخدام typeof لفحص الأعداد. إليك تجربة حية للتمرين. الحل function multiplyNumeric(obj) { for (let key in obj) { if (typeof obj[key] == 'number') { obj[key] *= 2; } } } ترجمة -وبتصرف- للفصل Objects من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: كنس البيانات المهملة المقال السابق: تعويض نقص دعم المتصفحات لجافاسكربت
  7. تتطور لغة جافاسكربت باستمرار نتيجةً لوجود العديد من المقترحات المبنية على الحاجة إلى تطويرها دوريًا. تُحلَّل هذه المقترحات وتُضاف إن كانت جيدة إلى القائمة https://tc39.github.io/ecma262/ ثم النظر في وصفها. لدى الفِرَق العاملة على محركات جافاسكربت أفكارها الخاصة حول ما يجب تنفيذه أولًا. قد تقرر هذه الفِرَق تنفيذ مقترح ما زال مسودة وتأجيل أشياء أصبح وصفها جاهزا لأنها قد تكون أقل أهمية أو صعبة التنفيذ. لذلك، نجد أن هذه المحركات تتضمن المعايير المتعارفة فقط. لترى الميزات التي تدعمها اللغة، انظر في هذه الصفحة https://kangax.github.io/compat-table/es6/ (يوجد الكثير من الميزات، فما زال لدينا الكثير لنتعلمه). Babel تفشل بعض المحركات في دعم بعض الشيفرات البرمجية عند استخدام ميزات اللغة الحديثة. فكما ذكرنا سابقا، ليست جميع الميزات مُضَمَّنة في كل مكان. هنا يأتي دور Babel لإصلاح الوضع. يُعد Babel مُفَسِّرًأ تحويليًا transpiler، إذ يعيد كتابة شيفرة جافاسكربت حديثة بإصدار سابق. في الواقع، يوجد جزئين في Babel: أولا: برنامج المُفَسِّر التحويلي (transpiler): يعيد كتابة الشيفرة. يُشَغله المطورون على أجهزتهم. يقوم يعيد البرنامج كتابة الشيفرة البرمجية كتابةً متوافقةً مع إصدار سابق للغة ثم يتم توصيل الكود للمستخدمين إلى الموقع. توفر مشاريع بناء الأنظمة الحديثة مثل webpack الوسائل المناسبة لتشغيل المُفَسِّرالتحويلي (transpiler) تلقائيا مع كل تغيير ِللشيفرة البرمجية ما يجعل الاندماج في عملية التطوير أسهل. ثانيا: برنامج لتعويض نقص دعم المتصفحات (Polyfills): قد تتضمن ميزات اللغة الجديدة بعض الوظائف المدمجة وهياكل الجُمَل. يعيد المُفَسِّر التحويلي كتابة الشيفرة البرمجية مُحولًا هياكل الجمل إلى إصدارات أقدم، لكن يجب تضمين الوظائف المُدمجة الجديدة. تُعد جافاسكربت لغة ديناميكية للغاية، وقد تضيف/تعدل السكريبتات أي دالة حتى تتعامل وفقا للمعايير الحديثة. السكريبت الذي يضيف/يحدث دالة جديدة يسمى "polyfill"، معوِّض نقص الدعم. فهو يغطي الفجوة وإضافة المحتوى المفقود. برامج تعويض نقص دعم المتصفحات التي قد تثير اهتمامك: core js تدعم الكثير وتصمح بتضمين الميزات المُرادة فقط. polyfill.io هي خدمة تزود السكريبت بوسائل تعويض نقص دعم المتصفحات وفقا للميزات والمتصفح. لذلك، إن احتجت استخدام ميزات اللغة الحديثة، فمن المهم استخدام مُفَسِّرتحويلي و برنامج دعم نقص المتصفحات. أمثلة من الشرح معظم الأمثلة قابلة للتشغيل في مكانها مثل: alert('Press the "Play" button in the upper-right corner to run'); لن تعمل الأمثلة التي تستخدم جافاسكربت حديثة إلا في المتصفحات التي تدعمها. يُعد متصفح جوجل كروم الأكثر حداثة دوما مع ميزات اللغة الحديثة، من الجيد تشغيل العروض الحديثة بدون أي مُفَسِّرتحويلي. تعمل العديد من المتصفحات الأخرى المُحدَّثة جيدا. ترجمة -وبتصرف- للفصل Polyfills من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: الكائنات المقال السابق: الاختبار الآلي باستخدام mocha
  8. يُستخدَم الاختبار الآلي في الكثير من المهام، كما يستخدم بكثرة في المشاريع الحقيقية. لم نحتاج الاختبارات؟ عند كتابة دالة، يمكننا تخيل ما يجب أن تقوم به: ما هي المعاملات التي تعطي نتائج معينة. يمكننا فحص الدالة أثناء التطوير من خلال تشغيلها وموازنة مخرجاتها مع ما هو متوقع. مثلا يمكننا القيام بذلك في الطرفية. إن كان هناك خطأ، فإننا نُصلِح الشيفرة البرمجية، ونُعيد تشغيلها، ونفحص النتائج. وهكذا حتى تصبح صحيحة. لكن هذه الطريقة «إعادة التشغيل» غير مثالية. عند اختبار شيفرة برمجية عن طريق إعادة التشغيل اليدوية، فمن السهل نسيان شيئٍ ما. على سبيل المثال، عند إنشاء الدالة f. نكتب فيها بعض الشيفرات البرمجية، ثم نَفحصها: "f(1)‎" تعمل لكن "f(2)‎" لا تعمل. صلِح الشيفرة حتى تعمل "f(2)‎". ثم تبدو الدالة كأنها مكتملة، لكننا ننسى إعادة اختبار "f(1)‎" مما قد يؤدي إلى خطأ. هذا الأمر وارد بكثرة. فعند تطوير أي شيء، نُبقِي العديد من الاحتمالات في الحسبان. لكنه من الصعب توقع أن يختبر المبرمج جميع هذه الحالات يدويا بعد كل تغيير، فيصبح من السهل إصلاح شيء ما وإفساد شيء آخر. يعني الاختبار الآلي أن الاختبارات تُكتب مستقلة، بالإضافة إلى الشيفرة البرمجية. تشغِّل هذه الاختبارات الدوال بعدة طرق وتوازنها مع النتائج المتوقعة. التطوير المستند إلى السلوك لنبدأ بتقنية تسمى التطوير المستند إلى السلوك Behavior Driven Development أو كاختصار BDD. هذه التقنية BDD هي 3 في 1: اختبارات وتوثيق وامثلة. سنجرب حالة تطوير عملية لفهم BDD. تطوير الدالة "pow": الوصف لنفترض أننا نريد إنشاء الدالة pow(x, n)‎ التي ترفع الأساس x إلى القوة n. مع الأخذ بالحسبان أن n≥0. هذه المهمة هي مجرد مثال: المعامل ** يقوم بهذه العملية في JavaScript، لكننا نركز هنا على تدفق التطوير الذي يمكن تطبيقه على مهام أكثر تعقيدا. يمكننا تخيل ووصف ما يجب أن تقوم به الدالة pow قبل إنشاء شيفرتِها البرمجية. هذا الوصف يُسمى "specification" أو باختصار "spec" ويحتوي على وصف حالات الاستخدام بالإضافة إلى اختبارات لهذه الحالات كالتالي: describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); }); }); تحتوي المواصفات على 3 أجزاء رئيسية كما ترى في الأعلى: describe("title", function() { ... }) ماهي الوظيفة التي نصفها، في هذه الحالة، نحن نصف الدالة pow. تستخدم بواسطة العاملين- أجزاء it. it("use case description", function() { ... }) نصف (نحن بطريقة مقروءة للبشر) حالة الاستخدام المخصصة في عنوان it، والمعامل الآخر عبارة عن دالة تفحص هذه الدالة. assert.equal(value1, value2) الشيفرة البرمجية بداخل it يجب أن تُنَفَّذ بدون أخطاء في حال كان التنفيذ صحيحًا. تستخدم الدوال assert.* لِفحص ما إن كانت الدالة pow تعمل بالشكل المتوقع أم لا. تستخدم إحدى هذه الدوال في هذا المثال - assert.equal، والتي توازن معاملَين وتُرجِع خطأ في حال عدم تساويهما. في المثال تفحص هذه الدالة إن كانت نتيجة تنفيذ الدالة pow(2, 3)‎ تساوي 8. كما يوجد العديد من أنواع التحقق والمقارنة والتي سنُضيفها لاحقا. يمكن تنفيذ الوصف، وسينفِّذ الفحص الموجود بداخل it كما سنرى لاحقا. تدفق التطوير يبدو تدفق التطوير غالبا كما يلي: يُكتب الوصف الأولي مع فحص للوظيفة الرئيسية. يُنشَئ تنفيذ أولي. للتأكد من صحة عمل التنفيذ، نُشَغِّل إطار التقييم Mocha الذي يُشَغِّل الوصف. ستظهر أخطاء في حال عدم اكتمال الوظائف. نُصحح الأخطاء حتى يصبح كل شيء صحيحًا. هكذا أصبح لدينا تنفيذ مبدئي يعمل كالمطلوب بالإضافة إلى فحصه. نضيف المزيد من حالات الاستخدام للوصف، ربما بعض هذه الميزات ليس مضمنا في التنفيذ بعد. حينها يبدأ الاختبار بالفشل. عُد للخطوة 3 وحدِّث التنفيذ إلى أن تختفي كل الأخطاء. كرر الخطوات 3-6 حتى تجهز كل الوظائف. إذا، تُعد عملية التطوير تكرارية. نكتب الوصف، ننفذه، نتأكد من اجتياز التنفيذ للفحص، ثم نكتب المزيد من الاختبارات، نتأكد من صحة عملها. حتى نحصل على تنفيذ صحيح مع اختباراته في الأخير. لنُجرب تدفق التطوير هذا على حالتنا العملية. الخطوة 1 أصبحت جاهزة: لدينا وصفًا مبدئيًّا للدالة pow. الآن وقبل التنفيذ، لِنستخدم بعض مكاتب جافاسكربت لتشغيل الاختبار حتى نتأكد من إن كانت تعمل (لن تعمل). المواصفات أثناء التنفيذ سنستخدم في هذا الشرح مكاتب جافاسكربت التالية للاختبار: Mocha - الإطار الرئيسي: يوفر دوال الفحص الأكثر استخدامًا ما يشمل describe و it بالإضافة إلى الدوال الرئيسية التي تُشَغِّل الاختبار. Chai - المكتبة المحتوية على دوال تأكيدية. تتيح لنا استخدام العديد من هذه الدوال، نحتاج الآن assert.equal فقط. Sinon - مكتبة للتجسس على الدوال، ومحاكاة الدوال المدمجة، والمزيد؛ سنحتاج هذه المكتبة لاحقا. تُعد هذه المكاتب مفيدة للاختبار في كل من المتصفح والخادم. سنأخذ بعين الاعتبار هنا جهة المتصفح. صفحة HTML كاملة مع هذه المكاتب ووصف الدالة pow: <!DOCTYPE html> <html> <head> <!-- add mocha css, to show results --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css"> <!-- add mocha framework code --> <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script> <script> mocha.setup('bdd'); // minimal setup </script> <!-- add chai --> <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script> <script> // chai has a lot of stuff, let's make assert global let assert = chai.assert; </script> </head> <body> <script> function pow(x, n) { /* function code is to be written, empty now */ } </script> <!-- the script with tests (describe, it...) --> <script src="test.js"></script> <!-- the element with id="mocha" will contain test results --> <div id="mocha"></div> <!-- run tests! --> <script> mocha.run(); </script> </body> </html> يمكن تقسيم الصفحة إلى 5 أجزاء: <head> - لإضافة مكاتب وأنماط للاختبارات. <script> يحتوي على الدالة التي سيتم اختبارها، في هذا المثال الشيفرة البرمجية للدالة pow. الاختبار - عبارة عن سكريبت خارجي في هذا المثال test.js يحتوي على describe("pow", ...)‎ الموضح سابقا. عنصر HTML ‏<div id="mocha"‎> والذي سيُستخدم بواسطة Mocha لعرض النتائج. يتم بدء الاختبارات باستخدام الأمر mocha.run()‎. النتائج: يفشل الاختبار ويظهر خطأ في الوقت الحالي. يُعد هذا منطقيا: فالدالة pow ما زالت فارغة، فإن pow(2,3)‎ تُرجع undefined بدلا من 8. لاحظ أن بإمكانك تشغيل أكثر من اختبار مختلف مستقبلا، فهناك مُشغلات عالية المستوى مثل karma وغيرها. التنفيذ الأولي لنقم بتنفيذ مبسط للدالة pow حتى تعمل الاختبارات: function pow(x, n) { return 8; // :) we cheat! } الآن ستعمل… تطوير الوصف ما قمنا به هو غش فقط، لا تعمل الدالة كالمطلوب: إن قمنا بحساب pow(3,4)‎ فسنحصل على قيمة غير صحيحة، لكنها ستجتاز الاختبارات. هذه الحالة غير عملية، وتحدث بكثرة. الدالة تتجاوز الاختبارات لكن آلية عملها خاطئة. هذا يعني أن الوصف غير مثالي. ونحتاج لإضافة المزيد من حالات استخدام الدالة إليه. لنضف اختبارًا آخر للتأكد ما إن كان pow(3, 4) = 81. يمكنُنا اختيار إحدى الطريقتين لتنظيم الاختبارات: الخيار الأول - إضافة assert إلى it: describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); assert.equal(pow(3, 4), 81); }); }); الخيار الآخر - عمل اختبارين: describe("pow", function() { it("2 raised to power 3 is 8", function() { assert.equal(pow(2, 3), 8); }); it("3 raised to power 3 is 27", function() { assert.equal(pow(3, 3), 27); }); }); يختلف المبدئ في أنه عند وجود خطأ في assert، فإن it تتوقف عن العمل. لذا ففي الخيار الأول عند فشل assert الأولى فلن نرى مخرجات assert الأخرى. يُعد الخيار الثاني أفضل للحصول على المزيد من المعلومات حول ما يحدث بعمل اختبارين منفصلين. بالإضافة إلى وجود قاعدة أخرى من الجيد اتباعها. كل اختبار يفحص شيئًا واحدًا فقط. إن وجدنا اختبارًا يفحص شيئين مختلفين فمن الأفضل فصلُهما إلى اختبارين. لنكمل باستخدام الخيار الثاني. النتائج: سيفشل الاختبار الثاني كَالمتوقع. فَالدالة تُرجع دوما 8، بينما تتوقع الدالة assert النتيجة 27. تطوير التنفيذ لنكتب شيئا أكثر واقعية لاجتياز الاختبارات: function pow(x, n) { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } للتأكد من صحة عمل الدالة K نختبرها لأكثر من قيمة. يمكننا القيام بذلك باستخدام الحلقة for بدلا من تكرار it يدويا: describe("pow", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); النتيجة: دالة describe متداخلة سنضيف المزيد من الاختبارات، لكن، قبل ذلك لاحظ أنه يجب جمع الدالة المساعدة makeTest والدالة for. لن نَحتاج لاستخدام makeTest في باقي الاختبارات، نحتاجها فقط في for: لأن وظيفتهُما العامة هي فحص كيف ترفع الدالة pow قيمة ما إلى قوة معينة. يتم جمع الدالتين باستخدام الدالة describe المتداخلة: describe("pow", function() { describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); // ... more tests to follow here, both describe and it can be added }); تُعرِّف describe الداخلية مجموعة فرعية جديدة من الاختبارات. يمكن ملاحظة الإزاحة في المخرجات: يمكننا إضافة المزيد من دوال it و describe في الطبقة العلوية مع دوال مساعدة لها، هذه الدوال لن ترى الدالة makeTest. before/after و beforeEach/afterEach يمكننا إعداد دوال before/after التي تُنَفَّذ قبل/بعد تنفيذ الاختبارات، كما يمكننا استخدام beforeEach/afterEach قبل/بعد كل it. على سبيل المثال: describe("test", function() { before(() => alert("Testing started – before all tests")); after(() => alert("Testing finished – after all tests")); beforeEach(() => alert("Before a test – enter a test")); afterEach(() => alert("After a test – exit a test")); it('test 1', () => alert(1)); it('test 2', () => alert(2)); }); تسلسل التنفيذ سيكون كالتالي: Testing started – before all tests (before) Before a test – enter a test (beforeEach) 1 After a test – exit a test (afterEach) Before a test – enter a test (beforeEach) 2 After a test – exit a test (afterEach) Testing finished – after all tests (after) تستخدم beforeEach/afterEach و before/after غالبا لتنفيذ الخطوات الأولية، العدادات التي تُخرج 0 أو للقيام بشيء ما بين الاختبارات أو مجموعة اختبارات. توسيع الوصف أصبحت الوظيفة الرئيسية للدالة pow مكتملة. تم تجهيز الدورة الأولى من التطوير. الآن يمكننا الاحتفال بالانتهاء من ذلك وبدء تطوير الدالة. كما ذكرنا مسبقا، فإن الدالة pow(x, n)‎ ستتعامل مع أرقام موجبة فقط n. تُرجع دوال جافاسكربت دائما NaN عند وجود خطأ حسابي. لنقم بهذا الأمر مع قيم n. أولا، نضيف هذا إلى الوصف: describe("pow", function() { // ... it("for negative n the result is NaN", function() { assert.isNaN(pow(2, -1)); }); it("for non-integer n the result is NaN", function() { assert.isNaN(pow(2, 1.5)); }); }); النتائج مع الاختبارات الجديدة: تفشل الاختبارات المُضافة مؤخرا وذلك لأن التنفيذ لا يدعمها. هكذا هي الطريقة التي يعمل بها BDD: نبدأ بكتابة الاختبارات التي نعلم بأنها ستفشل ثم نكتب التنفيذ الخاص بها. دوال تأكيد أخرى لاحظ أن الدالة assert.isNaN: تفحص وجود القيمة NaN. يوجد المزيد من دوال التأكيد في Chai، مثلا: assert.equal(value1, value2)‎ - تفحص التساوي value1 == value2. assert.strictEqual(value1, value2)‎ - تفحص التساوي التام value1 === value2. assert.notEqual، assert.notStrictEqual - تفحص عكس الدالتين أعلاه. assert.isTrue(value)‎ - تفحص أن value === true. assert.isFalse(value)‎ - تفحص أن value === false. يمكنك قراءة باقي الدوال في التوثيق لذا، يجب أن نضيف بعض الأسطر للدالة pow: function pow(x, n) { if (n < 0) return NaN; if (Math.round(n) != n) return NaN; let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } الآن أصبحت تعمل وتَجتاز جميع الاختبارات: الخلاصة يكون الوصف في البداية في BDD، ثم يأتي التنفيذ. فنحصل في الأخير على كل من الوصف والشيفرة البرمجية. يمكن استخدام الوصف بثلاث طرق: اختبارات - تضمن صحة عمل الشيفرة البرمجية. توثيق - توضح العناوين في describe وit وظيفة الدالة. أمثلة - تُعد الاختبارات أمثلة فعالة تعرض كيف يمكن استخدام الدالة. يمكننا تطوير، تغيير، أو إعادة كتابة دالة من الصفر من خلال الوصف مع ضمان صحة عملها. يعد هذا الأمر مهمًا خاصة في المشاريع الكبيرة عند استخدام دالة في عدة أماكن. فعند تغيير هذه الدالة، يكون من الصعب التحقق من صحة عملها يدويًا في كل مكان. يوجد خيارين بدون اختبارات: تنفيذ التغيير بغض النظر عن النتائج. هكذا قد نواجه الكثير من الأخطاء عند الفحص اليدوي. يتجنب المطورون تحديث الشيفرة عند توقع حدوث أخطاء فادحة وعدم وجود اختبارات لفحص هذه الأخطاء فتبقى الشيفرة البرمجية بدون تحديث. يساعد الفحص الآلي على تجنب هذه المشاكل! إن كان المشروع مليئا بالاختبارات، فلن يكون هناك أي مشكلة إطلاقا. فبعد أي تغيير يمكننا تنفيذ الاختبارات لنرى العديد من التحقيقات أجريت في غضون ثوانٍ. علاوة على ذلك، الشيفرة البرمجية المختبرة جيدا تكون بهيكل أفضل. منطقيا، لأن الشيفرة البرمجية المختبرة سهلة التعديل والتطوير. مع وجود سبب آخر أيضا. يجب أن تكون الشيفرة البرمجية منظمة من أجل كتابة اختبار بحيث يكون لدى كل دالة وظيفة رئيسية موصوفة، ومدخلات ومخرجات معروفة جيدا. ذلك يعني 1 هيكلة جيدة منذ البداية. لا يكون الأمر بهذه السهولة في الواقع. ففي بعض الأحيان يكون من الصعب كتابة وصف للدالة قبل شيفرتها البرمجية لأن سلوكها لا يكون واضحًا بعد. لكن عموما، كتابة الاختبارات يجعل التطوير أسرع وأكثر استقرارًا. ستواجه الكثير من المهام التي تتضمن ذلك لاحقا في الشرح. فسترى المزيد من الأمثلة العملية. يتطلب كتابة الاختبارات معرفة جيدة بِجافاسكربت. لكننا في بداية تعلمها. فلترتيب كل شيء، لست مطالبًا بكتابة الاختبارات في الوقت الحالي، لكن يجب أن تكون قادرًا على قرائتها حتى إن كانت معقدة أكثر قليلا مما تم شرحه هنا. تمارين ما الخطأ في الاختبار التالي؟ الأهمية: 5 ما الخطأ في الاختبار للدالة pow أدناه؟ it("Raises x to the power n", function() { let x = 5; let result = x; assert.equal(pow(x, 1), result); result *= x; assert.equal(pow(x, 2), result); result *= x; assert.equal(pow(x, 3), result); }); الحل: يوضح الاختبار أحد الإغراءات التي يواجهها المطور أثناء كتابة اختبار. ما لدينا الآن هو ثلاثة اختبارات، لكنها مرتبة كدالة واحدة تتضمن ثلاثة تأكيدات. تكون هذه الطريقة أسهل في بعض الأحيان، لكن إن وُجِد أي خطأ، فلن يكون من السهل معرفة سبب ذلك الخطأ. إن حدث خطأ ما أثناء تنفيذ شيفرة معقدة، فَسنحتاج لمعرفة قيم البيانات عند تلك النقطة. أي أننا سنحتاج لتنقيح الاختبار. من الأفضل تجزئة الاختبار إلى أجزاء it متعددة مع مدخلات ومخرجات محددة بوضوح كما يلي: describe("Raises x to power n", function() { it("5 in the power of 1 equals 5", function() { assert.equal(pow(5, 1), 5); }); it("5 in the power of 2 equals 25", function() { assert.equal(pow(5, 2), 25); }); it("5 in the power of 3 equals 125", function() { assert.equal(pow(5, 3), 125); }); }); بدلنا it مكان describe وبعدد من it. إن فشل شيء ما الآن فَسَنتمكن من رؤية البيانات. يمكننا أيضًا عزل اختبار واحد وتشغيلة في وضع مستقل باستخدام it.only بدلًا من it: describe("Raises x to power n", function() { it("5 in the power of 1 equals 5", function() { assert.equal(pow(5, 1), 5); }); // Mocha will run only this block it.only("5 in the power of 2 equals 25", function() { assert.equal(pow(5, 2), 25); }); it("5 in the power of 3 equals 125", function() { assert.equal(pow(5, 3), 125); }); }); ترجمة -وبتصرف- للفصل Automated testing with mocha من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: تعويض نقص دعم المتصفحات لجافاسكربت المقال السابق: شيفرة النينجا البرمجية
  9. استخدم مبرمجو النينجا القدماء هذه الحيل لشحذ عقول مراجعو الشيفرات البرمجية. يبحث معلمو مراجعة الشيفرات البرمجية عن هذه الحيل في مهام الاختبار. أحيانًا، يستخدم المبرمجون المبتدئون هذه الحيل بصورة أفضل من مبرمجي النينجا. اقرأها جيدا ثم حدد من أنت - نينجا، مبتدئ، أو مُراجِع شيفرات برمجية؟ البلاغة في الإيجاز اختصر الشيفرة البرمجية قدر الإمكان. أظهر مدى ذكائك. فلتَقُدكَ ميزات اللغة الخفية. مثال: ألق نظرة على العامل الثلاثي التالي '?': // مأخوذ من مكتبة جافاسكربت شهيرة i = i ? i < 0 ? Math.max(0, len + i) : i : 0; إن كتبت بهذه الطريقة، فإن أي مطور آخر يقرأ هذا السطر ويحاول فهم ما هي قيمة i سيأتي إليك باحثًا عن الإجابة. أخبرهم في ذلك الحين أن الاختصار أفضل دومًا ودلهم على طريق النينجا. المتغيرات ذات الحرف الواحد استخدام المتغيرات ذات الحرف الواحد هي طريقة أخرى لكتابة الشيفرة البرمجية بصورة أسرع مثل a، أو b، أو c. يختبئ المتغير ذو الحرف الواحد داخل الشيفرة البرمجية كما يختبئ النينجا الحقيقي في الغابة. لن يتمكن أي شخص من العثور عليه بسهولة باستخدام "البحث" المُدمج مع المحرر. حتى إن عثر شخص ما علبه فلن يفهم ما معنى المتغير a أو b. لكن، يوجد استثناء. مبرمج النينجا الحقيقي لن يستخدم المتغير i كعداد للحلقة "for". سيستخدمه في أي مكان عدا هنا. يوجد العديد من الأحرف الغريبة لاستخدامها كعداد الحلقة بدلا منه مثل x أو y. متغير بحرف غريب عدادًا للحلقة يُعد مناسبا إن كانت الحلقة تمتد إلى صفحة أو صفحتين. فإن أتى شخص ما للتعمق في هذه الحلقة فلن يعرف أن المتغير x هو عدَّاد هذه الحلقة. استخدم الاختصارات إن كانت قوانين الفريق تمنع استخدام المتغيرات ذات الحرف الواحد أو المتغيرات الغامضة، اختصر أسماء المتغيرات. list ‏← lst userAgent ‏← ua browser ‏← brsr وهكذا… سيفهم شخص فطن فقط الأسماء المستخدمة. حاول اختصار كل شيء. فقط شخص مؤهل يجب أن يُكمل تطوير شيفرتك البرمجية. حلق عاليا واستخدم التجريد حاول اختيار الكلمة الأكثر تجريدا عند اختيار اسم ما مثل obj، و data، و value، و item، و elem وهكذا. الاسم الأمثل لمتغير هو data. استخدمه حيثما استطعت. فبالحقيقة، كل متغير يحتوي على بيانات "data". لكن، ماذا إن كان هناك متغير بالاسم data بالفعل؟ جرب الاسم value، فهو عالمي أيضا، فكل متغير يحتوي على قيمة. سمِّ المتغير حسب نوعه: str، num… جرب هذه الطريقة. قد يتساءل أي شخص مبتدئ حول إن كانت مثل هذه الأسماء مفيدة لمبرمج النينجا؟ بالفعل هي مفيدة. اسم المتغير يُخبر عن شيء ما. يدل اسم المتغير في هذه الطريقة عن نوع البيانات التي يحويها: نص، رقم أو أي نوع آخر. لكن عندما يحاول شخص آخر فهم الشيفرة البرمجية، سيُصدَم بعدم توفر معلومات كافية لفهمها! وسيفشل تمامًا في تعديل شيفرتِك البرمجية. يمكن معرفة قيمة المتغير عبر تتبع الشيفرة البرمجية. لكن ما معنى المتغير؟ وماهو النص أو الرقم الذي يخزنة؟ لا يوجد طريقة لمعرفة ذلك بدون تأمل جيد! لكن ماذا إن لم يكن هناك المزيد من هذه الأسماء؟ فقط أضف رقمًا إليها مثل: data1، item2، elem5… اختبار الملاحظة يمكن لِمبرمج قوي الملاحظة فقط فهم شيفرتُك البرمجية. لكن، كيف تختبر ذلك؟ إحدى الطرق هي استخدام متغيرات بأسماء متشابهة مثل date و data. اخلُط ما بين المتغيرين حيث يمكن ذلك. ستصبح قراءة شيفرة برمجية بهذه الطريقة مستحيلة. وعند وجود خطأ كتابي فسيَعلق القارئ لوقت طويل. مرادفات ذكية على سبيل المثال، لنأخذ بالحسبان بوادِئ الدوال. إن كانت الدالة تعرض رسالة على الشاشة، ابدأها ب display…‎ مثل displayMessage. وإن كانت دالة أخرى تعرض شيئًا آخر على الشاشة مثل اسم المستخدم ابدأها ب show…‎ مثل showName. اجعل من يقرأ الشيفرة يظن أن هناك اختلاف خفي بين الدوال بينما لا يوجد أي اختلاف. اتفق مع فريق مبرمجي النينجا: إن بدأ أحمد دالة العرض ب display...‎ في شيفرته البرمجية، فيمكن لمحمد استخدام render...‎ ويمكن لِأمل استخدام paint...‎. لاحظ كيف ستصبح الشيفرة البرمجية أكثر اختلافًا وتشويقًا. الآن إلى خدعة القبعة! استخدم نفس البادئة ِلدالتين مهمتين بوظيفتين مختلفتين! مثلا، الدالة "printPage(page)‎" ستستخدم طابعة، بينما الدالة "printText(text)‎" ستضع النص على الشاشة. هكذا تجعل القارئ يفكر جيدا بالدالة المُسماة printMessage: "أين تضع هذه الدالة الرسالة؟ إلى الطابعة أو على الشاشة؟". لجعل الأمر أوضح، يجب أن تضع الدالة "printMessage(message)‎" الرسالة في النافذة الجديدة! (وظيفة مهمة بطريقة مختلفة). أعد استخدام الأسماء ضع متغيرًا جديدًا عند الحاجة فقط. عوضا عن ذلك، أعد استخدام الأسماء الموجودة. اكتب قيمًا جديدة إلى هذه المتغيرات. وفي الدوال، حاول استخدام المتغيرات المُمَرَّرة إلى الدالة كمُعاملات. ستُصعِّب بهذه الطريقة معرفة ما في المتغير الآن. ومن أين أتى هذا المتغير أيضا. الغرض من هذه الطريقة هو تطوير حدس وذاكرة قارِئ الشيفرة البرمجية. سيحتاج الشخص ذو الحدس الضعيف إلى تحليل الشيفرة البرمجية سطرًا تلو الاخر وتَعقَّب التغييرات في كل جزء من أجزاء الشيفرة البرمجية. إحدى الطرق المتقدمة هي باستبدال قيمة متغير ما بمحتوى آخر مختلف تماما عن السابق خفية داخل حلقة أو دالة. على سبيل المثال: function ninjaFunction(elem) { // elem عشرون سطرًا من الشيفرة يتعامل مع المتغير elem = clone(elem); // elem عشرون سطرًا إضافيًا يتعامل الآن مع نسخة من المتغير } سيتفاجئ المبرمج الآخر الذي يريد التعامل مع elem في الجزء الثاني من الدالة لأنه سيكتشف فقط أثناء التنفيذ والتعقب وبعد تفحص الشيفرة بأنه يتعامل مع elem بعد استنساخه! الشرطة السفلية للمتعة ضع الشرطات السفلية _ و __ قبل أسماء المتغيرات. مثل "‎_name" أو "‎__value". سيكون جيدا إن كنت أنت فقط من تعرف معنى ذلك، أو استخدمها للمتعة فقط دون معنى معين. أو استخدم معانٍ مختلفة في أماكن مختلفة. بهذا ترمي عصفورين بحجر. أولا، تصبح الشيفرة البرمجية أطول وأصعب للقراءة. ثانيا، سيأخذ القارئ وقتا لمعرفة معنى الشرطات السفلية. يضع مبرمج النينجا الذكي الشرطات السفلية في مكان معين من الشيفرة البرمجية ويتجنبها في أماكن أخرى. هذا يجعل الشيفرة البرمجية أكثر هشاشة ويزيد من احتمالية الخطأ المستقبلي. اظهر حبك أظهر للجميع مدى روعة المكونات التي تستخدمها! ستُبهِر الأسماء مثل superElement، megaFrame، و niceItem القارئ. يمكن كتابة: "super..‎"، و "mega..‎"، و"nice..‎" لكن بطريقة لا تُظهِر تفاصيلًا حول المعنى. قد يبحث القارئ عن معانِ خفية ويتأمل لساعة أو اثنتين من وقته. داخِل المتغيرات الخارجية استخدم أسماء المتغيرات نفسها داخل وخارج الدوال. ببساطة، لا حاجة لاختراع أسماء جديدة. let user = authenticateUser(); function render() { let user = anotherValue(); ... ...many lines... ... ... // <-- هنا user يريد مبرمجٌ ما التعامل مع المتغير ... } سيفشل المبرمج الذي يقفز بِداخل الدالة render في ملاحظة أن هناك متغير user محلي يغطي على المتغير الخارجي. سيتعامل مع user مُعتبِرًا أنه المتغير الخارجي نفسه، وبذلك ستكون نتائج الدالة "authenticateUser()‎" غير متوقعة وسينجح الفخ. آثار جانبية في كل مكان يوجد بعض الدوال التي تبدو كأنها لا تقوم بشيء مثل، "isReady()‎"، و "checkPermission()‎"، و "findTags()‎" …إلخ يُتَوَقَّع أنها تقوم بعمليات حسابية، تُوجِد وتٌرجِع قيم البيانات بدون تغيير شيء خارج نطاقها. بمعنى آخر، بدون آثار جانبية. إضافة حدث مفيد إليها سيكون خدعة جيدة، بجانب مهمتها الرئيسية. بالتأكيد، ستوسِّع نظرة الدهشة التي على وجه قارئ الشيفرة -عندما يرى دالة بالاسم "is..‎"، أو "check..‎"، أو "find..‎" وتُغَيِّر شيئا ما - آفاق المنطق لديك. طريقة أخرى هي إرجاع نتيجة غير قياسية. أظهر ذكائك! اجعل الدالة checkPermission تُرجِع كائنًا معقدًا يحتوي على نتيجة الفحص المُراد من الدالة. سَيتساءل المطورون الذين سيكتبون "if (checkPermission(..))‎" عن سبب عدم عملها. حينها أخبرهم أن يقرأوا التوثيق ووجههم لهذا المقال :). دوال قوية! لا تجعل الدالة محدودة بما يتضمنه اسمها. توسَّع أكثر. على سبيل المثال، يمكن للدالة "validateEmail(email)‎" (بالإضافة إلى فحص صحة البريد الالكتروني) عرض رسالة خطأ وطلب إعادة إدخال البريد الإلكتروني. لا يجب أن تكون باقي الوظائف واضحة من مسمى الدالة. مبرمج النينجا الحقيقي لن يجعل هذه الوظائف واضحة حتى في الشيفرة البرمجية. الخلاصة جميع الملاحظات السابقة هي من شيفرات برمجية حقيقية مكتوبة بواسطة مبرمجين محترفين، ربما أكثر احترافية منك اتبع بعض الملاحظات وستكون شيفرتك البرمجية مليئة بالمفاجآت. اتَّبِع اغلبها وستكون شيفرتك البرمجية حقا ملك لك أنت فقط، لن يريد أي شخص تغييرها. اتَّبِع جميعها وستصبح شيفرتك البرمجية درسًا قيمَا للمطورين المبتدئين الباحثين عن إرشاد. ترجمة -وبتصرف- للفصل Ninja code من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: الاختبار الآلي باستخدام mocha المقال السابق: التعليقات
  10. كما نعلم من جزء بنية الشيفرة البرمجية، يمكن ان تكون التعليقات ذات سطر واحد: وتبدأ ب // أو متعددة الأسطر: /* ... */. تُستخدم التعليقات لوصف الغرض من الشيفرة البرمجية وآلية عملها. في أول مرة، قد تبدو التعليقات مهمة، لكن المبرمجين المبتدئين يستخدمونها بطريقة غير صحيحة. التعليقات السيئة يستخدم المبتدئون التعليقات لشرح "ماذا يحدث في الشيفرة البرمجية". كما يلي: // ستقوم هذه الشيفرة البرمجية بهذا الأمر (...) وذلك الأمر (...) // وربما أشياء أخرى very; complex; code; يجب أن يكون عدد التعليقات التوضيحية أقل في الشيفرات البرمجية الجيدة، لأن الشيفرة البرمجية يجب أن تكون مفهومة بدون تعليقات. يوجد قاعدة مهمة حول هذا الأمر:"إن كانت الشيفرة البرمجية غير مفهومة لدرجة أنها تحتاج إلى التعليقات لشرحها، يجب أن تُكتب من جديد بدلًا من التعليقات التوضيحية الكثيرة". طريقة: أخرج الدوال أحيانا، يكون استبدال جزء من الشيفرة البرمجية أمرًا مهمًا كما يلي: function showPrimes(n) { nextPrime: for (let i = 2; i < n; i++) { // check if i is a prime number for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } alert(i); } } البديل الأفضل هو إخراج دالة isPrime: function showPrimes(n) { for (let i = 2; i < n; i++) { if (!isPrime(i)) continue; alert(i); } } function isPrime(n) { for (let i = 2; i < n; i++) { if (n % i == 0) return false; } return true; } بهذه الطريقة يمكننا فهم الشيفرة البرمجية بشكل أفضل لأن الدالة أصبحت بديلة عن التعليق. هذه الشيفرة البرمجية تسمى شيفرة برمجية موصوفة بحد ذاتها. طريقة: أنشئ دوالًا إن كان هناك شيفرة برمجية طويلة كما يلي: // here we add whiskey for(let i = 0; i < 10; i++) { let drop = getWhiskey(); smell(drop); add(drop, glass); } // here we add juice for(let t = 0; t < 3; t++) { let tomato = getTomato(); examine(tomato); let juice = press(tomato); add(juice, glass); } // ... فيُفضل إخراجه إلى دالة كما يلي: addWhiskey(glass); addJuice(glass); function addWhiskey(container) { for(let i = 0; i < 10; i++) { let drop = getWhiskey(); //... } } function addJuice(container) { for(let t = 0; t < 3; t++) { let tomato = getTomato(); //... } } مرة اخرى.. يجب أن يكون عمل الدالة واضحا من اسمها وموضعها. هكذا لن نحتاج إلى الكثير من التعليقات. وستكون بنية الشيفرة البرمجية أفضل عند تجزئتها. وسيصبح من الواضح ماهي آلية عمل الدالة، وماذا تستقبل وماذا تُخرِج. في الواقع لا يمكننا تجنب التعليقات التوضيحية. فهناك بعض الخوارزميات المعقدة وأدوات التعديل والتحسين الذكية (smart tweaks) للشيفرة البرمجية. لكن يجب أن نحاول أن نجعل الشيفرة البرمجية سهلا ويشرح نفسه. التعليقات الجيدة إن كانت التعليقات التوضيحية سيئة، فأي التعليقات هو الجيد؟ وصف الهيكلة يُعطي نظرة عامة عن المكونات، وطريقة تفاعلها، وطريقة تدفق التحكم في مختلف الحالات، وما إلى ذلك. باختصار، ما يُسمى بالنظرة الشاملة. يوجد لغة خاصة UML لبناء رسوم هيكلية عالية المستوى لشرح الشيفرة البرمجية. هذه اللغة تستحق التعلم فعلا. توثيق مُعامِلات الدوال واستخدامها يوجد تركيب خاص بالجمل البرمجية يُسمَّى JSDoc لتوثيق الدوال: استخدامها، مُعامِلاتها، والقيم التي تُرجِعُها. إليك المثال التالي: /** * Returns x raised to the n-th power. * * @param {number} x The number to raise. * @param {number} n The power, must be a natural number. * @return {number} x raised to the n-th power. */ function pow(x, n) { ... } تتيح لنا هذه التعليقات فهم الغرض من هذه الدالة واستخدامها مباشرة دون الحاجة للنظر إلى تفاصيل الشيفرة البرمجية فيها. يمكن للعديد من المُحررات فهم هذه التعليقات أيضا واستخدامها في الإكمال التلقائي وبعض حالات فحص الشيفرة التلقائي. يوجد أيضا بعض الأدوات مثل JSDoc 3 والتي يمكنها توليد توثيق HTML من هذه التعليقات. للمزيد من المعلومات حول JSDoc اقرأ هنا http://usejsdoc.org/. لم تم حل هذه المهمة بهذه الطريقة؟ ما هو مكتوب هو مهم، لكن قد يكون ما هو غير مكتوب أكثر أهمية لفهم ما يجري. لا تستطيع الشيفرة البرمجية الإجابة عن السؤال "لم تم حل هذه المهمة بهذه الطريقة؟". إن كان هناك العديد من الطرق لحل هذه المهمة. لم تم اختيار هذه الطريقة؟ خاصة عندما لا تكون هذه الطريقة هي الأفضل. بدون تعليقات توضح السبب يمكن حدوث التالي: ستفتح الشيفرة البرمجية المكتوبة منذ وقت طويل (أنت أو أي شخص آخر) وسترى أنها "غير مناسبة". ستفكر: "كم كنت غبيا حينها، وكم أنا ذكي الآن"، ثم ستُعيد كتابة الشيفرة البرمجية باستخدام البديل الذي تراه أكثر صحة وملائمة. فكرة إعادة الكتابة تكون سهلة، لكن عند القيام بها تكتشف أن الحل "الأمثل" لا يتناسب مع الغرض المطلوب. حينها قد تتذكر السبب لأنك جربت الأمر ذاته مسبقا بالفعل ولم يفلح. ثم ستعود إلى الخيار السابق لكن بعد ضياع الوقت. التعليقات التي توضح سبب استخدام طريقة ما هي مهمة جدا. فهي تساعد على الاستمرار بالتطوير مباشرة. أي ميزات غير ملحوظة في الشيفرة البرمجية ومكان استخدامها إن احتوت الشيفرة البرمجية على شيء غير ملحوظ أو شيء يُدرَك حدسيا، فيجب تعليقه. الخلاصة التعليقات هي علامة مهمة عن المطوِّر الجيد: وجودها أو عدم وجودها، إذ تتيح التعليقات الجيدة صيانة الشيفرة البرمجية جيدا، عند العودة لها بعد مدة من الوقت واستخدامها بكفاءة. علِّق ما يلي: الهيكلة العامة، النظرة عالية المستوى. استخدام الدوال. الحلول المهمة، خاصة تلك الغير ملحوظة مباشرة. تجنب التعليقات: التي تشرح آلية عمل الشيفرة البرمجية والغرض منها. ضع مثل هذه التعليقات عندما يكون من المستحيل جعل الشيفرة البرمجية سهلة القراءة وتصف نفسها وتحتاج لتوضيح فقط تستخدم التعليقات أيضا للأدوات التي تقوم بالتوثيق تلقائيا مثل JSDoc3: تقرأ هذه الأدوات التعليقات وتولِّد ملفات HTML أو بأي صيغة أخرى. ترجمة -وبتصرف- للفصل Comments من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: شيفرة النينجا البرمجية المقال السابق: نمط كتابة الشيفرة
  11. يجب أن تكون الشيفرة البرمجية مرتبة ونظيفة وسهلة القراءة قدر الإمكان. هذا ما يسمى بفن البرمجة وهو أخذ مهمة معقدة وبرمجتها بطريقة صحيحة ومقروءة في الوقت ذاته. يساعد نمط كتابة الشيفرة كثيرا في ذلك. الصياغة في الصورة أدناه يوجد صفحة تحوي بعض القواعد المهمة في تركيب الشيفرات البرمجية. سنناقش هذه القواعد وأسبابها بالتفصيل. ملاحظة: لا يوجد قواعد الزامية. لا يوجد قاعدة إلزامية لنمط التكويد. هذه القواعد تعد تفضيلات وليست أساسيات برمجية. الأقواس المعقوصة (Curly Braces) تُكتب الأقواس المعقوصة في معظم مشاريع JavaScript المعقوصة بالطريقة "المصرية" بوضع قوس الفتح في نفس السطر الذي يحوي الكلمة المفتاحية - ليس في سطر جديد. يجب وضع مسافة قبل القوس الافتتاحي كما يلي: if (condition) { // افعل هذا //…. وذاك //…. وذاك } التعليمة المكونة من سطر واحد مثل if(condition) doSomething()‎ تعد حالة طرفية مهمة، فهل يجب استخدام الأقواس فيها؟ فيما يلي بعض البدائل المشروحة. يمكنك قراءتها والحكم على درجة سهولة قراءتها بنفسك: 1- يقوم بعض المبتدئون أحيانًا بما يلي. ما يعد ممارسة خاطئة. فلا حاجة لِلأقواس المعقوصة هنا: if (n < 0) {alert(`Power ${n} is not supported`);} 2- الانتقال إلى سطر جديد بدون استخدام أقواس. تجنب هذا الأمر لأنه يسبب بعض الأخطاء: if (n < 0) alert(`Power ${n} is not supported`); 3- سطر واحد بدون أقواس يُعد مقبولا في حال كان السطر قصيرًا: if (n < 0) alert(`Power ${n} is not supported`); 4- أفضل الطرق: if (n < 0) { alert(`Power ${n} is not supported`); } يمكن استخدام سطر واحد في حالة الشيفرات البرمجية المختصرة مثل: if (cond) return null. لكن استخدام شيفرة برمجية كتلية (كما في رقم 4) هو الأفضل من ناحية سهولة القراءة. طول السطر لا يحب أحدٌ قراءة سطر برمجي طويل. أصبح فصل الأسطر الطويلة ممارسة عامة لدى الجميع. إليك المثال التالي: // تسمح أقواس الفاصلة العليا المائلة ` بتقسيم النص إلى عدة أسطر let str = ` Ecma International's TC39 is a group of JavaScript developers, implementers, academics, and more, collaborating with the community to maintain and evolve the definition of JavaScript. `; بالنسبة للتعليمة البرمجية if: if ( id === 123 && moonPhase === 'Waning Gibbous' && zodiacSign === 'Libra' ) { letTheSorceryBegin(); } يتم الاتفاق على الحد الأقصى لطول السطر البرمجي على مستوى فريق العمل. يكون طول السطر البرمجي غالبا 80 أو 120 محرفَا. مسافة البادئة يوجد نوعان من البادئة "Indents": البادئة الأفقية: 2 أو 4 مسافات: تُصنع البادئة الأفقية بوضع 2 أو 4 مسافات أو باستخدام رمز البادئة الأفقية (الزر Tab). وُجدت اختلافات قديمة حول أيهما أفضل، لكن المسافات هي الأكثر شيوعا هذه الأيام. تتميز المسافات عن الزر Tab بكونها أكثر مرونة أثناء التعديل. مثلًا، يمكننا إزاحة المتغيرات داخل قوس مفتوح كالتالي: show(parameters, aligned, // 5 spaces padding at the left one, after, another ) { // ... } البادئة العمودية: الأسطر الفارغة لفصل الشيفرات البرمجية إلى أجزاء منطقية: يمكن تقسيم حتى دالة بحد ذاتها إلى أجزاء منطقية لتسهل قرائتها. في المثال أدناه، تم تقسيم تعريف المتغيرات وحلقة التكرار الرئيسية والنتيجة عموديا: function pow(x, n) { let result = 1; // <-- for (let i = 0; i < n; i++) { result *= x; } // <-- return result; } يمكنك وضع سطر فارغ حيثما تريد لجعل الشيفرة البرمجية مقروءة بسهولة. لا يجب أن يوجد أكثر من 9 أسطر بدون بادئة عمودية. الفواصل المنقوطة ";" يجب أن توضع فاصلة منقوطة بعد كل تعليمة برمجية، حتى وإن كان من الممكن عدم إضافتها. يوجد بعض اللغات حيث يكون استخدام الفاصلة المنقوطة اختياريا وتُستخدم نادرا. لكن في بعض الحالات في JavaScript لا يحل السطر الجديد محل الفاصلة المنقوطة مما يجعل الشيفرة البرمجية عرضة للخطأ. يمكنك الاطلاع أكثر عن ذلك في جزء بنية الشيفرة البرمجية. إن كنت مبرمجًا متمرسًا في JavaScriptK، يمكنك اختيار نمط تكويد بدون فاصلة منقوطة مثل StandardJS. أو يُفَضَّل استخدام فواصل منقوطة لتجنب الأخطاء. أغلب المبرمجين يضعون فواصل منقوطة. مستويات التداخل تجنب تداخل الشيفرة البرمجية للعديد من المستويات. مثلًا، في الحلقة المتكررة يُفَضَّل استخدام التعليمة continue لتجنب التداخل العميق. مثلا، بدلا من إضافة if شرطية داخلية كالتالي: for (let i = 0; i < 10; i++) { if (cond) { ... // <- مستوى تشعيب إضافي } } يمكننا كتابة: for (let i = 0; i < 10; i++) { if (!cond) continue; ... // <- لا مزيد من التشعيبات } يمكن استخدام الأسلوب ذاته مع if/else و return. مثلًا، نحتاج لجزئين في المثال أدناه. خيار 1: function pow(x, n) { if (n < 0) { alert("Negative 'n' not supported"); } else { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } } خيار 2: function pow(x, n) { if (n < 0) { alert("Negative 'n' not supported"); return; } let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } يُعد الخيار 2 أسهل قراءةً من الخيار 1 لأن الحالة الخاصة n < 0 فُحصت مسبقًا. بعد فحص قيمتها يمكننا الانتقال إلى جزء الشيفرة البرمجية الرئيسية بدون الحاجة لتداخل أكثر. موضِع الدوال في حال كنت تكتب العديد من الدوال المساعدة والتي تستخدمها الشيفرة البرمجية، فإن هناك 3 طرق لتنظيم هذه الدوال. 1- تعريف الدوال أعلى الشيفرة البرمجية التي تستخدمها: // تعريفات الدوال function createElement() { ... } function setHandler(elem) { ... } function walkAround() { ... } // الشيفرة التي تستخدمها let elem = createElement(); setHandler(elem); walkAround(); 2- الشيفرة البرمجية أولا ثم الدوال: // الشيفرة التي تستخدم الدوال let elem = createElement(); setHandler(elem); walkAround(); // --- دوال مساعدة --- function createElement() { ... } function setHandler(elem) { ... } function walkAround() { ... } 3- الطريقة المختلطة: تُعرَّف الدالة في أول مكان تستخدم فيه. الخيار الثاني هو الأفضل غالبا. ذلك لأنه عند قراءة شيفرة برمجية، فإننا نريد معرفة ما تقوم به أولا. إن كانت الشيفرة البرمجية في البداية فإن غرضها يكون واضحا مباشرة. أيضا، قد لا نحتاج لقراءة الدوال، خاصة إن كانت وظائفها واضحة من مسمياتها. شروحات لأنماط كتابة الشيفرة يحتوي دليل أنماط التكويد قواعد عامة حول كيفية كتابة شيفرة برمجية. مثلًا، الأقواس المستخدمة، وعدد مسافات البادئة، وأقصى طول للسطر، …الخ. والعديد من التفاصيل الدقيقة. عندما يستخدم جميع أعضاء الفريق الدليل نفسه لنمط التكويد، ستبدو الشيفرة البرمجية موحدة بغض النظر عمن قام بكتابتها. يمكن لأي الفريق الكتابة بنمط تكويد خاص بهم، لكن لا حاجة لذلك لوجود العديد من المعايير العالمية للاختيار منها. بعض الخيارات الشهيرة: Google JavaScript Style Guide Airbnb JavaScript Style Guide Idiomatic.JS StandardJS وغيرها في حال كنت مطورًا مبتدئًا، ابدأ بالشرح المزود هنا. ثم يمكنك الانتقال إلى دليل آخر مما ذكرناه لتتعرف على المزيد من التفاصيل وتختار الأسلوب الأنسب لك. منقحات الصياغة التلقائية (Automated Linters) منقحات الصياغة (Linters): هي عبارة عن أدوات يمكنها فحص نمط الشيفرة البرمجية تلقائيا واقتراح تعديلات لتحسينها. الأمر الذي يجعلها مفيدة أكثر هو قدرتها على العثور على بعض الأخطاء، مثل الخطأ باسم متغير أو دالة ما، لهذا فإنه من المستحسن استخدام منقح صياغة (Linter) حتى لو لم تُرد اتباع نمط تكويد معين. هنا بعض أدوات تنقيح الصياغة المعروفة مثل: JSLint – تُعد من أدوات تنقيح الصياغة الأولى. JSHint – تحوي اعدادات أكثر من JSLint. ESLint – الأحدث تقريبا. كلها تؤدي الغرض ذاته. الكاتب هنا يستخدم ESLint. معظم هذه الأدوات تكون مدمجة مع العديد من المحررات الشهيرة: يجب عليك أن تُفَعِّل الإضافة في المحرر وتحدد نمط التكويد الذي تريده. مثلا، لاستخدام ESLint اتَّبع ما يلي: ثبت Node.js. ثبت ESLint باستخدام الأمر npm install -g eslint (يُعد npm مُثَبِّت حزم JavaScript). أنشئ ملف إعداد وسمِّه "‎.eslintrc" في ملف مشروع JavaScript الرئيسي (الملف الذي يحتوي على جميع الملفات). ثبت/فعِّل الإضافة لمحررك الذي يدعم ESLint. معظم المحررات تدعم ESLint. هنا مثال على ملف "‎.eslintrc": { "extends": "eslint:recommended", "env": { "browser": true, "node": true, "es6": true }, "rules": { "no-console": 0, "indent": ["warning", 2] } } التعليمة "extends" هنا تعني أن الإعداد يعتمد على مجموعة إعدادات ESLint الافتراضية "eslint:recommended". يُمكننا تعديل الإعدادات التي نريدها لاحقًا. يمكن أيضا تنزيل مجموعة قواعد نمط التكويد وتوسيعها بدلا من ذلك. انظر في الرابط http://eslint.org/docs/user-guide/getting-started للمزيد من التفاصيل حول كيفية التثبيت. تحتوي بعض المحررات على منقح صياغة مدمج فيها إلا أنها ليست بدقة ESLint. الخلاصة تهدف جميع قواعد بناء الجمل في هذا الفصل (وفي باقي مراجع أنماط التكويد) لرفع مستوى سهولة قراءة الأكواد. وجميع القواعد قابلة للنقاش. عند التفكير في كتابة شيفرة برمجية بشكل أفضل، يجب أن نسأل أنفسنا: "ما الذي يجعل الشيفرة البرمجية أسهل للقراءة والفهم؟" و "ما الذي قد يساعدنا لتجنب الأخطاء؟" يوجد العديد من الأشياء التي يجب الانتباه لها أثناء اختيار نمط تكويد معين. سيتيح لك قراءة العديد من أنماط التكويد معرفة أحدث الأفكار عن أنماط التكويد وأفضل الممارسات. تمارين نمط تكويد سيء الأهمية: 4 ما الخطأ في نمط التكويد أدناه؟ function pow(x,n) { let result=1; for(let i=0;i<n;i++) {result*=x;} return result; } let x=prompt("x?",''), n=prompt("n?",'') if (n<=0) { alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else { alert(pow(x,n)) } قم بإصلاحه. الحل يمكنك ملاحظة ما يلي: function pow(x,n) // <- لا يوجد مسافات بين المُعاملات { // <- قوس الفتح في سطر مستقل let result=1; // <- عدم وجود مسافات قبل أو بعد = for(let i=0;i<n;i++) {result*=x;} // <- لا يوجد مسافات // يجب أن يكون محتوى { ... } في سطر جديد return result; } let x=prompt("x?",''), n=prompt("n?",'') // <-- ممكنة تقنيا، // لكن يُفَضَّل جعلها في سطرين، بالإضافة إلى عدم وجود مسافات وعدم وجود ; // if (n<0) // <- لا يوجد مسافات بين (n < 0), ويجب وجود سطر فارغ قبلها { // <- قوس الفتح في سطر مستقل // يمكن فصل الأسطر الطويلة في الأسفل حتى تصبح سهلة القراءة alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else // <- يمكن كتابتها في سطر واحد هكذا: "} else {" { alert(pow(x,n)) // لا يوجد مسافات ولا يوجد ; } بعد تصحيح الأخطاء تصبح الشيفرة البرمجية كما يلي: function pow(x, n) { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } let x = prompt("x?", ""); let n = prompt("n?", ""); if (n < 0) { alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else { alert( pow(x, n) ); } ترجمة -وبتصرف- للفصل Coding Style من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: التعليقات المقال السابق: تنقيح الأخطاء في Chrome
  12. قبل كتابة شيفرات برمجية أكثر تعقيدا، لنتطرَّق إلى تنقيح الأخطاء. تنقيح الأخطاء هي عملية إيجاد الأخطاء في السكريبت وإصلاحها. تدعم جميع المتصفحات الحديثة وبعض البيئات الأخرى "تنقيح الأخطاء" -واجهة مستخدم خاصة في أدوات المطور والتي تجعل العثور على الأخطاء وتصحيحها أمرا سهلا. تُتيح هذه الواجهة أيضا تَتَبُّع الأكواد خطوة بخطوة لمعرفة ما يحدث فيها بالتفصيل. سنستخدم Chrome لأن لديه ميزات كافية لذلك، كما تتوفر هذه الميزات في معظم المتصفحات الأخرى. جزء الموارد "sources" قد يبدو إصدار Chrome لديك مختلفًا بعض الشيء، إلا أن المحتوى هو ذاته. افتح صفحة example في Chrome. شغِّل أدوات المطور بالضغط على F12 (على أجهزة Mac، استعمل الاختصار Cmd+Opt+I). اختر الجزء sources. إن كانت هذه هي مرتك الأولى للدخول إلى جزء sources، فهذا ما ستراه: يفتح هذا الزر علامة تبويب تحوي الملفات. اضغط عليها واختر hello.js من العرض الشجري "tree view". يجب أن ترى التالي: هنا يمكننا رؤية ثلاث مناطق: منطقة الموارد "Resources zone" والتي تعرض ملفات HTML، و JavaScritp، و CSS، وغيرها من الملفات بما في ذلك الصور المُلحقة بالصفحة. قد تظهر إضافات Chrome هنا أيضًا. منطقة المصدَر "Source zone" والتي تعرض الشيفرة البرمجية المصدرية. منطقة المعلومات والتحكم "Information and control zone" لتنقيح الأخطاء، سَنكتشفها أكثر فيما يلي يمكنك الضغط على الزر مُجددًا لِإخفاء قائمة الموارد وإعطاء الشيفرة البرمجية مساحة كافية. شاشة التحكم (Console) تظهر شاشة تحكم عند الضغط على Esc. يمكن كتابة الأوامر في شاشة التحكم ثم الضغط على Enter لتنفيذها. تظهر مخرجات تنفيذ أمر ما أسفله مباشرة في شاشة التحكم. مثال: كما في الصورة بالأسفل 1+2 ينتج 3، بينما hello("debugger")‎ لا يُظهِر أي نتائج، لذلك فإننا نرى undefined: نقاط التوقف (Breakpoints) لنختبر ما يحدُث أثناء تنفيذ الشيفرة البرمجية في صفحة example. في الصفحة hello.js، اضغط على السطر رقم 4؛ نضغط على الرقم ذاته وليس السطر. هكذا تكون قد أنشأت نقطة توقف. اضغط على الرقم 8 أيضًا. يجب أن يبدو الشكل كما في الصورة التالية: نقطة التوقف هي نقطة يتوقف فيها مصحح الأخطاء عن تنفيذ JavaScript تلقائيًا. يمكننا فحص المتغيرات الحالية وتنفيذ الأوامر أو أي شيء آخر في لوحة التحكم أثناء توقف عمل الشيفرة البرمجية. أي أنه يمكننا تتبع الشيفرة البرمجية عند نقطة معينة عبر ايقافها والتأكد من أي شيء فيها كما نريد. يمكننا رؤية قائمة بالعديد من نقاط التوقف في الجزء الأيمن من الشاشة. يكون الأمر مفيدًا عند وجود عدة نقاط توقف في أكثر من ملف، وذلك يتيح لنا: التنقل بسرعة إلى نقاط التوقف في الشيفرة البرمجية (بالضغط عليها من الجزء الأيمن). إلغاء تفعيل نقاط التوقف مؤقتا بإلغاء تحديدها. حذف نقطة التوقف بالضغط عليها باليمين واختيار حذف "remove". وهكذا … نقاط التوقف الشرطية يتيح لك الضغط يمينَا على رقم السطر إنشاء نقطة توقف شرطية تُنَفَّذ عند تحقق الشرط المحدد فقط. يكون ذلك مفيدَا عندما تريد إيقاف التنفيذ لمعرفة قيمة أي متغير أو قيمة أي معامل في دالة. أمر Debugger يمكن أيضا إيقاف تنفيذ الشيفرة البرمجية بوضع الأمر debugger فيه كما يلي: function hello(name) { let phrase = `Hello, ${name}!`; debugger; // <-- يتوقف المنقح هنا say(phrase); } هذه الطريقة سهلة عندما نُعدِّل الشيفرة البرمجية باستخدم محرر الشيفرات البرمجية ولا نريد الانتقال إلى المتصفح وتشغيل السكربت في وضع أدوات المطور لإنشاء نقاط توقف. توقف وتحقق في المثال، يتم استدعاء الدالة hello()‎ أثناء تحميل الصفحة، لذلك فإن أسهل طريقة لتفعيل مُنقِّح الأخطاء (بعد إعداد نقاط التوقف) هي إعادة تحميل الصفحة. اضغط على F5 (لمستخدمي ويندوز أو لينكس)، أو اضغط على Cmd+R (لمستخدمي Mac). سيتوقف تنفيذ الشيفرة البرمجية في السطر الرابع حيث تم إنشاء نقطة التوقف: افتح قوائم المعلومات المنسدلة على اليمين (موضحة بأسهم). تتيح هذه القوائم التحقق من حالة السكريبت الحالية: 1- Watch - تعرض القيم الحالية لأي تعابير. يمكنك الضغط على + وإدخال أي تعبير تريده. سيعرض المعالج قيمته في أي وقت وحساب قيمته تلقائيا أثناء التنفيذ. 2- Call Stack - تعرض سلسلة الاستدعاءات المتداخلة. في الوقت الحالي، المعالج وصل حتى استدعاء الدالة hello()‎، المُستدعاة من خلال السكريبت index.html (لا يوجد دوال أخرى لذلك سُمِّيَت "anonymous"). إن ضَغَطت على عنصر من الحزمة (stack) مثلا "anonymous"، فسينتقل المعالج مباشرة إلى الشيفرة البرمجية المُمَثِّل لهذا العنصر وستتمكن من فحص جميع متغيراته أيضا. 3- Scope - تعرض المتغيرات الحالية. Local تعرض متغيرات الدالة المحلية. يمكنك أيضا رؤية قيم هذه المتغيرات موضحة على اليمين. Global تعرض المتغيرات الخارجية (خارج نطاق أي دالة). يوجد أيضا الكلمة المفتاحية this والتي لم تُشرح بعد، لكن سيتم شرحها قريبا. تتبع التنفيذ يوجد بعض الأزرار لِتتبع التنفيذ أعلى يمين اللوحة. لِنتطرق إليها. - زر استمرار التنفيذ F8. يستأنف التنفيذ. إن لم توجد أي نقاط توقف، فإن التنفيذ سيستمر بعد الضغط على هذا الزر وسَيفقد مصحح الأخطاء السيطرة على السكريبت. هذا ما سنراه بعد الضغط عليه: تم استئناف التنفيذ، ووصل لنقطة توقف أخرى داخل الدالة say()‎ وتوقف هناك. انظر في Call stack على اليمين. تم تنفيذ استدعاء آخر. وصل التنفيذ الآن حتى الدالة say()‎. - نفِّذ خطوة (نفّذ الأمر التالي)، لكن لا تنتقل إلى الدالة الأخرى، الوصول السريع F10. سيظهر alert إذا ضغطنا على هذا الزر الآن. الأمر المهم هنا هو أن alert قد يحوي على أي دالة، لذلك فإن التنفيذ سيتخطاها. - نفِّذ خطوة، الوصول السريع F11. يقوم بنفس آلية عمل الأمر السابق بالإضافة إلى تنفيذ الدوال المتداخلة. سَتتنفذ أوامر السكريبت بالضغط عليه خطوة بخطوة. - استمر بالتنفيذ حتى نهاية الدالة الحالية، باستخدام الزر Shift+F11. سيتوقف التنفيذ عند آخر سطر للدالة الحالية. يكون هذا الأمر مفيدا عند الدخول إلى استدعاء دالة مصادفةَ باستخدام الزر ، لكن تتبع هذه الدالة ليس أمرا مهما، لذلك نقوم بتخطي تتبعها. - تفعيل/تعطيل جميع نقاط التوقف. لا يقوم هذا الزر بمتابعة التنفيذ، إنما يُفَعِّل/يُعطِّل كمَا كبيرا من نقاط التوقف. - تفعيل/تعطيل التوقف التلقائي في حال حدوث خطأ. عند تفعيله في وضع أدوات المطور، فإن الشيفرة البرمجية تتوقف عن التنفيذ تلقائيا عند أي خطأ في السكريبت. ثم يمكننا تحليل المتغيرات لمعرفة سبب الخطأ. لذلك، إن أوقف خطأ مَا متابعة تنفيذ الشيفرة البرمجية، يمكننا فتح مصحح الأخطاء وتفعيل هذا الخيار وإعادة تحميل الصفحة لرؤية مكان توقف الشيفرة البرمجية وما هو المحتوى عند تلك النقطة. الاستمرار حتى هنا "Continue to here" الضغط يمينا على سطر من الشيفرة البرمجية يفتح قائمة السياق المحتوية على خيار مفيد يُدعى "Continue to here". يكون هذا الخيار مُفيدا عندما نريد التقدم بضع خطوات للأمام بدون وضع نقطة توقف. التسجيل (Logging) يمكن استخدام الدالة console.log لِعرض شيء على الشاشة كمُخرج من الشيفرة البرمجية. مثلا، يعرض الأمر التالي القيم من 0 حتى 4 إلى الشاشة: // open console to see for (let i = 0; i < 5; i++) { console.log("значение", i); } لا يرى المستخدم العادي هذه المخرجات. لرؤيتها، افتح علامة التبويب Console أو اضغط على Esc إن كنت في علامة تبويب أخرى هكذا تُفتًح الشاشة في الأسفل. إن كانت الشيفرة البرمجية تحتوي على أوامر console.log، فسنرى ما يحدث من خلال سجلات التتبع بدون الدخول إلى المُصحح. الخلاصة كما رأينا، فإن هناك ثلاث طرائق رئيسية لإيقاف السكريبت: باستخدام نقطة توقف. الأمر debugger. وجود خطأ (في حال كانت أداوت المطور مفتوحة وكان الزر مُفَعًّلَأ). عند توقف السكريبت، يمكننا فحص المتغيرات وتتبع الشيفرة البرمجية لرؤية أي أخطاء. يوجد العديد من الخيارات الأخرى في أدوات المطور أكثر مما تم شرحه سابقا. تجد الدليل كاملا على https://developers.google.com/web/tools/chrome-devtools. تُعَد المعلومات التي وُضِعَت كافية لبدء تتبع أي شيفرة برمجية وتنقيحها، لكنك ستحتاج للاطلاع على الدليل لاحقا لتعلم ميزات متقدمة في أدوات المطور، خاصة إن كنت تتعامل كثيرا مع المتصفح. يمكنك الضغط على عدة أماكن في أدوات المطور ورؤية ما يحدث. تُعد هذه أفضل طريقة لتعلم أدوات المطور. لا تنسَ الضغط يمينا وتجريب قوائم السياق. ترجمة -وبتصرف- للفصل Debugging in Chrome من كتاب 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; } code { background-color: rgb(250, 250, 250); border-radius: 3px; } اقرأ أيضًا المقال التالي: نمط كتابة الشيفرة المقال السابق: مراجعة لما سبق
  13. حزمة LEMP هي مجموعة من البرامج التي يمكن استخدامها لتٌقدّم صفحات الويب الديناميكية وتطبيقات الويب. وهو عبارة عن اختصار يصف نظام تشغيل لينكس مع خادم Nginx (يُنطق "Engine-X"). البيانات الخلفية تكون مخزنة في قاعدة بيانات MySQL وتقوم PHP بالمعالجة الديناميكية. يشرح هذا المقال كيفية تثبيت حزمة LEMP على خادم أوبونتو 18.04. يتكفل نظام أوبونتو بالمتطلب الأول. سنشرح هنا كيفية تجهيز باقي المكونات وتشغيلها. المتطلبات قبل البدء، تحتاج لمستخدم عادي غير مسؤول بصلاحيات sudo. يمكنك إعداد ذلك باتباع مقال الإعداد الأولي لخادم أوبونتو 18.04. عندما تنتهي من تجهيز المستخدم، يمكنك بدء الخطوات التالية. خطوة 1 - تثبيت خادم الويب Nginx كي نتمكن من عرض صفحات الويب لزوار الموقع، سنثبِّت خادم الويب الحديث والفعال Nginx. جميع البرامج المستخدمة في هذا المقال متوفرة في مخزن حزم أوبونتو الافتراضي. ما يعني أنه يمكننا استخدام نظام إدارة الحزم apt لإنهاء التثبيتات الهامة. في أول مرة نَستخدم فيها apt في أي جلسة، نبدأ بتحديث محتوى الحزمة ثم نثبت الخادم: $ sudo apt update $ sudo apt install nginx Nginx مُعد كي يعمل تلقائيا بعد التثبيت على أوبونتو 18.04. إن كان جدار حماية ufw يعمل لديك كما تم الشرح في مقال الإعداد الأولي، فستحتاج للسماح باتصالات Nginx. يُسجل Nginx في ufw تلقائيا خلال التثبيت ما يجعل العملية أسهل. يُستحسن السماح للمنفذ الذي سيتيح لحركة المرور بالوصول مع أعلى قيود ممكنة. لأننا لم نقم بإعداد SSL بعد، فستحتاج لإتاحة حركة المرور في المنفذ 80 فقط. لتفعيل ذلك: $ sudo ufw allow 'Nginx HTTP' للتأكد من التغييرات: $ sudo ufw status سَتعرض مخرجات هذا الأمر أن حركة المرور خلال HTTP متاحة: المخرجات: Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere Nginx HTTP ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) Nginx HTTP (v6) ALLOW Anywhere (v6) بعد إضافة هذا الإعداد إلى جدار الحماية يمكنك اختبار إن كان الخادم يعمل عبر الدخول إلى اسم نطاق الخادم أو عنوان بروتوكول الإنترنت العام للخادم عبر المتصفح. إن كنت لا تملك اسم نطاق محدد للخادم وكنت لا تعلم عنوان بروتوكول الإنترنت العام للخادم يمكنك إيجاده من خلال تنفيذ لأمر: $ ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//' هذا الأمر سيزودك بمجموعة من عناوين بروتوكول الإنترنت، يمكنك تجريبها على متصفحك. كطريقة بديلة، يمكنك التحقق إن كان عنوان بروتوكول الإنترنت متاح من مكان آخر على الإنترنت: $ curl -4 icanhazip.com أدخِل عنوان بروتوكول الإنترنت الذي حصلت عليه في متصفحك، وستظهر لك صفحة Nginx الرئيسية: http://server_domain_or_IP (صورة) إن رأيت الصفحة التي في الأعلى، فإن Nginx مُثبّت بنجاح. خطوة 2 - تثبيت MySQL لإدارة بيانات الموقع الآن وبما أنه أصبح لديك خادم ويب، ستحتاج لِتثبيت MySQL (نظام إدارة قواعد بيانات) وإدارة بيانات الموقع. ثبّت MySQL باستخدام الأمر: $ sudo apt install mysql-server تم تثبيت قاعدة بيانات MySQL لكن إعدادها لم يكتمل بعد. لتأمين التثبيت، تُرفق MySQL بسكربت يسألك إن كنت تريد تغيير الإعدادات الافتراضية الغير آمنة. لبدء السكربت: $ sudo mysql_secure_installation سيسألك السكربت إن كنت تريد إعداد VALIDATE PASSWORD PLUGIN. تحذير: يجب أن تفهم هذه الميزة قبل تفعيلها. فعند تفعيل هذه الميزة، سيرفض MySQL أي كلمة مرور لا تطابق معاييره المحددة وسيظهر خطأ. مما يتسبب في بعض المشاكل في حال كنت تستخدم كلمة مرور ضعيفة بالإضافة إلى برنامج يُعِد بيانات مستخدمي MySQL تلقائيا مثل حزمة phpMyAdmin. يُفضل عدم تفعيل هذا الأمر لتفادي مثل هذه الأخطاء. لكن، يُفضَّل أن تستخدم كلمات مرور قوية وفريدة لمستخدمي قواعد البيانات دائما. أجب Y للتفعيل أو أي حرف آخر للاستمرار بدون تفعيل الإضافة. VALIDATE PASSWORD PLUGIN can be used to test passwords and improve security. It checks the strength of password and allows the users to set only those passwords which are secure enough. Would you like to setup VALIDATE PASSWORD plugin? Press y|Y for Yes, any other key for No: في حال اخترت تفعيل التحقق، فَسَيسألك السكربت لاختيار مستوى تحقق كلمة المرور. تذكر أنه إن اخترت 2 -أقوى مستوى - فسترى بعض الأخطاء عند إدخال كلمة مرور لا تحتوي على أرقام، أحرف كبيرة وصغيرة، ورموز أو أي كلمة اعتيادية من قاموس اللغة. There are three levels of password validation policy: LOW Length >= 8 MEDIUM Length >= 8, numeric, mixed case, and special characters STRONG Length >= 8, numeric, mixed case, special characters and dictionary file Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 1 ثم سيُطلب منك إدخال وتأكيد كلمة مرور مستخدم مسؤول: Please set the password for root here. New password: Re-enter new password: لباقي الأسئلة قم بالضغط على Y ثم ENTER في كل شاشة. بهذه الطريقة، سيتم حذف بعض المستخدمين المجهولين، وفحص قاعدة البيانات، وتعطيل تسجيل دخول المستخدمين المسؤولين عن بعد، ويعيد تحميل الإعدادات الجديدة كي يقوم MySQL بتطبيقها مبشرة. لاحظ أنه في اصدارات MySQL 5.7 وما يليها على أوبونتو يُعد المستخدم المسؤول للمصادقة باستخدام إضافة auth_socket افتراضيا بدلا من التصديق باستخدام كلمة مرور. هذه الميزة توفر أمانًا أكبر وسهولة استخدام في حالات متعددة، لكنها قد تكون معقدة عندما تريد السماح لبرامج خارجية مثل (phpMyAdmin) بالوصول للمستخدم. إن كان استخدام إضافة auth_socket للوصول إلى MySQL يتناسب مع طبيعة عملك يمكنك الاستمرار بالخطوة 3. لكن إن كنت تفضل استخدام كلمة المرور للاتصال ب MySQL كمستخدم مسؤول فستحتاج لتغيير طريقة مصادقته من auth_socket إلى mysql_native_password. للقيام بذلك، افتح شاشة اوامر MySQL: $ sudo mysql ثم قم بالتحقق من طريقة المصادقة التي يستخدمها كل حساب من مستخدمي MySQL باستخدام الأمر: mysql> SELECT user,authentication_string,plugin,host FROM mysql.user; المخرجات: +------------------+-------------------------------------------+-----------------------+-----------+ | user | authentication_string | plugin | host | +------------------+-------------------------------------------+-----------------------+-----------+ | root | | auth_socket | localhost | | mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | debian-sys-maint | *CC744277A401A7D25BE1CA89AFF17BF607F876FF | mysql_native_password | localhost | +------------------+-------------------------------------------+-----------------------+-----------+ 4 rows in set (0.00 sec) يمكنك ملاحظة أن المستخدم المسؤول يستخدم التصديق بإضافة auth_socket. لإعداد حساب المستخدم المسؤول للمصادقة بكلمة مرور، نفّذ أمر ALTER USER التالي. تأكد من استخدامك كلمة مرور قوية من اختيارك: mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password'; ثم نفّذ الأمر FLUSH PRIVILEGES والذي سيجعل الخادم يعيد جداوِل الصلاحيات حتى تعمل التغييرات التي أجريتها: mysql> FLUSH PRIVILEGES; تحقق من طرق التصديق لكل مستخدام مجددًا لتتأكد من أن المستخدم المسؤول لم يعد يستخدم المصادقة باضافة auth_socket: mysql> SELECT user,authentication_string,plugin,host FROM mysql.user; المخرجات: +------------------+-------------------------------------------+-----------------------+-----------+ | user | authentication_string | plugin | host | +------------------+-------------------------------------------+-----------------------+-----------+ | root | *3636DACC8616D997782ADD0839F92C1571D6D78F | mysql_native_password | localhost | | mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE | mysql_native_password | localhost | | debian-sys-maint | *CC744277A401A7D25BE1CA89AFF17BF607F876FF | mysql_native_password | localhost | +------------------+-------------------------------------------+-----------------------+-----------+ 4 rows in set (0.00 sec) يمكنك رؤية أن مستخدم MySQL المسؤول أصبح يستخدم المصادقة بكلمة المرور. بعد تأكدك من هذا على الخادم الخاص بك أغلق شاشة اوامر MySQL: mysql> exit ملاحظة: بعد تغييرك لطريقة مصادقة المستخدم المسؤول إلى كلمة مرور، فلن تستطيع الوصول إلى MySQL باستخدام الأمر sudo mysql المُستخدم سابقا، بدلا عن ذلك يجب أن تنفذ الأمر التالي: $ mysql -u root -p بعد ادخال كلمة المرور التي أعددتها قبل قليل، ستُفتح لك شاشة اوامر MySQL. هكذا يكون نظام قواعد البيانات جاهزًا ويمكنك الانتقال إلى خطوة تثبيت PHP. خطوة 3 - تثبيت PHP وإعداد Nginx لاستخدام معالج PHP أصبح لديك الآن Nginx لعرض صفحات الويب، و MySQL لتخزين وإدارة البيانات. لكن ليس لديك شيء يقوم بتوليد صفحات ويب ديناميكية. هنا يأتي دور PHP. لأن Nginx لا يحتوي على وحدة معالجة PHP مثل باقي خوادم الويب، ستحتاج لتثبيت php-fpm والتي تعني "fastCGI process manager". سنجعل Nginx يُمرر طلبات PHP إلى هذا البرنامج للمعالجة. ملاحظة: اعتمادًا على مزود الخدمة السحابية لديك، قد تحتاج لإضافة مخزن أوبونتو universe الذي يتضمن برامج مجانية ومفتوحة المصدر أنشئت بواسطة مجتمع أوبونتو؛ يمكنك القيام بذلك قبل تثبيت php-fp: $ sudo add-apt-repository universe ثبّت php-fpm مع الحزمة المساعدة php-mysql التي تسمح ل PHP بالتواصل مع قاعدة البيانات. سيجلب التثبيت ملفات PHP الرئيسية: $ sudo apt install php-fpm php-mysql الآن أصبح لديك جميع مكونات حزمة LEMP، لكن ما زلت تحتاج لتعديل بعض إعدادت التكوين حتى تجعل Nginx بتعامل مع معالج PHP لعرض المحتوى الديناميكي. يتم ذلك على مستوى أجزاء الخادم (أجزاء الخادم تشبه مُستضيفوا Apache الوهميون). للقيام بذلك، افتح ملف تكوين جزء خادم جديد في مجلد /etc/nginx/sites-available/. في هذا المثال، سنُسمي ملف جزء السيرفر example.com. يمكنك اختيار الاسم الذي تريده: $ sudo nano /etc/nginx/sites-available/example.com ستتمكن بتعديل ملف تكوين جزء خادم جديد بدلا من تعديل الجزء الافتراضي من استعادة الاعدادات الافتراضية في حال احتجتها. أضف المحتوى التالي المأخوذ من ملف تكوين جزء الخادم الرئيسي مع بعض التعديلات الطفيفة إلى ملف تكوين جزء الخادم الجديد: /etc/nginx/sites-available/example.com server { listen 80; root /var/www/html; index index.php index.html index.htm index.nginx-debian.html; server_name example.com; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; } location ~ /\.ht { deny all; } } هذا ما تقوم به التوجيهات وأجزاء التحديد: listen - تُعرف المنفذ الذي يستمع له Nginx، في هذه الحالة يستمع Nginx إلى المنفذ 80، المنفذ الافتراضي ل HTTP. root - يُعرّف المجلد الرئيسي حيث تُخزن ملفات الموقع. index - يقوم بإعداد Nginx كي يجعل الأولوية لعرض الملفات المسماه index.php - إن كانت متاحة عند طلب ملف index. server_name - تعرّف الجزء الذي يجب أن يُستخدم لطلب ما للخادم. حدد هذه التوجيهة إلى اسم نطاق الخادم أو عنوان بروتوكول الإنترنت للخادم. location /‎ - أول جزء تحديد يحتوي توجيهة try_files والتي تتحقق من وجود ملفات تطابق طلب عنوان URI. إن لم يجد Nginx الملف المناسب، يقوم بإرجاع خطأ 404. "location ~ \.php$‎" - جزء التحديد هذا يهتم بالمعالجة الفعلية ل PHP عبر توجيه Nginx إلى ملف تكوين fastcgi-php.conf وملف php7.2-fpm.sock الذي يُعرّف الحزمة المرتبطة ب php-fpm. location ~ /\.ht - آخر جزء تحديد والذي يتعامل مع ملف .htaccess الذي لا يقوم Nginx بمعالجته. بإضافة توجيهة deny all فإن ملفات "‎.htaccess" التي قد تتواجد في المجلد الرئيسي لن تُعرض للزائر. بعد إضافة هذا المحتوى، احفظ الملف واغلقه. فعّل جزء الخادم الجديد بإنشاء رابط رمزي من ملف تكوين جزء الخادم الجديد في المجلد /etc/nginx/sites-available/ إلى المجلد /etc/nginx/sites-enabled/: $ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ ثم ألغِ ربط ملف التكوين الرئيسي من مجلد /sites-enabled/: $ sudo unlink /etc/nginx/sites-enabled/default ملاحظة: إن احتجت لاستعادة الإعدادت الافتراضية، يمكنك ذلك بإعادة إنشاء الرابط الرمزي: $ sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/ افحص ملف التكوين الجديد من الأخطاء الإملائية : $ sudo nginx -t إن ظهرت أي أخطاء عد وتحقق من الملف قبل الاستمرار. عند انتهائك أعد تشغيل Nginx كي تُطبّق التغييرات: $ sudo systemctl reload nginx وبهذا ننتهي من تثبيت وإعداد حزمة LEMP. لكن ما زلنا لم نتأكد ما إن كانت جميع المكونات تتواصل ببعضها البعض. خطوة 4 - انشاء ملف PHP للتحقق من الاعدادات الآن، يجب أن تكون حزمة LEMP مُعدة بشكل تام. يمكنك اختبار ذلك للتحقق من أن Nginx يعالج ملفات "‎.php" من معالج PHP بطريقة صحيحة. للقيام بذلك استخدم محرر النصوص لإنشاء ملف PHP تجريبي وسمّه info.php في المجلد الرئيسي. $ sudo nano /var/www/html/info.php أدخل الأسطر التالية إلى الملف. هذا الكود هو كود PHP صحيح والذي سيعرض معلومات عن الخادم: الملف ‎/var/www/html/info.php: <?php phpinfo(); عند انتهائك احفظ الملف واغلقه. يمكن الآن زيارة هذه الصفحة من متصفحك بزيارة اسم نطاق الخادم أو عنوان بروتوكول الإنترنت العام للخادم متبوعا ب /info.php: http://your_server_domain_or_IP/info.php يجب أن ترى صفحة ويب تم توليدها باستخدام PHP تحتوي معلومات الخادم: (صورة) إن رأيت صفحة تشبه الصورة السابقة، فإن إعدادك لمعالج PHP مع Nginx صحيح. بعد التحقق من أن Nginx يعالج الصفحات بصورة صحيحة، يُفضل أن تحذف الملف الذي أنشاته لأنه قد يعطي المستخدمين المخولين بالوصول بعض المعلومات عن إعداد الخادم مما قد يساعدهم في محاولة اقتحامه. يمكنك توليد هذا الملف عند احتياجك له. قم بحذف الملف: $ sudo rm /var/www/html/info.php وبهذا تكون قد أعددت وشغّلت حزمة LEMP على خادم أوبونتو 18.04. الخلاصة تعد حزمة LEMP هيكل قوي يمكنك من بناء وتشغيل أي موقع أو تطبيق ويب تقريبا من الخادم الخاص بك. يوجد العديد من الخطوات التي يمكنك القيام بها بعد هذه الخطوة. مثلا، يجب أن تتأكد من أن الاتصالات إلى الخادم آمنة. ختاما، يمكنك تأمين تثبيت Nginx باستخدام تشفير Let's. باتباع هذا المقال، سوف تحصل على شهادة TLS/SSL للخادم الخاص بك كي يخدم المحتوى عبر HTTPS. ترجمة -وبتصرف- للمقال How To Install Linux, Nginx, MySQL, PHP (LEMP stack) on Ubuntu 18.04 لأصحابه الكتاب Mark Drake و Justin Ellingwood.
  14. MongoDB عبارة عن قاعدة بيانات NoSQL مستندات مجانية ومفتوحة المصدر تُستخدم غالبا في تطبيقات الويب الحديثة. في هذا المقال سنُثبِّت MongnDB، ونُدير خدماتها، ونحميها، ونُفعل الوصول البعيد لها. المتطلبات تحتاج لتطبيق هذا الدرس إلى خادم أوبونتو مُعَد باتباع مقال الإعداد الأولي، ويحتوي على مستخدم sudo غير مسؤول وجدار حماية. الجزء الأول: تثبيت MongoDB خطوة 1 - تثبيت MongoDB حزمة مخزن أوبونتو الرسمي يحتوي على نسخة مُحدثه من MongoDB، ما يعني أنه بإمكاننا تثبيت حزماتِه المهمة باستخدام apt. بداية حدِّث قائمة الحُزم كي تحصل على أحدث الاصدارات: $ sudo apt update الآن ثبّت حزمة MongoDB: $ sudo apt install -y mongodb يُثبّت هذا الأمر عدة حُزم تحتوي على أحدث إصدار من MongoDB بالاضافة إلى أدوات إدارة مساعدة لخادم MongoDB. يبدأ خادم قاعدة البيانات بالعمل تلقائيا بعد التثبيت. تاليًا، نتحقق ما إن كان الخادم بعمل بصورة صحيحة. خطوة 2 - التحقق من الخدمة وقاعدة البيانات قامت عملية تثبيت MongoDB بتشغيله تلقائيا، لكن لنتأكد من بدء الخدمة ومن صحة عمل قاعدة البيانات. أولا تحقق من حالة الخدمة: $ sudo systemctl status mongodb سترى هذه المخرجات: ● mongodb.service - An object/document-oriented database Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2018-05-26 07:48:04 UTC; 2min 17s ago Docs: man:mongod(1) Main PID: 2312 (mongod) Tasks: 23 (limit: 1153) CGroup: /system.slice/mongodb.service └─2312 /usr/bin/mongod --unixSocketPrefix=/run/mongodb --config /etc/mongodb.conf يصرِّح systemd أنَّ خادم MongoDB يعمل. يمكننا التحقق أكثر عن طريق الاتصال بخادم قاعدة البيانات وتنفيذ أمر للفحص .نفّذ الأمر التالي: $ mongo --eval 'db.runCommand({ connectionStatus: 1 })' المخرجات: MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.6.3 { "authInfo" : { "authenticatedUsers" : [ ], "authenticatedUserRoles" : [ ] }, "ok" : 1 } القيمة 1 للحقل ok تعني أن الخادم يعمل بطريقة صحيحة. بعد ذلك، سنتطرق إلى كيفية إدارة حالة الخادم. خطوة 3 - إدارة خدمة MongoDB تُثبت MongoDB كخدمة ضمن النظام، ما يعني أنه يُمكنك إدارتها باستخدام أوامر systemd القياسية مثل باقي خدمات النظام على أوبونتو. لتتأكد من حالة الخدمة نفذ الأمر: $ sudo systemctl status mongodb يمكنك إيقاف الخادم في أي وقت من خلال الأمر: $ sudo systemctl stop mongodb لبدء الخادم عندما يكون متوقفًا عن العمل: $ sudo systemctl start mongodb يمكن إعادة تشغيل الخادم باستخدام أمر واحد: $ sudo systemctl restart mongodb MongoDB مُعد لِلبدء تلقائيا مع بدء الخادم، لإيقاف البدء التلقائي: $ sudo systemctl disable mongodb لتفعيل الميزة مجددا: $ sudo systemctl enable mongodb تاليًا، إعداد جدار الحماية لعملية تثبيت MongoDB. خطوة 4 - إعداد جار الحماية (اختياري) بافتراض أنك اتبعت مقال الإعداد الأولي لخادم أوبونتو لِتفعيل جدار الحماية، فإن خادم MongoDB لا يمكن أن يُوصل من خلال الإنترنت. في حال كنت تنوي استخدام خادم MongoDB محليا مع التطبيقات على نفس الخادم فقط فهذا الإعداد هو الأمثل. لكن إن كنت تريد أن تتصل بخادم قاعدة بيانات MongoDB من خلال الانترنت فيجب أن تسمح للاتصالات الخارجية في ufw. للسماح بالوصول من أي مكان إلى MongoDB على المنفذ الافتراضي 27017 يمكنك استخدام sudo ufw allow 27017. لكن تفعيل وصول الإنترنت إلى خادم MongoDB في التثبيت الافتراضي يعطي دخول غير محدود بضوابط لِخادم قاعدة البيانات وبياناته. في معظم الحالات يجب الوصول إلى MongoDB من أماكن موثوقة معينة فقط، مثل خادم آخر يستضيف تطبيقا مًا. للقيام بذلك، يمكنك السماح بالوصول إلى منفذ MongoDB الافتراضي مع تحديد عنوان بروتوكول الإنترنت للخادم الآخر الذي سيتمكن من الوصول إلى خادم MongoDB مباشرة: $ sudo ufw allow from your_other_server_ip/32 to any port 27017 يمكنك التحقق من التغييرات في جدار الحماية باستخدام ufw: $ sudo ufw status يجب أن ترى أن حركة المرور للمنفذ 27017 متاحة في المخرجات "output": Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 27017 ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) 27017 (v6) ALLOW Anywhere (v6) إن كنت قد قمت بتحديد عنوان بروتوكول إنترنت معين للاتصال بخادم MongoDB، فستراه بدلا من كلمة Anywhere في المخرجات. للمزيد من إعدادات جدار الحماية المتقدمة لحد الوصول إلى خدمات UFW الأساسية: قواعد وأوامر جدار الحماية الشائعة. على الرغم من أنَّ المنفذ مفتوح، إلا أن MongoDB يسمع فقط للعنوان المحلي 127.0.0.1. للسماح بالوصول من بعد، أضف عنوان بروتوكول الخادم العام و القابل للتوجيه إلى ملف mongod.conf. افتح ملف ضبط MongoDB: $ sudo nano /etc/mongodb.conf أضف عنوان بروتوكول الانترنت الخاص بالخادم إلى قيمة bindIP: ... logappend=true bind_ip = 127.0.0.1,your_server_ip #port = 27017 ... لا تنسَ وضع فاصلة بين عناوين بروتوكول الإنترنت الموجودة والعنوان الذي أضفته الآن. احفظ الملف واغلقه، ثم أعد تشغيل MongoDB: $ sudo systemctl restart mongodb الجزء الثاني: تأمين MongoDB كانت الاصدارات السابقة ل MongoDB مُعرَّضة للاختراق لأنَّها لا تتطلب أي مصادقة للتفاعل مع قاعدة البيانات. يمكن لأي مستخدم إنشاء وحذف قواعد بيانات وقراءة محتواها والكتابة فيها تلقائيا. أعدَّت أيضا هذه الاصدارات برنامج MongoDB خفي يسمع لجميع الواجهات تلقائيًا، مما يعني أن السكربتات التلقائية يمكن أن تكتشف حالات MongoDB الغير محمية بجدار حماية، وإن لم تكُن المصادقة مفعلة، يمكن لهذه السكربتات الحصول على وصول كامل إلى MongoDB. خُفِّفَت الحالة في الاصدار ‎3.x وبعض الاصدارات السابقة المُقدَّمة من بعض مديري الحُزم لأن البرنامج الخفي أصبح الآن مرتبط ب 127.0.0.1، لذلك سيقبل الاتصالات على حزمة يونكس فقط. أي أنه أصبح غير مفتوحًا للإنترنت. لأن المصادقة ما زالت مُعَطَّلة افتراضيا، فإن لدى أي مستخدم على النظام المحلي وصول كامل لقاعدة البيانات. سنُنشِئ مستخدمًا مسؤولًا لتأمين ذلك، ونُفَعِّل المصادقة ونتحقق من ذلك. خطوة 1 - إضافة مستخدم مسؤول سنتَّصِل بسطر أوامر Mongo لإضافة مستخدم: $ mongo تُحذِّر المُخرجات عندما نستخدم سطر أوامر Mongo من أن التحكم بالوصول معطل لقاعدة البيانات وأن وصول القراءة والكتابة للبيانات والإعدادت غير مقيدة. مخرجات تنفيذ الأمرالسابق هي: MongoDB shell version v3.4.2 connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.4.2 Welcome to the MongoDB shell. For interactive help, type "help". For more comprehensive documentation, see http://docs.mongodb.org/ Questions? Try the support group http://groups.google.com/group/mongodb-user Server has startup warnings: 2017-02-21T19:10:42.446+0000 I STORAGE [initandlisten] 2017-02-21T19:10:42.446+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine 2017-02-21T19:10:42.446+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem 2017-02-21T19:10:42.534+0000 I CONTROL [initandlisten] 2017-02-21T19:10:42.534+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2017-02-21T19:10:42.534+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. 2017-02-21T19:10:42.534+0000 I CONTROL [initandlisten] > يمكنك اختيار الاسم الذي تريده للمستخدم المسؤول لأنَّ الصلاحيات تأتي من إعطاء الدور userAdminAnyDatabase. تُحدد قاعدة البيانات admin أين تُخَزَّن بيانات الاعتماد. يمكنك الاطلاع أكثر عن المصادقة في جزء مصادقة أمنية MongoDB. حدد اسم مستخدم وتأكد من اختيار كلمة مرور آمنة واستبدِلهما في الأمر التالي: > use admin > db.createUser( > { > user: "AdminSammy", > pwd: "AdminSammy'sSecurePassword", > roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] > } > ) عند بدء أمر db.createUser، فإن سطر الأوامر سيضع ثلاث نقاط قبل كل سطر حتى انتهاء الأمر. بعد ذلك، يظهر رد بأن المستخدم قد أُنشِئ كالتالي: المخرجات: > use admin switched to db admin > db.createUser( ... { ... user: "AdminSammy", ... pwd: "AdminSammy'sSecurePassword", ... roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] ... } ... ) Successfully added user: { "user" : "AdminSammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] } اكتب "exit" ثم اضغط على ENTER أو اضغط على CTRL+C للخروج. سَيُتاح الآن للمستخدم إدخال بيانات الاعتماد، لكن لن يُطلَب هذا من المستخدام حتى تفعيل المصادقة وإعادة تشغيل برنامج MongoDB الخفي. خطوة 2 - تفعيل المصادقة المصادقة مفعلة في ملف mongod.conf. عند تفعيلها وإعادة تشغيل mongod، سيظل المستخدمون قادرون على الاتصال ب Mongo بدون مصادقة، لكن سيُطلَب منهم اسم مستخدم وكلمة مرور قبل أي تفاعل. لنفتح ملف الإعداد: $ sudo nano /etc/mongod.conf في الجزء ‎#security، ستُزيل التعليق من أمام security لتفعيل المقطع. ثم سنُضيف إعداد المصادقة. يجب أن تبدو الأسطر كما في الأسفل بعد إلغاء تعليقها: الملف mongodb.conf: . . . security: authorization: "enabled" . . . لاحظ أنَّ سطر "Security" لا يحوي مسافة في بدايته، بينما سطر "authorization" يجب أن يبدأ بِمسافتين. نُعيد تشغيل البرنامج الخفي بعد حفظ الملف وإغلاقه: $ sudo systemctl restart mongod إن أخطأنا في ملف الإعداد فلن يبدأ البرنامج الخفي بالعمل. ولأن systemctl لا تعرض أي مخرجات فَسنستخدم خيار status للتحقق من ذلك: $ sudo systemctl status mongod إن ظهر Active: active (running)‎ في المخرجات وانتهى بشيء كما يلي، فإن أمر restart قد نُفِّذ بنجاح: المخرجات: Jan 23 19:15:42 MongoHost systemd[1]: Started High-performance, schema-free document-oriented database. بعد التأكد من أن البرنامج الخفي يعمل، لنُجَرِّب المصادقة. خطوة 3 - التحقق من منع المستخدمين غير المخولين أولا، نحاول الاتصال بدون معلومات اعتماد للتحقق من أن الدخول ممنوع: $ mongo تم حل جميع التحذيرات بعد تفعيل المصادقة. المخرجات: MongoDB shell version v3.4.2 connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.4.2 اتصلنا بقاعدة بيانات test. سَنتأكد من أن الوصول ممنوع باستخدام الأمر show dbs: > show dbs المخرجات: 2017-02-21T19:20:42.919+0000 E QUERY [thread1] Error: listDatabases failed:{ "ok" : 0, "errmsg" : "not authorized on admin to execute command { listDatabases: 1.0 }", "code" : 13, "codeName" : "Unauthorized" . . . لن نتمكن من إنشاء مستخدمين أو تنفيذ أوامر بصلاحيات مشابهة بدون مصادقة. نُغلِق سطر الأوامر للمتابعة: > exit ثانيًا، نتحقق من أن المستخدم المسؤول الذي أنشأناه يمكنه الوصول. خطوة 4 - التحقق من إمكانية وصول المستخدم المسؤول سَنَتَّصِل باستخدام المستخدم المسؤول الذي أنشأناه باستخدام الخيار ‎-u لتزويد اسم المستخدم والخيار ‎-p ليطلب كلمة مرور. سنحتاج أيضا إلى تزويد اسم قاعدة البيانات التي خُزِّنَت فيها بيانات اعتماد المستخدم باستخدام الخيار ‎--authenticationDatabase. $ mongo -u AdminSammy -p --authenticationDatabase admin سيُطلب منَّا كلمة المرور، لذلك ندخلها. سننتقل إلى سطر الأوامر عند ادخال كلمة المرور الصحيحة حيث يمكننا تنفيذ الأمر show dbs: المخرجات MongoDB shell version v3.4.2 Enter password: connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.4.2 > يجب أن نرى قاعدة البيانات بدلًا من رفض الوصول: > show dbs المخرجات: admin 0.000GB local 0.000GB للخروج، أدخل exit أو اضغط CTRL+C. خاتمة ثبَّتنا الآن MongoDB وإجرينا عمليات التهيئة والحماية المطلوبة وتحققنا من عملها. يمكنك الآن بدء استعمالها وأنت مطمئن القلب ومرتاح البال. يمكنك العثور على المزيد من الدروس المتعمقة في كيفية إعداد واستخدام MongoDB في هذه المقالات من أكاديمية حسوب. يُعد التوثيق الرسمي ل MongoDB أيضا مصدر ممتاز للخيارات التي يقدمها MongoDB. كما يمكنك الاطلاع على توثيق MongoDB لتتعلم أكثر عن المصادقة، والتحكم بالوصول وفقا للدور، والمستخدمون والأدوار. ترجمة -وبتصرف- للمقال How to Install MongoDB on Ubuntu 18.04 لصاحبه الكاتب Mateusz Papiernik.
  15. Nginx هو أحد أشهر خوادم الويب عالميا وهو المسؤول عن استضافة بعض أكبر المواقع وأكثرها ازدحاما على الإنترنت. يُعد Nginx غالبا أفضل من Apache من ناحية ملاءمته للموارد، ويمكن استخدامه كخادم ويب أو وكيل عكسي (reverse proxy). في هذا المقال سنشرح كيفية تثبيت Nginx على خادم أوبونتو 18.04. المتطلبات قبل البدء، تحتاج لمستخدم عادي - غير مسؤول - بصلاحيات "sudo" معد على الخادم. يمكنك معرفة كيفية إعداد خادم عادي في مقال الإعداد الأولي لِخادم أوبونتو 18.04. إن كان لديك حساب متاح، سجل الدخول إلى المستخدم غير المسؤول للبدء. خطوة 1 - تثبيت Nginx يمكنك تثبيت Nginx باستخدام مدير الحزم "apt" وذلك لأنه متاح في مخزن أوبونتو الافتراضي. ولأنها أول مرة نَستخدم فيها نظام تحزيم "apt"، سنُحدِث الحزمة المحلية الرئيسية كي نحصل على قائمة بأحدث الحزم. ثم ننفذ أمر تثبيت nginx: $ sudo apt update $ sudo apt install nginx بعد الموافقة على الإجراءات سيُثبِّت apt Nginx وأي متعلقات يتطلبها الخادم. خطوة 2 - إعداد جدار الحماية قبل اختبار Nginx، يجب إعداد جدار الحماية كي يتيح الوصول للخدمة. يُسجل Nginx نفسه كخدمة في ufw أثناء التثبيت مما يجعل السماح بالدخول إلى Nginx أسهل. أدرج إعدادت التطبيق التي تعلم ufw كيف تتعامل معها باستخدام الأمر: $ sudo ufw app list يجب أن تحصل على قائمة بملفات تعريف التطبيق: المخرجات: Available applications: Nginx Full Nginx HTTP Nginx HTTPS OpenSSH يوجد ثلاثة ملفات تعريف ل Nginx متاحة كما ترى: Nginx Full: يفتح هذا الملف كِلا المنافذ 80 (حركة مرور الويب العادية وغير المشفرة)، والمنفذ 443 (حركة المرور المشفرة TLS/SSL) Nginx HTTP: يفتح هذا الملف المنفذ 80 فقط (حركة مرور الويب العادية وغير المشفرة) Nginx HTTPS: يفتح هذا الملف المنفذ 443 فقط (حركة المرور المشفرة TLS/SSL) يستحسن تفعيل الملف الأكثر صرامة والذي لا يزال يسمح بحركة المرور التي تم إعدادها. لأننا لم نقم بإعداد SSL للخادم بعد، نحتاج لتفعيل المنفذ 80 فقط. يمكنك تفعيله باستخدام الأمر: $ sudo ufw allow 'Nginx HTTP' للتحقق من التغييرات: $ sudo ufw status يجب أن ترى أن حركة المرور باستخدام HTTP مفعلة كما في المخرجات: المخرجات: Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere Nginx HTTP ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) Nginx HTTP (v6) ALLOW Anywhere (v6) خطوة 3 - فحص خادم الويب بعد انتهاء عملية التثبيت، سيُشغل أوبونتو Nginx. يجب أن يكون خادم الويب يعمل. يمكننا التحقق من ذلك باستخدام نظام التهيئة systemd عن طريق الأمر: $ systemctl status nginx المخرجات: ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2018-04-20 16:08:19 UTC; 3 days ago Docs: man:nginx(8) Main PID: 2369 (nginx) Tasks: 2 (limit: 1153) CGroup: /system.slice/nginx.service ├─2369 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; └─2380 nginx: worker process كما ترى في الأعلى، يظهر أن الخدمة بدأت بنجاح. لكن الطريقة المثلى لاختبار ذلك هو جلب صفحة من Nginx فعليا. يمكنك الوصول إلى صفحة Nginx الرئيسية لتتأكد من أن التطبيق يعمل بطريقة صحيحة عن طريق الدخول إلى عنوان بروتوكول الإنترنت (IP) الخاص بالخادم. يمكنك الحصول عليه بعدة طرق. جرب تنفيذ هذا الأمر على شاشة أوامر الخادم: $ ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//' ستحصل على بعض الأسطر التي يمكنك تجريبها سطرًا تلو الآخر على متصفح الإنترنت لتتأكد إن كانت تعمل. الطريقة الأخرى هي عبر تنفيذ الأمر التالي، والذي سيعطيك عنوان بروتوكول الإنترنت العام للخادم كما يُرى على المواقع الآخرى على الإنترنت: $ curl -4 icanhazip.com عند حصولك على عنوان بروتوكول الإنترنت الخاص بالخادم، أدخله إلى شريط المتصفح: http://your_server_ip يجب أن ترى الصفحة الرئيسية ل Nginx: تُضمَّن هذه الصفحة مع Nginx لتأكيد أن Nginx يعمل بطريقة صحيحة. خطوة 4 - إدارة عملية Nginx الآن وبما أن خادم الويب أصبح يعمل بطريقة صحيحة، لنراجع بعض أوامر التحكم الأساسية. لإيقاف خادم الويب نفذ الأمر: $ sudo systemctl stop nginx لِتشغيل خادم الويب عندما يكون موقفا، نفذ الأمر: $ sudo systemctl start nginx لِإيقاف وإعادة تشغيل الخدمة استخدم الأمر: $ sudo systemctl restart nginx إن كنت تقوم بتعديل في إعدادت Nginx، فإنه يٌعيد التحميل بدون قطع اتصالاته. للقيام بذلك نفذ الأمر: $ sudo systemctl reload nginx Nginx مٌعد ليبدأ تلقائيا مع بدء تشغيل الخادم. إن كنت لا تريد ذلك؛ يمكنك إلغاء تفعيل هذه الميزة باستخدام الأمر: $ sudo systemctl disable nginx لإعادة تفعيل هذه الميزة: $ sudo systemctl enable nginx خطوة 5 - إعداد أجزاء الخادم (Server Blocks) (إعداد مستحسن) عند استخدام خادم Nginx، أجزاء الخادم (شبيهة بالمضيف الوهمي [virtual host] في Apache) تُستخدم لتغليف تفاصيل الإعدادات وتمكين استضافة أكثر من نطاق من خادم واحد. سنقوم بإعداد نطاق ونُسمية example.com، يمكنك استبدال هذا الاسم باسم النطاق الذي تريده. لدى Nginx جزء خادم مُفعَّل تلقائيا على أوبونتو ومُعَد لتشغيل الملفات في مجلد "‎/var/www/html". رغما من أن هذا يعمل بشكل جيد لموقع واحد؛ إلا أنه ليس عمليا إن كنت تستضيف عدة مواقع. عوضا عن التعديل على ‎/var/www/html؛ لنقم بإنشاء بينة هيكلية في ‎/var/www لموقع example.com ونترك ‎/var/www/html في مكانه كَمجلد رئيسي ليعمل في حال طلب أحد العملاء صفحة لا توافق بقية المواقع. أنشئ مجلدا ل example.com كما يلي مستخدما -p لإنشاء أي مجلدات ضرورية أخرى: $ sudo mkdir -p /var/www/example.com/html ثم قم بمنح ملكية الجلد باستخدام متغير البيئة ‎$USER: $ sudo chown -R $USER:$USER /var/www/example.com/html يجب أن تكون صلاحيات جذور الويب صحيحة إن كنت لم تقم بتعديل قيمة umask يمكنك التأكد من ذلك باستخدام الأمر: $ sudo chmod -R 755 /var/www/example.com قم بإنشاء صفحة كعينة index.html باستخدام الأمر nano على مُحررك المفضل: $ nano /var/www/example.com/html/index.html بداخل هذه الصفحة، ضع عينة HTML التالية: /var/www/example.com/html/index.html <html> <head> <title>Welcome to Example.com!</title> </head> <body> <h1>Success! The example.com server block is working!</h1> </body> </html> بعد الانتهاء احفظ وأغلق الملف. كي يقوم Nginx بمعالجة هذا المحتوى، فمن الضروري إنشاء جزء خادم (server block) بالموجهات الصحيحة. بدلا من القيام بذلك عبر تعديل ملفالإعداد الافتراضي مباشرة، لنُنشئ ملفًا جديدًا في ‎/etc/nginx/sites-available/example.com: $ sudo nano /etc/nginx/sites-available/example.com قم بنسخ جزء الإعداد التالي الشبيه بالافتراضي لكنه محدث للمجلد واسم المجلد الجديد: /etc/nginx/sites-available/example.com server { listen 80; listen [::]:80; root /var/www/example.com/html; index index.html index.htm index.nginx-debian.html; server_name example.com www.example.com; location / { try_files $uri $uri/ =404; } } لاحظ أننا قمنا بتحديث إعداد root للمجلد الجديد و server_name لاسم النطاق الجديد. ثم نقوم بتفعيل الملف من خلال إنشاء رابط منه إلى مجلد sites-enabled والذي يقوم Nginx بالقراءة منه أثناء التشغيل: $ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ الآن أصبح لدينا جزئي خادم مفعلين ومُعدين للاستجابة للطلبات وفقا لموجهي listen و server_name (يمكنك قراءة المزيد حول كيفية معالجة Nginx لهذه الموجهات في هذا المقال example.com: سيستجيب للطلب على example.com و www.example.com. default: سيستجيب لأي طلبات على المنفذ 80 والتي لا تطابق جزء آخر. لتجنب مشكلة "hash bucket memory" التي قد تطرأ من إضافة أسماء خوادم إضافية، من الضروري تعديل قيمة واحدة في ملف ‎/etc/nginx/nginx.conf. افتح هذا الملف: $ sudo nano /etc/nginx/nginx.conf ابحث عن الموجّه server_names_hash_bucket_size واحذف رمز # لإلغاء تعليق هذا السطر: /etc/nginx/nginx.conf ... http { ... server_names_hash_bucket_size 64; ... } ... ثم نتحقق من عدم وجود من أي أخطاء في جمل ملفات Nginx: $ sudo nginx -t احفظ وأغلق الملف بعد انتهائك. إن لم يكن هناك أي أخطاء أعد تشغيل Nginx لتفعيل التغييرات: $ sudo systemctl restart nginx يجب أن يقوم Nginx الآن بالاستجابة للطلبات على النطاق الجديد. للتحقق من ذلك، ادخل على http://example.com. هنا يجب أن ترى شيئا كالتالي: خطوة 6 - التعرف على مجلدات وملفات Nginx المهمة الآن، وبعد أن أصبحت تعرف كيف تُدير خدمة Nginx. يجب أن تأخذ لمحة سريعة حول بعض المجلدات والملفات المهمة. المحتوى ‎/var/www/html: محتوى الويب الفعلي ل Nginx والذي يحتوي فقط على صفحة Nginx الافتراضية التي رأيناها سابقا. تُعرض هذه الصفحة من مجلد ‎/var/www/html. يمكن تغيير هذا الإعداد عن طريق ملف إعداد Nginx. إعداد الخادم ‎/etc/nginx: مجلد إعداد Nginx والذي يحوي جميع ملفات التكوين. ‎/etc/nginx/nginx.conf: ملف تكوين Nginx الرئيسي. يمكن تعديل هذا الملف لتغيير الإعدادات العامة ل Nginx. ‎/etc/nginx/sites-available/‎: المجلد الذي يتم تخزين أجزاء الخادم فيه. لن يقوم Nginx باستخدام ملفات التكوين الموجودة في هذا المجلد إلا إذا تم ربطها لمجلد sites-enabled. بشكل عام، يتم تخزين ملفات إعداد أجزاء الخادم في هذا المجلد ثم تُفعّل بربطها بالمجلد الآخر. /etc/nginx/sites-enabled/: المجلد الذي يتم تخزين أجزاء الخادم لكل موقع (per-site server blocks) فيه. تُنشئ هذه المجلدات بربط ملفات التكوين الموجودة في مجلد sites-available. ‎/etc/nginx/snippets: يحتوي هذا المجلد على أجزاء التكوين التي يمكن تضمينها في أي مكان آخر في تكوين Nginx. أجزاء الإعداد المُكررة عن قصد تُعد بدائل جيدة لإعادة بناء أجزاء الأكواد البرمجية. سجلات الخادم ‎/var/log/nginx/access.log: يتم تخزين كل طلب يصل إلى خادم الويب في ملف السجل هذا إلا إن كان Nginx مُعد لعدم القيام بذلك. ‎/var/log/nginx/error.log: يتم تسجيل أي خطأ في Nginx في هذا السجل. الآن وبما أن خادم الويب أصبح مُعدًا لديك، فلديك عدة خيارات للمحتوى الذي يمكن أن يعالجه الخادم والتقنيات التي يمكن أن تستخدمها لتجربة أفضل. الخلاصة إن كنت تريد بناء حزمة تطبيق متكاملة، اطلع على هذا المقال عن كيفية إعداد حزمة LEMP على أوبونتو 18.04. ترجمة -وبتصرف- للمقال How To Install Nginx on Ubuntu 18.04 لصاحبيه Justin Ellingwood و Kathleen Juell.