Trong lập trình hướng đối tượng, một lớp là một mẫu code trong chương trình để tạo ra các đối tượng, cung cấp các giá trị ban đầu cho trạng thái (biến thành viên) và triển khai hành vi (hàm thành viên hoặc phương thức).

Trong thực tế, chúng ta thường cần tạo ra nhiều đối tượng cùng kiểu, như người dùng, hoặc hàng hóa hoặc bất cứ thứ gì.

Như chúng ta đã biết từ chương Constructor vs toán tử new, new functioncó thể giúp tạo các đối tượng.

Nhưng trong JavaScript hiện đại, có một cấu trúc lớp nâng cao hơn, giới thiệu các tính năng mới tuyệt vời, hữu ích cho lập trình hướng đối tượng.

1. Cú pháp của class

Cú pháp cơ bản là:

class MyClass {
  // class methods
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}

Sau đó sử dụng new MyClass()để tạo một đối tượng mới với tất cả các phương thức được liệt kê.

Các phương thứcconstructor()được gọi tự động bằng toán tử new, vì vậy chúng ta có thể khởi tạo các đối tượng đó.

Ví dụ:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

// Usage:
let user = new User("John");
user.sayHi();

Khi new User("John")được gọi là:

  1. Một đối tượng mới được tạo ra.
  2. Việc constructorchạy với đối số đã cho và gán this.namecho nó.

Sau đó chúng ta có thể gọi các phương thức đối tượng, chẳng hạn như user.sayHi(). Không có dấu phẩy giữa các phương thức lớp

Một cạm bẫy phổ biến đối với các developer mới làm quen là đặt dấu phẩy giữa các phương thức lớp, điều này sẽ dẫn đến lỗi cú pháp.

Ký hiệu ở đây không được nhầm lẫn với nghĩa đen của đối tượng. Trong lớp, không yêu cầu dấu phẩy.

2. Một class là gì?

Vì vậy, chính xác là classgì? Đó không phải là một thực thể cấp ngôn ngữ hoàn toàn mới, như người ta có thể nghĩ.

Chúng ta hãy xem một lớp thực sự là gì. Điều đó sẽ giúp hiểu được nhiều khía cạnh phức tạp.

Trong JavaScript, một lớp là một kiểu hàm.

Ở đây, hãy xem:

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// proof: User is a function
alert(typeof User); // function

Những gì class User {...}xây dựng thực sự là:

  1. Tạo một hàm có tên User, trở thành kết quả của khai báo lớp. Code được lấy từ phương thứcconstructor (giả sử trống nếu chúng ta không viết phương thức đó).
  2. Lưu trữ các phương thức của lớp, chẳng hạn như sayHi, trong User.prototype.

Sau khi đối tượngnew User được tạo, khi chúng ta gọi phương thức của nó, nó được lấy từ nguyên mẫu, giống như được mô tả trong chương F.prototype. Vì vậy, đối tượng có quyền truy cập vào các phương thức của lớp.

Chúng ta có thể minh họa kết quả class Usertrong khai báo sau:

Đây là code:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// class is a function
alert(typeof User); // function

// ...or, more precisely, the constructor method
alert(User === User.prototype.constructor); // true

// The methods are in User.prototype, e.g:
alert(User.prototype.sayHi); // alert(this.name);

// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

3. Không chỉ là một cú pháp đơn giản

Đôi khi mọi người nói rằng đó classlà một cú pháp đơn giản (cú pháp được thiết kế để làm cho mọi thứ dễ đọc hơn, nhưng không giới thiệu bất cứ điều gì mới), bởi vì chúng ta thực sự có thể làm một điều tương tự mà không cần từ khóa class:

// rewriting class User in pure functions

// 1. Create constructor function
function User(name) {
  this.name = name;
}
// a function prototype has "constructor" property by default,
// so we don't need to create it

// 2. Add the method to prototype
User.prototype.sayHi = function() {
  alert(this.name);
};

// Usage:
let user = new User("John");
user.sayHi();

Kết quả của định nghĩa này là giống nhau. Vì vậy, có những lý do thực sự tại sao classcó thể được coi là một cú pháp đơn giản để định nghĩa một hàm tạo cùng với các phương thức nguyên mẫu của nó.

Tuy nhiên, có những khác biệt quan trọng.

  1. Đầu tiên, một hàm được tạo bởi classđược gắn nhãn bởi một thuộc tính nội bộ đặc biệt [[FunctionKind]]:"classConstructor". Vì vậy, nó không hoàn toàn giống như việc tạo nó bằng tay. Ngôn ngữ kiểm tra thuộc tính đó ở nhiều nơi. Ví dụ, không giống như một hàm thông thường, nó phải được gọi bằng new:
class User {   
 constructor() {} 
} 
alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new'

Ngoài ra, một biểu diễn chuỗi của constructor của lớp trong hầu hết các công cụ JavaScript bắt đầu với class

class User {
  constructor() {}
}

alert(User); // class User { ... }

Có những khác biệt, chúng ta sẽ sớm thấy chúng.

Các phương thức lớp là không thể đếm được. Một định nghĩa lớp đặt cờ enumerable falsecho tất cả các phương thức trong "prototype". Điều đó tốt, bởi vì nếu chúng ta for..invượt qua một đối tượng, chúng ta thường không muốn các phương thức lớp của nó.

Class luôn use strict. Tất cả các code bên trong cấu trúc lớp được tự động ở chế độ nghiêm ngặt.

