كل ما تحتاج إلى معرفته لفهم النموذج الأولي لـ JavaScript

في معظم الأحيان ، يربك النموذج الأولي لـ JavaScript الأشخاص الذين بدأوا للتو في تعلم JavaScript - خاصةً إذا كانوا من خلفية C ++ أو Java.

في JavaScript ، تعمل الوراثة بشكل مختلف قليلاً مقارنة بـ C ++ أو Java. تُعرف وراثة JavaScript على نطاق واسع باسم "الوراثة النموذجية".

تصبح الأمور أكثر صعوبة في الفهم عندما تصادفها أيضًا classفي JavaScript. classتبدو الصيغة الجديدة مشابهة لـ C ++ أو Java ، لكنها في الواقع تعمل بشكل مختلف.

في هذه المقالة ، سنحاول فهم "الوراثة النموذجية" في JavaScript. نحن ننظر أيضًا في classبناء الجملة الجديد ونحاول فهم ما هو في الواقع. اذا هيا بنا نبدأ.

أولاً ، سنبدأ بوظيفة JavaScript والنموذج الأولي للمدرسة القديمة.

فهم الحاجة إلى النموذج الأولي

إذا سبق لك العمل مع مصفوفات JavaScript أو كائنات أو سلاسل ، فقد لاحظت أن هناك طريقتين متاحتين افتراضيًا.

فمثلا:

var arr = [1,2,3,4];arr.reverse(); // returns [4,3,2,1]
var obj = {id: 1, value: "Some value"};obj.hasOwnProperty('id'); // returns true
var str = "Hello World";str.indexOf('W'); // returns 6

هل تساءلت يومًا من أين تأتي هذه الأساليب؟ لم تحدد هذه الطرق بنفسك.

هل يمكنك تحديد الأساليب الخاصة بك مثل هذا؟ يمكنك القول أنه يمكنك بهذه الطريقة:

var arr = [1,2,3,4];arr.test = function() { return 'Hi';}arr.test(); // will return 'Hi'

هذا سوف يعمل ، ولكن فقط لهذا المتغير يسمى arr. لنفترض أن لدينا متغيرًا آخر يسمى arr2ثم arr2.test()سيظهر خطأ "TypeError: arr2.test ليس دالة".

إذن كيف تصبح هذه الطرق متاحة لكل مثيل من المصفوفة / السلسلة / الكائن؟ هل يمكنك إنشاء طرقك الخاصة بنفس السلوك؟ الجواب نعم. عليك أن تفعل ذلك بالطريقة الصحيحة. للمساعدة في ذلك ، يأتي نموذج JavaScript الأولي.

دعونا نرى أولاً من أين تأتي هذه الوظائف. ضع في اعتبارك مقتطف الشفرة أدناه:

var arr1 = [1,2,3,4];var arr2 = Array(1,2,3,4);

لقد أنشأنا اثنين من صفائف بطريقتين مختلفتين: arr1مع الحرفية مجموعة و arr2مع Arrayوظيفة المنشئ. كلاهما مكافئ لبعضهما البعض مع بعض الاختلافات التي لا تهم هذه المقالة.

نأتي الآن إلى دالة المُنشئ Array- وهي وظيفة مُنشئ مُحددة مسبقًا في JavaScript. إذا قمت بفتح كروم أدوات المطور والذهاب إلى وحدة التحكم ونوع console.log(Array.prototype)وضرب enterسترى شيئا مثل أدناه:

هناك سترى جميع الأساليب التي كنا نتساءل عنها. إذن نحن الآن نصل من حيث تأتي هذه الوظائف. لا تتردد في المحاولة مع String.prototypeو Object.prototype.

لنقم بإنشاء دالة المُنشئ البسيطة الخاصة بنا:

var foo = function(name) { this.myName = name; this.tellMyName = function() { console.log(this.myName); }}
var fooObj1 = new foo('James');fooObj1.tellMyName(); // will print Jamesvar fooObj2 = new foo('Mike');fooObj2.tellMyName(); // will print Mike

هل يمكنك تحديد مشكلة أساسية في الكود أعلاه؟ المشكلة هي أننا نهدر الذاكرة بالطريقة المذكورة أعلاه. لاحظ أن الطريقة tellMyNameهي نفسها لكل مثيل foo. في كل مرة نقوم فيها بإنشاء مثيل fooللطريقة ، tellMyNameينتهي الأمر بأخذ مساحة في ذاكرة النظام. إذا كان tellMyNameهو نفسه بالنسبة لجميع الحالات ، فمن الأفضل الاحتفاظ به في مكان واحد وجعل جميع مثيلاتنا تشير من هذا المكان. دعونا نرى كيف نفعل ذلك.

