برنامه نویسی و طراحی سایت

Prototype در جاوا اسکریپت چیست؟ – توضیح به زبان ساده + مثال و کد

Prototype در جاوا اسکریپت چیست؟ – توضیح به زبان ساده + مثال و کد

لازمه یادگیری زبان برنامه نویسی جاوا اسکریپت آشنایی خوب و عمیق با مفهوم شی در برنامه نویسی است. تسلط بر مفهوم «اشیا» (Objects) و مباحث پیرامون آن به عنوان یکی از اجزای اساسی زبان‌های برنامه نویسی شی گرا محسوب می‌شود که جاوا اسکریپت نیز از این قاعده مستثنی نیست. در این مطلب از مجله تم آف پیرامون نمونه‌های اولیه یا Prototype در جاوا اسکریپت بحث شده است تا مفهوم اشیا در جاوا اسکریپت و نحوه تعامل با آن‌ها را بهتر درک کنیم. پیش از پرداختن به بحث Prototype در جاوا اسکریپت، آشنایی با مفهوم اشیا یا شی در جاوا اسکریپت خالی از لطف نیست.

فهرست مطالب این نوشته
اشیا در جاوا اسکریپت

نمونه‌ سازی عملکردی

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

object.create در جاوا اسکریپت چیست؟

نمونه سازی کاربردی با متدهای مشترک و Object.create

Prototype در جاوا اسکریپت چیست؟

کاربرد Prototype در جاوا اسکریپت چیست؟

عمیق شدن در Prototype در جاوا اسکریپت

متدهای آرایه در جاوا اسکریپت

متدهای استاتیک

دریافت نمونه اولیه شی

تعیین وجود ویژگی در Prototype در جاوا اسکریپت

بررسی نمونه های کلاس برای اشیا

ایجاد توابع سازنده آگنوستیک جدید

ایجاد دوباره Object.create

توابع پیکان

سخن پایانی

faradars mobile

اشیا در جاوا اسکریپت

در جاوا اسکریپت، شی نوعی ساختار داده است که به کاربر امکان می‌دهد داده‌ها را در جفت‌های «کلید-مقدار» (Key-Value) ذخیره و سازماندهی کند. شی یکی از انواع داده‌های اساسی در جاوا اسکریپت به حساب می‌آید و مجموعه‌ای از ویژگی‌ها را در بر می‌گیرد که هر ویژگی از کلیدی (که نام ویژگی نیز نامیده می‌شود) و مقدارش تشکیل شده است.

آموزش جاوا اسکریپت JavaScript
فیلم آموزش جاوا اسکریپت JavaScript در تم آف

کلیک کنید

اشیاء در جاوا اسکریپت پویا هستند، به این معنی که کاربر می‌تواند در هر زمان ویژگی‌ها را تغییر دهد یا آن‌ها را اضافه یا حذف کند. مقادیر ویژگی‌های شی می‌توانند از هر نوع داده‌ای باشند، از جمله اشیای دیگر، می‌توان آرایه‌ها، توابع و انواع اولیه مانند اعداد و «رشته‌ها» (استرینگ) را نام برد. مثال زیر مفهوم ایجاد شی در جاوا اسکریپت را بیان می‌کند.

const car = {
  brand: "Tesla",
  model: "Model S",
  getInfo: function() {
    console.log(`Brand: ${this.brand}, Model: ${this.model}`);
  }
};

const bike = {
  brand: "Honda",
  model: "CBR 1000RR"
};

// Using bind to bind the car's getInfo method to the bike object
const boundGetInfo = car.getInfo.bind(bike);

boundGetInfo(); // Output: Brand: Honda, Model: CBR 1000RR

تصویر زیر نحوه ساخت شی در جاوا اسکریپت را نشان می‌دهد:

اشیا در جاوا اسکریپت چیست؟

اگر فقط یک شی وجود داشته باشد، روند بالا ساده خواهد بود. اما در برخی از سناریوها، لازم است چندین چند شی مختلف ایجاد شود که این کار روند را کمی پیچیده‌تر می‌کند. منطقی‌ترین روش برای چنین سناریوهایی این است که منطق ایجاد شی در تابعی قرار بگیرد. با فراخوانی این تابع، می‌توان در صورت نیاز شیئی جدید را ایجاد کرد. از این الگو به عنوان «نمونه‌سازی عملکردی یا کاربردی» (Functional Instantiation) یاد می‌کنند و تابع درگیر به عنوان «عملکرد سازنده» (Constructor Function) شناخته می‌شود زیرا اساساً شیئی جدید را می‌سازد.

نمونه‌ سازی عملکردی

یکی از پیش‌نیازهای اصلی Prototype در جاوا اسکریپت درک مفهوم نمونه‌سازی عملکردی است.

آموزش JavaScript ES6 جاوا اسکریپت
فیلم آموزش JavaScript ES6 جاوا اسکریپت در تم آف

کلیک کنید

