تعریف و استفاده از اشیاء - بخش اول
در این بخش قصد داریم به بررسی مقدماتی روشهای تعریف و استفاده از اشیاء در جاوا اسکریپت بپردازیم. توجه کنید که برنامهنویسی شئگرا، مبحث نسبتاً پیچیدهای است که در این فصل مقدمات آن را بررسی میکنیم. البته در عمل بیشتر برنامهنویسان جاوا اسکریپت، نیاز چندانی به آشنایی با مباحث پیچیدهی برنامهنویسی شئگرا ندارند و مباحث مطرح شده در این فصل برای اکثر برنامهنویسان جاوا اسکریپت کافی است. با این حال در فصل ۱۲ با جزئیات بیشتری به برنامهنویسی شئگرا و تکنیکهای آن خواهیم پرداخت.
تعریف اشیاء در جاوا اسکریپت
برای تعریف یک متغیر جدید از نوع شئ یا Object دو روش وجود دارد. روش اول استفاده از تابع سازندهی Object و روش دوم استفاده از فرم Literal است. در قطعه کد زیر دو شئ obj1 و obj2 با دو روش متفاوت ذکر شده ایجاد شدهاند.
let obj1 = new Object(); // روش اول
let obj2 = { }; // روش دوم
هر دو دستور فوق یک شئ کاملاً تهی را بدون هیچ خاصیت و متدی ایجاد میکنند و کاملاً معادل یکدیگر هستند (البته تمام اشیاء در جاوا اسکریپت دارای خاصیتها و متدهای مشترکی هستند که فعلاً از آنها صرف نظر میکنیم). به یاد دارید که برای تعریف آرایهها نیز دو روش مشابه وجود داشت. یکی استفاده از تابع سازندهی Array و دیگری استفاده از فرم Literal یا لفظی. به دلیل سادگی فرم Literal، معمولاً اشیاء را با این روش ایجاد میکنیم. همانطور که آرایهها را نیز معمولاً به صورت Literal تعریف میکردیم. ضمناً فرم Literal مزیت دیگری نیز دارد که در ادامه خواهیم دید.
حال برای افزودن یک خاصیت جدید به هر یک از اشیاء ایجاد شده، میتوان از عملگر نقطه به صورت زیر استفاده کرد.
obj1.property1 = 10;
این دستور یک خاصیت جدید به نام property1 را به شئ obj1 اضافه میکند و مقدار آن را برابر با ۱۰ قرار میدهد. توجه کنید که اگر خاصیتی با این نام از قبل برای این شئ تعریف شده باشد، دستور فوق مقدار آن را تغییر میدهد و خاصیت جدیدی ایجاد نمیکند. مثلاً اگر پس از دستور فوق، دستور زیر را اجرا کنید، مقدار خاصیت property1 از ۱۰ به ۲۰ تغییر خواهد کرد.
obj1.property1 = 20;
همچنین برای افزودن یک متد، میتوان از عملگر نقطه به صورت زیر استفاده کرد. یادآوری میشود که یک متد یک تابع است. در واقع تابعی که متعلق به یک شئ خاص باشد، متدی از آن شئ خواهد بود.
obj1.method1 = function(){
console.log('I am a method');
};
حال برای فراخوانی یک متد و یا خواندن مقدار یک خاصیت، باز هم میتوان از عملگر نقطه استفاده کرد. تنها تفاوت بین دسترسی به متدها و خاصیتها این است که برای متدها حتماً باید از یک جفت پرانتز هم استفاده شود.
obj1.property1;
← 20
obj1.method1();
← "I am a method"
در صورتی که قصد خواندن مقدار خاصیتی را داشته باشید که تعریف نشده است، مقدار undefined بازگردانده میشود. همچنین در صورت فراخوانی متدی که تعریف نشده است نیز، با خطا مواجه شده و برنامه متوقف خواهد شد.
obj1.property2;
← undefined
obj1.method2(); // Uncaught TypeError: obj1.method2 is not a function
روش دیگر برای دسترسی به متدها و خاصیتهای یک شئ، استفاده از یک جفت براکت "[ ]" است. در واقع در آرایهها (که نوعی شئ هستند) از همین روش برای دسترسی به عناصر آرایه استفاده میشود. با این تفاوت که در آرایهها از اندیسهای عددی استفاده میشود. اما برای سایر اشیاء از نام متدها یا خاصیتها استفاده میشود. در قطعه کد زیر روش دسترسی به متد و خاصیت تعریف شده در مرحلهی قبل نشان داده شده است.
obj1['property1'];
← 20
obj1['method1']();
← "I am a method"
در اکثر مواقع استفاده از عملگر نقطه به روش فوق ترجیح داده میشود. اما این روش مزایایی دارد که در برخی مواقع (به ندرت) مجبور به استفاده از آن هستیم. یکی از مزایای این روش، امکان استفاده از شناسههای نامعتبر به عنوان نام متد یا خاصیت است. در صورتی که عملگر نقطه فقط میتواند برای تعریف و دسترسی به متدها و خاصیتهایی به کار برده شود که نامشان یک شناسهی معتبر باشد. به مثال زیر توجه کنید.
obj1['my-property'] = 30;
obj1['my-property'];
← 30
obj1.my-property; // Uncaught ReferenceError: property is not defined
طبق قوانین شناسهها در جاوا اسکریپت، my-property یک شناسهی نامعتبر است. با این حال در صورت استفاده از براکت، میتوان خاصیتی با این نام تعریف کرد و یا مقدار آن را خواند. اما در صورت استفاده از عملگر نقطه، برنامه با خطا مواجه شده و متوقف میشود.
شاید این سوال برای شما پیش آمده باشد که اصلاً چه نیازی به استفاده از شناسههای نامعتبر وجود دارد؟ پاسخ این است که اگر تمام کنترل برنامه و نامگذاریهای انجام شده در آن، در اختیار برنامهنویس جاوا اسکریپت باشد، هیچ نیازی به شناسههای نامعتبر نخواهیم داشت. اما همیشه این شرایط برقرار نیست و در برخی شرایط مجبور به استفاده از شناسههایی هستیم که توسط برنامهنویسان دیگری در زبانهای دیگر تعریف شدهاند. مثال بارز این شرایط، دریافت اطلاعات از سرور در قالب JSON است. با توجه به این که قوانین نامگذاری شناسهها در زبانهای مختلف متفاوت است. ممکن است شناسههایی در دادههای JSON تعریف شده باشند که در JSON معتبر، اما در جاوا اسکریپت نامعتبر باشند. در چنین شرایطی فقط میتوانیم از براکت برای دسترسی به این شناسهها استفاده کنیم. (دریافت اطلاعات از سرور در جاوا اسکریپت با تکنیکی به نام Ajax انجام میشود که در فصل ۱۱ به آن میپردازیم.)
اما استفاده از براکت برای دسترسی به متدها و خاصیتهای اشیاء، مزیت دیگری نیز نسبت به عملگر نقطه دارد. در صورت استفاده از براکت، میتوان از متغیرها داخل براکت استفاده کرد. اما عملگر نقطه چنین قابلیتی ندارد. یعنی ممکن است نام یک خاصیت یا متد در یک متغیر ذخیره شده باشد. در این حالت برای دسترسی به آن خاصیت یا متد باید از براکت استفاده کرد و متغیر را در براکت قرار داد.
let obj = {};
obj.color = 'Red';
let property = 'color';
obj.property;
← undefined
obj[property];
← "Red"
همانطور که مشاهده میکنید در صورت استفاده از عملگر نقطه، مفسر جاوا اسکریپت فرض میکند که هدف شما دسترسی به خاصیتی به نام property است. و با توجه به اینکه چنین خاصیتی برای این شئ تعریف نشده است، مقدار undefined بازگردانده میشود. اما در صورت استفاده از براکت، مقدار متغیر property، یعنی "color" به عنوان نام خاصیت در نظر گرفته میشود. همچنین در صورت استفاده از براکت میتوان از انواع عبارتهای محاسباتی و منطقی و ... در تولید نام خاصیتها و متدها استفاده کرد. مثلاً در قطعه کد زیر برای دسترسی به خاصیت color از الحاق چند رشته که منجر به تولید رشتهی "color" میشود استفاده شده است.
let a = 'col';
let b = 'r';
obj[a + 'o' + b];
← "Red"
البته استفاده از براکت با وجود مزیتهایی که به آنها اشاره شد، به ندرت به کار برده میشود و تقریباً همیشه از عملگر نقطه استفاده میشود. مگر در شرایطی که مجبور به استفاده از براکت باشیم.
افزودن متدها و خاصیتها در تعریف اشیاء
در صورت تعریف اشیاء به روش Literal، این امکان وجود دارد که متدها و خاصیتهای اشیاء را نیز در زمان تعریف، به شئ مورد نظر اضافه کنیم. قطعه کد زیر نحوهی انجام این کار را نشان میدهد.
let person = {
firstname: 'Abbas',
lastname: 'Moqaddam',
age: 33,
showBio: function(){
console.log('I am Abbas Moqaddam & I am 33 years old');
}
};
در کد فوق متغیری به نام person از نوع Object به روش Literal تعریف شده است. این شئ دارای سه خاصیت و یک متد است. در مثال فوق ابتدا خاصیتها تعریف شدهاند و تنها متد این شئ پس از خاصیتها تعریف شده است. اما هیچ محدودیتی در ترتیب قرار دادن خاصیتها و متدها وجود ندارد. در صورتی که در زمان تعریف یک شئ، خاصیتها و متدهای مورد نیاز آن شئ مشخص باشند، معمولاً از روش فوق استفاده میشود و تمام موارد در یک دستور تعریف میشوند. البته هنوز این امکان وجود دارد که خاصیتها و متدهای دیگری را نیز با روشهای قبلی به این شئ اضافه کنیم. مثلاً در قطعه کد زیر، خاصیت دیگری به نام job به این شئ اضافه میشود.
person.job = 'Teacher';
نکته : در تعریف اشیاء به فرم Literal، میتوان از شناسههای نامعتبر نیز برای نامگذاری متدها و خاصیتها استفاده کرد. در این صورت باید این شناسهها را مانند رشتهها در Single Quote یا Double Quote قرار داد. مانند خاصیت my-property در مثال زیر.
let obj = {
"my-property": 44
};
البته در صورت تمایل میتوانید شناسههای معتبر را نیز داخل Quotation قرار دهید.
یادآوری میشود که متدها، در واقع توابعی هستند که به یک شئ تعلق دارند. در نتیجه متدها نیز مانند توابع میتوانند دارای تعدادی پارامتر ورودی بوده و مقداری را نیز به عنوان خروجی با استفاده از دستور return بازگردانند. مثلاً میتوان متد showBio را به صورت زیر اصلاح کرد تا نام، نام خانوادگی و سن شخص را دریافت کرده و و رشتهای را با استفاده از این ورودیها تولید کرده و بازگرداند.
person.showBio = function(firstname , lastname , age){
return 'I am ' + firstname + ' ' + lastname + ' & I am ' + age + ' years old';
};
توجه کنید که دستور فوق متد showBio را بازنویسی میکند و یک تابع جدید را برای آن تعریف میکند. حال میتوان به صورت زیر متد showBio را فراخوانی کرده و مقدار بازگشتی از آن را در محل مناسب نمایش داد.
let str = person.showBio('Abbass' , 'Moqaddam' , 33);
console.log(str);
← "I am Abbas Moqaddam & I am 33 years old"
نکته : در استاندارد ES6 برای افزودن متدها در زمان تعریف اشیاء، میتوان کلمهی کلیدی function را حذف کرد. این کار باعث کوتاهتر شدن کدنویسی میشود. اما در این فصل برای بالا بردن خوانایی برنامهها از این روش استفاده نمیکنیم. قطعه کد زیر نحوهی استفاده از این روش برای افزودن متد showBio به شئ person را نشان میدهد.
let person = {
firstname: 'Abbas',
lastname: 'Moqaddam',
age: 33,
showBio(){
console.log('I am Abbas Moqaddam & I am 33 years old');
}
};
کلمهی کلیدی this
یک بار دیگر به مثال قبل توجه کنید. برای فراخوانی متد showBio چه آرگومانهایی به آن ارسال شده است؟ هر ۳ آرگومان ارسال شده به این متد، از خاصیتهای شئ person هستند. این پاسخ سوال جدیدی را پیش میآورد. آیا این امکان وجود دارد که مقدار این خاصیتها در بدنهی متد showBio از طریق دیگری و بدون نیاز به ارسال آرگومان به دست آید؟ پاسخ این سوال مثبت است.
برای دسترسی به خاصیتها و متدهای یک شئ در بدنهی متدهای همان شئ، میتوان از کلمهی کلیدی this (یا اشارهگر this) استفاده کرد. البته کلمهی کلیدی this در شرایط مختلف معانی متفاوتی دارد. اما فعلاً تمرکز ما بر همین کاربرد از کلمهی کلیدی this است که در واقع کاربرد اصلی آن نیز میباشد. مثال قبل را میتوان به صورت زیر اصلاح کرد تا نیازی به ارسال مقدار خاصیتها به عنوان آرگومان نباشد.
person.showBio = function(){
return 'I am ' + this.firstname + ' ' + this.lastname + ' & I am ' + this.age + ' years old';
};
let str = person.showBio();
console.log(str);
← "I am Abbas Moqaddam & I am 33 years old"
پس در بدنهی هر متدی از یک شئ خاص، میتوان با استفاده از کلمهی کلیدی this به خاصیتها و سایر متدهای همان شئ دسترسی داشت. یکی از مزایای استفاده از کلمهی کلیدی this این است که در این حالت با تغییر مقدار خاصیتهای یک شئ، نیازی به تغییر آرگومانهای ورودی متد نیست و این تغییرات به صورت خودکار در زمان خواندن مقدار خاصیتها در بدنهی متد، اعمال خواهند شد. مثلاً اگر در مثال فوق مقدار خاصیت age را به ۳۴ تغییر دهیم. باز هم میتوان بدون ارسال هیچ آرگومانی متد showBio را فراخوانی کرده و نتیجه را با اطلاعات جدید دریافت کرد. قطعه کد زیر این ویژگی را نشان میدهد.
person.age = 34;
let str = person.showBio();
console.log(str);
← "I am Abbas Moqaddam & I am 34 years old"
این مثال را (با کمی تغییر) میتوانید اینجا اجرا کنید.
نکته : کلمهی کلیدی this در بسیاری از زبانهای برنامهنویسی با کاربرد مشابه وجود دارد. اما در برخی زبانها، از جمله در C++ به آن "اشارهگر this" یا "this pointer" گفته میشود. در مستندات ECAMScript همیشه از لفظ "کلمهی کلیدی this" یا "this keyword" استفاده شده است. با این حال در این کتاب در برخی موارد ممکن است از لفظ "اشارهگر this" برای آن استفاده شود. (در ادامهی همین فصل در مورد مفهوم اشارهگر نیز بحث خواهیم کرد.)