var foo = function(name) { this.myName = name;}
foo.prototype.tellMyName = function() { console.log(this.myName);}
var fooObj1 = new foo('James');fooObj1.tellMyName(); // will print Jamesvar fooObj2 = new foo('Mike');fooObj2.tellMyName(); // will print Mike

دعنا نتحقق من الاختلاف مع النهج أعلاه والنهج السابق. باستخدام النهج أعلاه ، إذا كنت console.dir()في الحالات ، فسترى شيئًا مثل هذا:

لاحظ أنه كخاصية للحالات التي لدينا فقط myname. tellMyNameيتم تعريفه تحت __proto__. سوف آتي إلى هذا __proto__بعد وقت ما. الأهم من ذلك ، لاحظ أن مقارنة tellMyNameكلتا الحالتين يتم تقييمها على أنها صحيحة. تُقيّم مقارنة الوظائف في JavaScript صوابًا فقط إذا كانت مراجعها هي نفسها. هذا يثبت أنه tellMyNameلا يستهلك ذاكرة إضافية لحالات متعددة.

دعونا نرى نفس الشيء مع النهج السابق:

لاحظ أنه tellMyNameيتم تعريف هذا الوقت كخاصية للحالات. لم يعد تحت ذلك __proto__. لاحظ أيضًا أن هذه المرة تُقيم مقارنة الوظائف بالخطأ. هذا لأنهم في موقعين مختلفين للذاكرة ومراجعهم مختلفة.

أتمنى الآن أن تفهم ضرورة prototype.

الآن دعونا نلقي نظرة على مزيد من التفاصيل حول النموذج الأولي.

سيكون لكل وظيفة JavaScript prototypeخاصية من نوع الكائن. يمكنك تحديد الممتلكات الخاصة بك تحت prototype. عندما تستخدم الوظيفة كدالة مُنشئ ، فإن جميع مثيلاتها سترث الخصائص من prototypeالكائن.

الآن دعنا نصل إلى تلك __proto__الخاصية التي رأيتها أعلاه. هذا __proto__هو مجرد إشارة إلى كائن النموذج الأولي الذي ورث المثيل منه. تبدو معقدة؟ الأمر في الواقع ليس بهذا التعقيد. دعونا نتخيل هذا بمثال.

النظر في الكود أدناه. نحن نعلم بالفعل أن إنشاء مصفوفة بمصفوفة حرفية سيرث الخصائص من Array.prototype.

var arr = [1, 2, 3, 4];

ما قلته للتو أعلاه هو " إن __proto__مجرد إشارة إلى كائن النموذج الأولي الذي ورث المثيل منه ". لذلك arr.__proto__يجب أن يكون هو نفسه مع Array.prototype. دعنا نتحقق من هذا.

الآن لا ينبغي لنا الوصول إلى كائن النموذج الأولي باستخدام __proto__. وفقًا لـ MDN ، فإن الاستخدام محبط __proto__للغاية وقد لا يكون مدعومًا في جميع المتصفحات. الطريقة الصحيحة للقيام بذلك:

var arr = [1, 2, 3, 4];var prototypeOfArr = Object.getPrototypeOf(arr);prototypeOfArr === Array.prototype;prototypeOfArr === arr.__proto__;

السطر الأخير من البرامج رمز أعلاه مقتطف __proto__و Object.getPrototypeOfترجع نفس الشيء.

حان الوقت الآن للاستراحة. احصل على فنجان قهوة أو أي شيء تريده وجرب الأمثلة أعلاه بنفسك. بمجرد أن تصبح جاهزًا ، عد إلى هذه المقالة وسنواصل بعد ذلك.

تسلسل النموذج الأولي والوراثة

في الشكل 2 أعلاه ، هل لاحظت وجود جسم آخر __proto__داخل __proto__الجسم الأول ؟ إذا لم يكن الأمر كذلك ، فانتقل قليلاً إلى الشكل: 2. ألق نظرة وعد هنا مرة أخرى. سنناقش الآن ما هو في الواقع. يُعرف ذلك باسم تسلسل النموذج الأولي.

في JavaScript ، نحقق الوراثة بمساعدة تسلسل النموذج الأولي.

Consider this example: We all understand the term “Vehicle”. A bus could be called as a vehicle. A car could be called a vehicle. A motorbike could be called a vehicle. Bus, car, and motorbike have some common properties that's why they are called vehicle. For example, they can move from one place to another. They have wheels. They have horns, etc.