برای درک مفهوم نمونه‌ سازی عملکردی، فرض می‌شود که قطعه کد زیر را داریم. کدهای زیر حاوی تابعی برای تعریف حیوانات مختلف با ویژگی نام و انرژی است.

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy

  animal.eat = function (amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }

  animal.sleep = function (length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }

  animal.play = function (length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

در کدهای بالا، هر زمان که حیوان جدیدی ایجاد شود، باید هر بار متدهای عمومی (eat و sleep ،play)

  را بازسازی کرد. این روش کارآمد نیست و حافظه غیر ضروری را مصرف خواهد کرد. برای حل این مشکل، می‌توان الگویی به نام «Functional Instantiation with Shared Methods» اتخاذ کرد. این الگو شامل انتقال متدهای عمومی به شیئی جداگانه و اجازه دادن به هر حیوانی برای ارجاع به آن شی است. با انجام این کار، می‌توان از بازآفرینی متدها برای هر حیوان جلوگیری کرد و کدها را با حافظه بهینه‌تری نوشت.

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

به منظور پرداختن به مسئله زیاده‌روی در مصرف حافظه، الگوی نمونه‌سازی کاربردی با متدهای مشترک در کدهای زیر پیاده‌سازی شده‌اند.

const animalMethods = {
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  },
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  },
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy
  animal.eat = animalMethods.eat
  animal.sleep = animalMethods.sleep
  animal.play = animalMethods.play

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

در کدهای بالا، شیئی جداگانه به نام animalMethods 

 تعریف شده است که شامل متد عمومی (eat,sleep,play)

باشد. در تابع Animal

  ، یک شی حیوان جدید ایجاد و نام و خواص انرژی به آن داده شده است. به جای بازآفرینی متدها برای هر حیوان، اکنون با متدهای مشترک از شی animalMethods 

کار انجام گرفته است. این رویکرد تضمین می‌کند که متدها تکراری نمی‌شوند و در نتیجه کدها با حافظه کارآمدتری ایجاد می‌شوند. با اتخاذ این الگو، مشکل اتلاف حافظه با موفقیت حل خواهد شد و اندازه اشیا کاهش داده می‌شود.

object.create در جاوا اسکریپت چیست؟

object.create نوعی متد جاوا اسکریپت داخلی است که به کاربر امکان می‌دهد شیئی جدید ایجاد و شی دیگری را به عنوان نمونه اولیه آن تنظیم کند. هنگامی که ویژگی خاصی در شی جدید یافت نمی‌شود، جاوا اسکریپت سعی می‌کند آن را در نمونه اولیه خود جستجو کند. به این رفتار، «تفویض» (Delegation) می‌گویند. بیایید این مفهوم را با مثالی توضیح دهیم.

آموزش پروژه محور جاوا اسکریپت، CSS و HTML – طراحی صفحه فرود واکنشگرا
فیلم آموزش پروژه محور جاوا اسکریپت، CSS و HTML – طراحی صفحه فرود واکنشگرا در تم آف

کلیک کنید

فرض بر این است که شیئی والد به صورت زیر وجود دارد.

const parent = {
  name: 'Stacey',
  age: 35,
  heritage: 'Irish'
}

می‌توان شی child 

 با parent 

 به عنوان نمونه اولیه آن به صورت زیر ایجاد کرد.

const child = Object.create(parent);
child.name = 'Ryan';
child.age = 7;

حال، اگر کاربری بخواهد به ویژگی‌های شی child 

دسترسی پیدا کند، باید طبق قطعه کد زیر عمل شود.

console.log(child.name) // Ryan
console.log(child.age) // 7
console.log(child.heritage) // Irish

حتی اگر ویژگی heritage 

 مستقیماً روی شی child 

تعریف نشده باشد، جاوا اسکریپت همچنان می‌تواند آن را در شی parent 

پیدا کند که نمونه اولیه شی child 

است. بنابراین چگونه می‌توان از Object.create 

برای ساده کردن کد Animal

قبلی استفاده کرد؟ با این اوصاف می‌توان از Object.create 

برای به اشتراک گذاشتن متدها بین نمونه‌های حیوانی مختلف استفاده کرد، نه اینکه این متدها به هر حیوان جداگانه اضافه شوند. این رویکرد را می‌توان به عنوان نمونه‌سازی کاربردی یا عملکردی با متدهای مشترک به وسیله Object.create 

نامید.

Object.create در جاوا اسکریپت

نمونه سازی کاربردی با متدهای مشترک و Object.create

متد Object.create

ابزاری مفید در جاوا اسکریپت به خساب می‌آید که به کاربر امکان می‌دهد شیئی را ایجاد کرده و جستجوهای ویژگی را به شیئی دیگر واگذار کند. این مفهوم به وسیله مثال مربوط به حیوانات به خوبی نشان داده شده است.

آموزش پروژه محور جاوا اسکریپت – پیاده سازی سبد خرید فروشگاهی با JavaScript
فیلم آموزش پروژه محور جاوا اسکریپت – پیاده سازی سبد خرید فروشگاهی با JavaScript در تم آف

کلیک کنید

برای درک بهتر، بلوک کد زیر ارائه شده است که متدهای رایج برای حیوانات را تعریف می‌کند.

const animalMethods = {
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  },
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  },
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

سپس می‌توان تابع Animal

را برای ایجاد اشیای حیوانی که به animalMethods 

 واگذار می‌شوند، به صورت زیر تعریف کرد:

