Chào mừng bạn đến với Cafedev! Trong bài viết này, chúng ta sẽ cùng khám phá cách học JavaScript từ cơ bản đến nâng cao qua việc sử dụng các `Native prototypes`. Bạn sẽ tìm hiểu về cách các prototype tích hợp sẵn như `Array.prototype` và `Object.prototype` có thể mở rộng và làm phong phú thêm các đối tượng trong JavaScript, giúp bạn viết mã hiệu quả và sáng tạo hơn. Hãy cùng Cafedev bắt đầu hành trình học tập thú vị này!

Thuộc tính "prototype" được sử dụng rộng rãi trong lõi của JavaScript. Tất cả các hàm tạo tích hợp sẵn đều sử dụng nó.
Đầu tiên, chúng ta sẽ xem xét chi tiết, sau đó là cách sử dụng nó để thêm các khả năng mới vào các đối tượng tích hợp sẵn.

1. Object.prototype

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


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

Đoạn mã nào tạo ra chuỗi "[object Object]"? Đó là phương thức toString tích hợp sẵn, nhưng nó ở đâu? Đối tượng obj rỗng!
…Nhưng ký hiệu ngắn gọn obj = {} giống như obj = new Object(), trong đó Object là một hàm tạo đối tượng tích hợp sẵn, với prototype của nó tham chiếu đến một đối tượng lớn với toString và 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 theo cú pháp {...} được tạo), [[Prototype]] của nó được đặt thành Object.prototype theo quy tắc mà chúng ta đã thảo luận trong bài trước:


Vì vậy, khi obj.toString() được gọi, phương thức này được lấy từ Object.prototype.
Chúng ta có thể kiểm tra điều này như sau:


let obj = {};

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

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

Lưu ý rằng không có [[Prototype]] nào khác trong chuỗi phía trên Object.prototype:


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

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

Các đối tượng tích hợp khác như Array, Date, Function và những đối tượng khác cũng giữ các 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 nội bộ. Vì vậy, Array.prototype trở thành nguyên mẫu của nó và cung cấp các phương thức. Điều này rất hiệu quả về mặt bộ nhớ.

Theo đặc tả, tất cả các nguyên mẫu tích hợp đều có Object.prototype ở trên cùng. Đó là lý do tại sao một số người nói rằng “mọi thứ đều kế thừa từ đối tượng”.

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


Hãy kiểm tra các nguyên mẫu thủ công:


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 thức trong nguyên mẫu có thể bị trùng lặp, chẳng hạn, Array.prototype phương thức toString riêng liệt kê các phần tử được phân tách 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.prototype cũng có toString, nhưng Array.prototype gần hơn trong chuỗi, vì vậy biến thể của mảng được sử dụng.

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



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 Function, và các phương thức của chúng (call/apply và các phương thức khác) được lấy từ Function.prototype. Các hàm cũng có toString của riêng chúng.


function f() {}

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

3. Nguyên thủy

Điều phức tạp nhất xảy ra với chuỗi, số và boolean.
Như chúng ta biết, chúng không phải là các đối tượng. Nhưng nếu chúng ta cố gắng truy cập các thuộc tính của chúng, các đối tượng bọc tạm thời sẽ được tạo ra bằng cách sử dụng các constructor tích hợp String, NumberBoolean. Chúng cung cấp các phương thức và biến mất.

Những đối tượng này được tạo ra một cách vô hình đối với chúng ta và hầu hết các engine tối ưu hóa chúng ra ngoài, nhưng đặc tả mô tả chính xác theo cách này. Các phương thức của những đối tượng này cũng nằm trong các prototype, có sẵn dưới dạng String.prototype, Number.prototypeBoolean.prototype.

Các giá trị đặc biệt nullundefined đứng riêng biệt. Chúng không có các wrapper đố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ó các prototype tương ứng.

4. Thay đổi các native prototypes

Các prototype gốc có thể được chỉnh sửa. Ví dụ, nếu chúng ta thêm một phương thức vào String.prototype, phương thức đó 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 về 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ể bị cám dỗ để thêm chúng vào các prototype tích hợp. Nhưng đó thường là một ý tưởng không tốt.

Các prototype là toàn cục, vì vậy rất dễ xảy ra xung đột. Nếu hai thư viện thêm một phương thứcString.prototype.show`, thì một trong số chúng sẽ ghi đè phương thức của thư viện còn lại.

Vì vậy, nói chung, việc thay đổi một prototype tích hợp được coi là một ý tưởng không tốt.

Trong lập trình hiện đại, chỉ có một trường hợp duy nhất mà việc thay đổi các prototype tích hợp được chấp nhận. Đó là polyfilling.

Polyfilling là thuật ngữ chỉ việc tạo một sự 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 engine JavaScript cụ thể.
Chúng ta có thể thực hiện nó một cách thủ công và bổ sung vào prototype tích hợp.

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. Mượn prototypes

Trong bài Decorators và forwarding, call/apply, chúng ta đã nói về việc mượn phương thức
Đó là khi chúng ta lấy một phương thức từ một đối tượng và sao chép nó vào một đối tượng khác. Được gọi là mượn phương thức

Một số phương thức của các prototype tích hợp sẳn thường được mượn.

Ví dụ, nếu chúng ta đang 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ố phương thức của Array vào đó.

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 nội bộ của phương thức tích hợp join chỉ quan tâm đến các chỉ mục chính xác và thuộc tính length. Nó không kiểm tra xem đối tượng có thực sự là một mảng hay không. Nhiều phương thức tích hợp hoạt động như vậy.
Một khả năng khác là kế thừa bằng cách đặt obj.__proto__ thành Array.prototype, để tất cả các phương thức Array đều có sẵn trong obj.

Nhưng điều đó không khả thi 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.

Việc mượn các phương thức rất linh hoạt, cho phép kết hợp các chức năng từ các đối tượng khác nhau nếu cần.

6. Tóm tắt

  • Tất cả các đối tượng tích hợp sẵn đều đi kèm theo cùng một mẫu:
    • Các phương thức được lưu trữ trong prototype (Array.prototype, Object.prototype, Date.prototype, v.v.)
    • Đối tượng tự lưu trữ dữ liệu (các phần tử mảng, thuộc tính đối tượng, ngày tháng)
  • Các kiểu dữ liệu nguyên thủy cũng lưu trữ các phương thức trong prototypes của các đối tượng bọc: Number.prototype, String.prototypeBoolean.prototype. Chỉ có undefinednull là không có đối tượng bọc
  • Các prototypes tích hợp sẵn có thể được sửa đổi hoặc bổ sung thêm các phương thức mới. Nhưng không khuyến khích thay đổi chúng. Trường hợp duy nhất có thể chấp nhận là khi chúng ta thêm một tiêu chuẩn mới, nhưng chưa được hỗ trợ bởi engine JavaScript

Chúng ta đã cùng nhau khám phá các `Native prototypes` trong JavaScript từ những điều cơ bản đến nâng cao. Hy vọng rằng các kiến thức này sẽ giúp bạn nắm vững cách sử dụng và mở rộng các đối tượng trong JavaScript. Cảm ơn bạn đã đồng hành cùng Cafedev trong hành trình học tập này. Hãy tiếp tục theo dõi Cafedev để cập nhật thêm nhiều kiến thức hữu ích và các bài viết thú vị khác!

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Javascrypt chi tiết, dễ hiểu từ cơ bản tới nâng cao + Full Bài Tập thực hành nâng cao trình dev

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

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!