هر چیزی که برای درک Prototype در JavaScript باید بدانید - بخش دوم
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 9 دقیقه

هر چیزی که برای درک Prototype در JavaScript باید بدانید - بخش دوم

به بخش دوم این مقاله خوش آمدید.

اکثر مواقع، prototype در JavaScript افرادی که به تازگی یادگیری آن را شروع کرده‌اند را گیج می‌کند؛ به خصوص اگر این افراد پیش‌زمینه‌ای در C++ یا Java داشته باشند.

در JavaScript، وراثت (inheritance) در مقایسه با C++ یا Java کمی متفاوت کار می‌کند. وراثت JavaScript بیشتر تحت عنوان «وراثت prototypeای» (prototypical inheritance) شناخته می‌شود.

وقتی که شما همچنین با class هم در JavaScript مواجه می‌شوید، درک همه چیز سخت‌تر می‌شود. سینتکس class جدید ظاهری مشابه به C++ یا Java دارد، اما در واقعیت به طور متفاوتی کار می‌کند.

در این مقاله، ما تلاش خواهیم کرد تا وراثت prototypeای را در JavaScript درک کنیم. ما همچنین به سینتکس بر پایه class جدید نگاهی داشته، و سعی خواهیم کرد تا درک کنیم که ماهیت آن چیست.

همانطور که اشاره کردیم، این بخش را با یک قطعه کد شروع خواهیم کرد. با ما همراه باشید...

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');

بگذارید قطعه کد بالا را توضیح دهم.

ما یک سازنده به نام Vehicle داریم که انتظار یک نوع وسیله نقلیه را دارد. با توجه به این که تمام وسایل نقلیه می‌توانند بوق بزنند، ما یک ویژگی به نام blowhorn در prototype مربوط به Vehicle داریم.

یک Bus، یک وسیله نقلیه است و ویژگی‌هایی را از آبجکت Vehicle به ارث خواهد برد.

ما فرض کرده‌ایم که تمام اتوبوس‌ها ۶ چرخ، و روندهای شتاب‌گیری و ترمز مشابهی را خواهند داشت. پس ما ویژگی‌های noOfWheels، accelerator و brake را در prototype مربوط به Bus تعریف کرده‌ایم.

همین منطق مشابه نسبت به خودرو و موتور هم صدق می‌کند.

بیایید به Chrome Developer Tools - > Console برویم و کد خود را اجرا کنیم.

پس از اجرا، ما سه آبجکت با نام‌های myBus، myCar و myMotorBike خواهیم داشت.

دستور console.dir(mybus) را در کنسول تایپ کرده، و کلید Enter را بفشارید. از آیکون مثلث برای گسترش آن استفاده کنید، و سپس چیزی به مانند این تصویر خواهید دید:

زیر myBus، ما ویژگی‌های make و vehicleType را داریم. دقت کنید که مقدار __proto__، در واقع prototype مربوط به Bus است. تمام ویژگی‌های prototype آن در اینجا در دسترس هستند: accelerator، brake، noOfWheels.

حال نگاهی به اولین آبجکت __proto__ داشته باشید. این آبجکت، یک آبجکت __proto__ دیگر را به عنوان ویژگی خود داشت.

و ما در زیر آن ویژگی‌های blowhorn و constructor را داریم.

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

خط بالا را به یاد دارید؟ Object.create(Vehicle.prototype) یک آبجکت خالی را خواهد ساخت که prototype آن Vehicle.prototype است. ما این آبجکت را به عنوان ویژگی Bus تنظیم می‌کنیم. ما برای Vehicle.prototype هیچ‌گونه prototypeای را تعریف نکرده‌ایم؛ پس به طور پیشفرض prototype خود را از Object.prototype به ارث می‌برد.

بیایید در اینجا جادو را ببینیم:

ما می‌توانیم به ویژگی make دسترسی داشته باشیم؛ زیرا ویژگی خود myBus است.

ما می‌توانیم به ویژگی brake از prototype مربوط به myBus دسترسی داشته باشیم

ما می‌توانیم به ویژگی blowhorn از prototype مربوط به prototype مربوط به myBus دسترسی داشته باشیم.

