برنامه نویسی

وراثت جاوا اسکریپت – آیا می دانید چگونه کار می کند؟

tl ؛ دکتر:

وراثت JavaScript را می توان به دو روش اصلی: مبتنی بر کلاس و نمونه اولیه. اگرچه class کلمه کلیدی یک نحو آشنا را برای توسعه دهندگان از پس زمینه OOP فراهم می کند ، در زیر کاپوت همه چیز در JavaScript مبتنی بر نمونه های اولیه است. این مقاله چگونگی عملکرد وراثت با استفاده از کلاس ها و سازندگان را تجزیه می کند ، تفاوت بین روش ها و خصوصیات را روشن می کند ، توضیح می دهد نمونه اولیه در مقابل __proto__، و نحوه گسترش رفتار با استفاده از تکنیک های مختلف را نشان می دهد. اگر در حال ایجاد اشیاء هستید که رفتار را به اشتراک می گذارند ، درک این امر شما را از کابین های کپی شده و کابوس های نگهداری نجات می دهد.


مقدمه

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

این باعث شد تا من فکر کنم: شاید وقت آن رسیده باشد که کمی چیزها را کم کنیم و برخی از مکانیک های اصلی JavaScript را دوباره بررسی کنیم. بنابراین من تصمیم گرفتم یک سری جدید به نام “آیا می دانید چگونه کار می کند؟” – با تمرکز بر تغییر رنگ موتور JS ، یک مفهوم در هر زمان.

من به طور رسمی مقاله Arrow Tbuns را به عنوان قسمت 0 در نظر می گیرم ، و پست امروز قسمت 1 است. بیایید با یکی از مفاهیم نادرست ترین (و بیش از حد) در JavaScript: وراثت-هم مبتنی بر کلاس و نمونه اولیه ، کار کنیم.


قبل از شیرجه رفتن-یک سر سریع

قبل از اینکه واقعاً وارد دنیای جاوا اسکریپت شویم ، می خواهم یک نکته مهم را برای هر کسی که از این پست ها به عنوان یک منبع یادگیری استفاده می کند به اشتراک بگذارم:

داشتن یک پایه اساسی در منطق برنامه نویسی-به خصوص از زبانهای سطح پایین مانند C یا C ++ -می تواند تفاوت زیادی در چگونگی درک آنچه در زیر کاپوت در جاوا اسکریپت (و سایر زبانهای سطح بالا) اتفاق می افتد ، ایجاد کند.

در حالی که شما نه نیاز به تسلط بر همه اینها ، من به شدت توصیه می کنم حداقل با مفاهیمی مانند:

  • انواع متغیر و دامنه
  • حلقه ها و ساختارهای شرطی
  • توابع و پارامتر عبور
  • دامنه عملکرد
  • آرایه ها و داده های چند بعدی
  • نشانگرها و منابع
  • لیست های مرتبط ، درختان ، نمودارها و برنامه نویسی پویا
  • پیچیدگی کد و تفکر الگوریتمی
  • اصول اساسی OOP

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

با توجه به این نکته … بیایید شروع کنیم! 🚀


وراثت مبتنی بر طبقاتی

وراثت مفهومی است که برای جلوگیری از تکثیر کد استفاده می شود – به عبارت دیگر ، برای جلوگیری از نوشتن همان چیزها بارها و بارها – و همچنین نگهداری کد را نیز آسان تر می کند. در JavaScript ، این لزوماً به کلاس ها گره خورده است ، اما بیایید با آنها شروع کنیم. فرض کنید ما می خواهیم برخی از افراد را در یک سیستم نمایندگی کنیم ، این کار را مانند این انجام می دهیم:

const person1 = {
  speak() {
    return "Speaking";
  }
}

const person2 = {
  speak() {
    return "Speaking";
  }
}

person1.speak(); // Speaking
person2.speak(); // Speaking
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

