Cú pháp `class` của ES2015 (ES6) mang lại những lợi ích gì?


87

Tôi có nhiều câu hỏi về các lớp ES6.

Lợi ích của việc sử dụng classcú pháp là gì? Tôi đọc rằng công khai / riêng tư / tĩnh sẽ là một phần của ES7, đó có phải là lý do?

Hơn nữa, là classmột loại OOP khác hay nó vẫn là sự kế thừa nguyên mẫu của JavaScript? Tôi có thể sửa đổi nó bằng cách sử dụng .prototypekhông? Hay chỉ là cùng một đối tượng nhưng hai cách khai báo khác nhau.

Có lợi ích về tốc độ không? Có lẽ sẽ dễ bảo trì / dễ hiểu hơn nếu bạn có một ứng dụng lớn như ứng dụng lớn?


Chỉ là ý kiến ​​của riêng tôi: cú pháp mới hơn dễ hiểu hơn nhiều và sẽ không yêu cầu nhiều kinh nghiệm trước đó (đặc biệt là vì hầu hết mọi người sẽ đến từ ngôn ngữ OOP). Nhưng mô hình đối tượng nguyên mẫu rất đáng để biết và bạn sẽ không bao giờ biết rằng sử dụng cú pháp mới, đồng thời, sẽ khó khăn hơn khi làm việc trên mã nguyên mẫu trừ khi bạn đã biết về nó. Vì vậy, tôi sẽ chọn để viết tất cả các mã của riêng tôi với cú pháp cũ khi tôi có rằng sự lựa chọn
Zach Smith

Câu trả lời:


120

Mới classcú pháp là, đối với doanh nghiệp , chủ yếu là đường cú pháp. (Nhưng bạn biết đấy, loại tốt .) Không có gì trong ES2015-ES2020 classcó thể làm được điều mà bạn không thể làm với các hàm khởi tạo và Reflect.construct(bao gồm phân lớp ErrorArray¹). (Đó khả năng sẽ có một số điều trong ES2021 mà bạn có thể làm với classrằng bạn không thể làm khác: lĩnh vực tư nhân , các phương pháp riêng , và các lĩnh vực tĩnh / phương pháp tĩnh tin .)

Hơn nữa, là classmột loại OOP khác hay nó vẫn là sự kế thừa nguyên mẫu của JavaScript?

Đó là sự kế thừa nguyên mẫu mà chúng ta luôn có, chỉ với cú pháp rõ ràng và thuận tiện hơn nếu bạn thích sử dụng các hàm khởi tạo ( new Foo, v.v.). (Đặc biệt trong trường hợp bắt nguồn từ Arrayhoặc Error, điều mà bạn không thể làm trong ES5 trở về trước. Giờ đây, bạn có thể với Reflect.construct[ spec , MDN ], nhưng không thể với kiểu ES5 cũ.)

Tôi có thể sửa đổi nó bằng cách sử dụng .prototypekhông?

Có, bạn vẫn có thể sửa đổi prototypeđối tượng trên phương thức khởi tạo của lớp sau khi bạn đã tạo lớp. Ví dụ: điều này hoàn toàn hợp pháp:

class Foo {
    constructor(name) {
        this.name = name;
    }
    
    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

Có lợi ích về tốc độ không?

Bằng cách cung cấp một thành ngữ cụ thể cho điều này, tôi cho rằng có thể động cơ có thể thực hiện công việc tối ưu hóa tốt hơn. Nhưng họ đã rất giỏi trong việc tối ưu hóa rồi, tôi sẽ không mong đợi sự khác biệt đáng kể.

classCú pháp ES2015 (ES6) cung cấp những lợi ích gì ?

Tóm lại: Nếu bạn không sử dụng các hàm khởi tạo ngay từ đầu, thì việc ưu tiên Object.createhoặc tương tự classsẽ không hữu ích cho bạn.

Nếu bạn sử dụng các hàm khởi tạo, có một số lợi ích classsau:

  • Cú pháp đơn giản hơn và ít lỗi hơn.

  • Việc thiết lập cấu trúc phân cấp kế thừa bằng cú pháp mới sẽ dễ dàng hơn nhiều (và một lần nữa, ít mắc lỗi hơn) so với cách sử dụng cú pháp cũ.

  • classbảo vệ bạn khỏi lỗi phổ biến khi không sử dụng được newvới hàm tạo (bằng cách để hàm tạo ném một ngoại lệ nếu thiskhông phải là đối tượng hợp lệ cho hàm tạo).

