تغییر موضوع ECMAScript

با استفاده از ECMAScript، نه تنها می‌توان اشیاء ایجاد کرد، بلکه می‌توان رفتار اشیاء موجود را تغییر داد.

ویژگی prototype نه تنها می‌تواند ویژگی‌ها و روش‌های فراخوانی را برای توابع تعریف کند، بلکه می‌تواند ویژگی‌ها و روش‌های محلی را به اشیاء اضافه کند.

ایجاد روش‌های جدید

ایجاد روش‌های جدید با استفاده از روش‌های موجود

می‌توان با استفاده از ویژگی prototype، برای هر کلاس موجود روش‌های جدید تعریف کرد، مانند تعریف روش‌ها برای کلاس خود. به عنوان مثال، به یاد دارید که روش toString() از کلاس Number است؟ اگر به آن پارامتر 16 بدهید، یک رشته شانزده‌دهی را نمایش می‌دهد. اگر پارامتر 2 باشد، یک رشته دودویی را نمایش می‌دهد. می‌توانیم روشی ایجاد کنیم که یک شیء عددی را به رشته شانزده‌دهی تبدیل کند. ایجاد این روش بسیار آسان است:

Number.prototype.toHexString = function() {
  return this.toString(16);
};

در این محیط، کلمه کلیدی this به نمونه Number اشاره دارد، بنابراین می‌توان به تمام روش‌های Number دسترسی داشت. با این کد، می‌توان عمل زیر را انجام داد:

var iNum = 15;
alert(iNum.toHexString());		// نمایش "F"

TIY

چون عدد 15 برابر با F در سیستم شانزده‌دهی است، هشدار "F" نمایش داده خواهد شد.

تغییر نام روش‌های موجود

همچنین می‌توانیم نام‌های قابل فهم‌تری برای روش‌های موجود انتخاب کنیم. به عنوان مثال، می‌توان دو روش enqueue() و dequeue() به کلاس Array اضافه کرد، تنها با تکرار استفاده از روش‌های push() و shift() قبلی می‌توانند:

Array.prototype.enqueue = function(vItem) {
  this.push(vItem);
};
Array.prototype.dequeue = function() {
  return this.shift();
};

TIY

افزودن روش‌های مستقل از روش‌های موجود

البته، می‌توان روش‌های مستقلی را که با روش‌های موجود مرتبط نیستند، اضافه کرد. به عنوان مثال، فرض کنید می‌خواهید موقعیت یک عنصر در آرایه را پیدا کنید، هیچ روش محلی برای انجام این کار وجود ندارد. می‌توانیم به راحتی روش زیر را ایجاد کنیم:

Array.prototype.indexOf = function (vItem) {
  for (var i=0; i<this.length; i++) {
    if (vItem == this[i]) {
	  return i;
	}
  }
  return -1;
}

این روش indexOf() با روش مشابه آن در کلاس String مطابقت دارد، به دنبال هر عنصر در آرایه می‌گردد تا زمانی که عنصر مشابهی با آنچه به آن وارد شده است پیدا کند. اگر عنصر مشابه پیدا شود، موقعیت آن را برمی‌گرداند، در غیر این صورت -1 را برمی‌گرداند. با این تعریف، می‌توانیم کد زیر را بنویسیم:

var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));	//چاپ "1"

TIY

افزودن روش‌های جدید به اشیاء محلی

در نهایت، برای اینکه به هر یک از اشیاء محلی ECMAScript روش‌های جدیدی اضافه کنید، باید آن را در ویژگی prototype کلاس Object تعریف کنید. در فصل‌های قبلی ذکر شده است که همه اشیاء محلی از کلاس Object ارث‌برداری می‌کنند، بنابراین هرگونه تغییر در کلاس Object، بر همه اشیاء محلی تأثیر می‌گذارد. به عنوان مثال، اگر بخواهید روشی برای چاپ ارزش فعلی اشیاء اضافه کنید، می‌توانید از کد زیر استفاده کنید:

