Menu

Kế thừa trong JavaScript: extends, super và ghi đè phương thức

Kế thừa hoạt động thế nào với lớp trong JavaScript: extends, super, ghi đè phương thức, và khi nào kế thừa không phải là lựa chọn đúng.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Một lớp xây dựng trên một lớp khác

Kế thừa (inheritance) cho phép một lớp bắt đầu từ bản thiết kế của lớp khác rồi mở rộng nó. Bạn có sẵn mọi trường và phương thức của lớp cha, và có thể thêm hoặc thay đổi bất cứ thứ gì bạn cần:

Dog extends Animal có nghĩa là: "một Dog là một Animal, cộng thêm một chút". rex không có phương thức speak của riêng mình, nhưng việc tìm kiếm sẽ đi tiếp lên Animal, nơi có phương thức đó. Việc đi tiếp lên đó chính là toàn bộ cơ chế: đơn giản là chuỗi prototype với cú pháp dễ nhìn hơn.

super trong hàm khởi tạo

Lớp con có hàm khởi tạo riêng phải tuân theo một quy tắc bắt buộc: gọi super(...) trước khi đụng đến this. super chạy hàm khởi tạo của lớp cha, chính là thứ thật sự tạo ra và khởi tạo object:

Bỏ dòng super(name) đi và bạn sẽ nhận ReferenceError ngay khi cố đọc hoặc ghi this. Engine đơn giản là không cho bạn this cho đến khi lớp cha chạy xong phần của nó.

Nếu lớp con không khai báo hàm khởi tạo riêng, JavaScript tự tạo một hàm chuyển mọi đối số cho super, nên bạn chỉ cần viết hàm khởi tạo khi thêm trường hoặc cần thiết lập thêm.

Ghi đè phương thức

Lớp con có thể định nghĩa lại bất kỳ phương thức nào nó kế thừa. Phương thức gần nhất trên chuỗi sẽ được dùng:

Không có gì kỳ diệu ở đây: khi bạn gọi speak() trên một Dog, engine tìm speak trên instance, rồi trên Dog.prototype, thấy nó và dừng lại. Nó không bao giờ đi đến Animal.prototype.

Mở rộng thay vì thay thế: super.method()

Đôi khi bạn không muốn thay thế phương thức của lớp cha mà muốn bổ sung vào nó. super.method(...) gọi phiên bản của lớp cha từ bên trong phương thức ghi đè:

Đây là lúc kế thừa thể hiện giá trị: lớp con dùng lại logic của lớp cha thay vì sao chép nó. Nếu sau này Animal.describe thay đổi, Dog.describe tự động nhận thay đổi đó.

super dùng được trong mọi phương thức, không chỉ trong hàm khởi tạo. Nó luôn trỏ đến phiên bản của lớp cha cho thứ bạn đang gọi.

instanceof và chuỗi kế thừa

instanceof kiểm tra xem chuỗi prototype của một object có chứa một lớp cho trước hay không. Mọi instance của lớp con cũng là instance của các lớp cha:

Cả bốn đều là true. Chuỗi đi theo thứ tự Puppy -> Dog -> Animal -> Object, và instanceof duyệt qua nó. Hữu ích để kiểm tra kiểu, dù trên thực tế bạn sẽ dùng nó ít hơn bạn nghĩ: phần lớn code chỉ gọi phương thức và để tính đa hình lo phần còn lại.

Một ví dụ lớn hơn một chút

Một mẫu thường gặp: một lớp cơ sở chứa logic dùng chung, và vài lớp con chuyên biệt hóa nó.

Hãy để ý rằng describe nằm trên Shape và không bao giờ phải viết lại: nó chỉ gọi this.area(), và lời gọi này được phân giải đến đúng lớp con lúc chạy. Đó là tính đa hình (polymorphism): cùng một chỗ gọi, hành vi khác nhau tùy object thực tế.

Kế thừa và composition

Kế thừa rất hấp dẫn vì nó có cảm giác hiệu quả: một dòng code cho bạn cả đống phương thức. Nhưng nó trở nên khó bảo trì khi cây lớp lớn dần.

Quy tắc dễ nhớ: dùng extends khi quan hệ rõ ràng là "X là một Y" và lớp con thật sự dùng chung phần lớn hành vi của lớp cha. Khi bạn tìm đến kế thừa chỉ để dùng chung một hai phương thức hỗ trợ, hãy ưu tiên composition: cho lớp một trường chứa object hỗ trợ đó:

Cây kế thừa nhiều tầng (Animal -> Mammal -> Dog -> WorkingDog -> PoliceDog) trông gọn gàng trên sơ đồ nhưng gây khổ trong code: một thay đổi gần gốc lan ra khó lường đến mọi lớp con cháu. Phần lớn codebase lành mạnh chỉ sâu một hoặc hai tầng và dựa vào composition cho phần còn lại.

Tiếp theo: thành viên static

Mọi thứ trong trang này đều nằm trên instance: những phương thức bạn gọi qua new Thing().something(). Đôi khi bạn muốn có phương thức hoặc dữ liệu thuộc về chính lớp đó, chứ không thuộc về một instance cụ thể nào. Đó là việc của static, chủ đề của trang tiếp theo.

Câu hỏi thường gặp

Kế thừa trong JavaScript hoạt động thế nào?

Một lớp có thể kế thừa từ lớp khác bằng extends. Lớp con nhận mọi phương thức và trường của lớp cha, và có thể thêm cái riêng hoặc ghi đè những cái đã có. Bên dưới, JavaScript nối prototype của lớp con với prototype của lớp cha, nên việc tìm phương thức tự động đi tiếp lên lớp cha.

super trong JavaScript dùng để làm gì?

super(...) gọi hàm khởi tạo của lớp cha: bạn phải gọi nó trước khi dùng this trong hàm khởi tạo của lớp con. super.method(...) gọi phiên bản phương thức của lớp cha, đây là cách để mở rộng hành vi thay vì thay thế hoàn toàn.

Nên dùng kế thừa hay composition trong JavaScript?

Dùng kế thừa khi có quan hệ 'là một' thật sự và lớp con dùng chung phần lớn hành vi của lớp cha. Hãy chọn composition, tức object chứa các object khác, khi bạn chỉ muốn chia sẻ chức năng. Cây lớp nhiều tầng thường khó bảo trì theo thời gian; phần lớn codebase thực tế chỉ sâu một hoặc hai tầng.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu