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

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
– تفاوت واقعی چیست؟