وای ، این آسان بود ، درست است؟ ایجاد اشیاء در JS با استفاده از JSON ساده است ، اما دارای نقاط ضعف آن است. در اینجا ، ما هیچ کلاس نداریم که یک “الگوی” را برای این اشیاء تعریف کند ، بنابراین اگر می خواهم بیشتر ایجاد کنم یا آنچه را که یک شخص انجام می دهد ، ویرایش کنم ، باید آن افراد را به صورت خطی ویرایش یا ایجاد کنم. این هنوز هم در اینجا قابل کنترل است – دو نفر ، همان پرونده – اما یک سیستم پیچیده را تصور کنید که 1000 نفر در پرونده های مختلف پراکنده هستند.

اینجاست که مفهوم کلاس ها وارد می شوند.

class Person {
  speak() {
    return "Speaking";
  }
}

const person1 = new Person();
const person2 = new Person();
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بوم ما مشکل کپی برداری کد طولانی را حل کردیم (اگرچه در این مثال طولانی نبود). کسانی که با مفهوم OOP (برنامه نویسی شی گرا) در زبانهای دیگر آشنا هستند ، احتمالاً این نحو را دیده اند. اما اینجاست که همه چیز کمی تغییر می کند.

بیایید به مشکل دوم بپردازیم: ویرایش شخص در هنگام بروز مشکل. اول ، بیایید بفهمیم چه چیزی person1 وت person2 هستند:

typeof(person1); // "object"
console.log(person1); // Person {}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

person1 یک موضوع از نوع است Personبشر با این حال ، وقتی آن را گسترش می دهیم ، روش “صحبت” را در داخل آن نمی بینیم. هنوز person1.speak() خوب کار می کند چرا این است؟

اگر به درون شی نگاه کنیم ، یک ویژگی جالب را پیدا می کنیم: __proto__بشر در داخل آن خاصیت ، ما یک سازنده و خود داریم speak روش

اگر به طور خاص به کلاس شخص نگاه کنیم Person.prototype، ما چیزی بسیار مشابه می بینیم. آنها نه تنها مشابه هستند – آنها همان چیز هستند.

person1.__proto__;
//{
//  constructor: class Person()
//  speak: function speak()
//  __proto__: Object
//}

Person.prototype;
//{
//  constructor: class Person()
//  speak: function speak()
//  __proto__: Object
//}

person1.__proto__ === Person.prototype; // true
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این بدان معنی است که ما می توانیم با استفاده از یکی از اشیاء ، ویژگی های کلاس را “اصلاح کنیم” و برعکس – می توانیم با تغییر خود کلاس ، خصوصیات همه اشیاء را که نمونه های یک کلاس هستند اصلاح کنیم (که به هر حال بهترین روش است).

// Modifying through the object
person1.__proto__.speak = function() {
  return "Speaking from the object";
}

Person.prototype.speak(); // "Speaking from the object"

// Modifying through the class (better practice)
Person.prototype.speak = function() {
  return "Improved speaking";
}

person1.speak(); // "Improved speaking"
person2.speak(); // "Improved speaking"
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

همه اینها میراث مبتنی بر کلاس است. اما بیایید کلاسها را فعلاً کنار بگذاریم.

ارث اولیه

حتی اگر ما از آن استفاده کردیم class کلمه کلیدی در مثالهای بالا ، JavaScript کمی متفاوت کار می کند. تمام وراثت مبتنی بر نمونه های اولیه است (__proto__ وت prototype). حتی وقتی از کلاس ها استفاده می کنیم ، در زیر کاپوت ، هنوز هم نمونه اولیه است. استفاده از کلاس ها فقط است قند نحوی – میانبر مفید برای مدیریت وراثت و نمونه های اولیه.

اما درک آنچه در پشت صحنه اتفاق می افتد مهم است. اساساً ، وقتی اعلام می کنیم Person کلاس ، JS در واقع یک عملکرد را اعلام می کند و آنچه را که در کلاس ایجاد کردیم به آن عملکرد اختصاص می دهیم. بنابراین ، کد زیر در واقع همان نتیجه ایجاد یک کلاس است:

