سینتکس معمولی {…} اجازه ساخت یک شیء را می دهد. اما غالبا ما نیاز داریم که شیء های متشابه زیادی ایجاد کنیم، مثل چند کاربر یا آیتم های منو و…
این می تواند با استفاده از تابع های سازنده و عملگر new انجام شود.
تابع سازنده
تابع های سازنده از لحاظ فنی همان تابع های معمولی هستند. با این حال دو قرارداد وجود دارد:
- آنها با حرف بزرگ انگلیسی نامگذاری می شوند.
- آنها باید فقط با عملگر
newاجرا شوند.
برای مثال:
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // نادرست
زمانی که یک تابع با new اجرا می شود، مراحل زیر را انجام می دهد:
- یک شیء خالی جدید ساخته می شود و به
thisاختصاص می یابد. - بدنه ی تابع اجرا می شود. معمولا
thisرا تغییر می دهد، ویژگی های جدید را به آن اضافه می کند. - مقدار
thisبرگردانده می شود.
به عبارتی دیگر، new User(...) چیزی مانند این را انجام می دهد:
function User(name) {
// this = {}; (به صورت ضمنی)
// ویژگی ها را به this اضافه میکند
this.name = name;
this.isAdmin = false;
// return this; (به صورت ضمنی)
}
پس let user = new User("Jack") نتیجه مشابهی مانند کد زیر را می دهد:
let user = {
name: "Jack",
isAdmin: false
};
حالا اگر ما بخواهیم که user های دیگری بسازیم، می توانیم new User("Ann")، new User("Alice") و … را صدا بزنیم. این کار بسیار کوتاه تر از استفاده همیشگی از literal ها است، و همچنین برای خواندن آسان است.
این هدف اصلی سازنده ها است – پیاده سازی کد قابل استفاده مجدد ساخت شیء.
بیایید دوباره به این موضوع اشاره کنیم – از لحاظ فنی، هر تابعی می تواند به عنوان سازنده استفاده شود. به این معنی که: هر تابعی می تواند با new اجرا شود، و الگوریتم بالا را اجرا کند. “حرف اول بزرگ انگلیسی” یک قرارداد عمومی است، تا این موضوع را که یک تابع باید با new اجرا شود را شفاف سازی کند.
اگر ما خطوط زیادی از کد که همه آنها مربوط به ساخت یک شیء پیچیده هستند را داشته باشیم، می توانیم آنها را درون تابع سازنده بپیچیم، به این صورت:
// create a function and immediately call it with new
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ... کد های دیگر برای ساخت user
// شاید شامل منطق و دستورالعمل پیچیده ای باشد
// متغیرهای محلی و...
};
سازنده نمی تواند دوباره صدا زده شود، چون در جایی ذخیره نشده، فقط ساخته و صدا زده شده است. پس این ترفند، کپسول کردن کدی که یک شیء می سازد و در آینده استفاده نمی شود را مورد هدف قرار می دهد.
سازنده هایی با سینتکس دوگانه: new.target
سینتکس این بخش به ندرت استفاده می شود، آن را از قلم بندازید مگر اینکه بخواهید همه چیز را بدانید.
درون یک تابع، ما می توانیم چک کنیم که همراه با new صدا زده شده یا بدون آن، با استفاده از ویژگی new.target.
آن(new.target) برای مواقعی که تابع به صورت معمولی صدا زده می شود undefined است و درصورتی که همراه با new صدا زده شود برابر با تابع است:
function User() {
alert(new.target);
}
// بدون "new":
User(); // undefined
// همراه با "new":
new User(); // function User { ... }
از آن می توان استفاده کرد تا هم صدا زدن تابع با new و هم صدا زدن معمولی تابع به یک شکل کار کنند. یعنی اینکه شیء متشابه بسازند:
function User(name) {
if (!new.target) { // اگر تو مرا بدون new اجرا کنی
return new User(name); // ... من new را برای تو اضافه میکنم
}
this.name = name;
}
let john = User("John"); // فراخوانی را به new User هدایت میکند
alert(john.name); // John
این شیوه بعضی اوقات درون کتابخانه ها استفاده می شود تا سینتکس را منعطف تر کند. با این روش شاید مردم تابع را همراه با یا بدون new صدا بزنند، و آن همچنان کار میکند.
اگرچه شاید خوب نباشد همه جا استفاده شود، چون حذف کردن new مقداری از واضح بودن اینکه چه چیزی در حال رخ دادن است کم میکند. همراه با new همه ما میدانیم که شیء جدیدی در حال ساخته شدن است.
برگرداندن از سازنده ها
معمولا، سازنده ها دستور return ندارند. وظیفه آنها این است که تمام چیزهای ضروری را داخل this بریزند، و آن(this) تبدیل به نتیجه می شود.
اما اگر دستور return وجود داشته باشد، سپس قاعده ساده است:
- اگر
returnهمراه با شیء صدا زده شود، سپس شیء به جایthisبرگردانده می شود. - اگر
returnهمراه با یک primitive صدا شده شود، نادیده گرفته می شود.
به عبارتی دیگر، return همراه با یک شیء همان شیء را برمیگرداند، در دیگر موارد this برگردانده می شود.
برای مثال، اینجا return