function Animal (name, energy) {
  let animal = Object.create(animalMethods)
  animal.name = name
  animal.energy = energy


  return animal
}


const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)


leo.eat(10)
snoop.play(5)

در این مورد، وقتی leo.eat

 فراخوانی می‌شود، جاوا اسکریپت ابتدا بررسی می‌کند که eat

 در leo

 وجود دارد یا خیر و از آنجایی که leo

این متد را ندارد، جاوا اسکریپت سپس به animalMethods

 نگاه می‌کند. این به دلیل تفویض اختیاری اتفاق می‌افتد‌ که از راه Object.create

ایجاد شده است.

با این حال، داشتن نوعی شی animalMethods

مجزا برای نگهداری متدهای مشترک، ممکن است چندان هم حرفه‌ای به نظر نرسد. امکان دارد کاربری فکر کند این نوع به اشتراک‌گذاری متُد باید نوعی ویژگی باشد که در زبان تعبیه شده است. در واقع جاوا اسکریپت دارای چنین ویژگی است که به آن نمونه اولیه یا Prototype در جاوا اسکریپت می‌گویند. هر تابع در جاوا اسکریپت دارای یک ویژگی به نام نمونه اولیه یا Prototype است که به شی اشاره می‌کند. مثال زیر این مفهوم را بیان می‌کند:

function doThing () {}
console.log(doThing.prototype) // {}

اگر متدهای مشترک مستقیماً روی نمونه اولیه Animal

 قرار داده شوند به این ترتیب، به جای تفویض اختیار به animalMethods

، به Animal.prototype

 تفویض انجام خواهد شد. این الگو به عنوان Prototype در جاوا اسکریپت شناخته شده است.

Prototype در جاوا اسکریپت چیست؟

prototype جاوا اسکریپت ساز و کاری است که به وسیله آن، اشیا ویژگی‌هایی را از یکدیگر به ارث می‌برند. به این ساز و کار، وراثت نمونه اولیه یا Prototype گفته می‌شود. هنگامی که تابعی در جاوا اسکریپت ایجاد می‌شود، موتور جاوا اسکریپت ویژگی Prototype را به تابع اضافه می‌کند. این ویژگی، نمونه اولیه نوعی شی به نام شی نمونه اولیه محسوب می‌شود که به طور پیش‌فرض دارای ویژگی سازنده است. ویژگی سازنده به تابعی اشاره می‌کند که در آن شی نمونه اولیه نوعی ویژگی است. می‌توان به وسیله «FunctionName.prototype» به ویژگی نمونه اولیه تابع دسترسی پیدا کرد.

آموزش جاوا اسکریپت JavaScript
فیلم آموزش جاوا اسکریپت JavaScript در تم آف

کلیک کنید

تمامی مباحث بالا، نوعی پیش‌نیاز برای Prototype در جاوا اسکریپت بودند. حال مفهوم Prototype در قالب مثالی ساده در این جا توضیح داده خواهد شد. فرض بر این است که تابعی به نام Animal

به صورت زیر وجود دارد:

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype)
  animal.name = name
  animal.energy = energy

  return animal
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

leo.eat(10)
snoop.play(5)

درک عمیق کدهای بالا تا حدود زیادی راه را برای یادگیری مفهوم Prototype در جاوا اسکریپت آسان می‌کند. هر تابع جاوا اسکریپت، حاوی نوعی ویژگی Prototype اولیه است. این ویژگی متدهای به اشتراک گذاشته شده را در تمام نمونه‌های یک تابع فعال می‌کند و این در حالی خواهد بود که عملکرد یکسان باقی می‌ماند و دیگر نیازی به مدیریت شیئی جداگانه برای متدها وجود نخواهد داشت. در عوض، از شیئی ساخته شده در خود تابع Animal

، یعنی Animal.prototype 

 استفاده می‌شود. این روش کدنویسی به کاهش پیچیدگی، بهبود کارایی و قابلیت نگهداری کدها کمک می‌کند.

آموزش Prototype در جاوا اسکریپت

کاربرد Prototype در جاوا اسکریپت چیست؟

هر زمان که تابع جاوا اسکریپت ایجاد می‌شوند، جاوا اسکریپت نوعی ویژگی Prototype به آن تابع اضافه می‌کند. Prototype در واقع نوعی شی است که می‌تواند متغیرها و متدهای جدیدی را به شی موجود اضافه کند. این یعنی Prototype نوعی کلاس پایه برای همه اشیا است و به کاربر کمک می‌کند تا از وراثت در برنامه نویسی استفاده کند.

عمیق شدن در Prototype در جاوا اسکریپت

تا اینجا از مبحث Prototype در جاوا اسکریپت موارد زیر پوشش داده شدند:

آموزش مقدماتی کتابخانه ReactJS در جاوا اسکریپت
فیلم آموزش مقدماتی کتابخانه ReactJS در جاوا اسکریپت در تم آف

کلیک کنید

  • نحوه ساخت تابع جدید
  • اضافه کردن متدهایی به Prototype در جاوا اسکریپت
  • استفاده از Object.create

    برای تفویض جستجوهای ناموفق به نمونه اولیه تابع

