Node.js - สืบทอดจาก EventEmitter


89

ฉันเห็นรูปแบบนี้ในไลบรารี Node.js ไม่กี่ไลบรารี:

Master.prototype.__proto__ = EventEmitter.prototype;

(ที่มาที่นี่ )

ใครช่วยอธิบายตัวอย่างให้ฉันฟังหน่อยได้ไหมว่าทำไมถึงเป็นรูปแบบทั่วไปและเมื่อไหร่ที่มันมีประโยชน์


อ้างอิงคำถามนี้สำหรับข้อมูลstackoverflow.com/questions/5398487/…
Juicy Scripter

14
หมายเหตุ__proto__เป็นลายต่อต้านโปรดใช้Master.prototype = Object.create(EventEmitter.prototype);
Raynos

69
ใช้ได้จริงutil.inherits(Master, EventEmitter);
thesmart

1
@Raynos แอนตี้แพทเทินคืออะไร?
starbeamrainbowlabs

1
ตอนนี้ง่ายขึ้นด้วยตัวสร้างคลาส ES6 ตรวจสอบ compat ที่นี่: kangax.github.io/compat-table/es6 ตรวจสอบเอกสารหรือคำตอบของฉันด้านล่าง
พันธุ์

คำตอบ:


84

ดังที่ความคิดเห็นด้านบนรหัสกล่าวว่ามันจะMasterสืบทอดจากEventEmitter.prototypeดังนั้นคุณสามารถใช้อินสแตนซ์ของ 'คลาส' นั้นเพื่อเปล่งและฟังเหตุการณ์ได้

ตัวอย่างเช่นตอนนี้คุณสามารถทำได้:

masterInstance = new Master();

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

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

อัปเดต : ตามที่ผู้ใช้หลายคนชี้ให้เห็นวิธีการ 'มาตรฐาน' ในการทำเช่นนั้นใน Node คือการใช้ 'util.inherits':

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

การอัปเดตครั้งที่ 2 : ด้วยคลาส ES6 สำหรับเราขอแนะนำให้ขยายEventEmitterคลาสตอนนี้:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

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

myEmitter.emit('event');

ดูhttps://nodejs.org/api/events.html#events_events