ما می‌توانیم به ویژگی hasOwnProperty از prototype مربوط به prototype مربوط به prototype مربوط به myBus دسترسی داشته باشیم.

این موضوع، زنجیره‌بندی prototype نام دارد. هر زمان که شما به یک ویژگی از یک آبجکت در JavaScript دسترسی دارید، JavaScript اول بررسی می‌کند که آیا ویژگی مورد نظر داخل آبجکت در دسترس است یا نه. اگر نباشد، آبجکت prototype آن را بررسی می‌کند. اگر باشد هم که خوب است و شما می‌توانید مقدار ویژگی مورد نظر را به دست بیاورید. در غیر این صورت، JavaScript بررسی خواهد کرد که آیا ویژگی مورد نظر در prototype مربوط به prototype وجود دارد یا نه، اگر وجود نداشته باشد باز هم در prototype مربوط به prototype مربوط به prototype بررسی می‌کند و این روند همینطور ادامه دارد.

پس در این موقعیت، JavaScript تا چه مدتی بررسی خواهد کرد؟ اگر ویژگی مورد نظر در هر نقطه‌ای یافته شده، یا این که مقدار __proto__ در هر نقطه‌ای برابر با null یا undefined باشد، JavaScript متوقف خواهد شد. سپس هم یک خطا را بروز خواهد داد، تا به شما اطلاع دهد که نتوانست ویژگی مطلوب شما را پیدا کند.

وراثت در JavaScript با کمک زنجیره‌بندی prototype به این صورت کار می‌کنند.

شما می‌توانید مثال‌های بالا را با myCar و myMotorBike آزمایش کنید.

همانطور که می‌دانیم، در JavaScript هر چیزی یک آبجکت است. شما پی خواهید برد که برای هر نمونه، زنجیره prototype با Object.prototype به پایان می‌رسد.

استثنای موجود برای قانون بال، وقتی است که شما یک آبجکت را با استفاده از Object.create(null) بسازید.

var obj = Object.create(null)

با کد بالا، obj یک آبجکت خالی بدون هیچ‌گونه prototype خواهد بود.

آیا شما می‌توانید آبجکت prototype یک آبجکت از پیش موجود را تغییر دهید؟ بله، با استفاده از Object.setPrototype می‌توانید.

آیا می‌خواهید بررسی کنید که یک ویژگی، ویژگی خود یک آبجکت است یا نه؟ شما می‌دانید چگونه این کار را انجام دهید. Object.hasOwnProperty به شما خواهد گفت که ویژگی مورد نظر از خود آبجکت می‌آید، یا از زنجیره prototype آن.

دقت کنید که __proto__ همچنین تحت عنوان [[Prototype]] هم مورد اشاره قرار می‌گیرد.

حال وقت یک استراحت دیگر فرا رسیده است. پس از این که آماده شدید، به این مقاله برگردید. سپس ما کار خود را ادامه خواهیم داد و این بخش، بخش آخر خواهد بود.

درک کلاس‌ها در JavaScript

طبقه گفته MDN:

«کلاس‌های JavaScript که در ECMAScript 2015 معرفی شدند، در درجه اول یک شکر سینتکسی برای وراثت از پیش موجود JavaScript بر پایه prototype  هستند. کلاس سینتکس هیچ مدل وراثت آبجکت‌گرای جدیدی را به JavaScript وارد نمی‌کند.»

کلاس‌ها در JavaScript یک سینتکس بهتر برای رسیدن به چیزی که در بالا به آن رسیدیم را به روشی بسیار بهتر فراهم می‌کنند. اول بیایید نگاهی به سینتکس کلاس داشته باشیم.

class Myclass {
  constructor(name) {
    this.name = name;
  }
 
  tellMyName() {
    console.log(this.name)
  }
}

const myObj = new Myclass("John");

متد constructor یک نوع خاص از متد است. این متد به طور خودکار هر زمان که شما یک نمونه از این کلاس را بسازید، اجرا خواهد شد. در داخل بدنه کلاس شما، فقط یک بار بروز constructor ممکن است.