ممکن است کاربری فکر کند که این عملیات باید ساده‌تر یا یکپارچه‌تر باشد. در جاوا اسکریپت، کلمه کلیدی New

 این کار را انجام می‌دهد و فرآیند را ساده‌تر می‌کند. مثال زیر در این رابطه مهم است:

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype)
  animal.name = name
  animal.energy = energy

  return animal
}

هنگامی که تابعی با استفاده از New

فراخوانی می‌شود، ایجاد و بازگشت شی به طور ضمنی انجام شده و شی ایجاد شده، this

 نامیده می‌شود. با فرض اینکه سازنده Animal

با New

فراخوانی شود، می‌توان آن را به صورت زیر بازنویسی کرد:

function Animal (name, energy) {
  // const this = Object.create(Animal.prototype)

  this.name = name
  this.energy = energy

  // return this
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

پس از حذف نظراتی که عملیات «Under tThe Hood» را توصیف می‌کنند، کد به صورت زیر خواهد بود.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

دلیل ایجاد شی this

 استفاده New

برای فراخوانی تابع سازنده است. بدون New

، نه شی this

ایجاد می‌شود و نه به طور ضمنی بازگردانده خواهد شد. این موضوع در ادامه نشان داده شده است.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)
console.log(leo) // undefined

اگر کاربر با زبان‌های برنامه نویسی دیگر آشنا باشد، ممکن است متوجه شود که در قطعه کد بالا نوعی نسخه تقریبی از کلاس در برنامه نویسی ایجاد شده است. کلاس به کاربر امکان می‌دهد نوعی طرح اولیه برای شی تعریف کند و همچنین به کاربر امکان می‌دهد نمونه‌هایی از کلاس را با ویژگی‌ها و متدهای مشخص شده در طرح ایجاد کند. این اساساً همان چیزی است که با عملکرد سازنده Animal

 به دست آمد.

جنبه مثبت این روش این است که جاوا اسکریپت در حال تکامل است و کمیته «TC-39» به طور مداوم آن را بهبود و گسترش می‌دهد. اگر چه نسخه اصلی جاوا اسکریپت از کلاس‌ها پشتیبانی نمی‌کرد، اما این بدان معنا نیست که نمی‌توان آن‌ها را در مشخصات رسمی گنجاند. کمیته TC-39 دقیقاً این کار را در سال «۲۰۱۵» (١٣٩٣ شمسی) با انتشار «EcmaScript6» انجام داد که شامل کلاس‌ها و کلمه کلیدی class 

 بود. عملکرد سازنده Animal

در کدهای بالا با «سینتکس» (Syntax) کلاس به صورت زیر خواهد بود:

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

متدهای آرایه در جاوا اسکریپت

در بخش بالا به طور گسترده، به اشتراک گذاشتن متدها در بین نمونه‌های کلاس و Prototype در جاوا اسکریپت بحث شد، بهترین روش قرار دادن این متدها در نمونه اولیه کلاس (یا تابع) است. این الگو زمانی که کاربر با کلاس Array 

 سر و کار داشته باشد، مورد نیاز خواهد بود.

آموزش برنامه نویسی تایپ اسکریپت TypeScript در جاوا اسکریپت
فیلم آموزش برنامه نویسی تایپ اسکریپت TypeScript در جاوا اسکریپت در تم آف

کلیک کنید

سینتکس ایجاد آرایه در جاوا اسکریپت به صورت زیر است:

const friends = []

سینتکس زیر هم راهی راحت‌تر برای ایجاد نمونه جدید از کلاس Array

است.

const friendsWithSugar = []

const friendsWithoutSugar = new Array()

چیزی که ممکن است ذهن کاربر را درگیر کند، این است چگونه هر نمونه آرایه همه متدهای آرایه در جاوا اسکریپت مانند spliceو slice،pop

  را دریافت می‌کند. این کار به این دلیل امکان‌پذیر است که  تمام متدها در Array.prototype

 قرار دارند. هنگامی که نمونه آرایه جدیدی با استفاده از کلمه کلیدی New  تولید می‌شود، نوعی بازگشت به Array.prototype

 برای جستجوهای ناموفق ایجاد خواهد شد. در مطلبی جداگانه در «مجله تم آف» به بحث متدهای آرایه در جاوا اسکریپت به صورت کامل پرداخته شده است.

متدهای آرایه در جاوا اسکریپت

می‌توان تمام متدهای آرایه را با ورود به سیستم Array.prototype

به صورت زیر مشاهد کرد.

console.log(Array.prototype)

/*
  concat: ƒn concat()
  constructor: ƒn Array()
  copyWithin: ƒn copyWithin()
  entries: ƒn entries()
  every: ƒn every()
  fill: ƒn fill()
  filter: ƒn filter()
  find: ƒn find()
  findIndex: ƒn findIndex()
  forEach: ƒn forEach()
  includes: ƒn includes()
  indexOf: ƒn indexOf()
  join: ƒn join()
  keys: ƒn keys()
  lastIndexOf: ƒn lastIndexOf()
  length: 0n
  map: ƒn map()
  pop: ƒn pop()
  push: ƒn push()
  reduce: ƒn reduce()
  reduceRight: ƒn reduceRight()
  reverse: ƒn reverse()
  shift: ƒn shift()
  slice: ƒn slice()
  some: ƒn some()
  sort: ƒn sort()
  splice: ƒn splice()
  toLocaleString: ƒn toLocaleString()
  toString: ƒn toString()
  unshift: ƒn unshift()
  values: ƒn values()
*/