  • Việc gọi phiên bản nguyên mẫu gốc của một phương thức đơn giản hơn nhiều với cú pháp mới so với cú pháp cũ ( super.method()thay vì ParentConstructor.prototype.method.call(this)hoặc Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Đây là một so sánh cú pháp cho một hệ thống phân cấp:

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

Thí dụ:

vs.

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

Ví dụ trực tiếp:

Như bạn có thể thấy, rất nhiều thứ lặp đi lặp lại và dài dòng ở đó, rất dễ nhập sai và nhàm chán khi gõ lại (đó là lý do tại sao tôi đã viết một kịch bản để làm điều đó , trước đó).


¹ "Không có gì trong ES2015-ES2018 classcó thể làm mà bạn không thể làm với các hàm khởi tạo và Reflect.construct(bao gồm cả phân lớp ErrorArray)"

Thí dụ:


9
Tôi không biết nếu đây là một so sánh công bằng. Bạn có thể đạt được một đối tượng tương tự mà không có tất cả các điểm mấu chốt. Nó sử dụng cách JS liên kết đối tượng thông qua chuỗi nguyên mẫu thay vì kế thừa gần đúng cổ điển. Tôi đã tạo một ý chính hiển thị một ví dụ đơn giản dựa trên ví dụ của bạn để hiển thị để làm điều này.
Jason Cust,

8
@JasonCust: Vâng, đó là một sự so sánh công bằng, vì tôi đang chỉ ra cách ES5 thực hiện những gì ES6 làm với class. JavaScript đủ linh hoạt để bạn không cần phải sử dụng các hàm khởi tạo nếu bạn không muốn, đó là những gì bạn đang làm, nhưng điều đó khác với class- toàn bộ điểm của vấn đề classlà đơn giản hóa kế thừa giả cổ điển trong JavaScript.
TJ Crowder

3
@JasonCust: Vâng, đó là một cách khác. Tôi sẽ không gọi nó là "bản địa hơn" (Tôi biết Kyle thì có, nhưng đó là Kyle). Các hàm tạo là cơ bản đối với JavaScript, hãy nhìn vào tất cả các kiểu dựng sẵn (ngoại trừ việc phe chống tạo bằng cách nào đó đã xoay xở để làm Symbolrối tung lên). Nhưng điều tuyệt vời, một lần nữa, là nếu bạn không muốn sử dụng chúng, bạn không cần phải làm thế. Tôi thấy trong công việc của mình rằng các hàm khởi tạo có ý nghĩa ở nhiều nơi và ngữ nghĩa của việc newcải thiện độ rõ ràng; và Object.createcó ý nghĩa ở nhiều nơi khác, đặc biệt. các tình huống mặt tiền. Chúng bổ sung cho nhau. :-)
TJ Crowder

4
meh. Tôi không cần cho cái này. Tôi bước khỏi c # để thoát khỏi oop và bây giờ oop đang leo thang trên javascript. Tôi không thích các loại. mọi thứ phải là một var / object / function. đó là nó. không có thêm từ khóa không liên quan. và thừa kế là lừa đảo. nếu bạn muốn xem một cuộc chiến tốt giữa các loại và không phải loại. xem qua xà phòng vs phần còn lại. và tất cả chúng ta đều biết ai đã giành được điều đó.
foreyez

3
@foreyez: classcú pháp không làm cho JavaScript được nhập nhiều hơn trước. Như tôi đã nói trong câu trả lời, nó chủ yếu chỉ là (nhiều) cú pháp đơn giản hơn cho những gì đã có. Hoàn toàn cần thiết cho điều đó, mọi người liên tục mắc lỗi cú pháp cũ. Nó cũng không có nghĩa là bạn phải sử dụng kế thừa nữa so với trước đây. (Và tất nhiên, nếu bạn không bao giờ thiết lập "lớp học" với cú pháp cũ, không cần phải làm như vậy với cú pháp mới, một trong hai.)
TJ Crowder

22

Các lớp ES6 là đường cú pháp cho hệ thống lớp nguyên mẫu mà chúng ta sử dụng ngày nay. Chúng làm cho mã của bạn ngắn gọn hơn và tự ghi lại, đó là lý do đủ để sử dụng chúng (theo ý kiến ​​của tôi).

Sử dụng Babel để chuyển đổi lớp ES6 này:

class Foo {
  constructor(bar) {
    this._bar = bar;
  }

  getBar() {
    return this._bar;
  }
}

sẽ cung cấp cho bạn một cái gì đó như:

var Foo = (function () {
  function Foo(bar) {    
    this._bar = bar;
  }

  Foo.prototype.getBar = function () {
    return this._bar;
  }

  return Foo;
})();

Phiên bản thứ hai không phức tạp hơn nhiều, nó cần nhiều mã hơn để duy trì. Khi bạn tham gia vào việc kế thừa, những mô hình đó thậm chí còn trở nên phức tạp hơn.

Bởi vì các lớp biên dịch theo cùng các mẫu nguyên mẫu mà chúng tôi đang sử dụng, bạn có thể thực hiện thao tác nguyên mẫu tương tự trên chúng. Điều đó bao gồm thêm các phương thức và những thứ tương tự trong thời gian chạy, truy cập các phương thức trên Foo.prototype.getBar, v.v.

Có một số hỗ trợ cơ bản cho quyền riêng tư trong ES6 ngày nay, mặc dù nó dựa trên việc không xuất các đối tượng mà bạn không muốn truy cập. Ví dụ, bạn có thể:

const BAR_NAME = 'bar';

export default class Foo {
  static get name() {
    return BAR_NAME;
  }
}

BAR_NAMEsẽ không có sẵn cho các mô-đun khác để tham khảo trực tiếp.

Rất nhiều thư viện đã cố gắng hỗ trợ hoặc giải quyết vấn đề này, chẳng hạn như Backbone với trình extendstrợ giúp của họ lấy một hàm băm chưa được kiểm chứng của các chức năng và thuộc tính giống như phương thức, nhưng không có hệ thống nhất quán để hiển thị kế thừa nguyên mẫu không liên quan đến việc sử dụng nguyên mẫu.

Khi mã JS trở nên phức tạp hơn và cơ sở mã lớn hơn, chúng tôi đã bắt đầu phát triển rất nhiều mẫu để xử lý những thứ như kế thừa và mô-đun. IIFE được sử dụng để tạo phạm vi riêng cho các mô-đun có rất nhiều dấu ngoặc nhọn và parens; thiếu một trong số đó có thể dẫn đến một tập lệnh hợp lệ thực hiện điều gì đó hoàn toàn khác (bỏ qua dấu chấm phẩy sau một mô-đun có thể chuyển mô-đun tiếp theo đến nó dưới dạng tham số, điều này hiếm khi tốt).

tl; dr: đó là đường cho những gì chúng tôi đã làm và làm cho ý định của bạn rõ ràng trong mã.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.