function Person() {}

Person.prototype.speak = function() {
  return "Speaking";
}

const person1 = new Person();
person1.speak(); // Speaking

person1.__proto__;
//{
//  constructor: f Person()
//  speak: function speak()
//  __proto__: Object
//}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

function Person() {
  this.speak = function() {
    return "Speaking";
  }
}

const person1 = new Person();
person1.speak(); // Speaking

person1;
//{
//  speak: function speak()
//  __proto__: Object
//}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

اما در اینجا ما یک اساسی تفاوت توجه کنید که اکنون speak عملکرد مستقیماً در داخل است person1بشر تفاوت چیست؟

روش/ویژگی در مقابل روش

وقتی استفاده می کنیم this و عملکرد را مستقیماً در محدوده شیء اختصاص دهید ، به یک ویژگی/ویژگی تبدیل می شود ، نه یک روش. مهمتر از همه ، این عملکرد بدست می آید کپی شده به هر نمونه از Person شیء. هنگامی که عملکرد در نمونه اولیه است ، یک روش در نظر گرفته می شود.

در پایان ، شما هنوز هم به همان روش به عملکرد دسترسی پیدا می کنید ، اما همانطور که گفته شد ، در هر نمونه کپی می شود. بنابراین اگر می خواهید رفتار عملکرد را تغییر دهید ، باید آن را در هر نمونه شیء جداگانه تغییر دهید – به مشکل ما در ویرایش 1000 نفر در سراسر پایگاه کد برگردید.

بیایید این را در عمل ببینیم:

function Person() {
  this.age = 12;
}

Person.prototype.speak = function() {
  return "Speaking";
}

const person1 = new Person();

person1.age; // 12
person1.speak(); // Speaking

// Editing Person
Person.prototype.speak = function() {
  return "Speaking more";
}

Person.age = 40;

// Accessing person1;

person1.age; // 12
person1.speak(); // "Speaking more"

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

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

یک جزئیات دیگر بیایید شخص را بازآفرینی کنیم:

function Person() {
  this.age = 12;
}

Person.age; // undefined
Person.prototype.age; // undefined

const person1 = new Person();
person1.age; // 12
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

توجه کنید که چگونه age ویژگی در تعریف دیگری وجود ندارد Person یا نمونه اولیه آن age فقط یک ویژگی/خاصیت است که در هر یک کپی می شود Person نمونه

این باعث می شود جدایی بین چیزهایی که من دارم (ویژگی ها/خصوصیات) و کارهایی که انجام می دهم (روش ها) بسیار واضح تر می کنم. چیزهایی که من فردی دارم و کارهایی که انجام می دهم می تواند بین اشیاء از همان نوع به اشتراک گذاشته شود و به ارث ببرد.

در اینجا مثالی با کلاس دیگر آورده شده است ، این بار با استفاده از سازندگان برای ایجاد شیء:

function Car(color, model, year) {
  this.color = color;
  this.model = model;
  this.year = year;
}

Car.prototype.accelerate = function() {
  return "Accelerating";
}

Car.prototype.showOff = function() {
  return `Hey, check out my ${this.color} ${this.year} ${this.model}`;
}

const car1 = new Car("aztec gold", "Vista Cruiser", 1969);
const car2 = new Car("black", "Impala", 1967);

car1.accelerate(); // Accelerating
car2.accelerate(); // Accelerating

car1.showOff(); // Hey, check out my aztec gold 1969 Vista Cruiser
car2.showOff(); // Hey, check out my black 1979 Impala  

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

توجه کنید که چگونه هر دو مورد از Car قوطی accelerate وت showOff، اما هرکدام هنگام انجام این کار ویژگی های خاص خود را نشان می دهند.


توجه: ویژگی در مقابل خاصیت