این اصل در مورد اشیا نیز صدق می‌کند. هر شی برای جستجوهای ناموفق به Object.prototype

 واگذار می‌شود، به همین دلیل است که همه اشیا، متدهایی مانند toString

 و hasOwnProperty

 دارند.

متدهای استاتیک

تا اینجا، ما یاد گرفتیم که چگونه متدها را می‌توان در بین نمونه‌های کلاس به اشتراک گذاشت، اما گاهی اوقات ممکن است به روشی نیاز پیدا کنیم که به کلاس مربوط است، اما نیازی به اشتراک‌گذاری در بین نمونه‌ها وجود نداشته باشد. برای مثال، فرض می‌کنیم متدی به نام nextToEat

 وجود دارد که آرایه‌ای از نمونه‌های Animal

 را می‌گیرد و تعیین می‌کند که کدام یک نیاز به تغذیه دارد.

آموزش فریم ورک نود جی اس – آپلود فایل با Node.js
فیلم آموزش فریم ورک نود جی اس – آپلود فایل با Node.js در تم آف

کلیک کنید

در اینجا، کدهای تابع nextToEat

آورده شده است:

function nextToEat(animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy;
  });

  return sortedByLeastEnergy[0].name;
}

منطقی نیست که nextToEat

را در Animal.prototype

  قرار دهیم، زیرا متدی نیست که بین همه نمونه‌ها به اشتراک گذاشته شود. در عوض، می‌توان آن را تابعی سودمند در نظر گرفت. می‌توان nextToEat

را در همان محدوده کلاس Animal

قرار داد و در صورت لزوم از آن استفاده کرد. مانند کدهای زیر:

class Animal {
  constructor(name, energy) {
    this.name = name;
    this.energy = energy;
  }
  eat(amount) {
    console.log(`${this.name} is eating.`);
    this.energy += amount;
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`);
    this.energy += length;
  }
  play(length) {
    console.log(`${this.name} is playing.`);
    this.energy -= length;
  }
}

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy;
  })

  return sortedByLeastEnergy[0].name;
}

const leo = new Animal('Leo', 7);
const snoop = new Animal('Snoop', 10);

console.log(nextToEat([leo, snoop])); // Leo

روش بالا روشی مفید است، اما راه‌حل ظریف‌تری نیز برای این هدف وجود دارد. اگر متدی به کلاسی مرتبط باشد، اما نیازی به توزیع در بین نمونه‌ها وجود نداشته باشد، می‌توان آن را به عنوان نوعی ویژگی ثابت کلاس اضافه کرد. نحوه انجام این کار به صورت زیر است:

class Animal {
  constructor(name, energy) {
    this.name = name;
    this.energy = energy;
  }
  eat(amount) {
    console.log(`${this.name} is eating.`);
    this.energy += amount;
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`);
    this.energy += length;
  }
  play(length) {
    console.log(`${this.name} is playing.`);
    this.energy -= length;
  }
  static nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a,b) => {
      return a.energy - b.energy;
    })

    return sortedByLeastEnergy[0].name;
  }
}

const leo = new Animal('Leo', 7);
const snoop = new Animal('Snoop', 10);

console.log(Animal.nextToEat([leo, snoop])); // Leo

از آنجایی که nextToEat

 اکنون نوعی ویژگی ثابت کلاس است، به خود کلاس Animal

تعلق دارد (نه نمونه اولیه آن) و می‌توان آن را با استفاده از Animal.nextToEat

  فراخوانی کرد. در نهایت، بیایید همان رفتار را با استفاده از سینتکس «ES5» تکرار کنیم. کلمه کلیدی static 

 در مثال قبلی متد را مستقیماً به کلاس اضافه کرد. با «ES5»، می‌توانیم به صورت دستی متد را به شی تابع اضافه کنیم:

function Animal(name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function(amount) {
  console.log(`${this.name} is eating.`);
  this.energy += amount;
};

Animal.prototype.sleep = function(length) {
  console.log(`${this.name} is sleeping.`);
  this.energy += length;
};

Animal.prototype.play = function(length) {
  console.log(`${this.name} is playing.`);
  this.energy -= length;
};

Animal.nextToEat = function(animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy;
  });

  return sortedByLeastEnergy[0].name;
};

const leo = new Animal('Leo', 7);
const snoop = new Animal('Snoop', 10);

console.log(Animal.nextToEat([leo, snoop])); // Leo

این رویکرد می‌تواند هنگام برخورد با متدهایی که به کلاس مربوط می‌شوند، به جای نمونه‌های فردی، خواناتر باشد و سازمان‌دهی و وضوح کدها را بیشتر بهبود بخشد.

دریافت نمونه اولیه شی

