سینتکس معمولی {…} اجازه ساخت یک شیء را می دهد. اما غالبا ما نیاز داریم که شیء های متشابه زیادی ایجاد کنیم، مثل چند کاربر یا آیتم های منو و…
این می تواند با استفاده از تابع های سازنده و عملگر 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
با برگرداندن یک شیء this
را نادیده میگیرد:
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- این شیء را برمیگرداند
}
alert( new BigUser().name ); // Godzilla, آن شیء را نتیجه داد
و اینجا هم یک مثال با یک return
خالی داریم (یا می توانستیم بعد از آن یک primitive بگذاریم، فرقی ندارد):
function SmallUser() {
this.name = "John";
return; // <-- this را برمیگرداند
}
alert( new SmallUser().name ); // John
معمولا سازنده ها دستور return
ندارند. اینجا ما این رفتار خاص برگرداندن شیءها را تنها برای کامل بودن خاطر نشان کردیم.
راستی، اگر هیچ آرگومانی در کار نباشد، ما می توانیم پرانترهای بعد از new
را حذف کنیم:
let user = new User; // <-- بدون پرانتز
// مشابه است با
let user = new User();
اینجا حذف کردن پرانتزها به عنوان یک “سبک خوب” فرض نمی شود، اما سینتکس طبق خصوصیات مجاز است.
متدها در سازنده
استفاده از تابع های سازنده برای ساخت شیءها انعطاف زیادی به ما میدهد. تابع سازنده ممکن است پارامترهایی داشته باشد که تعیین میکند چگونه شیء ساخته شود، و چه چیزی داخل آن قرار داده شود.
قطعا ما می توانیم علاوه بر ویژگی ها، متدها را هم به this
اضافه کنیم.
برای مثال، new User(name)
که در زیر قرار دارد یک شیء میسازد که به آن name
و متد sayHi
داده شده است:
function User(name) {
this.name = name;
this.sayHi = function() {
alert( "My name is: " + this.name );
};
}
let john = new User("John");
john.sayHi(); // My name is: John
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
برای ساخت شیءهای پیچیده، یک سینتکس پیشرفته تر، کلاس ها، وجود دارد که ما بعدا آن را پوشش می دهیم.
خلاصه
- تابع های سازنده، یا به اختصار، سازنده ها، تابع هایی معمولی هستند، اما یک توافق عمومی وجود دارد که آنها را با حرف بزرگ انگلیسی نامگذاری کنیم.
- تابع های سازنده باید تنها با
new
صدا زده شوند. چنین صدا زدنی به ساخت یکthis
خالی در آغاز و برگرداندن پر شده ی آن در پایان اشاره می کند.
ما می توانیم از تابع های سازنده برای ساخت چند شیء متشابه استفاده کنیم.
جاوااسکریپت تابع های سازنده را برای بسیاری از شیءهایی که درون زبان ساخته شده اند فراهم میکند: مثل Date
برای زمان ها، Set
برای set ها و بقیه که ما مطالعه آنها را در نظر داریم.
در این فصل ما فقط اصول اولیه را برای شیءها و سازنده ها را پوشش می دهیم. آنها برای یادگیری بیشتر درباره انواع داده و تابع ها در فصل های آینده ضروری هستند.
بعد از اینکه آن را یاد گرفتیم، در فصل مقاله "object-oriented-programming" پیدا نشد (برنامه نویسی شیءگرا) به شیءها بر می گردیم و آنها را به صورت عمیق پوشش می دهیم، که شامل وراثت و کلاس ها هم می شود.