این متدهایی که شما داخل بدنه کلاس تعریف خواهید کرد، به آبجکت prototype منتقل خواهند شد.

اگر می‌خواهید مقداری ویژگی داخل نمونه مورد نظر داشته باشید، می‌توانید آن‌ها را در داخل constructor تعریف کنید. به مانند پیش‌تر که ما با استفاده از this.name = name انجام دادیم.

بیایید نگاهی به myObj ما داشته باشیم.

دقت کنید که ما ویژگی name را داخل نمونه‌ای که myObj است داریم، و متد tellMyName هم در prototype قرار دارد.

قطعه کد زیر را در نظر داشته باشید:

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

const myObj = new Myclass("John");

بیایید خروجی آن را ببینیم:

دقت کنید که lastName به جای prototype، به داخل نمونه منتقل شده است. فقط متدهایی که شما داخل بدنه کلاس تعریف می‌کنید، به prototype منتقل خواهند شد. گرچه یک استثنا وجود دارد.

قطعه کد زیر را در نظر داشته باشید:

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

const myObj = new Myclass("John");

خروجی:

دقت کنید که حال tellMyName یک تابع پیکانی است، و به جای prototype به نمونه منتقل شده است. پس به یاد داشته باشید که توابع پیکانی همیشه به نمونه منتقل خواهند شد، و همیشه با احتیاط از آن‌ها استفاده کنید.

بیایید به ویژگی‌های کلاس استاتیک نگاهی داشته باشیم:

class Myclass {
  static welcome() {
    console.log("Hello World");
  }
}

Myclass.welcome();
const myObj = new Myclass();
myObj.welcome();

خروجی:

ویژگی‌های استاتیک، چیزی هستتند که شما می‌توانید بدون ساخت یک نمونه از کلاس به آن‌ها دسترسی داشته باشید. در سمت دیگر، نمونه مورد نظر به ویژگی‌های استاتیک یک کلاس دسترسی نخواهد داشت.

پس آیا ویژگی استاتیک، یک مفهوم جدید است که فقط در کلاس و نه در JavaScript قدیمی در دسترس است؟ نه، این مفهوم در JavaScript قدیمی هم در دسترس است. متد قدیمی رسیدن به ویژگی استاتیک، این است:

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

حال بیایید نگاهی به نحوه رسیدن به وراثت با استفاده از کلاس‌ها داشته باشیم:

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");

ما کلاس‌های دیگر را با استفاده از کلیدواژه extends به ارث می‌بریم.

super() به سادگی سازنده کلاس والد را اجرا خواهد کرد. اگر شما در حال به ارث بردن از کلاس‌های دیگر هستید و از سازنده در کلاس فرزند خود استفاده می‌کنید، باید super() را داخل سازنده کلاس فرزند خود فراخوانی کنید؛ در غیر این صورت یک خطا بروز داده خواهد شد.

ما از پیش می‌دانیم که اگر هر ویژگی‌ای به جز یک تابع معمولی را داخل بدنه کلاس تعریف کنیم، این ویژگی به جای prototype به نمونه منتقل خواهد شد. پس ما noOfWheel را بر روی Bus.prototype تعریف می‌کنیم.

اگر می‌خواهید در داخل بدنه کلاس خود متد کلاس والد را اجرا کنید، می‌توانید با استفاده از super.parentClassMethod() این کار را انجام دهید.

خروجی:

خروجی بالا ظاهری مشابه به رویکرد قبلی بر پایه تابع دارد.

جمع‌بندی

پس آیا باید از سینتکس کلاس جدید استفاده کنید، یا سینتکس سازنده قدیمی؟ به نظر من هیچ پاسخ قاطعی برای این سوال وجود ندارد. این تصمیم به موقعیت استفاده شما بستگی دارد.

در این مقاله، من برای بخش‌ کلاس‌ها، نحوه رسیدن به کلاس‌های وراثت prototypeای را نشان داده‌ام. چیزهای بیشتری برای دانستن درباره کلاس‌های JavaScript وجود دارند، اما این موراد خارج از محدوده این مقاله هستند.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
5 از 2 رای

/@er79ka

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید