با استفاده از function constructor شیء ایجاد کردیم. اما همان طور که گفته شد function constructor برای تقلید از سایر زبانها که prototype inheritance را پیاده سازی نمیکنند طراحی شده. سایر زبانها از کلاس استفاده میکنند. کلاس تعیین میکند که شیء باید چه شکلی باشد، سپس با کلمه کلیدی new شیء ایجاد میشود. این چیزی است که function constructor سعی میکند تقلید کند. اما بسیاری معتقدند که بهتر است فقط روی آنچه js روی prototype inheritance انجام میدهد تمرکز کرد و کاری با classical inheritance نداشت. پس میخواهیم از روشی برای ایجاد شیء استفاده کنیم که از زبان های دیگر تقلید نمیشود. همه بروزرهای مدرن آن را دارند: Object.creat.
var person = {
firstname: 'Default',
lastname: 'Default',
greet: function() { return 'Hi '+this.firstname; }
};
اگر داخل تابع greet از this استفاده نکنیم، داخل خود تابع greet دنبال firstname میگردد و چون آن را پیدا نمیکند داخل global میگردد و پیدایش نمیکند. چون firstname داخل person نوشته شده. شیء person یک execution context جدید درست نمیکند. شیء person را ایجاد کردیم. حالا میخواهیم شیء دیگری به روش دیگر ایجاد کنیم و prototype آن را تعیین کنیم:
var john = Object.create(person);
console.log(john);
شیء Object شیء پایه در js است. شیئی را که میخواهیم از آن شیء جدید ایجاد کنیم به create پاس دادیم. تابع create در موتور js م built-in است. خروجی کد بالا:
Object { firstname:'Default', lastname:'Default', greet:function }
این شیء کاملا خالی است و prototype آن شیء person است که firstname و متد greet و lastname را دارد. برای اینکه روی مقادیر Default که در prototype است overwrite کنیم:
john.firstname = 'John';
john.lastname = 'Doe';
با همون اسم ویژگی ها، به شیء جدید john ویژگی اضافه میکنیم تا وقتی موتور js میخواهد در prototype chain حرکت کند، مقدار ویژگی ها را داخل خود john ببیند و پایین نرود. در این حالت خروجی میشود:
Object { firstname:'John', lastname:'Doe', greet:function }
و همچنان prototype مان person (با مقادیر Default) است. یعنی میتوانیم john.greet() را بنویسیم تا خروجی Hi John را بدهد. این روش ساخت شیء pure prototypal inheritance است و هیچ مفهوم دیگری در ساخت شیء استفاده نشده است. اول یک شیء میسازیم، سپس از آن یک شیء جدید create میکنیم به طوری که به شیء اول به عنوان prototype ش اشاره دارد. اگر میخواهیم یک شیء جدید را تعریف کنیم آن را create میکنیم. میتوانیم روی متدها و ویژگی ها overwrite کنیم. این روش ساخت شیء بسیار ساده است.
این روش جدید است و در بروزرهای جدید ساپورت میشود. اما اگر نیاز باشد برای بروزرهای قدیمی که موتور js این را ساپورت نمیکند کار کنیم چه؟ میتوانیم از polyfill استفاده کنیم.
تعریف polyfill: کدی است که ویژگی را که احتمالا موتور ندارد اضافه میکند. معمولا از موتورهای مختلفی استفاده میکنیم، چون بروزرهای مختلف موتورهای مختلف دارند. میتوانیم کدهایی داشته باشیم که چک کنند آیا موتور یک ویژگی را دارد یا نه. اگر ندارد کدی مینویسیم که همان کار را در بروزرهای جدید انجام میدهد. یعنی گپ بین موتورهای قدیمی و جدید را پر میکنیم.
برای مثال در کد بالا که از create استفاده کرده ایم، در بروزرهای قدیمی ساپورت نمیشود. کد polyfill ای به اول کد اضافه میکنیم تا این مشکل را برطرف کند:
// polyfill
if (!Object.create) {
Object.create = function(o) {
if (arguments.length>1) { throw new Error('Object.create implementation' + ' only accepts the first parameter'); }
function F() { };
F.prototype = o;
return new F();
};
}
در این کد ابتدا چک میکنیم که آیا Object.create وجود دارد یا نه. اگر وجود نداشته باشد undefined را برمیگرداند و اگر وجود داشته باشد کل if را skip میکند. اگر وجود نداشته باشد طبق اولین خط کد این متد را به global object اضافه میکند چون شیء پایه Object در global object است. در کد polyfill ابتدا یک تابع خالی ایجاد میشود و سپس prototype (ویژگی که در function constructor داشتیم) آن تنظیم میشود. پس در این کد new یک شیء ایجاد میکند و تابع F() را که خالی است فراخوانی میکند و prototype این شیء خالی را برابر با آنچه پاس داده ایم قرار میدهد. این دقیقا همان چیزی است که Object.create انجام میدهد: یک شیء به آن میدهیم و این شیء prototype شیء جدید خالی میشود.
موقع استفاده از Object.create یک شیء ایجاد کردیم که اساس شیء های دیگر را تشکیل میدهد (person). با Object.create یک شیء دیگر از آن میسازیم و میتوانیم ویژگی هایش را overwrite یا hide کنیم. این کار pure prototypal inheritance است. پس میتوانیم prototype را on the fly تغییر دهیم. میتوانیم در هر قسمت از برنامه، بسته به آنچه نیاز داریم ویژگی هایی اضافه کنیم.
نسخه بعدی جاوا اسکریپت یعنی es 2015 یا ES6 یک مفوم جدید ارائه داده است. این مفهوم روش دیگری برای ایجاد شیء و تنظیم کردن prototype است. کلاس ها در زبان های برنامه نویسی دیگر خیلی رایج هستند. آنها روش تعریف شیء هستند. یعنی متدها و ویژگی هایش چطور باید باشند. در js کلاس نداریم. با این حال در ES6 به نحو متفاوتی کلاس داریم. کلاس js یک شیء را نشان میدهد. داخل آن یک constructor داریم که شبیه به constructor function ها عمل میکند. میتوانیم مقادیرش را از قبل تنظیم کنیم. بنابراین وقتی با new یک شیء از این کلاس ایجاد میکنیم، میتوانیم firstname و lastname را به آن پاس دهیم و کلمه this به شیء جدید ایجاد شده اشاره میکنید.
class Person {
constructor(firstname, lastname){
this.firstname = firstname;
this.lastname = lastname;
}
greet(){
return 'hi ' + firstname;
}
}
var john = new Person('John', 'Doe');
اما مشکلی وجود دارد. برنامه نویس هایی که از زبان های دیگر وارد js شده اند وقتی class را در js میبینند فکر میکنند مانند کلاسی است که در زبان های دیگر استفاده میکردند. اما در زبان های دیگر کلاس شیء نیست، فقط یک تعریف است. مثل template است و میگوید که شیء ها باید چه شکلی باشند. ولی تا زمانی که از new استفاده نکرده باشیم، شیء ندارم. اما در js با اینکه از کلمه کلیدی class استفاده میکند، باز هم یک شیء است. یعنی در مثال بالا class Person یک شیء است. به همین دلیل ممکن است برنامه نویس هایی که از زبان های دیگر آمده اند مثل همان زبان ها شروع به طراحی ساختار شیء کنند. همچنین ممکن است در استفاده از کلمه new هم مشکل پیش بیاید، گرچه چون از کلمه class استفاده شده احتمال اشتباه نسبت به function constructor کمتر است. توصیه میشود که به دنبال تقلید سایر زبان های برنامه نویسی نباشیم و از prototypal inheritance استفاده کنیم.
موقع استفاده از class برای تعیین prototype چه کنیم؟ مثلا میخواهیم یک InformalPerson درست کنیم که Person برای آن prototype باشد. برای این کار از کلمه کلیدی extends استفاده میکینم. در واقع همان ویژگی __proto__ در کروم است:
class InformalPerson extends Person {
constructor(firstname, lastname) { super(firstname, lastname); }
greet() { return 'Yo '+firstname; }
}
در constructor میتوانیم از کلمه کلیدی super استفاده کنیم تا constructor شیء prototype را فراخوانی کند. پس میتوانیم مقادیر اولیه را تا پایین زنجیره پاس دهیم. همچنین مثل مثال بالا میتوانیم متد greet را overwrite کنیم. این روشی است که دارد برای ساخت شیء و تعیین prototype به js وارد میشود. فقط یک سینتکس جدید است، در پشت صحنه همه مثل هم عمل میکنند.
تعریف syntactic sugar: یک روش تایپ متفاوت که آنچه پشت صحنه انجام میشود را تغییر نمیدهد.