Hướng dẫn về kế thừa lớp dựa trên nguyên mẫu trong JavaScript

Ngôn ngữ máy tính thường cung cấp một cách để một đối tượng được kế thừa từ
một đối tượng khác. Đối tượng được kế thừa chứa tất cả các thuộc tính từ đối tượng cha của nó. Ngoài ra, nó cũng sẽ chỉ định tập các thuộc tính duy nhất của riêng nó.

Theo dõi tôi trên Twitter để biết các mẹo về JavaScript và thông báo sách.

Các đối tượng JavaScript sử dụng kế thừa dựa trên nguyên mẫu. Thiết kế của nó tương tự về mặt logic (nhưng khác về cách triển khai) từ kế thừa lớp trong các ngôn ngữ lập trình hướng đối tượng nghiêm ngặt.

Nó có thể được mô tả một cách lỏng lẻo bằng cách nói rằng khi các phương thức hoặc thuộc tính được gắn vào nguyên mẫu đối tượng, chúng sẽ có sẵn để sử dụng cho đối tượng đó và hậu duệ của nó. Nhưng quá trình này thường diễn ra đằng sau hậu trường.

Khi viết mã, bạn thậm chí sẽ không cần chạm trực tiếp vào thuộc tính nguyên mẫu. Khi thực hiện phương thức phân tách, bạn sẽ gọi nó trực tiếp từ một chuỗi ký tự là:

Khi bạn sử dụng lớp và mở rộng từ khóa trong nội bộ, JavaScript vẫn sẽ sử dụng tính kế thừa dựa trên nguyên mẫu. Nó chỉ đơn giản hóa cú pháp. Có lẽ đây là lý do tại sao nó rất quan trọng để hiểu cách kế thừa dựa trên nguyên mẫu hoạt động. Nó vẫn là cốt lõi của thiết kế ngôn ngữ.

Đây là lý do tại sao trong nhiều hướng dẫn, bạn sẽ thấy String.prototype.split được viết thay vì chỉ String.split. Điều này có nghĩa là có một phân chia phương thức có thể được sử dụng với các đối tượng của chuỗi kiểu vì nó được gắn vào thuộc tính nguyên mẫu đối tượng đó.

Tạo một hệ thống phân cấp logic của các loại đối tượng

Mèo và Chó được thừa hưởng từ Pet được thừa hưởng từ Động vật.

Một con chó và một con mèo chia sẻ những đặc điểm tương tự. Thay vì tạo hai lớp khác nhau,
chúng ta chỉ cần tạo một lớp Pet và thừa hưởng Cat and Dog từ nó. Nhưng bản thân lớp Pet cũng có thể được thừa hưởng từ lớp Thú.

Trước khi chúng ta bắt đầu

Cố gắng để hiểu các nguyên mẫu giống như vượt sông đi từ mã hóa sang thiết kế ngôn ngữ máy tính. Hai lĩnh vực kiến ​​thức hoàn toàn khác nhau.

Về mặt kỹ thuật, chỉ cần kiến ​​thức nhẹ về lớp học và mở rộng từ khóa là đủ để viết phần mềm. Cố gắng để hiểu nguyên mẫu giống như mạo hiểm vào các góc tối hơn của thiết kế ngôn ngữ. Và đôi khi điều đó có thể sâu sắc.

Hướng dẫn này một mình giành chiến thắng là đủ. Tôi chỉ tập trung vào một số điều quan trọng mà hy vọng sẽ hướng dẫn bạn đi đúng hướng.

Dưới mui xe

Ý tưởng đằng sau sự kế thừa đối tượng là cung cấp cấu trúc cho một hệ thống phân cấp
đối tượng tương tự. Bạn cũng có thể nói một đối tượng con là bắt nguồn từ cha mẹ của nó.

Cách các chuỗi nguyên mẫu được tạo trong JavaScript.

Về mặt kỹ thuật, đây là những gì nó trông giống như. Cố gắng đừng nghĩ quá nhiều vào việc này. Chỉ cần biết rằng ở trên cùng của hệ thống phân cấp có đối tượng Object. Đó là lý do tại sao nguyên mẫu của nó chỉ ra null. Không có gì khác ở trên nó.

Kế thừa đối tượng dựa trên nguyên mẫu

JavaScript hỗ trợ kế thừa đối tượng thông qua một cái gì đó được gọi là nguyên mẫu. Có một thuộc tính đối tượng được gọi là nguyên mẫu gắn liền với mỗi đối tượng.

Làm việc với lớp và mở rộng các từ khóa là dễ dàng nhưng thực sự hiểu cách thức kế thừa dựa trên nguyên mẫu hoạt động không phải là chuyện nhỏ. Hy vọng rằng hướng dẫn này sẽ nâng ít nhất một số sương mù!

Hàm xây dựng đối tượng

Các hàm có thể được sử dụng như các hàm tạo đối tượng. Tên của hàm xây dựng thường bắt đầu bằng chữ in hoa để vẽ sự phân biệt giữa các hàm thông thường. Các hàm tạo đối tượng được sử dụng để tạo một thể hiện của một đối tượng.