Object.prototype.showValue = function () {
  alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();		//چاپ "hello"
iNum.showValue();		//چاپ "25"

TIY

در اینجا، روش showValue() از کلاس Object به کلاس‌های String و Number ارث‌برداری می‌کند، این روش در هر یک از اشیاء آن‌ها فراخوانی می‌شود، و "hello" و "25" را نمایش می‌دهد.

تعریف مجدد روش‌های موجود

مثل اینکه می‌توانید برای یک کلاس موجود روش‌های جدیدی تعریف کنید، می‌توانید روش‌های موجود را نیز دوباره تعریف کنید. مانند آنچه که در فصل‌های قبلی ذکر شده است، نام تابع فقط به تابع اشاره دارد، بنابراین می‌توان به راحتی به یک تابع دیگر اشاره کند. اگر روش محلی مانند toString() را تغییر دهید، چه چیزی رخ می‌دهد؟

Function.prototype.toString = function() {
  return "Function code hidden";
}

کد قبلی کاملاً معتبر است و نتیجه اجرا کاملاً مطابق انتظار است:

function sayHi() {
  alert("hi");
}
alert(sayHi.toString());	//چاپ "Function code hidden"

TIY

شاید شما هنوز به خاطر می‌آورید که، در فصل Function Object، روش toString() از Function معمولاً کد منبع تابع را چاپ می‌کند. با تغییر این روش، می‌توانید یک رشته دیگر را بازگردانید (در این مثال، می‌توانید "Function code hidden" را بازگردانید). اما، چه بر سر تابع اصلی toString() آمد؟ آن خواهد شد که توسط برنامه بازیافت حافظه از بین می‌رود، زیرا به طور کامل استفاده نشده است. هیچ روشی برای بازیابی تابع اصلی وجود ندارد، بنابراین بهترین کار قبل از تغییر روش اصلی، ذخیره نشانی آن برای استفاده بعدی است. گاهی حتی ممکن است در روش جدید، روش اصلی را فراخوانی کنید:

Function.prototype.originalToString = Function.prototype.toString;
Function.prototype.toString = function() {
  if (this.originalToString().length > 100) {
    return "Function too long to display.";
  } else {
    return this.originalToString();
  }
};

TIY

در این کد، اولین خط کد، مراجعه به روش toString() فعلی را در属性 originalToString ذخیره می‌کند. سپس یک روش سفارشی را برای پوشش روش toString() استفاده می‌کند. روش جدید بررسی می‌کند که طول کد منبع این تابع آیا از 100 بیشتر است یا خیر. اگر بله، یک پیام خطا بازمی‌گرداند که می‌گوید کد تابع بسیار بلند است، در غیر این صورت، روش originalToString() فراخوانی می‌شود و کد منبع تابع بازمی‌گردانده می‌شود.

بسته شدن بسیار دیرهنگام (Very Late Binding)

از نظر فنی، هیچ چیز به نام بسته شدن بسیار دیرهنگام وجود ندارد. این کتاب از این اصطلاح برای توصیف یک پدیده در ECMAScript استفاده می‌کند، یعنی توانایی تعریف روش‌های شیء پس از نمونه‌سازی آن‌ها. به عنوان مثال:

var o = new Object();
Object.prototype.sayHi = function () {
  alert("hi");
};
o.sayHi();

TIY

در اغلب زبان‌های برنامه‌نویسی برنامه، باید قبل از نمونه‌سازی شیء، روش‌های شیء را تعریف کنید. در اینجا، روش sayHi() پس از ایجاد یک نمونه از کلاس Object اضافه شده است. در زبان‌های سنتی نه تنها این عمل را نشنیده‌ام، بلکه این روش را نیز نشنیده‌ام که می‌تواند به طور خودکار به نمونه‌های شیء Object اختصاص داده شود و بلافاصله قابل استفاده باشد (در خط بعدی).

توجه:بهتر است از روش‌های بسته شدن بسیار دیرهنگام استفاده نکنید، زیرا ردیابی و ثبت آنها بسیار دشوار است. با این حال، باید این امکان را بدانید.