تحديد الفئة أو الأوبجكت ECMAScript
- الصفحة السابقة مجال الكائن
- الصفحة التالية تعديل الكائن
استخدام الكائنات المسبقة التعريف هو جزء من قدرات لغة البرمجة الموجهة للكائنات، ولكن القوة الحقيقية تكمن في القدرة على إنشاء فئات و كائنات مخصصة.
يملك ECMAScript العديد من الطرق لإنشاء كائنات أو فئات.
طريقة المصنع
الطريقة الأصلية
لأن خصائص الكائن يمكن تعريفها ديناميكياً بعد إنشاء الكائن، لذا يكتب العديد من المطورين في وقت إدخال JavaScript الأولي رموزًا مشابهة للآتي:
var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };
في الكود السابق، يتم إنشاء شيء car. ثم يتم تعيين بعض الخاصيات له: لونه أزرق، لديه أربعة أبواب، يمكنه السفر 25 ميلًا لكل جالون. الخاصية الأخيرة هي دالة تشير إلى دالة، مما يعني أن الخاصية هي دالة. بعد تنفيذ هذا الكود، يمكنك استخدام شيء car.
لكن هناك مشكلة واحدة هنا، وهي الحاجة إلى إنشاء عدة نسخ من car
الحل: الطريقة المصنع
لحل هذه المشكلة، ابتكر المطورون دوال المصنع التي يمكنها إنشاء وتقديم أشياء معينة النوع
على سبيل المثال، يمكن استخدام دالة createCar() لتغليف العمليات التي تم ذكرها سابقًا لإنشاء شيء car:
function createCar() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCar();
في هذا المكان، يحتوي جميع الكود في المثال الأول على دالة createCar(). بالإضافة إلى ذلك، هناك سطر إضافي، يعيد شيء oTempCar كقيمة للدالة. استدعاء هذه الدالة، سيتم إنشاء شيء جديد، ويتم تعيين جميع الخاصيات الضرورية، لنسخ شيء تم شرحه في الماضي.
تحويل المعلمات إلى الدالة
يمكننا أيضًا تعديل دالة createCar()، لتقديم القيم الافتراضية للخصائص بدلاً من فقط تعيين القيم الافتراضية للخصائص:
function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); //يُنتج "red" oCar2.showColor(); //يُنتج "blue"
بإضافة أ参数 إلى دالة createCar()، يمكنك تعيين قيم الخاصيات color،doors وmpg للشيء الجديد الذي يتم إنشاؤه. هذا يجعل لكل شيئين نفس الخاصيات، ولكن مع قيم مختلفة.
تعريف طرق الأشياء خارج دالة المصنع
على الرغم من أن ECMAScript يصبح أكثر رسمية، إلا أن طرق إنشاء الأشياء يتم تجاهلها، وقد استمرت معارضة التوحيد حتى الآن. جزء من السبب هو السبب اللغوي (يبدو غير رسمي عند استخدامه مع عميل بناء الجديد new)، وجزء من السبب هو السبب الوظيفي. السبب الوظيفي يتعلق بإنشاء طريقة لإنشاء الأشياء. في المثال السابق، كل مرة يتم استدعاء دالة createCar()، يتم إنشاء دالة جديدة showColor()، مما يعني أن كل شيء له نسخته الخاصة من showColor(). ولكن في الواقع، كل شيء يتشارك نفس الدالة.
بعض المطورين يحددون طرق الجسم خارج الدالة المصنع، ثم يشيرون إليها من خلال الخاصية لتفادي هذا المشكلة:}
function showColor() { alert(this.color); } function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = showColor; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); //يُنتج "red" oCar2.showColor(); //يُنتج "blue"
في هذا الكود المعدل، تم تعريف الدالة showColor قبل دالة createCar(). داخل دالة createCar()، يتم منح الجسم إشارة إلى دالة showColor موجودة مسبقًا. من الناحية الوظيفية، هذا حل مشكلة إنشاء جسم الدالة مرة أخرى؛ ولكن من الناحية اللغوية، لا تبدو الدالة مثل طريقة الجسم.
كل هذه المشاكل أثارتيحددها المطورظهور المكونة.
طريقة المكونة
إن إنشاء الدالة المكونة يشبه بسهولة إنشاء الدالة المصنع. الخطوة الأولى هي اختيار اسم الفئة، أي اسم الدالة المكونة. وفقًا للعرف، يتم كتابة اسم هذا الاسم بالحروف الكبيرة لتفرقته عن اسم المتغيرات التي تبدأ بحرف صغير. بالإضافة إلى هذا الاختلاف، تبدو الدالة المكونة مثل الدالة المصنع. لنفكر في المثال التالي:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.showColor = function() { alert(this.color); }; } var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25);
يُوضح لكم الآن الفرق بين الكود المقدم والأتمتة. أولاً، لم يتم إنشاء الجسم في الدالة المكونة، بل تم استخدام الكلمة المفتاحية this. عند استخدام محulator new لإنشاء الدالة المكونة، يتم إنشاء جسم قبل تنفيذ السطر الأول، فقط باستخدام this يمكن الوصول إلى الجسم. ثم يمكن تحديد خصائص this مباشرة، بشكل افتراضي هي قيمة العودة للدالة المكونة (لا يتطلب استخدام محulator return).
الآن، باستخدام عمليات new والفئة Car لإنشاء كائنات، يبدو إنشاء كائنات ECMAScript العادية أكثر.
قد تسأل، هل يوجد مشاكل في إدارة الوظائف في هذه الطريقة مثل الطريقة السابقة؟ نعم.
مثل وظائف المصنع، تقوم الدوال المُنشئة بإنشاء وظائف مكررة، وتقوم بإنشاء نسخ مستقلة من الوظيفة لكل عنصر. ومع ذلك، مثل وظائف المصنع، يمكن أيضًا إعادة كتابة الدالة المُنشئة باستخدام دالة خارجية، مما لا يملك أي معنى لغوي. هذا هو ميزة الطريقة الإ原型.
الطريقة الإ原型
يستخدم هذا الأسلوب خصائص prototype كنموذج لإنشاء كائنات جديدة.
في هذا المكان، يتم استخدام دالة مُنشئة فارغة لتعيين اسم الفئة. ثم يتم تعيين جميع الخصائص والمتغيرات مباشرة إلى خصائص prototype. نعيد كتابة المثال السابق، كما يلي:
function Car() { } Car.prototype.color = ";blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car();
في هذا الكود، يتم تعريف الدالة المُنشئة (Car) دون أي كود. في الأسطر التالية، يتم إضافة خصائص إلى خصائص prototype لتحديد خصائص كائن Car. عند استخدام new Car()، يتم فورًا تعيين جميع الخصائص من prototype إلى العنصر المُنشأ، مما يعني أن جميع نماذج Car تحتوي على إشارات إلى دالة showColor(). من الناحية اللغوية، تبدو جميع الخصائص وكأنها تخص كائنًا واحدًا، مما يحل مشاكل الطريقةين السابقتين.
بالإضافة إلى ذلك، يمكن استخدام هذا الأسلوب للتحقق من نوع الكائن الذي يشير إليه المتغير باستخدام عمليات التحقق من النوع instanceof. لذا، فإن الكود التالي سيُنتج TRUE:
alert(oCar1 instanceof Car); //يُنتج "true"
مشاكل الطريقة الإ原型
يبدو أن الطريقة الإ原型 حلًا جيدًا. لسوء الحظ، فإنه ليس كذلك.
أولاً، هذه الدالة المُنشئة لا تأخذ أي معاملات. باستخدام الطريقة الإ原型، لا يمكن تمرير معاملات إلى الدالة المُنشئة لتعيين قيم الخصائص، لأن خصائص color الخاصة بـ Car1 و Car2 تساوي ";blue"، وخصائص doors تساوي 4، وخصائص mpg تساوي 25. هذا يعني أن يجب تغيير قيم الخصائص الافتراضية بعد إنشاء العنصر، وهو أمر مزعج للغاية، لكن هناك المزيد. المشكلة الحقيقية تظهر عندما تشير الخصائص إلى كائن بدلاً من الدالة. لا يسبب مشاركة الدوال مشكلة، لكن كائنات الشبكة تُشارك نادرًا. لنفكر في مثال أدنى:
function Car() { } Car.prototype.color = ";blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.drivers = new Array("Mike","John"); Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car(); oCar1.drivers.push("Bill"); alert(oCar1.drivers); //يظهر "Mike,John,Bill" alert(oCar2.drivers); //يظهر "Mike,John,Bill"
في الكود السابق، يتم إعادة توجيه الخاصية drivers إلى نقطة إدراج Array، يحتوي هذا النص على أسماء "Mike" و"John". لأن drivers هو قيمة مرجعية، فإن كلا نماذج Car يشيران إلى نفس المصفوفة. مما يعني أن إضافة قيمة "Bill" إلى oCar1.drivers، يمكنك رؤيتها أيضًا في oCar2.drivers. عرض أي من هذه النقاط المرجعية، النتيجة هي عرض النص "Mike,John,Bill".
بسبب هذه القضايا التي تواجه إنشاء العناصر، ستجد أنك ستسأل نفسك، هل هناك طريقة معقولة لإنشاء العناصر؟ الجواب هو نعم، يجب استخدامهما معًا بناء المصفوفة والطريقة النموذجية.
طريقة بناء المصفوفة/النموذجية المختلطة
بالاستخدام المتكامل للبناء المصفوفة والطريقة النموذجية، يمكنك إنشاء عناصر كما تفعل في لغات البرمجة الأخرى. هذه الفكرة بسيطة للغاية، حيث يتم تعريف جميع خصائص العنصر غير الدوال باستخدام بناء المصفوفة، وتعريف خصائص الدوال باستخدام الطريقة النموذجية (الأساليب). نتيجة لذلك، يتم إنشاء جميع الدوال مرة واحدة فقط، بينما يحصل كل عنصر على نسخته الخاصة من خصائص العنصر.
قمنا بتعديل المثال السابق، ويمكنك رؤية الكود التالي:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); } Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25); oCar1.drivers.push("Bill"); alert(oCar1.drivers); //يظهر "Mike,John,Bill" alert(oCar2.drivers); //يظهر "Mike,John"
الآن يبدو أكثر مثل إنشاء عنصر عادي. جميع خصائص غير الدوال يتم إنشاؤها في بناء المصفوفة، مما يعني أنك تستطيع منح القيم الافتراضية للخصائص باستخدام معلمات بناء المصفوفة. لأنه يتم إنشاء نسخة واحدة فقط من دالة showColor()، لذا لا توجد تبديد للذاكرة. بالإضافة إلى ذلك، إضافة قيمة "Bill" إلى مصفوفة drivers الخاصة بـ oCar1، لن يؤثر على مصفوفة oCar2، لذا عند عرض قيم هذه المصفوفات، سيظهر oCar1.drivers كـ "Mike,John,Bill"، بينما سيظهر oCar2.drivers كـ "Mike,John". لأننا نستخدم الطريقة النموذجية، لذا يمكننا استخدامهندكس operator instanceof لتحديد نوع العنصر.
هذا الأسلوب هو الأسلوب الرئيسي الذي يتبعه ECMAScript، وهو يمتلك خصائص هذه الأساليب دون آثاره السلبية. ومع ذلك، يظل بعض المبرمجين يعتقدون أن هذا الأسلوب ليس مثاليًا جدًا.
أسلوب الصيغة التفاضلية
للمنشئين الذين يعتادون على استخدام لغات برمجة أخرى، يبدو أسلوب الصيغة المدمجة/النموذج غير متناسق.
class Car { public String color = "blue"; public int doors = 4; public int mpg = 25; public Car(String color, int doors, int mpg) { this.color = color; this.doors = doors; this.mpg = mpg; } public void showColor() { System.out.println(color); } }
تم تجميع فئة Car كلها من قبل Java، لذا يمكنك من خلال رؤية هذا الكود أن تعرف ماذا سيقوم بفعله، حيث يحدد معلومات عن الكائن.
الفكرة الأساسية لأسلوب الصيغة التفاضلية تشبه أسلوب الصيغة المدمجة/النموذج، أي أن يتم تعريف خصائص غير الوظيفية داخل الصيغة، بينما يتم تعريف خصائص الوظيفية باستخدام خصائص النموذج. الفرق الوحيد هو مكان منح العناصر للوظائف. إليك كيف تم إعادة كتابة فئة Car باستخدام أسلوب الصيغة التفاضلية:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); if (typeof Car._initialized == "undefined") { Car.prototype.showColor = function() { alert(this.color); }; Car._initialized = true; } }
لا يتغير هذا المكون حتى يتم التحقق من أن typeof Car._initialized يساوي "undefined". هذا هو الجزء الأكثر أهمية في طريقة الشبكة الأساسية المتغيرة. إذا لم يكن هذا القيمة معرفًا (وكانت القيمة صحيحة عند typeof، فإن القيمة كانت Boolean)، فإن المكون سيستمر في تعريف الطرق في النموذج، ثم سيقوم بتعيين Car._initialized إلى true. إذا كان هذا القيمة معرفًا (وكانت القيمة صحيحة عند typeof، فإن القيمة كانت Boolean)، فإنه لن يتم إنشاء هذا النوع. باختصار، يستخدم هذا النوع علامة (_initialized) لتحديد ما إذا تم تعيين أي طريقة إلى النموذج. يتم إنشاء وتعيين هذا النوع مرة واحدة فقط، وسيكون سعيدًا المطورون التقليديون في OOP بأن هذا الكود يبدو أكثر تشابهًا بمكونات اللغات الأخرى.
طريقة المصنع المدمجة
يستخدم هذا النوع عادةً كطريقة بديلة عندما لا يمكن تطبيق الطريقة السابقة.
يبدو هذا الكود بشكل مشابه جدًا لمكونات المصنع:
function Car() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; }
مختلفًا عن الطريقة الكلاسيكية، يستخدم هذا النوع عمودي الجديد، مما يجعله يبدو مثل مكون حقيقي:
var car = new Car();
بسبب استدعاء عمودي الجديد في داخل مكون Car()، سيتم تجاهل عمودي الجديد الثاني (الموجود خارج المكون)، وسيتم نقل الجسم المكون في الداخل إلى المتغير car.
يملك هذا النوع نفس المشاكل في إدارة الطرق في الأجسام مثل الطريقة الكلاسيكية. يُنصح بشدة: استخدم هذا النوع فقط إذا كان لا يمكن تجنب ذلك.
اختيار الطريقة
كما ذكرنا سابقًا، أكثر الطرق استخدامًا حاليًا هي مزيج الطريقة المكونة من المكونات/النموذج. بالإضافة إلى ذلك، الشبكات الأساسية المتغيرة شائعة أيضًا، وهي تساوي في الوظيفة مع الطريقة المكونة من المكونات/النموذج. يمكنك استخدام أي من هذه الطرق. ولكن لا تستخدم الطريقة الكلاسيكية المكونة من المكونات أو النموذج بشكل منفرد، لأن هذا سيؤدي إلى مشاكل في الكود.
مثال
نقطة مثيرة للاهتمام حول الأجسام هي الطريقة التي يستخدمونها لحل المشاكل. من أكثر المشاكل الشائعة في ECMAScript هو أداء توصيل الأنماط. مثل لغات أخرى، الأنماط في ECMAScript غير قابلة للتغيير، أي أن قيمتها لا يمكن تغييرها. لنفكر في الكود التالي:
var str = "hello "; str += "world";
في الواقع، الخطوات التي يتم تنفيذها خلف الكواليس كالتالي:
- إنشاء نموذج لتخزين "hello ";
- إنشاء نموذج لتخزين "world".
- إنشاء نموذج لتخزين النتيجة المربوطة.
- نسخ محتوى str الحالي إلى النتيجة.
- نسخ "world" إلى النتيجة.
- تحديث str، ليرتبط بالنتيجة.
كل مرة يتم فيها ربط الأنماط، يتم تنفيذ الخطوات 2 إلى 6، مما يجعل هذه العملية استهلاكية جدًا للموارد. إذا تم تكرار هذا العمل مئات أو حتى آلاف المرات، فإنه يمكن أن يؤدي إلى مشاكل في الأداء. الحل هو استخدام كائن Array لتخزين الأنماط، ثم إنشاء النمط النهائي باستخدام طريقة join() (معامل فارغ). تخيل استبدال الكود السابق بالكود التالي:
var arr = new Array(); arr[0] = "hello "; arr[1] = "world"; var str = arr.join(
بهذه الطريقة، لا يهم عدد الأنماط التي يتم إضافتها إلى الصفيف، لأن عملية الاتصال تحدث فقط عند استدعاء طريقة join(). في هذه الحالة، تكون الخطوات التي يتم تنفيذها كالتالي:
- إنشاء نماذج لتخزين النتيجة
- تكرار كل نماط إلى الموقع المناسب في النتيجة
على الرغم من أن هذا الحل جيد، إلا أن هناك طريقة أفضل. المشكلة هي أن هذا الكود لا يعكس دقيقًا نواياها. لجعلها أكثر فهمًا، يمكنك تعبئة هذه الوظيفة باستخدام فئة StringBuffer:
function StringBuffer () { this._strings_ = new Array(); } StringBuffer.prototype.append = function(str) { this._strings_.push(str); }; StringBuffer.prototype.toString = function() { return this._strings_.join( };
هذا الكود يجب الانتباه أولاً إلى خاصية strings، التي تعني الخاصية الخاصة. لها طريقتان فقط، وهي طرق append() و toString(). طريقة append() تحتوي على معامل، ويضيف هذا المعامل إلى مجموعة مصفوفة الأنماط، طريقة toString() تُطلق طريقة join للصفيف، وتُرجع الصفيف المُربوط بشكل فعلي. إذا كنت ترغب في ربط مجموعة من الأنماط باستخدام كائن StringBuffer، يمكنك استخدام الكود التالي:
var buffer = new StringBuffer(); buffer.append("hello "); buffer.append("world"); var result = buffer.toString();
يمكنك اختبار أداء كائن StringBuffer والطريقة التقليدية لتوصيل النصوص باستخدام الكود التالي:
var d1 = new Date(); var str = ""; for (var i=0; i < 10000; i++) { str += "text"; } var d2 = new Date(); document.write("Concatenation with plus: ") + (d2.getTime() - d1.getTime()) + " milliseconds"); var buffer = new StringBuffer(); d1 = new Date(); for (var i=0; i < 10000; i++) { buffer.append("text"); } var result = buffer.toString(); d2 = new Date(); document.write("<br />Concatenation with StringBuffer: ") + (d2.getTime() - d1.getTime()) + " milliseconds");
هذا الكود يجري اختبارين على توصيل النصوص، الأول باستخدام علامة Plus (+)، والثاني باستخدام فئة StringBuffer. يتم توصيل 10000 نص. يتم استخدام قيم التاريخ d1 وd2 لقياس وقت إكمال العملية. يرجى ملاحظة أن يتم منح التاريخ الحالي والوقت الحالي للكائن Date إذا لم يتم تقديم أي معامل. يمكن حساب وقت إكمال عملية التوصيل عن طريق طرح قيم الميليسي ثانية (باستخدام إعادة توجيه getTime() مرة أخرى). هذا هو طريقة قياس أداء JavaScript الشائعة. يمكن أن تساعدك نتائج هذا الاختبار في مقارنة كفاءة استخدام فئة StringBuffer مع استخدام علامة Plus.
- الصفحة السابقة مجال الكائن
- الصفحة التالية تعديل الكائن