Một số đối tượng tích hợp sẵn JavaScript đã được tạo theo cùng quy tắc. Ví dụ Số, Mảng và Chuỗi được kế thừa từ Object. Như chúng ta đã thảo luận trước đó, điều này có nghĩa là mọi tài sản gắn liền với Object sẽ tự động có sẵn cho tất cả các con của nó.

Người xây dựng

Nó không thể hiểu nguyên mẫu mà không hiểu cấu trúc của các hàm tạo.

Vì vậy, những gì chính xác xảy ra khi chúng ta tạo một hàm xây dựng tùy chỉnh? Hai thuộc tính xuất hiện một cách kỳ diệu trong định nghĩa lớp của chúng ta: constructor vàotype.constructor.

Họ không chỉ đến cùng một đối tượng. Hãy để phá vỡ chúng:

Hãy nói rằng chúng tôi định nghĩa một lớp cẩu mới (sử dụng từ khóa chức năng hoặc lớp.)

Một hàm tạo tùy chỉnh mà chúng ta vừa tạo hiện được gắn vào thuộc tính nguyên mẫu của lớp Crane tùy chỉnh của chúng ta. Nó có một liên kết trỏ đến hàm tạo của chính nó. Nó tạo ra logic tròn. Nhưng mà chỉ có một mảnh của câu đố.

Bây giờ, hãy xem ngay tại Crane.constructor:

Bản thân Crane.constructor chỉ vào loại đối tượng mà nó được tạo từ đó.
Bởi vì tất cả các hàm tạo đối tượng là các hàm nguyên, nên đối tượng Crane.constructor trỏ đến là một đối tượng của kiểu Hàm, nói cách khác là hàm tạo của hàm.

Động lực này giữa Crane.prototype.constructor và Crane.constructor là thứ cho phép kế thừa nguyên mẫu ở cấp độ phân tử. Bạn thậm chí hiếm khi phải suy nghĩ về điều này khi viết mã JavaScript. Nhưng đây chắc chắn là một câu hỏi phỏng vấn.

Hãy để ngắn gọn đi qua điều này một lần nữa. Crane.prototype.constructor trỏ đến hàm tạo của chính nó. Nó gần như nói rằng tôi là tôi.

Điều tương tự chính xác xảy ra khi bạn định nghĩa một lớp bằng từ khóa lớp:

Nhưng, thuộc tính Crane.constructor trỏ đến Hàm xây dựng hàm.

Và đó là cách thức liên kết được thiết lập.

Bây giờ, chính đối tượng Crane có thể là nguyên mẫu của thành phố khác của một đối tượng khác. Và đối tượng đó có thể là nguyên mẫu của một đối tượng khác. Và như vậy. Chuỗi có thể đi mãi mãi.

Lưu ý bên lề: Trong trường hợp các hàm kiểu ES5, chính hàm đó là
constructor. Nhưng từ khóa lớp ES6 đặt hàm tạo bên trong phạm vi của nó. Đây chỉ là một sự khác biệt cú pháp.

Kế thừa dựa trên nguyên mẫu

Chúng ta nên luôn luôn sử dụng lớp và mở rộng các từ khóa để tạo và kế thừa các đối tượng. Nhưng họ chỉ là một gói kẹo cho những gì thực sự diễn ra sau hậu trường.

Mặc dù việc tạo cấu trúc phân cấp kế thừa đối tượng bằng cú pháp kiểu ES5 đã lỗi thời và hiếm thấy trong các nhà phát triển phần mềm chuyên nghiệp, bằng cách hiểu nó, bạn sẽ hiểu sâu hơn về cách thức hoạt động của nó.

Hãy để định nghĩa một đối tượng mới Bird và thêm 3 thuộc tính: loại, màu sắc và trứng. Hãy cũng có thể thêm 3 phương thức: bay, đi bộ và lay_egg. Một cái gì đó tất cả các loài chim có thể làm:

Lưu ý rằng tôi cố tình tô màu phương thức lay_egg. Hãy nhớ chúng ta như thế nào
đã thảo luận trước đó rằng Bird.prototype trỏ đến hàm tạo của chính nó?

Bạn có thể đã gắn phương pháp đẻ trứng trực tiếp vào Bird.prototype như trong ví dụ tiếp theo:

Thoạt nhìn có vẻ như không có sự khác biệt giữa các phương thức đính kèm bằng cách sử dụng từ khóa này bên trong Bird và chỉ cần thêm nó trực tiếp vào thuộc tính Bird.prototype. Bởi vì nó vẫn hoạt động phải không?

Nhưng điều này không hoàn toàn đúng. Tôi đã giành chiến thắng đi vào chi tiết, bởi vì tôi không hiểu rõ sự khác biệt ở đây. Nhưng tôi có kế hoạch cập nhật hướng dẫn này khi tôi thu thập thêm một số cái nhìn sâu sắc về chủ đề này.

(ý kiến ​​từ các cựu chiến binh nguyên mẫu được chào đón!)

