JavaScript: Kế thừa trong JavaScript bằng cách sử dụng super và extends
Tổng quan
Tính kế thừa là một khía cạnh quan trọng trong lập trình JavaScript, mang lại khả năng tái sử dụng mã và tạo ra cấu trúc mã nguồn linh hoạt. Trong JavaScript, có hai cách chính để thực hiện kế thừa: sử dụng prototypal inheritance và sử dụng ES6 Classes. Kế thừa giúp chia sẻ các đặc tính và phương thức giữa các đối tượng một cách hiệu quả, giảm code duplication và tăng khả năng bảo trì. Trong hướng dẫn này, bạn sẽ tìm hiểu cách triển khai kế thừa JavaScript bằng cách sử dụng extends
và super
trong ES6.
Triển khai kế thừa JavaScript bằng cách sử dụng extends và super
Trước ES6, việc triển khai kế thừa cần có nhiều bước. Một trong những cách được sử dụng phổ biến nhất là kế thừa nguyên mẫu (prototypal inheritance).
Ví dụ sau minh họa cách Bird
kế thừa các thuộc tính từ Animal
sử dụng kỹ thuật kế thừa nguyên mẫu:
function Animal(legs) { this.legs = legs; } Animal.prototype.walk = function() { console.log('walking on ' + this.legs + ' legs'); } function Bird(legs) { Animal.call(this, legs); } Bird.prototype = Object.create(Animal.prototype); Bird.prototype.constructor = Animal; Bird.prototype.fly = function() { console.log('flying'); } var pigeon = new Bird(2); pigeon.walk(); // walking on 2 legs pigeon.fly(); // flyin
ES6 đã đơn giản hóa các bước này bằng cách sử dụng từ khóa extends
và super
.
Ví dụ sau định nghĩa các lớp Animal
và Bird,
thiết lập sự kế thừa thông qua từ khóa extends
và super
.
class Animal { constructor(legs) { this.legs = legs; } walk() { console.log('walking on ' + this.legs + ' legs'); } } class Bird extends Animal { constructor(legs) { super(legs); } fly() { console.log('flying'); } } let bird = new Bird(2); bird.walk(); bird.fly()
Giải thích
Đầu tiên sử dụng từ khóa extends để làm cho lớp Bird kế thừa từ lớp Animal:
class Bird extends Animal { // ... }
Lớp Animal
được gọi là lớp cơ sở (base class) hoặc lớp cha (parent class) trong khi lớp Bird được gọi là lớp kế thừa (derived class) hoặc lớp con (child class) . Bằng cách này, lớp Bird kế thừa tất cả các phương thức và thuộc tính của lớp Animal.
Thứ hai, trong hàm tạo của Bird, gọi super()
để gọi hàm tạo của Animal với đối số là legs.
JavaScript yêu cầu lớp con gọi super()
nếu nó có hàm tạo. Như bạn có thể thấy trong lớp Bird, câu lệnh super(legs)
tương đương với câu lệnh sau trong ES5:
Animal.call(this, legs);
Nếu lớp Bird không có hàm tạo, bạn không cần phải làm gì khác:
class Bird extends Animal { fly() { console.log('flying'); } }
Nó tương đương với:
class Bird extends Animal { constructor(...args) { super(...args); } fly() { console.log('flying'); } }
Tuy nhiên, nếu lớp con có một hàm tạo, nó cần gọi super()
. Ví dụ: đoạn mã sau dẫn đến lỗi:
class Bird extends Animal { constructor(legs) { } fly() { console.log('flying'); } }
Lỗi như sau:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Bởi vì super()
khởi tạo đối tượng this nên bạn cần gọi super()
trước khi truy cập đối tượng this. Cố gắng truy cập this
trước khi gọi super()
cũng dẫn đến lỗi.
Ví dụ: nếu bạn muốn khởi tạo thuộc tính color của lớp Bird, bạn có thể thực hiện như sau:
class Bird extends Animal { constructor(legs, color) { super(legs); this.color = color; } fly() { console.log("flying"); } getColor() { return this.color; } } let pegion = new Bird(2, "white"); console.log(pegion.getColor());
Phương thức của lớp con ghi đè phương thức lớp cha
ES6 cho phép lớp con và lớp cha có các phương thức có cùng tên. Trong trường hợp này, khi bạn gọi phương thức của một đối tượng của lớp con, phương thức ở lớp con sẽ ghi đè phương thức ở lớp cha.
Lớp Dog
kế thừa lớp Animal và định nghĩa lại phương thức walk():
class Dog extends Animal { constructor() { super(4); } walk() { console.log(`go walking`); } } let bingo = new Dog(); bingo.walk(); // go walking
Để gọi phương thức của lớp cha trong lớp con, bạn sử dụng super.method(arguments)
như sau:
class Dog extends Animal { constructor() { super(4); } walk() { super.walk(); console.log(`go walking`); } } let bingo = new Dog(); bingo.walk(); // walking on 4 legs // go walking
Kế thừa thành viên tĩnh
Lớp con còn kế thừa tất cả các thuộc tính và phương thức tĩnh của lớp cha. Ví dụ:
class Animal { constructor(legs) { this.legs = legs; } walk() { console.log('walking on ' + this.legs + ' legs'); } static helloWorld() { console.log('Hello World'); } } class Bird extends Animal { fly() { console.log('flying'); } }
Trong ví dụ này, lớp Animal có phương thức tĩnh helloWorld() và phương thức này có sẵn Bird.helloWorld() và hoạt động giống như phương thức Animal.helloWorld():
Bird.helloWorld(); // Hello World
Kế thừa built-in types
JavaScript cho phép bạn mở rộng một loại có sẵn như Array , String, Map và Set thông qua tính kế thừa.
Lớp Queue sau đây mở rộng kiểu tham chiếu Array. Cú pháp rõ ràng hơn nhiều so với cú pháp Queue được triển khai bằng cách sử dụng mẫu hàm tạo/nguyên mẫu (constructor/prototype pattern).
class Queue extends Array { enqueue(e) { super.push(e); } dequeue() { return super.shift(); } peek() { return !this.empty() ? this[0] : undefined; } empty() { return this.length === 0; } } var customers = new Queue(); customers.enqueue('A'); customers.enqueue('B'); customers.enqueue('C'); while (!customers.empty()) { console.log(customers.dequeue()); }