صرف نظر از روشی که برای ایجاد یک شی استفاده می‌شود، می‌توان نمونه اولیه شی یا Prototype در جاوا اسکریپت را با متد Object.getPrototypeOf

  به دست آورد.

آموزش پروژه محور جاوا اسکریپت – پیاده سازی سبد خرید فروشگاهی با JavaScript
فیلم آموزش پروژه محور جاوا اسکریپت – پیاده سازی سبد خرید فروشگاهی با JavaScript در تم آف

کلیک کنید

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

function Animal(name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function(amount) {
  console.log(`${this.name} is eating.`);
  this.energy += amount;
}

Animal.prototype.sleep = function(length) {
  console.log(`${this.name} is sleeping.`);
  this.energy += length;
}

Animal.prototype.play = function(length) {
  console.log(`${this.name} is playing.`);
  this.energy -= length;
}

const leo = new Animal('Leo', 7);
const prototype = Object.getPrototypeOf(leo);

console.log(prototype); // {constructor: ƒ, eat: ƒ, sleep: ƒ, play: ƒ}
console.log(prototype === Animal.prototype); // true

۲ بینش مهم در کدهای فوق وجود دارد. اول اینکه شی نمونه اولیه، شامل چهار متُد «سازنده» ( constructor 

 )، «خوردن» ( eat 

 )، «خوابیدن» ( sleep 

 ) و «بازی» ( play 

 ) است. این کار منطقی به حساب می‌آید، زیرا وقتی getPrototypeOf 

 با مثال leo 

 فراخوانی می‌شود، نمونه اولیه نمونه‌ای بازخواهد گشت که همه متدها در آن ذخیره می‌شوند. به طور پیش فرض، جاوا اسکریپت نوعی ویژگی سازنده را به نمونه اولیه اختصاص می‌دهد که به تابع یا کلاس اصلی که نمونه را ایجاد کرده است، پیوند داده خواهد شد. این بدان معنی است که نمونه‌ها می‌توانند به وسیله instance.constructor 

 به سازنده خود دسترسی داشته باشند.

نمونه اولیه در جاوا اسکریپت

همچنین در کد فوق Object.getPrototypeOf(leo) === Animal.prototype

 به درستی ارزیابی می‌شود و تأیید می‌کند که getPrototypeOf

 به کاربر اجازه می‌دهد نمونه اولیه خود نمونه را بررسی کند که باید مشابه Animal.prototype

 باشد.

function Animal(name, energy) {
  this.name = name;
  this.energy = energy;
}

const leo = new Animal('Leo', 7);
console.log(leo.constructor); // Logs the constructor function

این رفتار به بحث قبلی در Object.create 

 گره خورده است. وقتی leo.constructor

 فراخوانی می‌شود، leo

 خاصیت سازنده ندارد. بنابراین، جستجو را به Animal.prototype

واگذار می‌کند که دارای ویژگی سازنده است. ممکن است کاربران با __proto__  

 برای دسترسی به نمونه اولیه یک نمونه برخورد کرده باشند، این روش منسوخ شده و همان‌طور که در مثال‌ها نشان داده شده است، از Object.getPrototypeOf(instance) 

 استفاده می‌شود.

تعیین وجود ویژگی در Prototype در جاوا اسکریپت

در شرایط خاص، تشخیص اینکه آیا ویژگی در خود نمونه قرار دارد یا در نمونه اولیه واگذار شده، ضروری خواهد بود. فرض بر این است که کاربری می‌خواهد شی leo

مثال‌های قبل را پیمایش و تمام کلیدها و مقادیر آن را ثبت کند.

آموزش جاوا اسکریپت JavaScript
فیلم آموزش جاوا اسکریپت JavaScript در تم آف

کلیک کنید

با استفاده از حلقه for…in

 ، این کار به صورت زیر انجام خواهد شد.

function Animal(name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function(amount) {
  console.log(`${this.name} is eating.`);
  this.energy += amount;
}

Animal.prototype.sleep = function(length) {
  console.log(`${this.name} is sleeping.`);
  this.energy += length;
}

Animal.prototype.play = function(length) {
  console.log(`${this.name} is playing.`);
  this.energy -= length;
}

const leo = new Animal('Leo', 7);

for (let key in leo) {
  console.log(`Key: ${key}. Value: ${leo[key]}`);
}

کاربر انتظار دارد خروجی زیر را ببیند.

Key: name. Value: Leo
Key: energy. Value: 7

 با این حال خروجی کدهای بالا به صورت زیر است.

Key: name. Value: Leo
Key: energy. Value: 7
Key: eat. Value: function...
Key: sleep. Value: function...
Key: play. Value: function...

این اتفاق به این دلیل رخ می‌دهد که حلقه for…in

تمام ویژگی‌های شمارش‌پذیر شی و نمونه اولیه آن را پیمایش می‌کند. به طور پیش‌فرض، هر ویژگی اضافه شده به prototype در جاوا اسکریپت نوعی تابع قابل پیمایش است. بنابراین، نه تنها name

 و energy

 ، بلکه متدهای روی نمونه اولیه مانند splice و slice ،pop

 نیز قابل پیمایش هستند. برای جلوگیری از این امر، به مکانیزمی نیاز است که فقط ویژگی‌های خودِ شی leo

 را به استثنای موارد موجود در نمونه اولیه ثبت کند. اینجا است که متد hasOwnProperty

 وارد عمل می‌شود.

hasOwnProperty نوعی متد داخلی به حساب می‌آید که مقداری بولی را برمی‌گرداند و نشان می‌دهد که آیا شی دارای ویژگی مشخص شده متعلق به خود (و نه در نمونه اولیه) هست یا خیر. با این روش می‌توان حلقه را به صورت زیر اصلاح کرد.

const leo = new Animal('Leo', 7);

for (let key in leo) {
  if (leo.hasOwnProperty(key)) {
    console.log(`Key: ${key}. Value: ${leo[key]}`);
  }
}

اکنون، فقط ویژگی‌هایی قابل رویت هستند که روی خود شی leo

  موجودند.

Key: name. Value: Leo
Key: energy. Value: 7

 کدهای زیر به درک بهتر مفهوم hasOwnProperty

کمک می‌کند.

const leo = new Animal('Leo', 7);

console.log(leo.hasOwnProperty('name')); // true
console.log(leo.hasOwnProperty('energy')); // true
console.log(leo.hasOwnProperty('eat')); // false
console.log(leo.hasOwnProperty('sleep')); // false
console.log(leo.hasOwnProperty('play')); // false

کدهای فوق نشان می‌دهند که hasOwnProperty

فقط برای ویژگی‌های تعریف‌شده در خودِ نمونه name

و energy

مقدار true

 برمی‌گرداند، نه برای آن‌هایی که در نمونه اولیه مانند splice و slice ،pop

تعریف شده‌اند.

بررسی نمونه های کلاس برای اشیا

گاهی اوقات، ممکن است لازم باشد بررسی کنیم که آیا شی نمونه‌ای از کلاسی خاص است یا خیر، پس در چنین مواردی می‌توان از عملگر instanceof

 استفاده کرد.

آموزش JavaScript ES6 جاوا اسکریپت
فیلم آموزش JavaScript ES6 جاوا اسکریپت در تم آف

کلیک کنید

سینتکس استفاده از این عملگر به صورت زیر است.

object instanceof Class

اگر شی، نمونه‌ای از Class 

 باشد، عبارت فوق، مقدار true

و اگر نمونه‌ای از آن نباشد، false

 را برمی‌گرداند. در ادامه نمونه‌ی Animal

 برای این هدف دوباره بررسی می‌شود.

function Animal(name, energy) {
  this.name = name;
  this.energy = energy;
}

function User() {}

const leo = new Animal('Leo', 7);

console.log(leo instanceof Animal); // true
console.log(leo instanceof User); // false

در کدهای فوق، leo instanceof Animal 

 مقدار true

را برمی‌گرداند، زیرا leo

نمونه‌ای از Animal

است. برعکس، نمونه leo instanceof User 

 مقدار false

را بازمی‌گرداند، زیرا leo

 نمونه‌ای از User 

 نیست.

instanceof در جاوا اسکریپت

عملگر instanceof

با بررسی این مورد کار می‌کند که آیا constructor.prototype

  در هر نقطه از زنجیره prototype در جاوا اسکریپت وجود دارد یا خیر، به عنوان نمونه، در مثال بالا، نمونه leo

از Animal

مقدار  true

را برمی‌گرداند، زیرا Object.getPrototypeOf(leo) 

 برابر Animal.prototype 

 است. برعکس، leo instanceof User

 مقدار false

را بازمی‌گرداند، زیرا Object.getPrototypeOf(leo)

 برابر User.prototype 

 نیست.

ایجاد توابع سازنده آگنوستیک جدید

آیا می‌توان اشتباه کدهای زیر را تشخیص داد؟

function Animal(name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)

حتی توسعه‌دهندگان باتجربه جاوا اسکریپت ممکن است با این مثال برخورد کنند. مسئله اینجا است که وقتی تابع سازنده Animal

با استفاده از الگوی شبه کلاسیکی فراخوانی می‌شود، باید از کلمه کلیدی New

 استفاده کند. اگر این کار انجام نشود، کلمه کلیدی This در جاوا اسکریپت وجود نخواهد داشت و به طور ضمنی برگردانده نخواهد شد. برای یادآوری، هنگامی که از کلمه کلیدی New

در تابعی استفاده شود، این مراحلی که به صورت نظرات در کدهای زیر درج شده‌اند، رخ می‌دهد.

function Animal(name, energy) {
  // const this = Object.create(Animal.prototype)

  this.name = name
  this.energy = energy

  // return this
}

در کدهای بالا، آیا می‌توان تضمین کرد که سازنده Animal

همیشه با کلمه کلیدی جدید فراخوانی می‌شود؟

آموزش JavaScript ES6 جاوا اسکریپت
فیلم آموزش JavaScript ES6 جاوا اسکریپت در تم آف

کلیک کنید

می‌توان این کار را با استفاده از عملگر instanceof

 انجام داد که قبلاً مورد بحث قرار داده شد. اگر سازنده با کلمه کلیدی New

فراخوانی شود، this

 در داخل بدنه سازنده نمونه‌ای از خود تابع سازنده خواهد بود. در اینجا، نحوه کدگذاری آن آمده است.

function Animal(name, energy) {
  if (!(this instanceof Animal)) {
    console.warn('Forgot to call Animal with the new keyword')
  }

  this.name = name
  this.energy = energy
}

با این حال، به جای اینکه صرفاً هشداری صادر شود، اگر بتوان تابع را با کلمه کلیدی New

دوباره به صورت زیر فراخوانی کرد، چه اتفاقی می‌افتد؟

function Animal(name, energy) {
  if (!(this instanceof Animal)) {
    return new Animal(name, energy)
  }

  this.name = name
  this.energy = energy
}

با این پیاده‌سازی، Animal

چه با کلمه کلیدی New

فراخوانی شود یا نه، به درستی عمل می‌کند.

ایجاد دوباره Object.create

در طول این بحث، به طور گسترده از Object.create

 برای ایجاد اشیایی استفاده شد که در صورت شکست جستجو، به نمونه اولیه تابع سازنده واگذار می‌شوند. در حال حاضر، می‌دانیم که چگونه از Object.create

در کدهای خود استفاده کنیم. با این حال، ممکن است در مورد عملکرد داخلی Object.create

سوالاتی مطرح شود.

مجموعه آموزش جاوا اسکریپت (JavaScript)
فیلم مجموعه آموزش جاوا اسکریپت (JavaScript) در تم آف

کلیک کنید

برای درک واقعی عملکرد آن، بازسازی Object.create

از ابتدا لازم است. تجزیه و تحلیل آنچه قبلاً در مورد نحوه عملکرد Object.create

بیان شد به صورت زیر بود:

  1. Object.create

    شیئی را به عنوان استدلال می‌پذیرد.

  2. Object.create

    شیئی ایجاد می‌کند که در جستجوهای ناموفق به شیء آرگومان تفویض می‌شود.

  3. Object.create

    شی جدید ایجاد شده را برمی‌گرداند.

مورد اول به صورت زیر است:

Object.create = function (objectToDelegateTo) {

}

این مورد مشخص شد، اما در مورد نکته دوم باید شیئی به وجود آید که در جستجوهای ناموفق به شیء آرگومان واگذار شود. این کمی پیچیده‌تر است. برای انجام این کار باید درک خود را از نحوه عملکرد کلمه کلیدی New

و Prototype در جاوا اسکریپت به کار بگیریم. ابتدا باید تابعی خالی در اجرای Object.create

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

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

Object.create = function (objectToDelegateTo) {
  function F() {}
  F.prototype = objectToDelegateTo;
  return new F();
}

در کدهای فوق، هنگامی که تابع جدید، F  در کدها ایجاد می‌شود، دارای ویژگی Prototype خود است. اگر این تابع با کلمه کلیدی New

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

  با شیء ارسال شده در هنگام فراخوانی Object.create

، به نام objectToDelegateTo

  لغو می‌شود.

باید به این نکته توجه کرد که این نسخه از Object.create

تنها از یک آرگومان پشتیبانی می‌کند. پیاده‌سازی اصلی از آرگومان اختیاری دوم پشتیبانی کرده است که به کاربر امکان می‌دهد ویژگی های اضافی را به شی ایجاد شده اضافه کند.

مجموعه آموزش طراحی و برنامه ‌نویسی سایت
فیلم مجموعه آموزش طراحی و برنامه ‌نویسی سایت در تم آف

کلیک کنید

توابع پیکان

توابع پیکان فاقد کلمه کلیدی this

خود هستند. در نتیجه، آن‌ها نمی‌توانند به عنوان توابع سازنده عمل کنند. تلاش برای فراخوانی تابع پیکان با کلمه کلیدی New

منجر به خطا می‌شود.

const Animal = () => {}

const leo = new Animal() // Error: Animal is not a constructor

علاوه بر این، از آنجا که ثابت شد الگوی شبه کلاسیک با «توابع پیکان» (Arrow Function) ناسازگار است، این توابع همچنین دارای ویژگی prototype در جاوا اسکریپت نیستند.

const Animal = () => {}
console.log(Animal.prototype) // undefined

سخن پایانی

همان‌طور که بیان شد، Prototype در جاوا اسکریپت مکانیزم و ساز و کاری محسوب می‌شود که به وسیله آن اشیا ویژگی‌هایی را از یکدیگر به ارث می‌برند که به این وراثت، نمونه اولیه می‌گویند. در مطلب فوق از مجله تم آف بحث Prototype در جاوا اسکریپ با استفاده از زبانی ساده و مثال‌های متعددی مورد بحث قرار گرفت. یادگیری این مفهوم به کاربران کمک می‌کند کدهای خواناتر و واضح‌تری بنویسند و این شیوه، بهره‌وری کلی را در برنامه نویسی جاوا اسکریپت افزایش می‌دهد.

مجموعه آموزش جاوا اسکریپت (JavaScript)
فیلم مجموعه آموزش جاوا اسکریپت (JavaScript) در تم آف

کلیک کنید

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

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.