--- title: Classes slug: Web/JavaScript/Reference/Classes tags: - Class trong ES6 - JavaScript - biểu thức class - class declarations - class expressions - class trong javasript - khai báo class - thừa kế translation_of: Web/JavaScript/Reference/Classes ---
Thực tế các class giống như các "function đặc biệt", và cũng giống như bạn có thể định nghĩa hàm biểu thức (function expressions) và khai báo hàm (function declarations), cú pháp class có hai thành phần: biểu thức class (class expressions) và khai báo class (class declarations).
Một cách để định nghĩa class là sử dụng khai báo lớp class declaration. Để khai báo một class, bạn sử dụng từ khóa class
với tên của class đằng sau (ví dụ "Rectangle" như dưới đây).
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } }
Một sự khác biệt quan trọng giữa khai báo hàm và khai báo class mà các bạn cần chú ý đó là khai báo hàm được kéo lên đầu ({{Glossary("Hoisting", "hoisted")}}), và khai báo class thì không. Bạn cần khai báo class của bạn trước tiên sau đó mới có thể gọi và sử dụng nó, ngược lại nếu viết code giống như phía dưới đây thì sẽ xảy ra lỗi {{jsxref("ReferenceError")}}:
var p = new Rectangle(); // ReferenceError class Rectangle {}
Một biểu thức class là một cách khác để khai báo một class. Biểu thức class có thể có tên hoặc không tên. Biểu thức class nếu có tên, thì tên đó chỉ được nhìn thấy bên trong thân class. (Tuy nhiên từ bên ngoài nó có thể được lấy ra thông qua thuộc tính {{jsxref("Function.name", "name")}} của class (không phải instance)).
// biểu thức class không tên var Rectangle = class { constructor(height, width) { this.height = height; this.width = width; } }; console.log(Rectangle.name); // output: "Rectangle" (tên của biến được gán) // biểu thức class có tên var Rectangle = class Rectangle2 { constructor(height, width) { this.height = height; this.width = width; } }; console.log(Rectangle.name); // output: "Rectangle2" (tên phía sau từ khóa class)
Lưu ý: Các biểu thức class cũng theo quy luật hoisting như của {{anch("Class declarations")}} đề cập ở trên.
Phần thân của một class là phần nằm trong dấu ngoặc nhọn {}
. Tại đó bạn có thể định nghĩa các thành phần của class như phương thức (method) hoặc hàm khởi tạo (constructor).
Các phần tử khai báo trong class declarations và class expressions đã được thực hiện ở chế độ strict mode như constructor, static tương tự đối với prototype methods, setter, getter functions. (Nếu bạn chưa hiểu chế độ strict mode thì hãy tìm hiểu thêm tại đây).
Hàm khởi tạo (constructor) là một hàm đặc biệt, nhiệm vụ của nó là khởi tạo một đối tượng cho một class. Trong một class chỉ có thể tồn tại duy nhất một hàm khởi tạo, nghĩa là bạn chỉ có thể khai báo duy nhất một hàm với tên "constructor". Nếu bạn cố gắng làm ngược lại (khai báo nhiều hơn một hàm constructor) thì sẽ xuất hiện lỗi {{jsxref("SyntaxError")}}.
Một constructor có thể sử dụng từ khóa super để gọi tới hàm constructor của class cha.
Xem thêm định nghĩa hàm.
class Rectangle { constructor(height, width) { this.height = height; this.width = width; } get area() { return this.calcArea(); } calcArea() { return this.height * this.width; } } const square = new Rectangle(10, 10); console.log(square.area);
Từ khóa static
định nghĩa một hàm static (hàm tĩnh) trong một class. Nếu muốn gọi hàm static này thì bạn không cần gọi chúng thông qua các instantiating của class đó và bạn cũng không thể gọi chúng thông qua cách khởi tạo class. Hàm static thường được sử dụng vào mục đích tạo ra một hàm tiện ích (có thể gọi là hàm dùng chung) cho cả một ứng dụng.
class Point { constructor(x, y) { this.x = x; this.y = y; } static distance(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); } } const p1 = new Point(5, 5); const p2 = new Point(10, 10); console.log(Point.distance(p1, p2));
Khi hai hàm static và prototype được gọi trực tiếp (không cần tạo đối tượng có giá trị "this") thì bây giờ bên trong hàm gọi, giá trị this sẽ là undefine.
Autoboxing sẽ không xảy ra. Hành vi này sẽ giống nhau ngay cả khi chúng ta viết code ở strict mode bởi vì tất cả các hàm, phương thức, hàm khởi tạo, setter và getter đều thực thi mặc định ở strict mode. Chính vì vậy nếu chúng ta không chỉ định giá trị "this" thì giá trị của "this" sẽ là undefined.
class Animal { speak() { return this; } static eat() { return this; } } let obj = new Animal(); obj.speak(); // Animal {} let speak = obj.speak; speak(); // undefined Animal.eat() // class Animal let eat = Animal.eat; eat(); // undefined
Nếu chúng ta chỉnh sửa code như trên bằng cách sửa dụng prototype thì autoboxing sẽ tự động hiểu rằng giá trị this bấy giờ là dựa trên cái hàm được gọi. Tham khảo code bên dưới.
function Animal() { } Animal.prototype.speak = function() { return this; } Animal.eat = function() { return this; } let obj = new Animal(); let speak = obj.speak; speak(); // global object let eat = Animal.eat; eat(); // global object
Từ khóa extends
được sử dụng trong class declarations hoặc class expressions để tạo ra một class con kế thừa từ một class sẵn có (class cha).
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } var d = new Dog('Mitzie'); d.speak(); // Mitzie barks.
Nếu có một constructor trong lớp con (sub-class), nó cần gọi hàm super() trước khi có thể sử dụng "this".
Một cách khác cũng có thể gọi và mở rộng hàm có sẵn là dùng prototype:
function Animal (name) { this.name = name; } Animal.prototype.speak = function () { console.log(this.name + ' makes a noise.'); } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } var d = new Dog('Mitzie'); d.speak(); // Mitzie barks.
Cần lưu ý rằng các class không thể extend một object bình thường trong javascript (regular object). Do đó nếu bạn muốn kế thừa một hàm từ object bình thường này, bạn cần thay thế và sử dụng {{jsxref("Object.setPrototypeOf()")}}:
var Animal = { // regular object speak() { console.log(this.name + ' makes a noise.'); } }; class Dog { // constructor(name) { this.name = name; } } Object.setPrototypeOf(Dog.prototype, Animal);// Nếu bạn không làm điều này khi gọi hàm speak thì sẽ sinh ra lỗi var d = new Dog('Mitzie'); // đối tượng của class Dog d.speak(); // Mitzie makes a noise.
Bạn có thể muốn trả về các đối tượng {{jsxref("Array")}} trong mảng của class MyArray
. Mô hình species sẽ cho phép bạn ghi đè lên các hàm khởi tạo mặc định.
Ví dụ, khi sử dụng những phương thức như là {{jsxref("Array.map", "map()")}} điều đó sẽ trả về giá trị khởi tạo mặc định, bạn muốn những phương thức đó trả về một mảng đối tượng của Array, thay vì đối tượng của MyArray.
{{jsxref("Symbol.species")}} sẽ cho phép bạn thực hiện điều này:
class MyArray extends Array { // Overwrite species to the parent Array constructor static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); console.log(mapped instanceof MyArray); // false console.log(mapped instanceof Array); // true
super
Từ khóa super
dùng để gọi một hàm có sẵn ở đối tượng cha.
class Cat { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Lion extends Cat { speak() { super.speak(); console.log(this.name + ' roars.'); } } var l = new Lion('Fuzzy'); l.speak(); // Fuzzy makes a noise. // Fuzzy roars.
Abstract subclasses or mix-ins are templates for classes. An ECMAScript class can only have a single superclass, so multiple inheritance from tooling classes, for example, is not possible. The functionality must be provided by the superclass.
A function with a superclass as input and a subclass extending that superclass as output can be used to implement mix-ins in ECMAScript:
Tập hợp các class con hoặc mix-ins được gọi là khuôn mẫu cho các class. Trong ECMAScript một class chỉ có thể có một lớp cha, vì vậy để thừa kế từ tập hợp các class (kế thừa nhiều class) là điều không thể. Các chức năng phải được cung cấp bởi lớp mà nó kế thừa (cung cấp bởi lớp cha).
Một hàm mà đã được định nghĩa ở lớp cha và lớp con muốn kế thừa và mở rộng hàm đó ra thì có thể sự dụng các lệnh mix-ins trong ECMAScript như sau:
var calculatorMixin = Base => class extends Base { calc() { } }; var randomizerMixin = Base => class extends Base { randomize() { } };
Một class để sử dụng mix-ins này có thể viết code như thế này:
class Foo { } class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
Specification | Status | Comment |
---|---|---|
{{SpecName('ES2015', '#sec-class-definitions', 'Class definitions')}} | {{Spec2('ES2015')}} | Initial definition. |
{{SpecName('ES2016', '#sec-class-definitions', 'Class definitions')}} | {{Spec2('ES2016')}} | |
{{SpecName('ES2017', '#sec-class-definitions', 'Class definitions')}} | {{Spec2('ES2017')}} | |
{{SpecName('ESDraft', '#sec-class-definitions', 'Class definitions')}} | {{Spec2('ESDraft')}} |
{{CompatibilityTable}}
Feature | Chrome | Firefox (Gecko) | Edge | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|---|
Basic support | {{CompatChrome(42.0)}}[1] {{CompatChrome(49.0)}} |
{{CompatGeckoDesktop(45)}} | 13 | {{CompatNo}} | {{CompatOpera(43.0)}} | {{CompatSafari(9.0)}} |
Feature | Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome for Android |
---|---|---|---|---|---|---|
Basic support | {{CompatVersionUnknown}} | {{CompatGeckoMobile(45)}} | {{CompatUnknown}} | {{CompatUnknown}} | 9 | {{CompatChrome(56.0)}} |
[1] Yêu cầu chế độ strict (luôn chạy javascript ở chế độ này). Tắt strict mode trong flag "Enable Experimental JavaScript", mặc định flag này bị vô hiệu hóa.
class
declarationclass
expression