Thứ sáu, 04/05/2018 | 00:00 GMT+7

Hiểu các lớp trong JavaScript

JavaScript là một ngôn ngữ dựa trên nguyên mẫu và mọi đối tượng trong JavaScript đều có một thuộc tính ẩn bên trong được gọi là [[Prototype]] được dùng để mở rộng các thuộc tính và phương thức của đối tượng. Bạn có thể đọc thêm về các nguyên mẫu trong hướng dẫn Hiểu về Nguyên mẫu và Kế thừa trong JavaScript của ta .

Cho đến gần đây, các nhà phát triển siêng năng đã sử dụng các hàm khởi tạo để bắt chước một mẫu thiết kế hướng đối tượng trong JavaScript. Đặc tả ngôn ngữ ECMAScript 2015, thường được gọi là ES6, đã giới thiệu các lớp cho ngôn ngữ JavaScript. Các lớp trong JavaScript không thực sự cung cấp chức năng bổ sung và thường được mô tả là cung cấp “đường cú pháp” trên các nguyên mẫu và kế thừa ở chỗ chúng cung cấp một cú pháp rõ ràng và thanh lịch hơn. Bởi vì các ngôn ngữ lập trình khác sử dụng các lớp, nên cú pháp lớp trong JavaScript giúp các nhà phát triển di chuyển giữa các ngôn ngữ dễ dàng hơn.

Lớp học là chức năng

Lớp JavaScript là một loại hàm. Các lớp được khai báo với từ khóa class . Ta sẽ sử dụng cú pháp biểu thức hàm để khởi tạo một hàm và cú pháp biểu thức lớp để khởi tạo một lớp.

// Initializing a function with a function expression const x = function() {} 
// Initializing a class with a class expression const y = class {} 

Ta có thể truy cập [[Prototype]] của một đối tượng bằng phương thức Object.getPrototypeOf() . Hãy sử dụng nó để kiểm tra hàm trống mà ta đã tạo.

Object.getPrototypeOf(x); 
Output
ƒ () { [native code] }

Ta cũng có thể sử dụng phương thức đó trên lớp mà ta vừa tạo.

Object.getPrototypeOf(y); 
Output
ƒ () { [native code] }

Mã được khai báo với functionclass đều trả về một hàm [[Prototype]] . Với các nguyên mẫu, bất kỳ hàm nào cũng có thể trở thành một thể hiện của phương thức khởi tạo bằng cách sử dụng từ khóa new .

const x = function() {}  // Initialize a constructor from a function const constructorFromFunction = new x();  console.log(constructorFromFunction); 
Output
x {} constructor: ƒ ()

Điều này cũng áp dụng cho các lớp học.

const y = class {}  // Initialize a constructor from a class const constructorFromClass = new y();  console.log(constructorFromClass); 
Output
y {} constructor: class

Mặt khác, các ví dụ về phương thức khởi tạo nguyên mẫu này trống, nhưng ta có thể thấy bên dưới cú pháp, cả hai phương thức đều đạt được cùng một kết quả cuối cùng.

Xác định một lớp

Trong hướng dẫn nguyên mẫu và kế thừa , ta đã tạo một ví dụ dựa trên việc tạo nhân vật trong một trò chơi nhập vai dựa trên văn bản. Hãy tiếp tục với ví dụ đó ở đây để cập nhật cú pháp từ các hàm đến các lớp.

Một hàm khởi tạo được khởi tạo với một số tham số, những tham số này sẽ được gán làm thuộc tính của this , tham chiếu đến chính hàm. Chữ cái đầu tiên của mã định danh sẽ được viết hoa theo quy ước.

constructor.js
// Initializing a constructor function function Hero(name, level) {     this.name = name;     this.level = level; } 

Khi ta dịch điều này sang cú pháp lớp , được hiển thị bên dưới, ta thấy rằng nó được cấu trúc rất giống nhau.

class.js
// Initializing a class definition class Hero {     constructor(name, level) {         this.name = name;         this.level = level;     } } 

Ta biết một hàm khởi tạo nghĩa là một bản thiết kế đối tượng bằng cách viết hoa chữ cái đầu tiên của bộ khởi tạo (tùy chọn) và thông qua việc quen thuộc với cú pháp. Từ khóa class truyền đạt một cách đơn giản hơn mục tiêu của hàm của ta .

Sự khác biệt duy nhất trong cú pháp của quá trình khởi tạo là sử dụng từ khóa class thay vì function và gán các thuộc tính bên trong phương thức constructor() .

Phương pháp xác định

Thực tế phổ biến với các hàm khởi tạo là gán các phương thức trực tiếp cho prototype thay vì trong quá trình khởi tạo, như được thấy trong phương thức greet() bên dưới.

constructor.js
function Hero(name, level) {     this.name = name;     this.level = level; }  // Adding a method to the constructor Hero.prototype.greet = function() {     return `${this.name} says hello.`; } 

Với các lớp, cú pháp này được đơn giản hóa và phương thức có thể được thêm trực tiếp vào lớp. Sử dụng cách viết tắt định nghĩa phương thức được giới thiệu trong ES6, việc xác định phương thức là một quá trình ngắn gọn hơn nữa.

