Node.js - kế thừa từ EventEmitter


89

Tôi thấy mẫu này trong khá nhiều thư viện Node.js:

Master.prototype.__proto__ = EventEmitter.prototype;

(nguồn tại đây )

Ai đó có thể vui lòng giải thích cho tôi với một ví dụ, tại sao đây là một mô hình phổ biến như vậy và khi nào nó tiện dụng?


Tham khảo câu hỏi này để biết thông tin stackoverflow.com/questions/5398487/…
Juicy Scripter

14
Lưu ý __proto__là một mô hình chống, hãy sử dụngMaster.prototype = Object.create(EventEmitter.prototype);
Raynos

69
Trên thực tế, sử dụngutil.inherits(Master, EventEmitter);
thesmart

1
@Raynos Anti-pattern là gì?
starbeamrainbowlabs

1
Điều này giờ đây dễ dàng hơn với các hàm tạo Lớp ES6. Kiểm tra compat tại đây: kangax.github.io/compat-table/es6 . Kiểm tra tài liệu hoặc câu trả lời của tôi bên dưới.
Breedly

Câu trả lời:


84

Như nhận xét ở trên, đoạn mã đó sẽ tạo Masterkế thừa từ đó EventEmitter.prototype, vì vậy bạn có thể sử dụng các phiên bản của 'lớp' đó để phát ra và lắng nghe các sự kiện.

Ví dụ, bây giờ bạn có thể làm:

masterInstance = new Master();

masterInstance.on('an_event', function () {
  console.log('an event has happened');
});

// trigger the event
masterInstance.emit('an_event');

Cập nhật : như nhiều người dùng đã chỉ ra, cách 'tiêu chuẩn' để làm điều đó trong Node sẽ là sử dụng 'use.inherits':

var EventEmitter = require('events').EventEmitter;
util.inherits(Master, EventEmitter);

Cập nhật lần 2 : với các lớp ES6 cho chúng tôi, bạn nên mở rộng EventEmitterlớp học ngay bây giờ:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

myEmitter.on('event', () => {
  console.log('an event occurred!');
});

myEmitter.emit('event');

Xem https://nodejs.org/api/events.html#events_events