4
(เพียงเตือนเล็กน้อยให้ทำก่อน - require('events').EventEmitterฉันมักจะลืมนี่คือลิงค์ไปยังเอกสารในกรณีที่ใคร ๆ ต้องการ: nodejs.org/api/events.html#events_class_events_eventemitter )
mikermcneil

3
BTW ประชุมสำหรับกรณีที่เป็นตัวพิมพ์เล็กตัวอักษรตัวแรกจึงควรจะเป็นMasterInstance masterInstance
khoomeister

และคุณจะรักษาความสามารถในการตรวจสอบว่า: masterInstance instance ของ Master ได้อย่างไร?
jayarjo

3
util.inheritsทำสิ่งที่น่ารังเกียจในการฉีดsuper_คุณสมบัติเข้าไปในMasterวัตถุ ไม่จำเป็นและพยายามที่จะรักษามรดกต้นแบบเช่นมรดกคลาสสิก ลองดูที่ด้านล่างของที่นี้หน้าสำหรับคำอธิบาย
klh

2
@loretoparisi เพียงMaster.prototype = EventEmitter.prototype;. ไม่จำเป็นต้องซูเปอร์ นอกจากนี้คุณยังสามารถใช้ ES6 ขยาย (และจะได้รับการสนับสนุนในเอกสาร Node.js บนutil.inherits) เช่นนี้class Master extends EventEmitter- คุณจะได้รับคลาสสิกแต่ไม่ฉีดอะไรเข้าไปsuper() Master
klh

81

การสืบทอดคลาสสไตล์ ES6

ตอนนี้เอกสาร Node แนะนำให้ใช้การสืบทอดคลาสเพื่อสร้างตัวปล่อยเหตุการณ์ของคุณเอง:

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');

หมายเหตุ:หากคุณกำหนดconstructor()ฟังก์ชันในMyEmitterคุณควรเรียกใช้super()จากฟังก์ชันนี้เพื่อให้แน่ใจว่าตัวสร้างของคลาสพาเรนต์ถูกเรียกด้วยเว้นแต่คุณจะมีเหตุผลที่ดีที่จะไม่ทำ


9
ความคิดเห็นเหล่านี้ไม่ถูกต้องและสุดท้ายจะทำให้เข้าใจผิดในกรณีนี้ โทรsuper()จะไม่จำเป็นต้องตราบใดที่คุณไม่จำเป็นต้อง / กำหนดคอนสตรัคเพราะฉะนั้นคำตอบเดิม Breedly (ดูประวัติการแก้ไข) เป็นถูกต้องทั้งหมด ในกรณีนี้คุณสามารถคัดลอกและวางตัวอย่างเดียวกันนี้ในการจำลองได้ให้ลบตัวสร้างทั้งหมดและมันจะทำงานในลักษณะเดียวกัน นั่นคือไวยากรณ์ที่ถูกต้องอย่างสมบูรณ์
Aurelio

39

ในการสืบทอดจากวัตถุ Javascript อื่นโดยเฉพาะ EventEmitter ของ Node.js แต่โดยทั่วไปแล้วคุณต้องทำสองสิ่ง:

  • จัดเตรียมตัวสร้างสำหรับวัตถุของคุณซึ่งเริ่มต้นวัตถุอย่างสมบูรณ์ ในกรณีที่คุณรับช่วงจากออบเจ็กต์อื่นคุณอาจต้องการมอบหมายงานการเริ่มต้นบางส่วนนี้ให้กับตัวสร้างขั้นสูง
  • จัดเตรียมวัตถุต้นแบบที่จะใช้เป็น[[proto]]วัตถุที่สร้างจากตัวสร้างของคุณ ในกรณีที่คุณกำลังรับช่วงจากวัตถุอื่นคุณอาจต้องการใช้ตัวอย่างของวัตถุอื่นเป็นต้นแบบของคุณ

สิ่งนี้มีความซับซ้อนใน Javascript มากกว่าภาษาอื่น ๆ เนื่องจาก

  • Javascript แยกพฤติกรรมของวัตถุออกเป็น "ตัวสร้าง" และ "ต้นแบบ" แนวคิดเหล่านี้มีขึ้นเพื่อใช้ร่วมกัน แต่สามารถใช้แยกกันได้
  • Javascript เป็นภาษาที่อ่อนมากและผู้คนใช้แตกต่างกันและไม่มีคำจำกัดความที่แท้จริงเพียงคำเดียวว่า "การสืบทอด" หมายถึงอะไร
  • ในหลาย ๆ กรณีคุณสามารถหลีกเลี่ยงการทำบางส่วนของสิ่งที่ถูกต้องและคุณจะพบตัวอย่างมากมายให้ทำตาม (รวมถึงคำตอบอื่น ๆ สำหรับคำถาม SO นี้) ซึ่งดูเหมือนจะใช้ได้ดีกับกรณีของคุณ

สำหรับกรณีเฉพาะของ EventEmitter ของ Node.js สิ่งที่ได้ผลมีดังนี้

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);

ข้อเสียที่เป็นไปได้:

  • หากคุณใช้การตั้งค่าต้นแบบสำหรับคลาสย่อยของคุณ (Master.prototype) โดยจะใช้หรือไม่ใช้util.inheritsก็ได้ แต่อย่าเรียก super constructor ( EventEmitter) สำหรับอินสแตนซ์ของคลาสของคุณจะไม่ได้รับการเตรียมใช้งานอย่างถูกต้อง
  • หากคุณเรียก super constructor แต่ไม่ได้ตั้งค่าต้นแบบเมธอด EventEmitter จะไม่ทำงานบนวัตถุของคุณ
  • คุณอาจลองใช้อินสแตนซ์เริ่มต้นของ superclass ( new EventEmitter) Master.prototypeแทนการให้ตัวสร้างคลาสย่อยMasterเรียกตัวสร้างขั้นEventEmitterสูง ขึ้นอยู่กับลักษณะการทำงานของตัวสร้างคลาสระดับสูงที่อาจดูเหมือนว่ามันทำงานได้ดีในช่วงหนึ่ง แต่ไม่ใช่สิ่งเดียวกัน (และจะไม่ทำงานกับ EventEmitter)
  • คุณอาจลองใช้ super ต้นแบบโดยตรง ( Master.prototype = EventEmitter.prototype) แทนการเพิ่มชั้นของวัตถุเพิ่มเติมผ่าน Object.create; สิ่งนี้อาจดูเหมือนว่ามันใช้งานได้ดีจนกว่าจะมีคนจับลิงวัตถุของคุณMasterและมี Monkeypatched EventEmitterและลูกหลานอื่น ๆ ทั้งหมดโดยไม่ได้ตั้งใจ "คลาส" แต่ละชั้นควรมีต้นแบบของตัวเอง

อีกครั้ง: ในการสืบทอดจาก EventEmitter (หรืออ็อบเจ็กต์ "คลาส" ที่มีอยู่จริงๆ) คุณต้องการกำหนดคอนสตรัคเตอร์ที่เชื่อมโยงกับตัวสร้างขั้นสูงและจัดเตรียมต้นแบบที่ได้มาจากซูเปอร์ต้นแบบ