در JavaScript ، هیچ تفاوت دقیق بین این دو وجود ندارد ، اما در سایر زبان های OOP ، ویژگی های کلاس مقادیر اعلام شده با استفاده از این در داخل کلاس هستند. خواص مواردی هستند که دارای گیرنده و تنظیم کننده هستند (یعنی دسترسی و جهش).

کلاسهای گسترش

اکنون که می فهمیم کلاس ها چگونه در زیر کاپوت کار می کنند ، بیایید به نحو کلاس برگردیم. کلاس ها می توانند باشند تمدید شده به طوری که می توان از رفتارهای کلاس والدین در کلاسهای کودک استفاده مجدد کرد. در اینجا یک نسخه ی نمایشی عملی وجود دارد:

class Person {
  speak() {
    return "Speaking";
  }
}

class SuperHuman extends Person {
  fly() {
    return "I believe I can fly";
  }
}

const person1 = new Person();
person1.speak(); // Speaking

const hero1 = new SuperHuman();
hero1.speak(); // Speaking
hero1.fly();   // I believe I can fly
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

توجه کنید که ، حتی اگر صریحاً اعلام نشده باشد ، نمونه ای از آن SuperHuman می تواند مانند یک فرد معمولی صحبت کند ، اما می تواند پرواز کند – چیزی معمولی Person نمونه نمی تواند انجام دهد.

راه های دیگر برای ایجاد وراثت

تاکنون ، برای ایجاد اشیاء با خواص وراثت (__proto__) ، ما همیشه از کلمه کلیدی جدید استفاده کرده ایم. اما روش های دیگری برای انجام آن با استفاده از اشیاء خالص و کمی کمک از کلاس شی وجود دارد ، که نحوه ساختار اشیاء را مشخص می کند.

const person = {
  speak() {
    return "Speaking";
  }
}

// Method 1
const me = Object.create(person);

person.speak(); // Speaking
me.speak();     // Speaking

// Method 2
const you = {};
Object.setPrototypeOf(you, person);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در اصل ، همه آنها همان کار را انجام می دهند. شخصاً ، به دلایل سازمانی ، من ترجیح می دهم کلاسهای تعریف شده ای را برای اشیاء مورد استفاده مجدد در سیستم استفاده کنم – مانند بدنه های درخواست برای یک مسیر API یا نوع بازگشت یکی از آن مسیرها.

چه موقع از وراثت استفاده کنیم

ساده: هر زمان که در سیستم اشیاء ایجاد می کنید که رفتار را به اشتراک می گذارد. در اینجا ، ما از نمونه های اساسی در دنیای واقعی استفاده کردیم تا مواردی را آسانتر کنیم. اما در سناریوهای واقعی ، ما مواردی مانند مؤلفه ایجاد با استفاده از کلاس ها:

class LoginPanel extends React.Component { ... }
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ما در حال ایجاد مؤلفه خودمان هستیم که رفتارهای اساسی مؤلفه های React را گسترش می دهد.
هنگام استفاده از مؤلفه های وب ، ایده یکسان است – ما رفتار پایه یک عنصر HTML را گسترش می دهیم:

class DefaultTable extends HTMLElement { ... }
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

افکار نهایی

آن را تست کنید ، با این عناصر بازی کنید ، بازی کنید و زمینه وراثت را درک کنید. در پست بعدی ، ما به تفاوت بین prototype وت __proto__بشر


💬 هیچ افکار ، سؤال یا کاربردهای جالب از وراثتی که با آن روبرو شده اید دارید؟

یک نظر را رها کنید – من دوست دارم یاد بگیرم که چگونه از این در پروژه های خود استفاده می کنید!

👉 مرا دنبال کنید matheusjulidori برای قسمت های بعدی آیا می دانید چگونه کار می کند؟

بعدی: __proto__ در مقابل prototype – تفاوت واقعی چیست؟

نوشته های مشابه

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

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

دکمه بازگشت به بالا