Các thuộc tính"prototype" được sử dụng rộng rãi bởi cốt lõi của hoạt Javascript chính là nó. Tất cả các hàm xây dựng tích hợp sử dụng nó.

Đầu tiên chúng ta sẽ xem chi tiết và sau đó là cách sử dụng nó để thêm các khả năng mới cho các đối tượng tích hợp.

1. Object.prototype

Giả sử chúng ta xuất ra một đối tượng trống:

let obj = {};
alert( obj ); // "[object Object]" ?

Code tạo chuỗi "[object Object]"ở đâu? Đó là một phương thức toString, nhưng nó ở đâu? Cái objtrống rỗng!

Tuy nhiên, ký hiệu ngắn obj = {}cũng giống như obj = new Object(), trong đó Objectcó một hàm xây dựng đối tượng, với tham chiếuprototype riêng của nó một đối tượng lớn với toStringvà các phương thức khác.

Đây là những gì đang xảy ra:

Khi new Object()được gọi (hoặc một đối tượng bằng chữ {...}được tạo), [[Prototype]]nó được đặt thành Object.prototypetheo quy tắc mà chúng ta đã thảo luận trong chương trước:

Vì vậy, khi obj.toString()được gọi là phương thức được lấy từ Object.prototype.

Chúng ta có thể kiểm tra nó như thế này:

let obj = {};

alert(obj.__proto__ === Object.prototype); // true
// obj.toString === obj.__proto__.toString == Object.prototype.toString

Xin lưu ý rằng không còn [[Prototype]]nữa trong chuỗi trên Object.prototype:

alert(Object.prototype.__proto__); // null

2. Các nguyên mẫu tích hợp khác

Khác với constructor trong các đối tượng như Array, Date, Functionvà những đối tượng khác cũng giữ phương thức trong nguyên mẫu.

Ví dụ, khi chúng ta tạo một mảng [1, 2, 3], hàm tạo mặc định new Array()được sử dụng bên trong. Vì vậy, Array.prototypetrở thành nguyên mẫu của nó và cung cấp các phương thức. Nó thì rất hiệu quả.

Theo đặc điểm kỹ thuật, tất cả các nguyên mẫu tích hợp đều có Object.prototypetrên đầu. Đó là lý do tại sao một số người nói rằng mọi thứ đều được thừa hưởng từ các đối tượng.

Đây là bức tranh tổng thể (cho 3 tích hợp để phù hợp):

Hãy kiểm tra các kiểu nguyên mẫu bằng tay:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

let arr = [1, 2, 3];

// it inherits from Array.prototype?
alert( arr.__proto__ === Array.prototype ); // true

// then from Object.prototype?
alert( arr.__proto__.__proto__ === Object.prototype ); // true

// and null on the top.
alert( arr.__proto__.__proto__.__proto__ ); // null

Một số phương pháp trong nguyên mẫu có thể trùng nhau, ví dụ, Array.prototypecó riêng của mình một hàm toStringđể tạo một danh sách các phần tử phân cách nhau bằng dấu phẩy:

let arr = [1, 2, 3]
alert(arr); // 1,2,3 <-- the result of Array.prototype.toString

Như chúng ta đã thấy trước đây, Object.prototypetoStringlà tốt, nhưng Array.prototypelà gần gũi hơn trong chuỗi, do đó biến mảng được sử dụng hàm này.

Các công cụ trong trình duyệt như console cho nhà phát triển Chrome cũng hiển thị tính kế thừa ( console.dircó thể cần được sử dụng cho các đối tượng tích hợp):

Các đối tượng tích hợp khác cũng hoạt động theo cách tương tự. Ngay cả các hàm – chúng là các đối tượng của hàm tạo tích hợp Functionvà các phương thức của chúng ( call/ applyvà các hàm khác) được lấy từ đó Function.prototype. Hàm cũng có toString riêng.

function f() {}

alert(f.__proto__ == Function.prototype); // true
alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects

3. Nguyên thủy prototype

Điều phức tạp nhất xảy ra với chuỗi, số và booleans.

Như chúng ta nhớ, chúng không phải là đồ vật. Nhưng nếu chúng ta cố gắng truy cập vào thuộc tính của họ, các đối tượng wrapper tạm thời được tạo ra sử dụng được xây dựng trong hàm constructor String, NumberBoolean. Họ cung cấp các phương thức và biến mất.

Các đối tượng này được tạo ra vô hình cho chúng ta và hầu hết các công cụ tối ưu hóa chúng. Phương thức của các đối tượng này cũng nằm trong các nguyên mẫu, có sẵn như String.prototype, Number.prototypeBoolean.prototype.