19

นี่คือวิธีการสืบทอดต้นแบบ (prototypal?) ใน JavaScript จากMDN :

หมายถึงต้นแบบของวัตถุซึ่งอาจเป็นวัตถุหรือโมฆะ (ซึ่งโดยปกติหมายถึงวัตถุคือ Object.prototype ซึ่งไม่มีต้นแบบ) บางครั้งใช้เพื่อใช้การค้นหาคุณสมบัติตามการสืบทอดต้นแบบ

สิ่งนี้ใช้ได้เช่นกัน:

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

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

การทำความเข้าใจ JavaScript OOPเป็นหนึ่งในบทความที่ดีที่สุดที่ฉันอ่านเมื่อเร็ว ๆ นี้เกี่ยวกับ OOP ใน ECMAScript 5


7
Y.prototype = new X();เป็นรูปแบบการต่อต้านโปรดใช้Y.prototype = Object.create(X.prototype);
Raynos

นี่คือสิ่งที่ควรรู้ ฉันสามารถอ่านเพิ่มเติมได้หรือไม่? จะสนใจว่าวัตถุที่ได้นั้นแตกต่างกันอย่างไร
Daff

4
new X()ยกตัวอย่างเช่นของX.prototypeและเริ่มต้นได้โดยการวิงวอนXกับมัน Object.create(X.prototype)เพียงแค่อินสแตนซ์อินสแตนซ์ คุณไม่ต้องการEmitter.prototypeเริ่มต้น ฉันไม่พบบทความดีๆที่อธิบายเรื่องนี้
Raynos

สิ่งนี้สมเหตุสมผล ขอบคุณที่ชี้ให้เห็น ยังคงพยายามทำความคุ้นเคยกับ Node เบราว์เซอร์ไม่ได้มีไว้สำหรับ ECMA5 เท่านั้น (และอย่างที่ฉันเข้าใจว่า shim ไม่น่าเชื่อถือที่สุด)
Daff

1
ลิงค์อื่นก็เสียเช่นกัน ลองดูอันนี้robotlolita.github.io/2011/10/09/…
jlukanta

5

ฉันคิดว่าแนวทางนี้จากhttp://www.bennadel.com/blog/2187-Extending-EventEmitter-To-Create-An-Evented-Cache-In-Node-js.htmค่อนข้างเรียบร้อย:

function EventedObject(){

  // Super constructor
  EventEmitter.call( this );

  return( this );

}

Douglas Crockford มีรูปแบบการสืบทอดที่น่าสนใจเช่นกัน: http://www.crockford.com/javascript/inheritance.html

ฉันพบว่าการสืบทอดมักไม่จำเป็นใน JavaScript และ Node.js แต่ในการเขียนแอปที่การสืบทอดอาจส่งผลต่อความสามารถในการขยายขนาดฉันจะพิจารณาประสิทธิภาพที่ชั่งเทียบกับการบำรุงรักษา มิฉะนั้นฉันจะตัดสินใจเฉพาะว่ารูปแบบใดนำไปสู่การออกแบบโดยรวมที่ดีขึ้นสามารถบำรุงรักษาได้มากกว่าและมีข้อผิดพลาดน้อยลง

ทดสอบรูปแบบต่างๆใน jsPerf โดยใช้ Google Chrome (V8) เพื่อเปรียบเทียบคร่าวๆ V8 เป็นเอ็นจิ้น JavaScript ที่ใช้โดยทั้ง Node.js และ Chrome

นี่คือ jsPerfs เพื่อช่วยคุณเริ่มต้น:

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

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

http://jsperf.com/inheritance-perf


1
ฉันได้ลองใช้วิธีนี้และทั้งสองอย่างemitและonกำลังคิดว่าไม่ได้กำหนด
dopatraman

ไม่ใช่การกลับมา (นี้); เพียงเพื่อล่ามโซ่?
blablabla

1

เพื่อเพิ่มการตอบสนองของ wprl เขาพลาดส่วน "ต้นแบบ":

function EventedObject(){

   // Super constructor
   EventEmitter.call(this);

   return this;

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

1
ที่จริงแล้วคุณควรใช้ Object.create แทนใหม่มิฉะนั้นคุณจะได้รับเช่นรัฐเช่นเดียวกับพฤติกรรมต้นแบบตามที่ได้อธิบายไว้ในที่อื่น แต่จะดีกว่าที่จะใช้ ES6 และ Transpile หรือutil.inheritsเนื่องจากคนฉลาดจำนวนมากจะอัปเดตตัวเลือกเหล่านั้นให้คุณอยู่เสมอ
Cool Blue
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.