Bên cạnh đó, cú pháp class mang lại nhiều tính năng khác mà chúng ta sẽ khám phá sau.

4. Cách thể hiện một lớp

Cũng giống như các hàm, các lớp có thể được định nghĩa bên trong một biểu thức khác, được truyền xung quanh, trả về, được gán, v.v.

Đây là một ví dụ về biểu thức lớp:

let User = class {
  sayHi() {
    alert("Hello");
  }
};

Tương tự như Biểu thức hàm được đặt tên, biểu thức lớp có thể có tên.

Nếu một biểu thức lớp có tên, nó chỉ hiển thị bên trong lớp:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

// "Named Class Expression"
// (no such term in the spec, but that's similar to Named Function Expression)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass name is visible only inside the class
  }
};

new User().sayHi(); // works, shows MyClass definition

alert(MyClass); // error, MyClass name isn't visible outside of the class

Chúng ta thậm chí có thể tạo ra các lớp một cách linh hoạt, theo yêu cầu, như thế này:


function makeClass(phrase) {
  // declare a class and return it
  return class {
    sayHi() {
      alert(phrase);
    };
  };
}

// Create a new class
let User = makeClass("Hello");

new User().sayHi(); // Hello

5. Getters / setters

Cũng giống như các đối tượng theo nghĩa đen, các lớp có thể bao gồm getters / setters, các thuộc tính được tính toán, v.v.

Đây là một ví dụ để user.namethực hiện bằng cách sử dụng get/set:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/


class User {

  constructor(name) {
    // invokes the setter
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short.");
      return;
    }
    this._name = value;
  }

}

let user = new User("John");
alert(user.name); // John

user = new User(""); // Name is too short.

Về mặt kỹ thuật, khai báo lớp như vậy hoạt động bằng cách tạo getters và setters trong User.prototype.

6. Tên được tính toán […]

Dưới đây là một ví dụ với tên phương thức được tính bằng dấu ngoặc [...]:

class User {

  ['say' + 'Hi']() {
    alert("Hello");
  }

}

new User().sayHi();

Các tính năng như vậy rất dễ nhớ, vì chúng giống với các đối tượng theo nghĩa đen.

7. Class

Một trường(một thuộc tính) của lớp là một bổ sung gần đây cho ngôn ngữ.

Trước đây, các lớp của chúng ta chỉ có phương thức.

Các thuộc tính lớp là một cú pháp cho phép thêm bất kỳ thuộc tính nào.

Chẳng hạn, hãy thêm thuộc tính namevào class User:

class User {
  name = "John";

  sayHi() {
    alert(`Hello, ${this.name}!`);
  }
}

new User().sayHi(); // Hello, John!

Vì vậy, chúng ta chỉ viết “= “Trong khai báo, và đó là giá trị của nó.

Sự khác biệt quan trọng của các trường lớp là chúng được đặt trên các đối tượng riêng lẻ, không phải User.prototype:


class User {
  name = "John";
}

let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined

Về mặt kỹ thuật, chúng được xử lý sau khi hàm tạo thực hiện công việc của nó và chúng ta có thể sử dụng cho chúng các biểu thức và hàm gọi phức tạp:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

class User {
  name = prompt("Name, please?", "John");
}

let user = new User();
alert(user.name); // John

Tạo các phương thức ràng buộc với các trường(thuộc tính) của lớp

Như đã trình bày trong chương Hàm binding hàm trong JavaScript có this. Nó phụ thuộc vào bối cảnh của cuộc gọi.

Vì vậy, nếu một phương thức đối tượng được truyền xung quanh và được gọi trong một ngữ cảnh khác, thissẽ không còn là một tham chiếu đến đối tượng của nó nữa.

Chẳng hạn, mã này sẽ hiển thị undefined:

class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // undefined

Vấn đề được gọi là “mất this“.

Có hai cách tiếp cận để sửa nó, như đã thảo luận trong chương hàm binding:

  1. Truyền một hàm bao bọc, chẳng hạn như setTimeout(() => button.click(), 1000).
  2. Liên kết phương thức với đối tượng, ví dụ: trong hàm tạo:
/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/


class Button {
  constructor(value) {
    this.value = value;
    this.click = this.click.bind(this);
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // hello

Các trường lớp cung cấp một cú pháp tao nhã hơn cho giải pháp sau:

class Button {
  constructor(value) {
    this.value = value;
  }
  click = () => {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // hello

Trường lớp click = () => {...}tạo một hàm độc lập trên mỗi Buttonđối tượng, thisràng buộc với đối tượng. Sau đó, chúng ta có thể vượt qua button.clickbất cứ nơi nào, và nó sẽ được gọi với quyền this.

Điều đó đặc biệt hữu ích trong môi trường trình duyệt, khi chúng ta cần thiết lập một phương thức như một trình lắng nghe sự kiện.

8. Tóm lược

Cú pháp lớp cơ bản trông như thế này:

class MyClass {
  prop = value; // property

  constructor(...) { // constructor
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter method
  set something(...) {} // setter method

  [Symbol.iterator]() {} // method with computed name (symbol here)
  // ...
}

MyClassvề mặt kỹ thuật là một hàm (hàm mà chúng tôi cung cấp constructor), trong khi các phương thức, getters và setters được ghi vào MyClass.prototype.

Trong các chương tiếp theo, chúng ta sẽ tìm hiểu thêm về các lớp, bao gồm tính kế thừa và các tính năng khác.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

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!