Giá trị nullundefinedkhông có hàm bao đối tượng

Giá trị đặc biệt nullundefinedđứng ngoài. Chúng không có trình bao bọc đối tượng, vì vậy các phương thức và thuộc tính không có sẵn cho chúng. Và cũng không có nguyên mẫu tương ứng.

4. Thay đổi nguyên mẫu bản địa(nguyên mẫu của chính đối tượng đó)

Nguyên mẫu bản địa có thể được sửa đổi. Chẳng hạn, nếu chúng ta thêm một phương thức vào String.prototype, nó sẽ có sẵn cho tất cả các chuỗi:

String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

Trong quá trình phát triển, chúng ta có thể có ý tưởng cho các phương thức tích hợp mới mà chúng ta muốn có và chúng ta có thể muốn thêm chúng vào các nguyên mẫu bản địa. Nhưng đó thường là một ý tưởng tồi.

Quan trọng:

Nguyên mẫu(prototype) là toàn cục, vì vậy thật dễ dàng để có các cuộc xung đột. Nếu hai thư viện thêm một phương thức String.prototype.show, thì một trong số chúng sẽ ghi đè lên phương thức kia.

Vì vậy, nói chung, sửa đổi một nguyên mẫu bản địa được coi là một ý tưởng tồi.

Trong lập trình hiện đại, chỉ có một trường hợp sửa đổi nguyên mẫu gốc được phê duyệt. Đó là polyfilling.

Polyfilling là một thuật ngữ để tạo một thay thế cho một phương thức tồn tại trong đặc tả JavaScript, nhưng chưa được hỗ trợ bởi một công cụ JavaScript cụ thể.

Sau đó, chúng ta có thể triển khai thủ công và điền vào nguyên mẫu tích hợp với nó.

Ví dụ:

if (!String.prototype.repeat) { // if there's no such method
  // add it to the prototype

  String.prototype.repeat = function(n) {
    // repeat the string n times

    // actually, the code should be a little bit more complex than that
    // (the full algorithm is in the specification)
    // but even an imperfect polyfill is often considered good enough
    return new Array(n + 1).join(this);
  };
}

alert( "La".repeat(3) ); // LaLaLa

5. Vay từ nguyên mẫu

Đó là khi chúng ta lấy một phương thức từ một đối tượng và sao chép nó sang một đối tượng khác.

Một số phương pháp của nguyên mẫu bản địa thường được mượn.

Chẳng hạn, nếu chúng ta tạo một đối tượng giống như mảng, chúng ta có thể muốn sao chép một số Arrayphương thức vào nó.

Ví dụ

let obj = {
  0: "Hello",
  1: "world!",
  length: 2,
};

obj.join = Array.prototype.join;

alert( obj.join(',') ); // Hello,world!

Nó hoạt động vì thuật toán bên trong của phương thức join chỉ quan tâm đến các chỉ mục và thuộc tính length chính xác . Nó không kiểm tra xem đối tượng có thực sự là một mảng không. Nhiều phương thức tích hợp là như thế.

Một khả năng khác là để kế thừa bằng cách thiết lập obj.__proto__để Array.prototype, vì vậy tất cả các phương pháp của Array tự động có sẵn trong obj.

Nhưng điều đó là không thể nếu objđã kế thừa từ một đối tượng khác. Hãy nhớ rằng, chúng ta chỉ có thể kế thừa từ một đối tượng tại một thời điểm.

Phương thức vay rất linh hoạt, nó cho phép trộn các hàm từ các đối tượng khác nhau nếu cần.

6. Tóm lược

  • Tất cả các đối tượng tích hợp theo cùng một mẫu:
    • Các phương thức được lưu trữ trong các nguyên mẫu ( Array.prototype, Object.prototype, Date.prototype, vv)
    • Bản thân đối tượng chỉ lưu trữ dữ liệu (phần tử mảng, thuộc tính đối tượng, ngày)
  • Kiểu nguyên thuỷ cũng có các phương thức trong nguyên mẫu của các đối tượng wrapper: Number.prototype, String.prototypeBoolean.prototype. Chỉ undefinednullkhông có đối tượng trình bao bọc
  • Các nguyên mẫu tích hợp có thể được sửa đổi hoặc đưa vào bằng các phương thức mới. Nhưng nó không khuyến khích thay đổi chúng. Trường hợp được phép duy nhất có lẽ là khi chúng ta bổ sung một tiêu chuẩn mới, nhưng nó vẫn chưa được công cụ JavaScript hỗ trợ

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!