4
(chỉ là một chút nhắc nhở để làm require('events').EventEmitterfirst-- Tôi luôn luôn quên Dưới đây là các liên kết đến các tài liệu trong nhu cầu khác trường hợp ai đó:. nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil

3
BTW, quy ước cho các trường hợp là thành chữ thường chữ cái đầu tiên, vì vậy MasterInstancenên masterInstance.
khoomeister

Và làm cách nào để bạn có thể kiểm tra xem: masterInstance instanceof Master?
jayarjo

3
util.inheritsthực hiện một điều tồi tệ là tiêm super_tài sản vào Masterđối tượng. Nó không cần thiết và cố gắng coi kế thừa nguyên mẫu giống như kế thừa cổ điển. Hãy xem ở dưới cùng của này trang cho lời giải thích.
klh

2
@loretoparisi thôi Master.prototype = EventEmitter.prototype;. Không cần supers. Bạn cũng có thể sử dụng ES6 mở rộng (và nó được khuyến khích trong tài liệu Node.js trên util.inherits) như thế này class Master extends EventEmitter- bạn nhận được cổ điển super(), nhưng không cần tiêm bất cứ thứ gì vào Master.
klh

81

Kế thừa lớp phong cách ES6

Các tài liệu Node hiện khuyên bạn nên sử dụng tính năng kế thừa lớp để tạo bộ phát sự kiện của riêng bạn:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {
  // Add any custom methods here
}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
myEmitter.emit('event');

Lưu ý: Nếu bạn định nghĩa một constructor()hàm trong MyEmitter, bạn nên gọi super()từ nó để đảm bảo hàm tạo của lớp cha cũng được gọi, trừ khi bạn có lý do chính đáng để không làm như vậy.


9
Những nhận xét này không đúng và cuối cùng sẽ gây hiểu lầm trong trường hợp này. Gọi super()không bắt buộc , miễn là bạn không cần / xác định một phương thức khởi tạo, do đó câu trả lời ban đầu của Breedly (xem lịch sử EDIT) là hoàn toàn chính xác. Trong trường hợp này, bạn có thể sao chép và dán chính ví dụ này vào repl, xóa hoàn toàn hàm tạo và nó sẽ hoạt động theo cách tương tự. Đó là cú pháp hoàn toàn hợp lệ.
Aurelio

39

Để kế thừa từ một đối tượng Javascript khác, EventEmitter của Node.js nói riêng nhưng thực sự là bất kỳ đối tượng nào nói chung, bạn cần thực hiện hai việc:

  • cung cấp một phương thức khởi tạo cho đối tượng của bạn, hàm này khởi tạo hoàn toàn đối tượng; trong trường hợp bạn đang thừa kế từ một số đối tượng khác, bạn có thể muốn ủy quyền một số công việc khởi tạo này cho hàm tạo siêu.
  • cung cấp một đối tượng nguyên mẫu sẽ được sử dụng làm [[proto]]đối tượng được tạo từ phương thức khởi tạo của bạn; trong trường hợp bạn đang kế thừa từ một số đối tượng khác, bạn có thể muốn sử dụng một thể hiện của đối tượng kia làm nguyên mẫu của mình.

Điều này phức tạp hơn trong Javascript so với các ngôn ngữ khác vì

  • Javascript tách hành vi của đối tượng thành "phương thức khởi tạo" và "nguyên mẫu". Các khái niệm này có nghĩa là được sử dụng cùng nhau, nhưng có thể được sử dụng riêng biệt.
  • Javascript là một ngôn ngữ rất dễ uốn và mọi người sử dụng nó theo cách khác nhau và không có định nghĩa đúng duy nhất về "kế thừa" nghĩa là gì.
  • Trong nhiều trường hợp, bạn có thể thực hiện một tập hợp con những gì đúng và bạn sẽ tìm thấy rất nhiều ví dụ để làm theo (bao gồm một số câu trả lời khác cho câu hỏi SO này) dường như phù hợp với trường hợp của bạn.

Đối với trường hợp cụ thể của EventEmitter của Node.js, đây là những gì hoạt động:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

// Define the constructor for your derived "class"
function Master(arg1, arg2) {
   // call the super constructor to initialize `this`
   EventEmitter.call(this);
   // your own initialization of `this` follows here
};

// Declare that your class should use EventEmitter as its prototype.
// This is roughly equivalent to: Master.prototype = Object.create(EventEmitter.prototype)
util.inherits(Master, EventEmitter);

Fibles có thể có:

  • Nếu bạn sử dụng thiết lập nguyên mẫu cho lớp con của mình (Master.prototype), có hoặc không sử dụng util.inherits, nhưng không gọi hàm tạo siêu ( EventEmitter) cho các thể hiện của lớp của bạn, chúng sẽ không được khởi tạo đúng cách.
  • Nếu bạn gọi hàm tạo siêu nhưng không đặt nguyên mẫu, các phương thức EventEmitter sẽ không hoạt động trên đối tượng của bạn
  • Bạn có thể cố gắng sử dụng một phiên bản đã khởi tạo của superclass ( new EventEmitter) Master.prototypethay vì để hàm tạo của lớp con Mastergọi hàm tạo siêu EventEmitter; tùy thuộc vào hành vi của hàm tạo lớp cha có thể có vẻ như nó hoạt động tốt trong một thời gian, nhưng không giống như vậy (và sẽ không hoạt động đối với EventEmitter).
  • Bạn có thể thử sử dụng trực tiếp siêu nguyên mẫu ( Master.prototype = EventEmitter.prototype) thay vì thêm một lớp đối tượng bổ sung thông qua Object.create; điều này có vẻ như nó hoạt động tốt cho đến khi ai đó khớp với đối tượng của bạn Mastervà vô tình cũng đã khớp khỉ EventEmittervà tất cả các con cháu khác của nó. Mỗi "lớp" nên có nguyên mẫu riêng của nó.

Một lần nữa: để kế thừa từ EventEmitter (hoặc thực sự là bất kỳ "lớp" đối tượng hiện có nào), bạn muốn xác định một phương thức khởi tạo nối chuỗi với phương thức siêu khởi tạo và cung cấp một nguyên mẫu có nguồn gốc từ siêu nguyên mẫu.


19

Đây là cách kế thừa nguyên mẫu (nguyên mẫu?) Được thực hiện trong JavaScript. Từ MDN :

Đề cập đến nguyên mẫu của đối tượng, có thể là một đối tượng hoặc null (thường có nghĩa là đối tượng là Object.prototype, không có nguyên mẫu). Nó đôi khi được sử dụng để thực hiện tra cứu tài sản dựa trên nguyên mẫu-kế thừa.

Điều này cũng hoạt động:

var Emitter = function(obj) {
    this.obj = obj;
}

// DON'T Emitter.prototype = new require('events').EventEmitter();
Emitter.prototype = Object.create(require('events').EventEmitter.prototype);

Hiểu về JavaScript OOP là một trong những bài viết hay nhất mà tôi đọc gần đây về OOP trong ECMAScript 5.


7
Y.prototype = new X();là một mô hình chống, hãy sử dụngY.prototype = Object.create(X.prototype);
Raynos

Điều này là tốt để biết. Tôi có thể đọc thêm ở đâu đó không? Sẽ quan tâm đến cách các đối tượng kết quả khác nhau.
Daff

4
new X()khởi tạo một thể hiện của X.prototypevà khởi tạo nó bằng cách gọi Xtrên đó. Object.create(X.prototype)chỉ khởi tạo một thể hiện. Bạn không muốn Emitter.prototypeđược khởi tạo. Tôi không thể tìm thấy một bài báo tốt giải thích điều này.
Raynos

Điều này thật ý nghĩa. Cảm ơn đã chỉ ra điều đó. Vẫn đang cố gắng tạo thói quen tốt trên Node. Các trình duyệt không có sẵn cho ECMA5 (và như tôi hiểu thì miếng đệm không đáng tin cậy nhất).
Hừng đông

1
Liên kết còn lại cũng bị hỏng. Hãy thử cái này: robotlolita.github.io/2011/10/09/…
jlukanta

5

Tôi nghĩ rằng cách tiếp cận này từ http://www.bennadel.com/blog/2187-Exfining-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htm khá gọn gàng:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford cũng có một số mẫu kế thừa thú vị: http://www.crockford.com/javascript/inheritance.html

Tôi thấy rằng tính kế thừa ít cần thiết hơn trong JavaScript và Node.js. Nhưng khi viết một ứng dụng mà tính kế thừa có thể ảnh hưởng đến khả năng mở rộng, tôi sẽ xem xét hiệu suất được cân nhắc dựa trên khả năng bảo trì. Nếu không, tôi sẽ chỉ đưa ra quyết định dựa trên những mẫu nào dẫn đến thiết kế tổng thể tốt hơn, dễ bảo trì hơn và ít lỗi hơn.

Kiểm tra các mẫu khác nhau trong jsPerf, sử dụng Google Chrome (V8) để có một so sánh sơ bộ. V8 là công cụ JavaScript được sử dụng bởi cả Node.js và Chrome.

Dưới đây là một số jsPerfs để giúp bạn bắt đầu:

http://jsperf.com/prototypes-vs-functions/4

http://jsperf.com/inheritance-proto-vs-object-create

http://jsperf.com/inheritance-perf


1
Tôi đã thử cách tiếp cận này và cả hai emitonsắp tới là không xác định.
dopatraman

Không phải là sự trở lại (cái này); chỉ để xâu chuỗi?
blablabla

1

Để thêm vào phản hồi của wprl. Anh ấy đã bỏ lỡ phần "nguyên mẫu":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

}
EventObject.prototype = new EventEmitter(); //<-- you're missing this part

1
Trên thực tế, bạn nên sử dụng Object.create thay vì new, nếu không bạn sẽ nhận được trạng thái cá thể cũng như hành vi trên nguyên mẫu như được giải thích ở phần khác . Nhưng tốt hơn nên sử dụng ES6 và transpile hoặc util.inheritsvì rất nhiều người thông minh sẽ cập nhật các tùy chọn đó cho bạn.
Cool Blue
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.