Again bus, car, and motorbike can be of different types for example Mercedes, BMW, Honda, etc.

In the above illustration, Bus inherits some property from vehicle, and Mercedes Benz Bus inherits some property from bus. Similar is the case for Car and MotorBike.

Let's establish this relationship in JavaScript.

First, let's assume a few points for the sake of simplicity:

  1. All buses have 6 wheels
  2. Accelerating and Braking procedures are different across buses, cars, and motorbikes, but the same across all buses, all cars, and all motorbikes.
  3. All vehicles can blow the horn.
function Vehicle(vehicleType) { //Vehicle Constructor this.vehicleType = vehicleType;}
Vehicle.prototype.blowHorn = function () { console.log('Honk! Honk! Honk!'); // All Vehicle can blow Horn}
function Bus(make) { // Bus Constructor Vehicle.call(this, "Bus"); this.make = make}
Bus.prototype = Object.create(Vehicle.prototype); // Make Bus constructor inherit properties from Vehicle Prototype Object
Bus.prototype.noOfWheels = 6; // Let's assume all buses have 6 wheels
Bus.prototype.accelerator = function() { console.log('Accelerating Bus'); //Bus accelerator}
Bus.prototype.brake = function() { console.log('Braking Bus'); // Bus brake}
function Car(make) { Vehicle.call(this, "Car"); this.make = make;}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.noOfWheels = 4;
Car.prototype.accelerator = function() { console.log('Accelerating Car');}
Car.prototype.brake = function() { console.log('Braking Car');}
function MotorBike(make) { Vehicle.call(this, "MotorBike"); this.make = make;}
MotorBike.prototype = Object.create(Vehicle.prototype);
MotorBike.prototype.noOfWheels = 2;
MotorBike.prototype.accelerator = function() { console.log('Accelerating MotorBike');}
MotorBike.prototype.brake = function() { console.log('Braking MotorBike');}
var myBus = new Bus('Mercedes');var myCar = new Car('BMW');var myMotorBike = new MotorBike('Honda');

Allow me to explain the above code snippet.

We have a Vehicle constructor which expects a vehicle type. As all vehicles can blow their horns, we have a blowHorn property in Vehicle's prototype.

As Bus is a vehicle it will inherit properties from Vehicle object.

We have assumed all buses will have 6 wheels and have the same accelerating and braking procedures. So we have noOfWheels, accelerator and brake property defined in Bus’s prototype.

Similar logic applies for Car and MotorBike.

Let’s go to Chrome Developer Tools -> Console and execute our code.

After execution, we will have 3 objects myBus, myCar, and myMotorBike.

Type console.dir(mybus) in the console and hit enter. Use the triangle icon to expand it and you will see something like below:

Under myBus we have properties make and vehicleType. Notice the value of __proto__ is prototype of Bus. All the properties of its prototype are available here: accelerator, brake, noOfWheels.

Now have a look that the first __proto__ object. This object has another __proto__ object as its property.

Under which we have blowHorn and constructor property.

Bus.prototype = Object.create(Vehicle.prototype);

Remember the line above? Object.create(Vehicle.prototype) will create an empty object whose prototype is Vehicle.prototype. We set this object as a prototype of Bus. For Vehicle.prototype we haven’t specified any prototype so by default it inherits from Object.prototype.

Let’s see the magic below:

We can access the make property as it is myBus's own property.

We can access the brake property from myBus's prototype.

We can access the blowHorn property from myBus's prototype’s prototype.

We can access the hasOwnProperty property from myBus's prototype’s prototype’s prototype. :)

This is called prototype chaining. Whenever you access a property of an object in JavaScript, it first checks if the property is available inside the object. If not it checks its prototype object. If it is there then good, you get the value of the property. Otherwise, it will check if the property exists in the prototype’s prototype, if not then again in the prototype’s prototype’s prototype and so on.

So how long it will check in this manner? It will stop if the property is found at any point or if the value of __proto__ at any point is null or undefined. Then it will throw an error to notify you that it was unable to find the property you were looking for.

This is how inheritance works in JavaScript with the help of prototype chaining.

Feel free to try the above example with myCar and myMotorBike.

As we know, in JavaScript everything is an object. You will find that for every instance, the prototype chain ends with Object.prototype.

The exception for the above rule is if you create an object with Object.create(null)

var obj = Object.create(null)

With the above code obj will be an empty object without any prototype.

For more information on Object.create check out the documentation on MDN.

Can you change the prototype object of an existing object? Yes, with Object.setPrototypeOf() you can. Check out the documentation in MDN.

Want to check if a property is the object’s own property? You already know how to do this.Object.hasOwnProperty will tell you if the property is coming from the object itself or from its prototype chain. Check out its documentation on MDN.

Note that __proto__ also referred to as [[Prototype]].

حان الوقت الآن لاستراحة أخرى. بمجرد أن تكون جاهزًا ، عد إلى هذه المقالة. سنواصل بعد ذلك وأعد أن هذا هو الجزء الأخير.

فهم الفئات في JavaScript

بحسب MDN:

فئات JavaScript ، التي تم تقديمها في ECMAScript 2015 ، هي في الأساس سكر نحوي على الميراث القائم على النموذج الأولي لـ JavaScript. لا يقدم بناء الجملة نموذج وراثة كائني جديد لجافا سكريبت.

ستوفر الفصول في JavaScript بنية أفضل لتحقيق ما فعلناه أعلاه بطريقة أوضح بكثير. دعنا نلقي نظرة على بناء الجملة أولا.

class Myclass { constructor(name) { this.name = name; } tellMyName() { console.log(this.name) }}
const myObj = new Myclass("John");

constructorالطريقة هي نوع خاص من الطرق. سيتم تنفيذه تلقائيًا عندما تقوم بإنشاء مثيل لهذه الفئة. داخل جسد صفك. من constructorالممكن حدوث مرة واحدة فقط .

The methods that you will define inside the class body will be moved to the prototype object.

If you want some property inside the instance you can define it in the constructor, as we did with this.name = name.

Let’s have a look into our myObj.

Note that we have the name property inside the instance that is myObj and the method tellMyName is in the prototype.

Consider the code snippet below:

class Myclass { constructor(firstName) { this.name = firstName; } tellMyName() { console.log(this.name) } lastName = "lewis";}
const myObj = new Myclass("John");

Let’s see the output:

See that lastName is moved into the instance instead of prototype. Only methods you that you declare inside the Class body will be moved to prototype. There is an exception though.

Consider the code snippet below:

class Myclass { constructor(firstName) { this.name = firstName; } tellMyName = () => { console.log(this.name) } lastName = "lewis";}
const myObj = new Myclass("John");

Output:

Note that tellMyName is now an arrow function, and it has been moved to the instance instead of prototype. So remember that arrow functions will always be moved to the instance, so use them carefully.

Let’s look into static class properties:

class Myclass { static welcome() { console.log("Hello World"); }}
Myclass.welcome();const myObj = new Myclass();myObj.welcome();

Output:

Static properties are something that you can access without creating an instance of the class. On the other hand, the instance will not have access to the static properties of a class.

So is static property a new concept that is available only with the class and not in the old school JavaScript? No, it’s there in old school JavaScript also. The old school method of achieving static property is:

function Myclass() {}Myclass.welcome = function() { console.log("Hello World");}

Now let’s have a look at how we can achieve inheritance with classes.

class Vehicle { constructor(type) { this.vehicleType= type; } blowHorn() { console.log("Honk! Honk! Honk!"); }}
class Bus extends Vehicle { constructor(make) { super("Bus"); this.make = make; } accelerator() { console.log('Accelerating Bus'); } brake() { console.log('Braking Bus'); }}
Bus.prototype.noOfWheels = 6;
const myBus = new Bus("Mercedes");

We inherit other classes using the extends keyword.

super() will simply execute the parent class’s constructor. If you are inheriting from other classes and you use the constructor in your child class, then you have to call super() inside the constructor of your child class otherwise it will throw an error.

We already know that if we define any property other than a normal function in the class body it will be moved to the instance instead of prototype. So we define noOfWheel on Bus.prototype.

Inside your class body if you want to execute parent class’s method you can do that using super.parentClassMethod().

Output:

The above output looks similar to our previous function based approach in Fig: 7.

Wrapping up

So should you use new class syntax or old constructor based syntax? I guess there is no definite answer to this question. It depends on your use case.

في هذه المقالة ، بالنسبة لجزء الفئات ، لقد أوضحت للتو كيف يمكنك تحقيق فئات وراثية نموذجية. هناك المزيد لمعرفته حول فئات JavaScript ، ولكن هذا خارج نطاق هذه المقالة. تحقق من توثيق الفصول على MDN. أو سأحاول كتابة مقال كامل عن الفصول في وقت ما.

إذا ساعدك هذا المقال في فهم النماذج الأولية ، فسأكون ممتنًا لو تفضلت بالتصفيق قليلاً.

إذا كنت تريد مني أن أكتب عن موضوع آخر ، فأخبرني بذلك في الردود.

يمكنك أيضًا التواصل معي عبر LinkedIn.

شكرا لقرائتك. :)