انواع ارجاعی
قبلا در مورد مقادیر ارجاعی صحبت کردیم. در
اینجا نیز به این موضوع اشاره میکنیم که هر مقدار ارجاعی، نمونهای ایجاد
شده از یک نوع ارجاعی میباشد. انواع ارجاعی در واقع ساختارهایی هستند که
جهت گروه بندی دادهها و عملکرد بین آنها استفاده میشوند. در سایر زبانهای برنامه نویسی شیء گرا، به انواع ارجاعی، کلاس و به مقادیر ارجاعی، شیء میگویند. در
جاوا اسکریپت نیز، به مقادیر ارجاعی و یا نمونههای ایجاد شده از انواع ارجاعی، شیء میگویند. به انواع ارجاعی، توصیف گر شیء نیز میگویند؛ زیرا ویژگیها و متدهای آن
شیء را معرفی و توصیف مینماید.
نحوهی ایجاد شیء از نوع ارجاعی Object
از آنجاییکه نوع ارجاعی Object هیچ ویژگی و
متد خاصی ندارد، متداولترین نوع ارجاعی جهت ایجاد اشیاء سفارشی میباشد. به دو
روش میتوان نمونهای را از یک Object
ایجاد نمود. روش اول استفاده از عملگر new و بصورت زیر میباشد:
var person = new Object ();
person . name = "Meysam" ;
person . age = 32 ;
با استفاده از عملگر new، شیء person را
از نوع Object
ایجاد نمودیم که شامل دو ویژگی (Property) به
نامهای name و age میباشد. در واقع شیء person
ساختاری را جهت نگهداری اطلاعات یک شخص معرفی میکند. این عمل موجب جلوگیری از
پراکندگی تعریف متغیرها و گروه بندی آنها در قالب یک شیء میشود. روش دوم استفاده
از Object Literal Notation یا
نماد تحت الفظی شیء و بصورت زیر میباشد:
var person = {
name : "Meysam" ,
age : 32
};
Object Literal Notation ،
دستور میانبری برای ایجاد یک شیء از نوع Object میباشد. در مثال فوق هم، همانند
روش اول، شیء person را
با دو ویژگی name و age ایجاد
نمودهایم. در این روش، نام ویژگیها میتوانند به
صورت رشتهای و عددی نیز به یک شیء اختصاص یابد.
var person = {
"name" : "Meysam" ,
"age" : 32
};
معمولا از دو روش فوق زمانی استفاده میشود که میخواهیم اشیایی را ایجاد نماییم که ویژگیهای آنها فقط خواندنی باشند. با استفاده
از روش دوم، حتی میتوان یک شیء خالی را ایجاد نمود که در ابتدا هیچ ویژگی ای
ندارد و در مراحل بعد، ویژگیهایی را به آن
اضافه نمود.
var person = {}; // var person = new Object();
person . name = "Meysam" ;
person . age = 32 ;
در مثال فوق شیء person بدون ویژگیها تعریف شده است؛ سپس به آن ویژگیهایی را اضافه نمودهایم.
استفاده از روش Object Literal Notation ،
یکی از روشهای محبوب برنامه نویسان جاوا اسکریپت میباشد. زیرا با کمترین کد و
بصورت بصری، شیء ای را ایجاد نموده و مثلا به یک متد ارسال مینمایند. به مثال زیر
توجه کنید:
function displayInfo ( arg ) {
var output = "" ;
if ( arg . name != undefined )
output += "Name: " + arg . name + "\n" ;
if ( arg . age != undefined )
output += "Age: " + arg . age + "\n" ;
return output ;
}
alert (displayInfo ({
name : "Meysam" ,
age : 32
}));
alert (displayInfo ({
name : "Sohrab"
}));
در این مثال یک تابع تعریف شده است که آرگومان
ورودی آن یک شیء میباشد. در تابع بررسی میشود که اگر ویژگی name و یا age
برای این آرگومان تعریف شده بود، خروجی تابع را تولید نماید. در واقع این ویژگیها اختیاری میباشند و میتوانند ارسال نگردند. در زمان فراخوانی تابع نیز شیء ای را
بصورت Object Literal Notation
ایجاد نموده و به تابع ارسال کردیم.
این الگو برای ارسال آرگومان، زمانی استفاده میشود که تعداد زیادی آرگومان اختیاری داریم و میخواهیم به تابع ارسال نماییم. معمولا
کار با آرگومانهای نامی (Named
Arguments) راحتتر است ولی زمانیکه تعداد
آرگومانهای اختیاری زیاد باشند، مدیریت و نگهداری آنها سخت و طاقت فرسا میگردد و
ظاهر زشتی را به تابع میدهد. بهترین راهکار جهت مدیریت چنین شرایطی این است که
آرگومانهای اجباری را به صورت آرگومانهای نامی تعریف کنیم و آرگومانهای اختیاری
را به صورت یک شیء به تابع ارسال کنیم.
نکتهی دیگری که باید به آن توجه نمود این است
که جهت دسترسی به ویژگیهای یک شیء از (.) استفاده میشود. همچنین میتوان به یک ویژگی
با استفاده از [] و بصورت یک آرایه دسترسی داشت که در این صورت نام ویژگی بصورت
رشتهای در [] ذکر خواهد شد.
alert ( person . name );
alert ( person [ "name" ]);
در عمل تفاوتی بین دو مورد فوق وجود ندارد. مهمترین
مزیت استفاده از [] این است که میتوانید توسط یک متغیر به ویژگیهای یک شیء دسترسی
داشته باشید. همچنین اگر نام ویژگی شامل کاراکترهایی باشد که خطای گرامری رخ میدهد یا از اسامی رزرو شده استفاده کرده باشید، میتوانید از [] جهت دسترسی به
ویژگی استفاده نمایید.
var propertyName = "name" ;
alert ( person [ propertyName ]);
alert ( person [ "first name" ]);
در دستور alert اول، با
استفاده از یک متغیر به ویژگی name از شیء person دسترسی پیدا
نمودیم. در دستور آخر نیز، به دلیل وجود space در نام ویژگی، مجبور هستیم جهت
دسترسی به ویژگی first name از
[] استفاده نماییم.
بررسی نوع ارجاعی Function
توابع در واقع یک شیء و نمونهای از نوع ارجاعی Function میباشند که میتوانند همانند سایر اشیاء ویژگیها و متدهای خاص خود را داشته باشند. بنابراین میتوان در یک عبارت، تابعی را به یک شیء نسبت داد. به مثال زیر توجه کنید:
var sum = function ( a , b ) {
return a + b ;
};
alert ( sum ( 10 , 20 ));
خروجی :
30
شیء sum را تعریف نموده و یک تابع را به
آن اختصاص دادیم که شامل دو آرگومان ورودی میباشد. حال میتوان با شیء sum
همانند یک تابع رفتار نمود و تابع مورد نظر را فراخوانی کرد. توجه داشته باشید که
هیچ نامی را در زمان تعریف تابع به آن اختصاص ندادهایم. به این شکل تعریف تابع، Function Expression میگویند.
همانند سایر اشیاء، نام تابع نیز اشارهگری به
تابع میباشد. بنابراین میتوان توابع را نیز به یکدیگر نسبت داد. به مثال زیر
توجه کنید:
function sum ( a , b ) {
return a + b ;
}
alert ( sum ( 10 , 10 ));
var anotherSum = sum ;
alert ( anotherSum ( 10 , 10 ));
sum = null ;
alert ( anotherSum ( 10 , 10 ));
alert ( sum ( 10 , 10 )); // Error: Object is not a function;
خروجی :
20
20
20
Error: Object is not a function
ابتدا تابعی را به نام sum ایجاد نمودیم
که دو عدد را با هم جمع میکند. دقت داشته باشید که به این شکل تعریف تابع sum ،
اعلان تابع یا Function
Declaration میگویند. سپس متغیری را به نام anotherSum ،
تعریف نموده و sum را
به آن نسبت دادیم. توجه داشته باشید که در زمان انتساب یک تابع به یک متغیر نباید
() را ذکر کنیم، زیرا ذکر () به منزلهی فراخوانی تابع و اختصاص خروجی آن به متغیر میباشد و نه انتساب اشاره گر تابع به آن متغیر. فراخوانی sum و anotherSum
خروجی یکسانی را دارند؛ زیرا هر دو به یک تابع اشاره میکنند. در خطوط بعدی، شیء sum را
با مقدار null تنظیم نمودیم. در واقع با این کار اشارهگر sum برابر null شده
است؛ یعنی دیگر به هیچ تابعی اشاره نمیکند. ولی تابع همچنان در حافظه وجود دارد؛
زیرا اشارهگر دیگری به نام anotherSum در
حال اشاره نمودن به آن میباشد. در مرحلهی بعدی که sum را فراخوانی
نمودیم، به دلیل null
بودن آن، خطا رخ خواهد داد.
بازنگری مجدد به مبحث Overloading
در اینجا فقط میخواهیم اشارهای کنیم به مبحث Overloading که
قبلا در مورد آن بحث کردیم تا دلیل فنی عدم وجود Overloading را
در جاوا اسکریپت درک کنیم. همانطور که قبلا بیان شد، نام تابع در واقع اشاره گری
به تابع میباشد؛ بنابراین تعریف دو تابع هم نام، همانند اختصاص مجدد مقدار یا
تغییر مقدار یک متغیر میباشد. به مثال زیر توجه کنید:
function calc ( num1 , num2 ) {
return num1 + num2 ;
}
function calc ( num1 , num2 ) {
return num1 - num2 ;
}
همانطور که پیشتر نیز عنوان شد، تابع دوم جایگزین تابع اول میگردد. در واقع تعریف فوق همانند تعریف زیر میباشد:
var calc = function ( num1 , num2 ) {
return num1 + num2 ;
};
calc = function ( num1 , num2 ) {
return num1 - num2 ;
};
همانطور که میبینید، ابتدا متغیری به نام calc
تعریف شدهاست و با یک تابع مقداردهی اولیه شده است. سپس با تابع دوم مقداردهی
مجدد گردیده است و دیگر به تابع قبلی اشاره نمیکند. بنابراین همیشه تابع آخر
جایگزین توابع قبلی میگردد.
Function Declaration در
مقابل Function
Expression
این دو روش تعریف و استفاده از توابع تقریبا
مشابه هم میباشند و فقط یک تفاوت اصلی بین آنها وجود دارد و آن به نحوهی رفتار موتور پردازشی جاوا
اسکریپت بر میگردد. Function
Declaration قبل از اینکه کدهای جاوا اسکریپت
خوانده و اجرا شوند، خوانده شده و در دسترس خواهند بود؛ اما Function Expression تا زمانی که روال اجرای کد به این خط از کد نرسد، اجرا نخواهد شد و در
دسترس نخواهد بود. به مثال Function Declaration زیر توجه کنید:
alert ( sum ( 10 , 20 ));
function sum ( a , b ) {
return a + b ;
}
خروجی :
30
قبل از اعلان تابع sum ، این تابع
فراخوانی شده است؛ ولی هیچ خطایی رخ نمیدهد. زیرا قبل از اجرای دستورات، تابع sum
خوانده و در دسترس خواهد بود. اما اگر تابع فوق بصورت Function Expression
تعریف شده بود، خطا رخ میداد و برنامه اجرا نمیشد.
alert ( sum ( 10 , 20 )); // Error: undefined is not a function
var sum = function ( a , b ) {
return a + b ;
};
خروجی :
Error: undefined is not a function
همانطور که میبینید، در خط اول برنامه، خطای
اجرایی رخ داده است. زیرا هنوز روال اجرایی برنامه به خط تعریف تابع sum
نرسیدهاست. بنابراین تابع sum در
دسترس نخواهد بود و فراخوانی آن موجب بروز خطا میگردد.
ارسال تابع به عنوان ورودی یا خروجی توابع دیگر
همانطور که قبلا بیان شد، نام تابع در واقع یک
متغیر میباشد که به تابع مورد نظر اشاره میکند. بنابراین میتوان همانند یک
متغیر با آن رفتار نموده و به عنوان آرگومان ورودی و یا مقدار خروجی یک تابع آن را
ارسال نمود. به مثال زیر توجه کنید:
function add ( a , b ) {
return a + b ;
}
function mult ( a , b ) {
return a * b ;
}
function calc ( a , b , fn ) {
return a * b + fn ( a , b );
}
alert ( calc ( 10 , 20 , add ));
alert ( calc ( 10 , 20 , mult ));
خروجی :
230
400
دو تابع به نامهای add و mult با
دو آرگومان ورودی تعریف شدهاند که به ترتیب حاصل جمع و حاصل ضرب دو آرگومان را بر میگردانند. تابع calc نیز
با 3 آرگومان ورودی تعریف شدهاست که قصد داریم برای آرگومان سوم یک تابع را ارسال
نماییم. تابع calc در
2 مرحله فراخوانی شدهاست که در یک مرحله تابع add و در مرحلهی دیگر تابع mult به
عنوان آرگومان ورودی ارسال شدهاند. همانطور که از قبل میدانید، نام تابع اشارهگری به خود تابع میباشد. در تابع calc نیز
با فراخوانی آرگومان fn در
واقع داریم تابع ارسالی را فراخوانی مینماییم. حال به مثال زیر توجه کنید که یک
تابع را به عنوان خروجی بر میگرداند:
function createFunction ( a , b ) {
return function ( c ) {
var d = ( a + b ) * c ;
return d ;
};
}
var fn = createFunction ( 10 , 20 );
alert ( fn ( 30 ));
خروجی :
900
تابع createFunction دارای 2
آرگومان ورودی میباشد و یک تابع را با 1 آرگومان ورودی بر میگرداند. در ابتدا
تابع createFunction را
با آرگومانهای 10 و 20 فراخوانی نمودیم. خروجی این تابع که خود یک تابع با یک
آرگومان ورودی میباشد، به عنوان خروجی برگردانده شده و در متغیر fn
قرار میگیرد. سپس تابع fn با
آرگومان ورودی 30 فراخوانی میگردد که مقادیر 10 و 20 را با هم جمع نموده و در 30
ضرب مینماید.