تعریف و استفاده از اشیاء - بخش دوم
ساختار تکرار for-in
در فصل سوم بیشتر ساختارهای تکرار در جاوا اسکریپت معرفی شدند. اما ساختار تکرار دیگری به نام for-in وجود دارد که در این بخش آن را معرفی خواهیم کرد. با توجه به این که ساختار for-in برای اشیاء به کار برده میشود، معرفی این ساختار تا این بخش به تعویق افتاد.
ساختار تکرار for-in به ازای تمام خاصیتها و متدهای یک شئ اجرا میشود. قطعه کد زیر نحوهی استفاده از این ساختار را نشان میدهد.
let person = {
firstname: 'Abbas',
lastname: 'Moqaddam',
age: 33,
showBio: function(){
console.log('I am ' + this.firstname + ' ' + this.lastname + ' & I am ' + this.age + ' years old');
}
};
for(let key in person){
console.log(key + ': ' + person[key]);
}
اجرای دستورات فوق خروجی زیر را تولید خواهد کرد.
← firstname: Abbas
← lastname: Moqaddam
← age: 33
← showBio: function(){
console.log('I am ' + this.firstname + ' ' + this.lastname + ' & I am ' + this.age + ' years old');
}
مشاهده میکنید که به ازای هر یک از متدها و خاصیتهای شئ person، دستور خط ۱۱ یک بار اجرا شده و نام و مقدار یکی از این موارد را در کنسول چاپ میکند. مقداری که در هر تکرار در متغیر key قرار میگیرد، شناسهی یکی از متدها یا خاصیتها است. در صورت نیاز به مقدار هر متد یا خاصیت نیز میتوان از عبارت person[key] استفاده کرد. این یکی از مواردی است که مجبور به استفاده از براکت برای دسترسی به اعضای یک شئ هستیم. چرا که نام متد یا خاصیت در یک متغیر ذخیره شده است.
همچنین توجه کنید که چاپ مقدار یک متد در کنسول، منجر به نمایش تعریف کامل آن متد شده است. حتماً از بخش قبل به یاد دارید که برای اجرای یک متد باید از پرانتز باز و بسته در مقابل نام آن استفاده شود. در مثال فوق با توجه به عدم استفاده از پرانتز در مقابل عبارت person[key]، متد مذکور اجرا نمیشود و تعریف کامل آن در کنسول نمایش داده میشود. این ویژگی در رابطه با توابع نیز صادق است.
بررسی وجود یا عدم وجود خاصیتها و متدها
برای بررسی این که آیا خاصیت یا متد خاصی در یک شئ وجود دارد یا خیر، چندین روش مختلف وجود دارد. روش اول استفاده از عملگر in است. قبل از این عملگر نام یک خاصیت یا متد قرار میگیرد. و بعد از آن نام یک شئ قرار میگیرد. در صورت وجود متد یا خاصیتی با نام مشخص شده در شئ مشخص شده، مقدار true، و در غیر این صورت مقدار false بازگردانده میشود.
'firstname' in person
← true
'family' in person
← false
روش دیگر برای انجام همین کار، مقایسه با مقدار undefined است. پیش از این اشاره شد که در صورت خواندن خاصیتی که در یک شئ وجود ندارد، مقدار undefined بازگردانده میشود. بنابراین میتوان مقدار هر خاصیتی را با undefined مقایسه کرد. در صورت وجود آن خاصیت (یا متد) مقدار false و در غیر این صورت مقدار true بازگردانده خواهد شد.
person.firstname === undefined
← false
person.family === undefined
← true
البته حالت خاصی وجود دارد که این روش نتیجهی صحیحی برای آن تولید نخواهد کرد. اگر خاصیتی در یک شئ وجود داشته باشد و مقدار آن برابر با undefined باشد، در این صورت مقایسهی فوق مقدار true را بازمیگرداند که این برخلاف انتظار است. البته چنین حالتی به ندرت رخ میدهد.
روش دیگر برای تشخیص وجود یا عدم وجود یک خاصیت یا متد در یک شئ خاص، استفاده از متد hasOwnProperty است. این متد یک پارامتر ورودی دارد که باید نام خاصیت یا متد مورد نظر را به آن ارسال کنیم. در صورت وجود متد یا خاصیتی با نام ارسال شده مقدار true، و در غیر این صورت مقدار false بازگردانده میشود.
person.hasOwnProperty('showBio');
← true
person.hasOwnProperty('lastname');
← true
person.hasOwnProperty('family');
← false
در فصل ۱۲ خواهید دید که اشیاء میتوانند متدها یا خاصیتهایی را از اشیائی دیگر به ارث ببرند. ذکر این نکته ضروری است که متد hasOwnProperty فقط برای متدها و خاصیتهایی که دقیقاً متعلق به شئ مورد نظر باشند مقدار true را بازمیگرداند و برای متدها و خاصیتهای به ارث رسیده مقدار false بازگردانده خواهد شد. اما در دو روش قبلی متدها و خاصیتهای به ارث رسیده، رفتار یکسانی با سایر متدها و خاصیتها دارند.
حذف خاصیتها و متدها
با استفاده از کلمهی کلیدی delete میتوان هر خاصیت یا متدی را از یک شئ حذف کرد. قطعه کد زیر نحوهی استفاده از delete را نشان میدهد.
person.firstname === undefined
← false
delete person.firstname;
person.firstname === undefined
← true
تعریف اشیاء به صورت تو در تو (Nested Objects)
تمام خاصیتهایی که پیش از این برای اشیاء به کار بردیم از نوع عددی یا رشتهای بودند. اما هیچ محدودیتی برای نوع خاصیتها وجود ندارد و مقدار هر خاصیتی میتواند از هر نوعی مانند آرایه، مجموعه و ... باشد. همچنین میتوان اشیاء را به صورت تو در تو ایجاد کرد. یعنی میتوان اشیائی تعریف کرد که اشیاء دیگری را به عنوان خاصیتهای خود داشته باشند. قطعه کد زیر شیئی به نام person را تعریف میکند که خاصیتی به نام account دارد، که این خاصیت خود از نوع شئ یا Object است و میتواند دارای خاصیتها و متدهای خاص خود باشد.
let person = {
firstname: 'Abbas',
lastname: 'Moqaddam',
age: 33,
account: {
accountNumber: 123456789,
balance: 10000000,
showBalance: function(){
console.log('Your balance is : ' + this.balance);
}
},
showBio: function(){
console.log('I am ' + this.firstname + ' ' + this.lastname + ' & I am ' + this.age + ' years old');
}
};
به محل قرار دادن کاراکتر کاما "," در تعریف اشیاء دقت کنید. همیشه در انتهای تعریف هر خاصیت یا متد باید کاما قرار داده شود. البته بعد از آخرین عضو (متد یا خاصیت)، نیازی به قرار دادن کاما نیست. به همین دلیل بعد از هر دو متد showBalance و showBio از کاما استفاده نشده است. اما بعد از تعریف شئ account از کاما استفاده شده است. زیرا این شئ، خاصیتی از شئ person است و آخرین عضو شئ person نیز نمیباشد. پس لازم است بعد از آن هم حتماً از کاما استفاده شود.
برای دسترسی به خاصیتها و متدهای اشیاء داخلی (مانند شئ account) باید دو بار از عملگر نقطه یا براکت استفاده شود. مثلاً برای تغییر مقدار خاصیت balance و یا اجرای متد showBalance میتوان از دستورات زیر استفاده کرد.
person.account.balance = 2000000;
person.account.showBalance();
← "Your balance is : 2000000"
انتساب و کپی کردن اشیاء
به قطعه کد زیر توجه کنید.
let a = 20;
let b = a;
b = 30;
console.log('a = ' + a);
← "a = 20"
console.log('b = ' + b);
← "b = 30"
در کد فوق ابتدا متغیری به نام a و با مقدار اولیهی ۲۰ تعریف شده است. سپس متغیر جدیدی به نام b تعریف شده و مقدار اولیهی آن برابر با a قرار گرفته است. یعنی در این لحظه مقدار هر دو متغیر ۲۰ است. در خط بعدی مقدار متغیر b به ۳۰ تغییر کرده و در دو خط بعدی مقدار متغیرهای a و b در کنسول چاپ شده است.
همانطور که انتظار میرود، تغییر مقدار متغیر b هیچ تاثیری بر مقدار متغیر a ندارد و متغیر a همچنان مقدار قبلی خود یعنی ۲۰ را حفظ کرده است. اما اگر همین عمل را برای اشیاء تکرار کنیم نتیجه متفاوتی را مشاهده خواهیم کرد. به قطعه کد زیر توجه کنید.
let a = {
m: 10,
n: 20
};
let b = a;
b.m = 30;
console.log('a.m = ' + a.m);
← "a.m = 30"
console.log('b.m = ' + b.m);
← "b.m = 30"
در کد فوق ابتدا متغیری به نام a از نوع Object تعریف شده است که دارای دو خاصیت به نامهای m و n است. سپس متغیر جدیدی به نام b تعریف شده و مقدار آن برابر با a قرار داده شده است. در خط بعد مقدار خاصیت m از شئ b برابر با ۳۰ قرار گرفته است و پس از آن مقدار خاصیت m از هر دو شئ a و b در کنسول نمایش داده شده است.
میبینید که بر خلاف انتظار، هر تغییری در مقدار خاصیتهای شئ b، همان تغییر را در خاصیت معادل آن در شئ a نیز اعمال میکند. در واقع زمانی که متغیری را با عملگر انتساب "="، برابر با متغیر دیگری از نوع Object (یا سایر انواع اشیاء مانند Array) قرار میدهیم، هر دو متغیر دقیقاً به یک محل از حافظه اشاره میکنند. یعنی یک شئ واحد در حافظه ذخیره شده است که با دو نام مختلف میتوان به آن دسترسی داشت. در نتیجه تغییرات اعمال شده به وسیلهی هر یک از نامهای a و b، همان شئ واحد در حافظه را تغییر میدهد.
توجه به این رفتار اشیاء از اهمیت بسیار بالایی برخوردار است و عدم توجه به آن میتواند برنامهنویس را با مشکلات پیشبینی نشدهای روبرو کند. دلیل بروز این رفتار در اشیاء این است که اشیاء در جاوا اسکریپت از انواع ارجاع (Reference Types) هستند. اما انواع دادهی اولیه (Primitive Data Types) از انواع مقدار (Value Types) هستند. در مورد مفهوم دقیق انواع ارجاع و انواع مقدار در بخش بعدی صحبت خواهیم کرد تا کاملاً با دلایل بروز این رفتار در اشیاء آشنا شوید.
البته ممکن است در برخی مواقع لازم باشد تا یک کپی از یک شئ خاص را در متغیر دیگری ذخیره کنیم. به طوری که تغییرات اعمال شده در هر یک از متغیرها، مستقل از متغیر دیگر باشد. برای انجام این کار روشهای مختلفی وجود دارد. یکی از این روشها استفاده از متد assign است. این متد متعلق به شئ Object است. قطعه کد زیر نحوهی استفاده از متد assign برای کپی کردن اشیاء را نشان میدهد.
let a = {
m: 10,
n: 20
};
let b = Object.assign({} , a);
b.m = 30;
console.log('a.m = ' + a.m);
← "a.m = 10"
console.log('b.m = ' + b.m);
← "b.m = 30"
مشاهده میکنید که با استفاده از متد assign، یک کپی کامل از شئ a در متغیر b ذخیره میشود. لذا تغییراتی که روی خاصیتهای b اعمال میشوند، هیچ تاثیری بر خاصیتهای a ندارند.
توجه کنید که متد assign دو آرگومان دریافت میکند. آرگومان اول که target نام دارد، شیئی را مشخص میکند که آرگومان دوم (یعنی a) باید در آن کپی شود. البته معمولاً در آرگومان اول یک شئ تهی قرار میگیرد که در این صورت شئ a و b دارای خاصیتها و متدهای یکسان خواهند بود. اما میتوان یکی شئ غیر تهی را در آرگومان اول قرار داد تا متدها و خاصیتهای شئ a به آن اضافه شده و در نهایت در b ذخیره شود. قطعه کد زیر نمونهای از انجام این کار را نشان میدهد.
let a = {
m: 10,
n: 20
};
let b = Object.assign({h: 11 , p: 'hello'} , a);
console.log(a);
← {m: 10, n: 20}
console.log(b);
← {h: 11, p: "hello", m: 10, n: 20}
مشاهده میکنید که شئ b علاوه بر خاصیتهای m و n، دو خاصیت دیگر به نامهای h و p با مقادیر مشخص شده دارد. این خاصیتها همان مواردی هستند که در آرگومان اول متد assign وجود داشتند. ضمناً دو شئ a و b کاملاً از یکدیگر مستقل بوده و تغییرات در هر یک، تاثیری بر دیگری ندارد.
البته روش سادهتر برای کپی کردن اشیاء استفاده از عملگر Spread است. پیش از این دیدیم که با استفاده از این عملگر میتوانستیم عناصر یک آرایه را در آرایهای دیگر کپی کنیم. اما این تنها کاربرد این عملگر نیست. با استفاده از این عملگر میتوان اعضای یک شئ (از هر نوعی) را در شیئی دیگر کپی کرد. قطعه کد زیر نحوهی استفاده از عملگر Spread برای کپی کردن اشیاء را نشان میدهد.
let a = {
m: 10,
n: 20
};
let b = {...a};
b.m = 30;
console.log('a.m = ' + a.m);
← "a.m = 10"
console.log('b.m = ' + b.m);
← "b.m = 30"
همچنین در صورت نیاز میتوان اعضای چند شئ را با عملگر Spread در شیئی دیگر کپی کرد. برای این کار باید اشیاء مورد نظر را در آکلاد قرار داده و با کاما از یکدیگر جدا کرد. حتی میتوان خاصیتها و متدهای جدیدی را با همین روش تعریف کرده و به شئ جدید اضافه کرد. قطعه کد زیر نحوهی انجام این کار را نشان میدهد.
let a1 = {
m: 10,
n: 20
};
let a2 = {
x: 30,
y: 40
};
let b = {
...a1,
...a2,
newProperty: 66
};
console.log(b);
← {m: 10, n: 20, x: 30, y: 40, newProperty: 66}
مشاهده میکنید که شئ b شامل تمام اعضای شئ a1 و شئ a2 و همچنین خاصیت newProperty میباشد.