لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 08/07/22 في كل الموقع
-
لدي موقع تعليمي به الكثير من الفيديوهات للمحاضرات التي تصل لمدة ساعتين وبجودة عالية وكذلك الصور والملفات الصوتية لذلك هل يوجد إستضافة توفر لي رفع جميع الملفات والموقع ؟ أم يجب إستخدام سيرفر أخر للملفات مثل خدمة أمازون s3 ؟ وكم يكون سعرها؟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; } اقرأ أيضًا المقال التالي: توابع الأنواع الأولية المقال السابق: التحويل من نوع كائن إلى نوع أولي1 نقطة
-
بعد إرسال طلب AJAX من المتصفح، يتوجب انتظار الرد من الخادم في دالة success حيث تستقبل الرد وتعدل الصفحة بدون تحديثها. لنفرض لديك وسم HTML تعرض فيه الناتج: <p id="result"> .. </p> سيحشر الرد فيه كالتالي: success:function(result_form_server){ document.getElemetById(result).innerHTML = result_form_server; } حالياً دع الخادم يعيد أي قيمة في return وبعد تأكدك من أن الرد يأتي بشكل سليم للصفحة و يعدل النتيجة قم بتعديل الشيفرة لإرجاع القيم الصحيحة def tab_1(request): .. ... return "123"1 نقطة
-
اخي الكريم هل هو يعتمد على id ام name في الوسم input انا قمت بطباعة المتغير Drywall_Ceiling وهو يطبع ما قمت بأدخاله و طبعت المعادلة فهو يقوم بحل المعادلة لكن في context لا يظهر لي الناتج حتى اقوم بتحديث الصفحة1 نقطة
-
في كل المواقع خيار تسجيل الدخول مثلاً ينشأ صفحة جديدة باسم حسابك فيها بياناتك فكيف تم ذلك1 نقطة
-
أرجو التأكد نم المفتاح أن يطابق ما أرسلته .get('Drywall_Ceiling') ^^^^^^^ أرجو إخبار إن حلت المشكلة1 نقطة
-
شكرا جزيلا على المقال الرائع! عندي سؤال...لماذا لا يمكن اسناد قيمة خاصية بخواص أخرى في نفس التابع الباني؟ اشترط صاحب المقال لحل مسألة (إنشاء حاسبة جديدة) باستخدام الدوال للخواص this.sum و this.mul...لقد حاولت ان احولهما بدون استخدام الدوال ل this.sum = this.v1 + this.v2 و this.mul = this.v1 * this.v2 لكن كان يرجع لي القيمة NaN فما السبب هنا؟..وما المنطق البرمجي وراء هذا؟ وشكرا جزيلا مرة أخرى1 نقطة
-
اخي انا استطيع فعل هذا، المشكلة وسؤالي هي كيف استخدم زر الرجوع في العوده مثلا كنت فاتح محتوى الصفحه واحد ثم انتقلت للصفحه اثنين واردت الرجوع للصفحه واحد هنا اضغط زر الرجوع العادي الموجود في اي هاتف او كمبيوتر، بس لما اضغط زر الرجوع لا يرجع للصفحه واحد. انا استطيع عمل ازرار رجوع تظهر في الشاشه ولكن غالبا ما يستخدم المستخدم زر الرجوع العادي في جهازه.1 نقطة
-
عندما أستعمل append يتم حقن العنصر في صفحة html لكن عندما أستعمل appendChild d يظهر خطأ Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. ملف js ipcRenderer.on('AddName-AndAge',function(e,item){ let Age=item[1]; let name=item[0]; window.localStorage.setItem('name',name); window.localStorage.setItem('Age',Age); let AgeMaladie=document.createTextNode(Age); let nameMaladie=document.createTextNode(name); let infoMal=`Nom de Maladie :${name} Age de Maladie :${Age}`; let maladieInfo=document.querySelector('#Maladie-info'); let z=document.createElement('p'); z.appendChild(infoMal); maladieInfo.appendChild(z); }); html <div class="maladie-side clearfix"> <button type="button" id="ajouter" class="btn btn-primary py-2 ajouter float-left"> Ajouter un maladie <!-- <a href="./maladieData.html"> </a> --> </button> <span id="Maladie-info" class="Maladie_Info text-left "> </span> </div>1 نقطة
-
كيف اكتب برنامج لإيجاد أصغر قيمة بين ثلاثة قيم صحيحة بإستخدام جملة if ارجو الرد1 نقطة
-
السلام عليكم ممكن حد من طلبة تخصصات البرمجة والحاسوب خاصة هندسة البرمجيات يشرحلي شوي عن التخصص وطبيعة الدراسة وشو المؤهلات اللي بتلزمني لأدخله وإذا بناسبني أو لا ؟ خاصة لحد عمره ما تعامل مع هيك مواضيع حتى الدراسة بالمدرسة كانت سطحية جدا (يعني عنجد ما بعرف اشي حتى اكسل ما بعرف شو هو) سمعت أنه رح يكون صعب لحد ما عمره برمج أو عنده مهارات بتخص هذا الموضوع. مستواي بالإنجليزي متوسط وعندي قابلية أنو أتعلم أو أدرس أي شي جديد ومعي عطلة شهر وشوي كيف بقدر أستفيد منهم بهاد المجال1 نقطة
-
@نوال سلامة وعليكم السلام ورحمة الله، البرمجة بالتعريف هي عملية تقسيم مهمة معينة يراد تنفيذها عبر الحاسوب إلى أجزاء صغيرة ومترابطة وقابلة للتنفيذ بأوامر بسيطة. بعد ذلك، يجري كتابة هذه الأوامر والتعليمات بإحدى لغات البرمجة، والتي هي وسيلة للتخاطب مع الحاسوب. لست بحاجة لأية مهارات مسبقة لتعلم البرمجة كل ما تحتاجينه هو حاسوب وان تعرفي كيفية استخدامه.(حتى انا لا اعرف كيفية استخدام الاكسل إلى بشكل سطحي جداً) وباعتبار ان مستواك في الانجليزية متوسط فهذا يسهل الموضوع اكثر واكثر. بالنسبة للحديث عن التخصص فأنصحك بقراءة المقالة التالية التي تجيب عن كل الاسئلة التي طرحتيها وأكثر : وتستطيعين الحكم بعدها إن كان هذا التخصص يناسبك أو لا.1 نقطة
-
سلام عليكم عندي قاعدة بيانات ضخمة منها جداول كثيرة مرتبطة بجدول اسمه order و الربط يتم بجقل اسمه order_id الان عاوز لو حذفت من جدول order مثلا ال id رقم 3 عاوز طريقة سهلة اخليه يلف عالداتابيز كلها لو لقا حقل اسمه order_id و قيمته تساوي 3 يحذفه مع الشكر1 نقطة
-
هل جربت تعريف القيد ON DELETE CASCADE عند تعريف العلاقات بين الجداول؟ يتم استخدام هذا القيد في MySQL لحذف الصفوف من الجدول الفرعي تلقائيا عند حذف الصفوف من الجدول الأصلي. مثلا، عندما يقوم المستخدمون بالتسجيل في الموقع ورفع منتجات خاصة بهم مثلا. سيتم التخلص من هاته المنتجات مباشرة بعد حدف المستخدم المعني. يمكن اضافة تعريف القيد الى تعريف العلاقة كـ: CREATE TABLE some_related_table ( id INT, order_id INT, FOREIGN KEY(order_id) REFERENCES orders(id) ON DELETE CASCADE ); ثم سيتم اعتبار هذا القيد عند كل حذف. راجع الاجابة1 نقطة
-
هل يمكنك ايضاح سؤالك بشكل افضل، اعتقد انه يمكنك تنفيذ الامر باستخدام react فهو يقدم هذه الخدمة وأكثر.1 نقطة
-
<a href=" " target="_blank" rel="noopener noreferrer" > ما فائده ال rel سمعت انها تحل مشاكل متعلقه بالامان ولكن ما هذه المشاكل وماذا لو لم استخدمها??1 نقطة
-
أهلا بك، في البريد الاكتروني، في المستطيل الرمادي، يمكنك تحديد أن الرسالة مرغوب بها لكي يتم نقلها لقسم inbox بدل spam وبالتالي يتوقف التجاهل لها لتكمل عملية التجريب1 نقطة
-
1 نقطة
-
يوجد سبعة أنواع للبيانات في 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; } اقرأ أيضًا المقال التالي: كنس البيانات المهملة المقال السابق: تعويض نقص دعم المتصفحات لجافاسكربت1 نقطة
-
مقدّمة نظرية كائن جافاسكريبت هو كيان لديه خاصيّات. كلّ خاصيّة عبارة عن زوج مفتاح وقيمة. المفتاح هو اسم الخاصيّة. يمكن أن تكون قيمة الخاصيّة بيانات (عددا، سلسلة محارف، …إلخ.) أو دالة. يُطلَق على الخاصيّة عندما تكون قيمتها دالة الاسم تابع Method. يُنشَأ كائن حرفي Object literal في جافاسكريبت بتحديد خاصيّاته ضمن زوج من الأقواس المعكوفة. const myObject = { property1: value1, property2: value2, // ... , method1(/* ... */) { // ... }, method2(/* ... */) { // ... } // ... }; myObject.property1 = newValue; // يعيّن القيمة الجديدة للخاصيّة property1 في الكائن myObject console.log(myObject.property1); // يعرض قيمة الخاصيّة property1 في الكائن myObject myObject.method1(...); // استدعاء التابع method1 في myObject تمثّل الكلمة المفتاحية this في تابع الكائن الذي يُستدعَى فيه التابع. تعرّف لغة البرمجة سلفا كائنات عدّة للاستفادة منها مثل console و Math. مقدّمة ما هو الكائن؟ انظر إلى الكائنات في معناها غير البرمجي، مثل قلم. يمكن أن يكون للقلم ألوان عدّة، يصنعه أشخاص متعدّدون، أطراف متنوّعة وخاصيّات أخرى كثيرة. على نحو مشابه، الكائن في البرمجة هو كيان لديه خاصيّات. تعرّف كل خاصيّة ميزة في الكائن. يمكن أن تكون الخاصيّة بيانات مرتبطة بالكائن (لون القلم) أو إجراء (قدرة القلم على الكتابة). ما علاقة هذا بالشفرة؟ البرمجة كائنية التوجّه Object-oriented programming (أو OOP اختصارا) هي طريقة لكتابة البرامج باستخدام الكائنات. يكتُب المبرمج - عند اتّباع هذه الطريقة - الكائنات، ينشئها ويعدّل عليها؛ تشكلّ الكائنات البرنامج. ** تغيّر البرمجة كائنية التوجّه الطريقة التي تُكتَب وتُنظَّم بها البرامج. كتبنا في الفصول السابقة برامج تعتمد على الدوالّ، وهي طريقة برمجيّة تُسمَّى البرمجة الإجرائية Procedural programming. فلنكتشف الآن كيف نكتب شفرة كائنية التوجّه. جافاسكريبت والكائنات تدعم جافاسكريبت، مثل لغات برمجة أخرى، البرمجة بالكائنات. كما توفّر كائنات معرَّفة مسبقا مع إتاحة الفرصة لإنشاء كائنات جديدة. إنشاء كائن في ما يلي تمثيل جافاسكريبت لقلم حبر جاف أزرق اللون علامته التجارية Bic. const pen = { type: "حبر جاف", color: "أزرق", brand: "Bic" }; يمكن إنشاء كائنات جافاسكريبت، كما ذكرنا سابقا، بسهولة بتعيين خاصيّات الكائن ضمن زوج أقواس معكوفة {...}. كلّ خاصيّة هي زوج من المفاتيح والقيم. يُسمَّى الكائن المعرَّف سابقا بالكائن الحَرْفي Object literal. ملحوظة: النقطة الفاصلة ; بعد زوج الأقواس اختيارية، إلا أنه من الآمن إضافتها على كلّ حال. تعرِّف الشفرةُ أعلاه متغيّرا يُسمَّى pen قيمته كائن، يمكننا القول إذن إن pen كائن. لهذا الكائن ثلاث خاصيّات هي: type (النوع)، color (اللون) وbrand (العلامة التجارية). لكلّ خاصيّة اسمٌ وقيمة، كما أنها متبوعة بفاصلة لاتينية , (ما عدا الخاصيّة الأخيرة) الوصول إلى خاصيّات الكائن يمكن الوصول إلى قيم الخاصيّات بعد إنشاء الكائن بالتنويت النقطي Dot notation مثل myObject.myProperty. const pen = { type: "حبر جاف", color: "أزرق", brand: "Bic" }; console.log(pen.type); // "حبر جاف" console.log(pen.color); // "أزرق" console.log(pen.brand); // "Bic" الوصول إلى خاصيّة كائن هو عبارة Expression تنتج قيمة. يمكن تضمين هذه العبارة في عبارات أكثر تعقيدا. يوضّح المثال التالي كيفية عرض خاصيّات القلم السابق في تعليمة واحدة: const pen = { type: "حبر جاف", color: "أزرق", brand: "Bic" }; console.log(`أكتب بقلم ${pen.type} لونه ${pen.color} وعلامته التجارية ${pen.brand}`); التعديل على كائن يمكن تعديل قيم الخاصيّات في كائن بعد إنشائه بالصيغة myObject.myProperty = newValue. const pen = { type: "حبر جاف", color: "أزرق", brand: "Bic" }; pen.color = "أحمر"; // تغيير لون القلم console.log(`أكتب بقلم ${pen.type} لونه ${pen.color} وعلامته التجارية ${pen.brand}`); توفّر جافاسكريبت إمكانية الإضافة الديناميكية لخاصيّات جديدة لكائن أنشأته قبْلا: const pen = { type: "حبر جاف", color: "أزرق", brand: "Bic" }; pen.price = "2.5"; // تعيين خاصية لسعر القلم console.log(`يبلغ سعر قلمي ${pen.price}`); البرمجة بالكائنات تعلّم الكثير من الكتب والدورات البرمجة كائنية التوجّه عبر أمثلة عن الحيوانات، السيّارات أو الحسابات المصرفية. فلنجرّب أمرا ألطف ولننشئ لعبة تقمّص أدوار Role playing game مصغَّرة باستخدام الكائنات. تُعرَّف كلّ شخصية في ألعاب تقمّص اﻷدوار بصفات مميَّزة عدّة مثل القوّة، القدرة على التحمّل والذكاء. في ما يلي لقطة شاشة للعبة تقمّص أدوار شهيرة على الإنترنت. سيكون للشخصيّات - في مثالنا الأبسط كثيرا - ثلاثُ صفات مميّزة: الاسم Name، الصّحة Health (عدد نقاط الحياة)، القوة Strength. مثال ساذج فلنقدّم أورورا، الشخصيّة الأولى في لعبتنا لتقمّص الأدوار: const aurora = { name: "أورورا", health: 150, strength: 25 }; للكائن aurora ثلاث خاصيّات: health، name وstrength. ملاحظة: يمكن - كما ترى في المثال أعلاه - إسناد أعداد، سلاسل محارف وحتى كائنات أخرى إلى خاصيّات الكائنات. تستعدّ أورورا للبدء في سلسلة من المغامرات العظيمة التي ستحدّث بعض منها خاصيّات الشخصيّة. تأمل المثال التالي: const aurora = { name: "أورورا", health: 150, strength: 25 }; console.log(`يوجد لدى ${aurora.name} نقاط قوة قدرها ${aurora.health} وقوة تبلغ ${aurora.strength}`); // أصاب سهم أورورا وبالتالي تقل نقاط الحياة aurora.health -= 20; // تتجهّز أورورا بقلادة قوة aurora.strength += 10; console.log(`يوجد لدى ${aurora.name} نقاط قوة قدرها ${aurora.health} وقوة تبلغ ${aurora.strength}`); التعريف بالتوابع احتجنا في الأمثلة السابقة إلى كتابة تعليمات console.log طويلة في كلّ مرة نريد عرض حالة الشخصية. توجد طريقة أنسب للوصول إلى هذا الغرض. إضافة تابع لكائن تأمل المثال التالي: const aurora = { name: "أورورا", health: 150, strength: 25 }; // ترجع وصف الشخصية function describe(character) { return `يوجد لدى ${character.name} نقاط صحة قدرها ${character.health} وقوة تبلغ ${character.strength}`; } console.log(describe(aurora)); معامل الدالة describe() هو كائن. تصل الدالة إلى خاصيّات الكائن وتنشئ سلسلة المحارف التي تصف الشخصية. أدناه مقاربة بديلة تستخدم الدالة describe() داخل الكائن. const aurora = { name: "أورورا", health: 150, strength: 25, // ترجع وصف الشخصية function describe(character) { return `يوجد لدى ${character.name} نقاط صحة قدرها ${character.health} وقوة تبلغ ${character.strength}`; } }; console.log(aurora.describe()); يتوفّر الكائن الآن على خاصيّة جديدة: describe(). قيمة هذه الخاصيّة دالة تُرجِع وصفا نصيًّا للكائن. نتيجة التنفيذ مطابقة تماما لما سبق. تُسمّى خاصيّة كائن عندما تكون قيمتها دالة بالتابع. تُستخدَم التوابع لتعريف إجراءات على كائن. يضيف التابع سلوكا إلى الكائن. استدعاء تابع في كائن فلنتأمل السطر الأخير من المثال السابق: console.log(aurora.describe()); نستخدم العبارة aurora.describe() لعرض وصف الشخصية بدلا من describe(aurora)، وهنا فرق جوهري. تستدعي العبارة describe(aurora) الدالة describe() مع تمرير الكائن aurora في المعطيات. الدالة خارجة عن الكائن. هذا مثال على البرمجة الإجرائية. تستدعي العبارة aurora.describe() الدالة describe() في الكائن aurora. الدالة خاصيّة من خاصيّات الكائن: تابع. هذا مثال على البرمجة كائنية التوجه. صيغة استدعاء التابع myMethod() في myObject هي myObject.myMethod(). تنبيه: تذكّر الأقواس، حتى وإن كانت خاوية، عند استدعاء تابع. الكلمة المفتاحية this تأمل جيّدا متن التابع describe() في المثال التالي: const aurora = { name: "أورورا", health: 150, strength: 25, // ترجع وصف الشخصية describe() { return `يوجد لدى ${this.name} نقاط صحة قدرها ${this.health} وقوة تبلغ ${this.strength}`; } }; سترى كلمة مفتاحية جديدة: this. تُعيَّن هذه الكلمة المفتاحية تلقائيا داخل تابع في جافاسكريبت وتمثّل الكائن الذي استُدعِي فيه التابع. لا يستقبل التابع describe() أي معاملات. يستخدم التابع this للوصول إلى خاصيّات الكائن الذي استُدعِي فيه. الكائنات المعرَّفة مسبقا في جافاسكريبت تتوفّر جافاسكريبت على كائنات عدّة معرَّفة مسبقا تخدم أغراضا متفرّقة. رأينا في ما مضى بعضا منها: يمنح الكائن console الوصول إلى بيئة الطرفية. التعليمة console.log() هي في الواقع استدعاء لتابع. يحوي الكائن Math خاصيّات رياضية كثيرة. على سبيل المثال، تُرجع الخاصيّة Math.PI قيمة تقريبية للعدد π، ويرجع التابع Math.random() عددا عشوائيا بين 0 و1. حان وقت كتابة الشفرة! إضافة تجربة الشخصية حسّن برنامج لعبة تقمّص الأدوار بإضافة خاصيّة التجربة؛ على أن يكون اسمها xp وقيمتها الابتدائية 0. يجب أن تظهر التجربة ضمن وصف الشخصية. // للإنجاز: إنشاء الكائن character هنا. // أصاب سهم أورورا وبالتالي تقل نقاط الحياة aurora.health -= 20; // تتجهّز أورورا بقلادة قوة aurora.strength += 10; // تعلّمت أورورا مهارة جديدة aurora.xp += 15; console.log(aurora.describe()); نمذجة Modeling كلب أكمل البرنامج التالي لإضافة تعريف بالكائن dog (كلب). // للإنجاز: أنشئ الكائن dog هنا console.log(`${dog.name} كلب نوعه ${dog.species} يبلغ طوله ${dog.size}`); console.log(`انظر هرّة! ${dog.name} ينبح: ${dog.bark()}`); نمذجة دائرة أكمل البرنامج التالي لإضافة تعريف الكائن circle (دائرة). يُدخل الزائر قيمة شعاع الدائرة const r = Number(prompt("أدخل قيمة شعاع الدائرة:")); // للإنجاز: أنشئ تعريف الدائرة هنا console.log(`يبلغ محيط الدائرة ${circle.circumference()}`); console.log(`تبلغ مساحة الدائرة ${circle.area()}`); نمذجة حساب مصرفي أنشئ برنامجا ينشئ كائن account يمثّل حسابا مصرفيا لديه الميزات التالية: خاصيّة name قيمتها “أحمد”. خاصيّة balance قيمتها 0. تابع credit تضيف القيمة (سالبة أو موجبة) المُمرَّرة في المعطى إلى رصيد الحساب balance. تابع describe يُرجع وصف الحساب. استخدم هذا الكائن لعرض وصف حساب مصرفي، أضف 250 إلى رصيده، اسحب منه 80 ثم اعرض وصفه مرة أخرى. تحصُل على الآتي عند عرض وصف البرنامج في المرة الأولى: “المالك: أحمد، الرصيد: 0” وعلى ما يلي في المرة الثانية: “المالك: أحمد، الرصيد: 170” ترجمة - بتصرّف - للفصل Create your first objects من كتاب The JS Way.1 نقطة
-
هل تعاني من صعوبة في تطبيق مفاهيم البرمجة الكائنية مع لغة JavaScript؟ إذًا أنت في المكان الصحيح، ففي هذا المقال سنبدأ بمطلع لمفاهيم البرمجة الكائنية (أو الشيئية كما قد يُطلق عليها البعض)، ومن ثم مراجعة نموذج جافا سكريبت في الكائنات، وأخيرًا شرح مفاهيم البرمجة الكائنية في جافا سكريبت، لتحصل على إلمام واف والقصة الكاملة. مراجعة في جافاسكريبت JavaScript يُمكن العودة إلى المقال إعادة تقديم JavaScript لمن أساء فهمها هنا في أكاديمية حسوب للحصول على مراجعة لأساسيات لغة جافا سكريبت وأخذ فكرة عن المغيرات وأنواع الدوال وبقية الأساسيات. البرمجة الكائنية Object-oriented programming إن البرمجة الكائنية (OOP) ما هي إلا نمط برمجي يَستخدم التجريد في إنشاء نماذج/نسخ لتجسيد العالم الحقيقي، وتَستخدم البرمجة الكائنية في ذلك أساليب مُتعدّدة من هذا النمط، فهي تستخدم الوحدات module، وتعدديّة الأشكال polymorphism والتغليف encapsulation، وتجدر الإشارة إلى أن معظم لغات البرمجة تدعم مفهوم OOP أمثال اللغات البرمجية: جافا، بايثون، روبي، وطبعًا جافا سكريبت. يُعالج أو لنقل يَتصور مفهوم البرمجة الكائنية OOP البرنامج كتشكيلة من الأشياء/الكائنات المتعاونة/المترابطة بدلًا من يتصوّره كتشكيلة من الدوال (functions) أو كسرد من الأوامر. ففي مفهوم OOP، كل كائن/شيء له القدرة على استقبال الرسائل، ومعالجة البيانات، وإرسال الرسائل إلى باقي الكائنات، ويُمكن اعتبار أنه لكل كائن object كينونة خاصة به ودور/وظيفة مستقلة عن الكائن الآخر. تُعزز البرمجة الكائنية القدرة على صيانة الشيفرة البرمجية والمرونة في التطوير، وأثبتت جدارتها على نطاق واسع في هندسة البرمجيات الكبيرة، ولأن البرمجة الكائنية تُشدد على استخدام الوحدات module، فإن الشيفرة/الكود المكتوب بمفهوم البرمجة الكائنية هو أبسط في التطوير وأسهل في الفهم مستقبلًا (عند التنقيح والتعديل)، وكما يعزز مفهوم البرمجة الكائنية التحليل المباشر للشيفرة، وفهم الحالات الشائكة فهمًا أفضل من باقي الأساليب البرمجية الأخرى. مصطلحات البرمجة الكائنية المجال في البرمجة الكائنية Namespace ما هو إلا عبارة عن حاوي تسمح للمطوّر بتحزيم جميع الوظائف تحت اسم محدد وفريد. الصنف أو الفئة Class في البرمجة الكائنية يعتني الصنف بكل ما يتعلّق بميزات وخصائص الكائن، والصنف ما هو إلا قالب template تعريفي بخاصيات properties وبطُرق/وظائف methods الكائن object. الكائن Object في البرمجة الكائنية الكائن ما هو إلا حالة/أمثولة instance من صنف class. الخاصية property في البرمجة الكائنية ما هي إلا مميزات وخصائص الكائن، كاللون مثلًا. الطريقة أو الوظيفة Method في البرمجة الكائنية تعتني الطريقة أو الوظيفة كما يُسميها البعض بقدرات الكائن، مثل قدرة المشي مثلًا، وهي دور أو وظيفة مرتبطة مع صنف class. المشيد Constructor في البرمجة الكائنية ما هو إلا طريقة method تُستدعى في لحظة استهلال instantiate الكائن، وعادةً ما يكون له نفس اسم الصنف الذي يحتويه. الوراثة Inheritance في البرمجة الكائنية يُمكن للصنف أن يرث مميزات من صنف آخر. التغليف Encapsulation في البرمجة الكائنية طريقة في تحزيم البيانات data والطُرق methods التي تستخدم البيانات. التجريد Abstraction في البرمجة الكائنية يجب على الاقتران الحاصل من: الوراثة والطُرق methods والخاصيات properties لكائن معقد وشائك التمثيل برمجيًا أن يعكس الواقع المراد محاكاته في البرمجة الكائنية. تعددية الأشكال Polymorphism في البرمجة الكائنية تحمل كلمة Poly بحد ذاتها المعنى "متعدد" وتحمل الكلمة morphism المعنى "أشكال، ويُشير المفهوم ككل إلى أن أكثر من صنف قد يُعرّف نفس الطريقة method أو الخاصية property. البرمجة المعتمدة على النموذج الأولي Prototype البرمجة المعتمدة على النموذج الأوّلي (Prototype-based programming) ما هي إلا نموذج من البرمجة الكائنية OOP ولكنها لا تستخدم الأصناف classes، بل تقوم أولًا بإعداد سلوك أي صنف class ما ومن ثم تُعيد استخدامه، ويُطلق البعض على هذا النموذج: البرمجة بلا أصناف classless، أو البرمجة المَبْدَئِية المنحى prototype-oriented، أو البرمجة المعتمدة على الأمثولة instance-based). يعود أصل اللغة المعتمدة على النموذج الأولي إلى لغة Self، والتي طوّرها David Ungar وRandall Smith، ولكن أسلوب البرمجة بدون أصناف class-less توسّع ونال شهرة كبيرة في العقد الأخير، واُعتمد من قبل العديد من اللغات البرمجية أشهرهم جافا سكريبت. البرمجة الكائنية باستخدام جافا سكريبت المجال Namespace في جافا سكريبت المجال هو أشبه بمستوعب/بحاوية (container) تسمح للمطوّر في تحزيم وظائف تحت اسم فريد، أو اسم تطبيق محدد، ففي جافا سكريبت المجال هو مجرد كائن object كأي كائن آخر يحتوي على طُرق methods، وخاصيات properties، وحتى كائنات objects. ملاحظة هامة: من المهم جدًا الانتباه إلى أنه في جافا سكريبت، لا يوجد فرق بين الكائنات العادية والمجالات namespaces، وهذا يختلف عن اللغات الكائنية الأخرى، الأمر الذي قد يُربك المبرمجين المبتدئين في جافا سكريبت. إن إنشاء مجال namespace في جافا سكريبت بسيطٌ للغاية، فمن خلال إنشاء كائن عام/مشترك/شامل global، ستصبح جميع المُتغيّرات variables والطرق methods، والدوال functions خاصياتٍ لهذا الكائن، ويٌقلل استخدام المجالات namespaces أيضًا من احتمالية تضارب الأسماء في التطبيق، منذ أن كل كائن من كائنات التطبيق ما هي إلى خاصيات كائن شامل/عام معرّفة على مستوى التطبيق. سيُنشئ في الخطوة التالية كائنًا عامًا global وبالاسم MYAPP: // global namespace var MYAPP = MYAPP || {}; يُظهر المثال السابق، كيف تم التأكّد أولًا فيما إذا كان MYAPP معرفًا (سواء في نفس الملف أو في آخر)، ففي حال الإيجاب سيُستخدم الكائن العام MYAPP، وفي حال عدم تعريفه مُسبقًا سيُنشئ كائنًا خالٍ وبالاسم MYAPP والذي سيغلّف encapsulate الطرق methods والدوال functions والمتغيرات variables والكائنات objects. كما يُمكن أيضًا إنشاء مجال فرعي sub-namespaces: // sub namespace MYAPP.event = {}; يوضّح المثال التالي الصيغة المستخدمة في إنشاء مجال namespace وإضافة متغيرات ودوال: // Create container called MYAPP.commonMethod for common method and properties MYAPP.commonMethod = { regExForName: "", // define regex for name validation regExForPhone: "", // define regex for phone no validation validateName: function(name){ // Do something with name, you can access regExForName variable // using "this.regExForName" }, validatePhoneNo: function(phoneNo){ // do something with phone number } } // Object together with the method declarations MYAPP.event = { addListener: function(el, type, fn) { // code stuff }, removeListener: function(el, type, fn) { // code stuff }, getEvent: function(e) { // code stuff } // Can add another method and properties } // Syntax for Using addListener method: MYAPP.event.addListener("yourel", "type", callback); الكائنات الأساسية/القياسية المبنية داخل لغة جافا سكريبت Standard built-in objects تتضمن لغة جافا سكريبت العديد من الكائنات في تركيبتها، على سبيل المثال، يوجد كائنات مثل Math، Object، Array، String، ويُظهر المثال التالي كيفيّة استخدام الكائن Math للحصول على رقم عشوائي باستخدام أحد طُرق method هذا الكائن وهي الطريقة ()random. console.log(Math.random()); ملاحظة: يَفترض المثال السابق وجميع الأمثلة التالية في المقال وجود دالة function بالاسم ()console.log معرّفة تعريفًا عامًا (globally)، مع العلم أن هذه الدالة ليست جزء من اللغة نفسها، ولكنها دالة متوفّرة في العديد من متصفحات الإنترنت لأغراض تشخيص الشيفرة البرمجية debugging. يُمكن العودة إلى مرجع لغة جافا سكريبت: الكائنات الأصلية المعيارية للحصول على قائمة بالكائنات المبينة داخل لغة جافا سكريبت نفسها. كل كائن في جافا سكريبت هو حالة/أمثولة instance من الكائن Object ويَرث كافة خاصياته properties وطُرقه methods. الكائنات المخصصة Custom objects في جافا سكريبت الصنف/الفئة The class لغة جافا سكريبت لغة من النوع prototype-based ولا تحتوي على العبارة class كما هو حال باقي لغات البرمجة الكائنية، كما في روبي أو بايثون، ويُربك هذا الأمر المبرمجين المعتادين على اللغات التي تعتمد على هذه العبارة أو المفهوم، وتستخدم جافا سكريبت بدلًا من ذلك الدوال functions لمحاكات مفهوم الأصناف classes، وتعريف صنف هو بسهولة تعريف أي دالّة: var Person = function () {}; الكائن (أمثولة الصنف class instance) يتطلب إنشاء حالة/أمثولة instance جديدة من كائن obj استخدام العبارة new obj، وتعيين النتيجة إلى متغيّر بغرض الوصول إلى فيما بعد. عُرّف في الشيفرة السابقة صنف class بالاسم Person، وفي الشيفرة التالية، سيُنشئ حالتين/أمثولتين instances من هذا الصنف، الأولى بالاسم person1 والثانية بالاسم person2. var person1 = new Person(); var person2 = new Person(); المشيد The constructor يُستدعى المُشيّد constructor في لحظة الاستهلال instantiation (اللحظة التي يُنشئ فيها الكائن)، والمُشيّد ما هو إلا طريقة method من طُرق الصنف class، وفي جافا سكريبت تعمل الدالة على تشييد الكائن، ولذلك لا داعي إلى تعريف طريقة method من أجل عميلة التشييد، وكل إجراء مصرّح في الصنف class يُنفّذ في لحظة الاستهلال instantiation. يُستخدم المُشيّد في تعيين خاصيات properties الكائن، أو في استدعاء طُرق methods معينة لتحضير الكائن للاستخدام، وأما إضافة طُرق صنف وتعريفها يحدث باستخدام صيغة syntax مختلفة سنتطرّق إليها فيما بعد خلال المقال. تُظهر الشيفرة التالية كيف يُسجّل log (يُرسل رسالة نصية إلى طرفية المتصفح console) مُشيّد الصنف Person رسالة نصية حينما يُستهل instantiated. var Person = function () { console.log('instance created'); }; var person1 = new Person(); var person2 = new Person(); الخاصية The property (خاصية الكائن object attribute) الخاصيات properties ما هي إلا متغيرات محتوات في الصنف class، وكل حالة/أمثولة من الكائن تمتلك هذه الخاصيات، وتُعيّن الخاصيات في دالة مُشيّد الصنف بحيثُ تُنشئ مع كل حالة/أمثولة instance. إن الكلمة المفتاحية this، والتي تُشير إلى الكائن الحالي، تسمح للمطوّر بالعمل مع الخاصيات من ضمن الصنف، والوصول (قراءةً وكتابةً) إلى الخاصية property من خارج الصنف يكون من خلال الصيغة InstanceName.Property كما هو الأمر في لغة C++ (سي بلس بلس) وJava والعديد من اللغات الأخرى، ومن داخل الصنف تُستخدم الصيغة this.Property للحصول على قيمة الخاصية أو لتعيين قيمتها. في الشيفرة التالية، عُرّفت الخاصية firstName للصنف Person وفي لحظة الاستهلال instantiation: var Person = function (firstName) { this.firstName = firstName; console.log('Person instantiated'); }; var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects console.log('person1 is ' + person1.firstName); // logs "person1 is Alice" console.log('person2 is ' + person2.firstName); // logs "person2 is Bob" الطرق The methods الطرق methods ما هي إلا دوال (وتُعرّف كما تعرّف الدوال functions)، فيما عدا ذلك فهي تُشبه الخاصيات properties، واستدعاء طريقة method مشابه إلى الوصول إلى خاصيّة ما، ولكن مع إضافة () في نهاية اسم الطريقة، وربما مع مُعطيات arguments، ولتعريف طريقة، تُعيّن دالة إلى خاصيّة مُسمّات من خاصيّة الصنف prototype، ويُمكن فيما بعد استدعاء الطريقة على الكائن بنفس الاسم الذي عُيّن للدالة. في الشيفرة التالية، عُرّفت ومن ثم اُستخدِمت الطريقة ()sayHello للصنف Person. var Person = function (firstName) { this.firstName = firstName; }; Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); // call the Person sayHello method. person1.sayHello(); // logs "Hello, I'm Alice" person2.sayHello(); // logs "Hello, I'm Bob" إن الطُرق methods في جافا سكريبت ما هي إلا دالة كائن عادية مرتبطة مع كائن كخاصية property، وهذا يعني أنه يُمكن استدعاء الطُرق خارج السياق، كما في المثال التالي: var Person = function (firstName) { this.firstName = firstName; }; Person.prototype.sayHello = function() { console.log("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); var helloFunction = person1.sayHello; // logs "Hello, I'm Alice" person1.sayHello(); // logs "Hello, I'm Bob" person2.sayHello(); // logs "Hello, I'm undefined" (or fails // with a TypeError in strict mode) helloFunction(); // logs true console.log(helloFunction === person1.sayHello); // logs true console.log(helloFunction === Person.prototype.sayHello); // logs "Hello, I'm Alice" helloFunction.call(person1); كما يُظهر المثال السابق، جميع الإحالات المستخدمة في استدعاء الدالة sayHello تُشير إلى نفس الدالة سواءً الاستدعاء الحاصل مع person1 أو Person.prototype أو حتى في المتغيّر helloFunction وقيمة this خلال استدعاء الدالة يعتمد على الكيفية التي تُستدعى فيها، حيث تُشير الكلمة المفتاحية this إلى الكائن الحالي الذي تُستدعى عليه الطريقة method، بمعنى عندما تم استدعاء الطريقة ()sayHello على الكائن person1 فإن this تُشير إلى الكائن person1، وعند استدعاء sayHello على الكائن person2 فإن this تُشير إلى الكائن person2، ولكن إن تم الاستدعاء بطريقة مختلفة، فإن this ستُعيّن تعينًا مختلفًا، فاستدعاء this من المتغيّر (كما في ()helloFunction) سيُعيّن this إلى الكائن العام global (والذي سيكون window في متصفح الإنترنت)، ومنذ أن هذا الكائن (على الأغلب) لا يملك الخاصّيّة firstName، ستكون النتيجة كما هو الحال في المثال السابق “Hello, I’m undefined”، كما يمكن دائمًا تعيين this صراحةً باستخدام Function#call (أو Function#apply) وهو كما كان في نهاية المثال. الوراثة تُستخدم الوراثة في جافا سكريبت في إنشاء صنف class كمثيل مخصص لصنف أو أكثر (تدعم جافا سكريبت وراثة وحيدة فقط single inheritance)، ويُطلق على الصنف المخصص عادةً ابن (child)، ويطلق على الصنف الآخر عادةً الأب (parent)، وفي جافا سكريبت يتمّ ذلك من خلال إسناد حالة/أمثولة من الصنف الأب إلى الصنف الابن، ومن ثم تخصيصه، وفي متصفحات الإنترنت الحديثة يُمكن استخدام Object.create في تحقيق الوراثة inheritance أيضًا. ملاحظة: لا تتفقد جافا سكريبت مُشيّد صنف الابن prototype.constructor (راجع Object.prototype)، وعليه يجب التصريح عن ذلك يدويًا، لمزيد من التفصيل راجع السؤال التالي على Stackoverflow. عُرّف في الشيفرة التالية الصنف Student كصنف ابن للصنف Person، ومن ثم أُعيد تعريف الطريقة ()sayHello وأُضيفت الطريقة ()sayGoodBye علاوة على ذلك. // Define the Person constructor var Person = function(firstName) { this.firstName = firstName; }; // Add a couple of methods to Person.prototype Person.prototype.walk = function(){ console.log("I am walking!"); }; Person.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName); }; // Define the Student constructor function Student(firstName, subject) { // Call the parent constructor, making sure (using Function#call) // that "this" is set correctly during the call Person.call(this, firstName); // Initialize our Student-specific properties this.subject = subject; }; // Create a Student.prototype object that inherits from Person.prototype. // Note: A common error here is to use "new Person()" to create the // Student.prototype. That's incorrect for several reasons, not least // that we don't have anything to give Person for the "firstName" // argument. The correct place to call Person is above, where we call // it from Student. Student.prototype = Object.create(Person.prototype); // See note below // Set the "constructor" property to refer to Student Student.prototype.constructor = Student; // Replace the "sayHello" method Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); }; // Add a "sayGoodBye" method Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); }; // Example usage: var student1 = new Student("Janet", "Applied Physics"); student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student1.walk(); // "I am walking!" student1.sayGoodBye(); // "Goodbye!" // Check that instanceof works correctly console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true فيما يخص السطر ;(Student.prototype = Object.create(Person.prototype في الإصدارات القديمة من جافا سكريبت والتي لا تدعم Object.create يمكن إما استخدام بعض الحيل في خداع المتصفحات –هذه الخدع معروفة إما بالاسم polyfill أو shim—أو استخدام دالة تحقق نفس النتيجة كما في المثال التالي: function createObject(proto) { function ctor() { } ctor.prototype = proto; return new ctor(); } // Usage: Student.prototype = createObject(Person.prototype); التأكّد من أن this تُشير إلى الكائن المطلوب بغض النظر عن كيف للكائن أن يُستهل يمكن أن يكون صعبًا، ومع ذلك يوجد صياغة أبسط من شأنها أن تسهّل الأمر. var Person = function(firstName) { if (this instanceof Person) { this.firstName = firstName; } else { return new Person(firstName); } } التغليف Encapsulation ليس بالضرورة أن يعلم الصنف Student كيف تمّ تنفيذ/تعريف الطريقة ()walk للصنف Person لكي يستطيع استخدام تلك الطريقة، ولا يحتاج الصنف Student إلى تعريف تلك الطريقة صراحةً إلا إذا كان المطلوب التعديل عليها، ويُطلق على هذا الإجراء مفهوم التغليف encapsulation، والذي فيه يَحزم كل صنف البيانات والطُرق methods داخل وحدة/كينونة وحيدة. إخفاء المعلومات سمة شائعة في باقي اللغات البرمجية وعادةً ما توجد كخاصيات/كطُرق إما بالاسم private أو protected، وعلى الرغم من أنه يُمكن مماثلة/محاكاة ذات الأمر في جافا سكريبت، إلا أن هذا الأمر ليس مطلبًا من متطلبات البرمجة الكائنية. التجريد Abstraction التجرير ما هو إلا ميكانيكية تسمح للمطوّر في تجسيد جانب من المشكلة التي يُعمل عليها، إما من خلال الوراثة inheritance (التخصيص specialization) أو التركيب composition، وتُحقق جافا سكريبت التخصيص من خلال الوراثة، والتركيب من خلال السماح لحالات/أمثولات الصنف لتكون قيمًا لخاصيات attributes الكائنات الأخرى. الصنف Function في جافا سكريبت يرث من الصنف Object (وهذا يوضّح التخصيص في هذا النموذج) والخاصية Function.prototype ما هي إلا حالة/أمثولة من الصنف Object (وهذا يوضّح جزئية التركيب composition). var foo = function () {}; // logs "foo is a Function: true" console.log('foo is a Function: ' + (foo instanceof Function)); // logs "foo.prototype is an Object: true" console.log('foo.prototype is an Object: ' + (foo.prototype instanceof Object)); تعددية الأشكال Polymorphism كما أن جميع الطُرق methods والخاصيات properties معرّفة ضمن الخاصية prototype، فيُمكن لبقية الأصناف أن تُعرِّف طُرقًا methods بنفس الاسم، وستكون الطُرق في نطاق الصنف الذي عُرفت به، إلا إذا كان الصنفان على علاقة من نوع أب وابن parent-child، بمعنى آخر أحد الصنفان يرث من الآخر خاتمة إن الأساليب التي تم التطرُّق إليها ليست الأساليب الوحيدة التي يمكن استخدامها في تطبيق مفاهيم البرمجة الكائنية في جافا سكريبت، والتي هي مرنة إلى حد كبير في هذا الصدد، ولم تلجئ إلى أي خدع في تطبيق هذه المفاهيم، ولم تقلّد أيضًا الأساليب والنظريات المستخدمة في اللغات الأخرى، وفي جعبة جافا سكريبت العديد من الأساليب الأخرى لتطبيق مفاهيم متقدمة في البرمجة الكائنية التوجّه، ولكن هذه الأساليب المتقدمة هي خارج إطار مقالة تمهيدية، ربما نتطرّق إليها لاحقًا في الأكاديمية. ترجمة وبتصرّف للمقال Introduction to Object-Oriented JavaScript.1 نقطة