class.js
class Hero {     constructor(name, level) {         this.name = name;         this.level = level;     }      // Adding a method to the constructor     greet() {         return `${this.name} says hello.`;     } } 

Ta hãy xem xét các thuộc tính và phương thức hoạt động. Ta sẽ tạo một version Hero bằng cách sử dụng từ khóa new và gán một số giá trị.

const hero1 = new Hero('Varg', 1); 

Nếu ta in thêm thông tin về đối tượng mới của bạn bằng console.log(hero1) , ta có thể xem thêm chi tiết về những gì đang xảy ra với quá trình khởi tạo lớp.

Output
Hero {name: "Varg", level: 1} __proto__: ▶ constructor: class Hero ▶ greet: ƒ greet()

Ta có thể thấy trong kết quả rằng hàm constructor()greet() đã được áp dụng cho __proto__ , hoặc [[Prototype]] của hero1 , và không trực tiếp dưới dạng một phương thức trên đối tượng hero1 . Mặc dù điều này rõ ràng khi tạo các hàm khởi tạo, nhưng nó không rõ ràng khi tạo các lớp. Các lớp cho phép một cú pháp ngắn gọn và đơn giản hơn, nhưng phải hy sinh một số sự rõ ràng trong quá trình này.

Mở rộng lớp học

Một tính năng thuận lợi của các hàm và lớp phương thức khởi tạo là chúng có thể được mở rộng thành các bản thiết kế đối tượng mới dựa trên cơ sở root . Điều này ngăn việc lặp lại mã cho các đối tượng tương tự nhưng cần một số tính năng bổ sung hoặc cụ thể hơn.

Các hàm khởi tạo mới có thể được tạo từ hàm cha bằng phương thức call() . Trong ví dụ dưới đây, ta sẽ tạo một lớp nhân vật cụ thể hơn có tên là Mage và gán các thuộc tính của Hero cho nó bằng cách sử dụng call() , cũng như thêm một thuộc tính bổ sung.

constructor.js
// Creating a new constructor from the parent function Mage(name, level, spell) {     // Chain constructor with call     Hero.call(this, name, level);      this.spell = spell; } 

Đến đây, ta có thể tạo một version mới của Mage bằng cách sử dụng các thuộc tính giống như Hero cũng như một version mới mà ta đã thêm vào.

const hero2 = new Mage('Lejon', 2, 'Magic Missile'); 

Gửi hero2 đến console , ta có thể thấy rằng ta đã tạo ra một Mage mới dựa trên hàm tạo.

Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: ▶ constructor: ƒ Mage(name, level, spell)

Với các lớp ES6, từ khóa super được sử dụng thay cho call để truy cập các hàm cha. Ta sẽ sử dụng extends để tham chiếu đến lớp cha.

class.js
// Creating a new class from the parent class Mage extends Hero {     constructor(name, level, spell) {         // Chain constructor with super         super(name, level);          // Add a new property         this.spell = spell;     } } 

Bây giờ ta có thể tạo một version Mage mới theo cách tương tự.

const hero2 = new Mage('Lejon', 2, 'Magic Missile'); 

Ta sẽ in hero2 ra console và xem kết quả kết quả .

Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: Hero ▶ constructor: class Mage

Đầu ra gần như giống hệt nhau, ngoại trừ việc trong cấu trúc lớp, [[Prototype]] được liên kết với root , trong trường hợp này là Hero .

Dưới đây là so sánh song song toàn bộ quá trình khởi tạo, thêm phương thức và kế thừa của một hàm khởi tạo và một lớp.

constructor.js
function Hero(name, level) {     this.name = name;     this.level = level; }  // Adding a method to the constructor Hero.prototype.greet = function() {     return `${this.name} says hello.`; }  // Creating a new constructor from the parent function Mage(name, level, spell) {     // Chain constructor with call     Hero.call(this, name, level);      this.spell = spell; } 
class.js
// Initializing a class class Hero {     constructor(name, level) {         this.name = name;         this.level = level;     }      // Adding a method to the constructor     greet() {         return `${this.name} says hello.`;     } }  // Creating a new class from the parent class Mage extends Hero {     constructor(name, level, spell) {         // Chain constructor with super         super(name, level);          // Add a new property         this.spell = spell;     } } 

Mặc dù cú pháp khá khác nhau nhưng kết quả cơ bản là gần giống nhau giữa cả hai phương pháp. Các lớp cung cấp cho ta một cách ngắn gọn hơn để tạo bản thiết kế đối tượng và các hàm khởi tạo mô tả chính xác hơn những gì đang xảy ra bên dưới.

Kết luận

Trong hướng dẫn này, ta đã tìm hiểu về những điểm giống và khác nhau giữa các hàm tạo JavaScript và các lớp ES6. Cả hai lớp và hàm tạo đều bắt chước mô hình kế thừa hướng đối tượng cho JavaScript, là một ngôn ngữ kế thừa dựa trên nguyên mẫu.

Hiểu kế thừa nguyên mẫu là điều tối quan trọng để trở thành một nhà phát triển JavaScript hiệu quả. Việc làm quen với các lớp là vô cùng hữu ích, vì các thư viện JavaScript phổ biến như React thường xuyên sử dụng cú pháp của class .


Tags:

Các tin liên quan

Truy cập API Rails trong ứng dụng khách JavaScript bằng Rails Ranger
2018-03-15
Hiểu các biến, phạm vi và lưu trữ trong JavaScript
2018-02-20
Tìm hiểu Nguyên mẫu và Kế thừa trong JavaScript
2018-01-12
Khám phá đối tượng ngày JavaScript
2017-12-06
chuỗi con so với chuỗi con trong JavaScript
2017-11-06
Hiểu Ngày và Giờ trong JavaScript
2017-10-19
Cách xác định các hàm trong JavaScript
2017-10-09
Vòng lặp Đối với, Đối với ... Trong vòng lặp và Đối với ... Trong Vòng lặp trong JavaScript
2017-10-02
Vòng lặp Đối với, Đối với ... Trong vòng lặp và Đối với ... Trong Vòng lặp trong JavaScript
2017-10-02
Xử lý các đối tượng trong JavaScript với Object.assign, Object.keys và hasOwnProperty
2017-09-29