Không phải tất cả các loài chim đều giống nhau

Toàn bộ điểm kế thừa đối tượng là sử dụng một lớp chung xác định tất cả các thuộc tính và phương thức mà tất cả các phần tử con của lớp đó sẽ tự động kế thừa. Điều này làm cho mã ngắn hơn và tiết kiệm bộ nhớ.

(Hãy tưởng tượng việc xác định các thuộc tính và phương thức giống nhau trên tất cả các đối tượng con riêng lẻ một lần nữa. Nó sẽ tốn gấp đôi bộ nhớ.)

Hãy tạo ra một số loại chim khác nhau. Mặc dù tất cả chúng vẫn có thể bay, đi bộ và lay_eggie (vì chúng được thừa hưởng từ lớp Bird chính), mỗi loại chim độc đáo sẽ thêm các phương thức riêng của nó vào lớp đó. Ví dụ, chỉ vẹt có thể nói chuyện. Và chỉ có quạ mới có thể giải câu đố. Chỉ có một con chim biết hót có thể hát.

Con vẹt
Hãy tạo ra một con vẹt và kế thừa nó từ Bird:

Parrot là một hàm xây dựng thông thường giống như Bird.

Sự khác biệt là chúng tôi gọi hàm tạo Bird Bird với Bird.call và vượt qua Parrotùi bối cảnh này, trước khi đính kèm các phương thức của chúng tôi. Bird.call chỉ cần thêm tất cả các thuộc tính và phương thức của nó vào Parrot. Ngoài ra, chúng tôi cũng đang thêm phương pháp của riêng mình: nói chuyện.

Bây giờ vẹt có thể bay, đi bộ, đẻ trứng và nói chuyện! Nhưng chúng tôi không bao giờ phải định nghĩa các phương pháp đi bộ và lay_egss bên trong chính Parrot.

Quạ
Theo cách tương tự, hãy để nhóm tạo ra Raven và kế thừa nó từ Bird:

Quạ là độc nhất ở chỗ họ có thể giải câu đố.

Chim sơn ca
Bây giờ, hãy để Lừa tạo Songbird và kế thừa nó từ Bird:

Chim biết hót có thể hót.

Kiểm tra các loài chim

Chúng tôi chỉ tạo ra một loạt các loài chim khác nhau với khả năng độc đáo. Hãy để xem những gì
họ có khả năng! Cho đến bây giờ chúng tôi chỉ xác định các lớp và thành lập
mối quan hệ thứ bậc.

Để làm việc với các đối tượng, chúng ta cần khởi tạo chúng:

Hãy để ngay lập tức sử dụng chim sẻ bằng cách sử dụng hàm tạo Bird gốc:

Sparrow có thể bay, đi bộ và đẻ trứng, vì nó được thừa hưởng từ Bird xác định tất cả các phương pháp đó.

Nhưng một con chim sẻ không thể nói chuyện. Bởi vì nó không phải là một con vẹt.

Hãy tạo ra một con vẹt từ lớp Parrot:

Vì Parrot được thừa hưởng từ Bird, chúng tôi có được tất cả các phương pháp của nó. Một con vẹt có khả năng nói chuyện độc đáo, nhưng nó không thể hát! Phương thức hát chỉ khả dụng trên các đối tượng thuộc loại Songbird. Hãy để thừa kế ngôi sao từ lớp Songbird:

Cuối cùng, hãy để Voi tạo ra một con quạ và giải một số câu đố:

Sử dụng lớp và mở rộng từ khóa

Các hàm tạo kiểu ES5 có thể hơi cồng kềnh.

May mắn thay, bây giờ chúng ta có lớp và mở rộng các từ khóa để thực hiện chính xác điều tương tự chúng ta vừa làm trong phần trước.

lớp thay thế chức năng

mở rộng và super () thay thế Bird.call từ các ví dụ trước.

Lưu ý chúng ta phải sử dụng super () để gọi hàm tạo của lớp cha.

Cú pháp này có vẻ dễ quản lý hơn nhiều!

Bây giờ tất cả những gì chúng ta phải làm là khởi tạo đối tượng:

Tổng quan

Kế thừa lớp giúp thiết lập một hệ thống phân cấp của các đối tượng.

Các lớp là các khối xây dựng cơ bản của thiết kế và kiến ​​trúc ứng dụng của bạn. Họ làm việc với mã nhiều hơn một chút.

Tất nhiên, Bird chỉ là một ví dụ. Trong một kịch bản trong thế giới thực, nó có thể là bất cứ thứ gì dựa trên loại ứng dụng mà bạn đang cố gắng xây dựng.

Lớp xe có thể là cha mẹ của Xe máy, Ô tô hoặc Xe tăng.

Cá có thể được sử dụng để thừa kế Cá mập, Cá vàng, Pike, v.v.

Kế thừa giúp chúng ta viết mã sạch hơn và tái mục đích đối tượng cha để lưu bộ nhớ khi lặp lại các định nghĩa thuộc tính và phương thức của đối tượng.