در معماری جاوااسکریپت built-in function constructor وجود دارد. یعنی تابع ها و prototype هایی وجود دارند. اگر در گوگل کروم قسمت کنسول بنویسیم:
var a = new Number(3);
یا حتی var a = new Number("3"); تا "3" را به عدد تبدیل کند. پس Number ای که استفاده کردیم یک function constructor است. میبینیم که خود js هم حرف اول آن را بزرگ انتخاب کرده. اگر بعد از خط بالا در کنسول مقدار a را ببینیم:
Number { [[PrimitiveValue]]: 3 }
توجه داشته باشید که a یک عدد نیست، primitive نیست. بلکه یک شیء است. چون function constructor شیء ایجاد میکند. یک مقدار primitive را داخل آن قرار داده. اگر در کنسول بنویسیم Number.prototype. به ما متدهایی را پیشنهاد میدهد، مثل toPrecision یا toFixed. پس مثلا میتوانیم بنویسیم a.toFixed(2) که خروجی 3.00 را برمیگرداند.
اگر در کنسول بنویسیم:
var a = new String("John");
تابع String هم یک function constructor است. a هم به متدهای زیادی دسترسی دارد. این متدها داخل خود a نیستند اما در String.prototype هستند. مثلا اگر بنویسیم String.prototype.indexOf("o"); مقدار -1 را برمیگرداند. وقتی مینویسیم a.indexOf("o"); خروجی 1 را برمیگرداند. یعنی a یک String و یک primitive نیست، یک شیء است. اگر a را در کنسول بزنیم خروجی میشود:
String { 0:'J', 1:'o', 2:'h', 3:'n', length:4, isLengthGreaterThan:function, [[PrimitiveValue]]:'John' }
پس با استفاده از String و Number یک شیء ایجاد کردیم، نه یک primitive. در برخی مواقع js متوجه میشود که ما میخواهیم به چیزی به عنوان یک شیء نگاه کنیم، نه primitive. بنابراین میتوانیم مثلا بنویسیم "John".length که خروجی 4 را میدهد. یعنی "John" که primitive بوده، توسط js در شیء String بسته شده تا به length دسترسی داشته باشیم. همین اتفاق برای کد زیر وقتی از .length استفاده میکند هم می افتد:
new String("John");
در چنین مواردی که موتور js یک primitive را به صورت یک شیء wrap میکند، میتوانیم از ویژگی و متدهای مورد نیاز استفاده کنیم. اما هر موقع که کلمه new را در کد ببینیم:
var a = new Date("3/1/2015');
خروجی کنسول برای a خواهد بود:
Sun Mar 01 2015 00:00:00 GMT-0500 (Eastern Standard Time)
پس a یک شیء است. پس اگر بعد از a نقطه بگذاریم ویژگی های زیادی را که js تامین کرده در اختیار داریم. اما این ویژگی و متدها واقعا کجا قرار دارند؟ آنها در Date.prototype قرار دارند. پس وقتی از این function constructor ها استفاده میکنیم داریم یک شیء ایجاد میکنیم. این مسئله میتواند خیلی پرکاربرد باشد، بخصوص وقتی میخواهیم ویژگی ها یا متدهای دیگری هم به کتابخانه ها یا framework ها یا خود primitive ها اضافه کنیم. مثلا میخواهیم در js یک ویژگی به همه string ها اضافه کنیم. متدها و ویژگی هایی که الان برای رشته ها قابل دسترس هستند، در prototype آن قرار دارند.
همان طور که گفته شد عبارت String.prototype نشان دهنده جایی است که همه شیء های رشته به عنوان prototype شان به آن اشاره میکنند.
String.prototype.isLenghtGreaterThan = function(limit) {
return this.length > limit;
}
console.log(“john”.isLengthGreaterThan(3));
در کد بالا اگر یک رشته باشیم، this به آن شیء رشته اشاره میکند. خروجی true میشود. در این کد “john” که یک رشته primitive است به صورت اتوماتیک به شیء string که با تابع String.prototype ایجاد میشود تبدیل شد. ما متد isLengthGreaterThan را به prototype اضافه کردیم. بنابراین همه رشته ها به این متد دسترسی دارن. این قدرت prototype و وراثت است. بسیاری از کتابخانه ها و framework ها برای اضافه کردن ویژگی ها و utility ها از این تکنیک استفاده میکنند. اما باید مراقب باشیم که روی یک ویژگی یا متد موجود overwrite نکنیم. پس میتوانیم با فهم function constructor ها و ویژگی prototype آن به طور موثری روی زبان تاثیر بگذاریم. کد بالا به علت اینکه “john” به طور خودکار به شیء تبدیل شد کار کرد. آیا میتوانیم همین کار را با اعداد هم انجام دهیم؟
کلمه Number یک built-in function constructor است. یعنی فقط یک تابع است و همراه با new استفاده میشود. اگر روی ویژگی prototype آن تاثیر بگذاریم، روی همه اشیائی که با Number constructor ایجاد شده اند تاثیر میگذاریم:
Number.prototype.isPositive = function() { return this>0; }
اگر در خروجی کد بالا در کنسول بزنیم:
3.isPositive()
خطا میدهد: unexpected token ILLEGAL. چون js نمیتواند به صورت خودکار عدد را به شیء تبدیل کند. اما اگر در کنسول بنویسیم:
var a = new Number(3);
a.isPositive()
خروجی true برمیگرداند. بنابراین میتوانیم به آسانی به built-in function constructor ها توانایی هایی را اضافه کنیم. بخصوص به آنها که آرایه یا رشته هستند. اما اگر بخواهیم با مقادیر primitive در کنار مقادیری که با این function constructor ها ایجاد شده کار کنیم کمی مشکل داریم. مثلا در کد بالا Number(3) شبیه به یک عدد است، اما عدد نیست. یک شیء است. پس در حالت کلی برای انواع primitive از built-in function constructor ها استفاده نمیکنیم، مگر اینکه مجبور شویم.
فرض کنید در کنسول مینویسیم:
var a = 3;
var b = new Number(3);
a == b
خروجی کد بالاtrue است چون عملگر == نوع ها را coerce میکند. تلاش میکند که نوع دو پارامترش را یکسان کند. اما اگر از a===b استفاده کنیم، خروجی false میشود. چون a یک primitive است و b یک شیء است. بنابراین موقع استفاده از built-in function constructor ها برای ایجاد primitive ها، واقعا یک primitive درست نمیکنیم و ممکن است موقع مقایسه مشکلاتی پیش آید. پس در حالت کلی بهتر است از built-in function constructor ها استفاده نکنیم، میتوانیم از literal ها یا مقدار واقعی primitive (مثلا 3 خالی) استفاده کنیم.
یک کتابخانه خیلی خوب به اسم moment.js داریم. میتوانیم به سایت momentjs.com برویم. در این کتابخانه تابع های زیادی برای کار کردن و فرمت بندی و محاسبه تاریخ وجود دارد. اگر در برنامه از تاریخ زیاد استفاده میکنیم، توصیه میشود به جای کار کردن با built-in js Date constructor از این کتابخانه استفاده کنید. تابع های این کتابخانه برخی از مشکلات استفاده از built-in function constructor را حل کرده است.
میتوانیم از built-in function constructor ها برای coercion استفاده کنیم:
var c = Number("3");
در این مثال از Number به عنوان یک تابع معمولی استفاده کرده ایم چون از new استفاده ای نشده. اگر قبل از آن new میگذاشتیم، یک شیء به ما برگردانده میشد.
در بخش reflection و extend روی همه ویژگی ها و همه متدهای یک شیء حلقه زدیم. همان طور که میدانیم آرایه ها شیء هستند و میتوانیم همین کار را برای آرایه ها هم انجام دهیم.
var arr = ['John', 'Jane', 'Jim'];
for (var prop in arr) {
console.log(prop + ': ' + arr[prop]);
}
خروجی میشود:
0: John
1: Jane
2: Jim
آرایه ها در js نسبت به سایر زبانها کمی متفاوت هستند. 0 و 1 و 2 در واقع اسم ویژگی هستند، به همین دلیل میتوانیم با [ ] آن ویژگی را بگیریم. در حقیقت آرایه ها شیء هستند و هر آیتم یک name/value است. هر آیتم یک ویژگی جدید است. در واقع داریم به آرایه ویژگی اضافه میکنیم. بنابراین اگر جای دیگری ویژگی به آرایه ها اضافه کرده باشیم مشکل پیش می آید. مثلا ابتدای کد بالا بنویسیم:
Array.prototype.myCustomFeature = 'cool';
این خط کد ویژگی myCustomFeature را به Array.prototype اضافه میکند. اگر خط بالا را اول کد اضافه کرده و خروجی بگیریم:
0: John
1: Jane
2: Jim
myCustomFeature: cool
یعنی وقتی از for in استفاده میکنیم، یک ویژگی دیگر هم در آرایه داریم. بنابراین در مورد آرایه ها از for in استفاده نمیکنیم و از حلقه for استاندارد استفاده میکنیم:
for (var i=0; i<arr.length; i++) { ... }