Chỉnh sửa đối tượng ECMAScript

Bằng cách sử dụng ECMAScript, không chỉ có thể tạo đối tượng mà còn có thể thay đổi hành vi của đối tượng đã có.

Thuộc tính prototype không chỉ có thể định nghĩa thuộc tính và phương thức của hàm xây dựng mà còn có thể thêm thuộc tính và phương thức cho đối tượng cục bộ.

Tạo phương pháp mới

Tạo phương pháp mới thông qua phương pháp hiện có

Chúng ta có thể sử dụng thuộc tính prototype để định nghĩa các phương pháp mới cho bất kỳ lớp nào đã có, giống như xử lý lớp của riêng mình. Ví dụ, nhớ lại phương pháp toString() của lớp Number không? Nếu truyền tham số 16 vào nó, nó sẽ xuất ra chuỗi số thập phân. Nếu tham số của phương pháp này là 2, nó sẽ xuất ra chuỗi số nhị phân. Chúng ta có thể tạo ra một phương pháp để chuyển đổi đối tượng số thành chuỗi số thập phân. Tạo phương pháp này rất đơn giản:

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

Trong môi trường này, từ khóa this chỉ đến đối tượng Number, vì vậy có thể truy cập hoàn toàn tất cả các phương pháp của Number. Với đoạn mã này, có thể thực hiện các hoạt động sau:

var iNum = 15;
alert(iNum.toHexString());		// Xuất "F"

Thử ngay

Do số 15 bằng F trong hệ nhị phân, vì vậy cảnh báo sẽ hiển thị "F".

Đặt tên lại phương pháp hiện có

Chúng ta cũng có thể đặt tên cho các phương pháp hiện có dễ hiểu hơn. Ví dụ, có thể thêm hai phương pháp enqueue() và dequeue() cho lớp Array, chỉ cần gọi lại các phương pháp push() và shift() hiện có là được:

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

Thử ngay

Thêm phương pháp không liên quan đến phương pháp hiện có

Tất nhiên, chúng ta có thể thêm các phương pháp không liên quan đến phương pháp hiện có. Ví dụ, giả sử muốn kiểm tra vị trí của một mục trong mảng mà không có phương pháp cục bộ có thể làm điều này. Chúng ta có thể dễ dàng tạo ra phương pháp sau:

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

Phương pháp indexOf() của该方法 tương tự như phương pháp tên cùng của lớp String, tìm kiếm mỗi mục trong mảng cho đến khi tìm thấy mục tương ứng với mục được truyền vào. Nếu tìm thấy mục tương ứng, sẽ trả về vị trí của mục đó, nếu không, sẽ trả về -1. Với định nghĩa này, chúng ta có thể viết mã sau:

var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));	//xuất ra "1"

Thử ngay

Thêm phương thức mới cho đối tượng cục bộ

Cuối cùng, nếu muốn thêm phương thức mới cho mọi đối tượng cục bộ trong ECMAScript, bạn phải định nghĩa nó trên thuộc tính prototype của đối tượng Object. Trong các chương trước, chúng ta đã nói rằng tất cả các đối tượng cục bộ đều kế thừa đối tượng Object, vì vậy bất kỳ thay đổi nào đối với đối tượng Object đều sẽ phản ánh trên tất cả các đối tượng cục bộ. Ví dụ, nếu muốn thêm một phương thức để cảnh báo giá trị hiện tại của đối tượng, có thể sử dụng mã sau:

Object.prototype.showValue = function () {
  alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();		//xuất ra "hello"
iNum.showValue();		//xuất ra "25"

Thử ngay

Tại đây, các đối tượng String và Number đều kế thừa phương thức showValue() từ đối tượng Object, gọi phương thức này trên các đối tượng của chúng, sẽ hiển thị "hello" và "25".

Việc định nghĩa lại phương thức đã có

Như đã được trình bày trong các chương trước, tên hàm chỉ là con trỏ chỉ đến hàm, vì vậy nó có thể dễ dàng chỉ đến các hàm khác. Nếu bạn thay đổi phương thức cục bộ, chẳng hạn như toString(), điều gì sẽ xảy ra?

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

Mã nguồn trước đó hoàn toàn hợp lệ và kết quả chạy hoàn toàn như mong đợi:

function sayHi() {
  alert("hi");
}
alert(sayHi.toString());	//xuất ra "Function code hidden"

Thử ngay

Bạn có thể nhớ lại, trong chương Function object, đã giới thiệu rằng phương thức toString() của Function thường xuất ra mã nguồn của hàm. Việc gọi lại phương thức này có thể trả về một chuỗi khác (trong ví dụ này, có thể trả về "Function code hidden"). Tuy nhiên, hàm gốc của toString() thế nào? Nó sẽ bị thu hồi bởi chương trình thu hồi đơn vị lưu trữ vô ích vì nó đã bị bỏ đi hoàn toàn. Không có cách nào để khôi phục lại hàm gốc, vì vậy việc lưu trữ con trỏ của nó trước khi gọi lại phương thức gốc là cách an toàn hơn. Thậm chí, bạn có thể gọi phương thức gốc trong phương thức mới:

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

Thử ngay

Trong đoạn mã này, dòng mã đầu tiên lưu trữ tham chiếu đến phương pháp toString() hiện tại trong thuộc tính originalToString. Sau đó, phương pháp tùy chỉnh thay thế phương pháp toString(). Phương pháp mới sẽ kiểm tra độ dài của mã nguồn của hàm có lớn hơn 100 không. Nếu có, nó sẽ trả về thông báo lỗi cho biết mã nguồn của hàm quá dài, nếu không, nó sẽ gọi phương pháp originalToString() và trả về mã nguồn của hàm.

Gán cuối cùng quá muộn (Very Late Binding)

Trên thực tế, không có việc gán cuối cùng quá muộn. Cuốn sách này sử dụng thuật ngữ này để mô tả một hiện tượng trong ECMAScript, đó là có thể định nghĩa phương pháp của đối tượng sau khi khởi tạo đối tượng. Ví dụ:

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

Thử ngay

Trong hầu hết các ngôn ngữ lập trình, bạn phải định nghĩa phương pháp của đối tượng trước khi khởi tạo đối tượng. Ở đây, phương pháp sayHi() được thêm vào sau khi tạo một đối tượng của lớp Object. Trong ngôn ngữ truyền thống, không chỉ không nghe nói về hành động này mà còn không nghe nói rằng phương pháp này sẽ tự động gán cho đối tượng Object và có thể sử dụng ngay lập tức (dưới dòng tiếp theo).

Lưu ý:Không nên sử dụng phương pháp gán cuối cùng quá muộn vì rất khó theo dõi và ghi lại. Tuy nhiên, vẫn nên hiểu về khả năng này.