Events and the Event Emitter

배경지식

events

앱안에서 일어나는 어떤일. 노드에서는 크게 두가지 다른 종류의 이벤트에 대해서 얘기할것이다.

  • system events : c++ core libuv 컴퓨터로부터 시작되는 이벤트 예를들어 파일을 열거나 읽는다 등등.
  • custom events : js core (Event Emitter)

event listener

the code that responds to an event. 자바스크립트에서 이벤트리스너는 function이다. 이벤트가 발생했을때 이코드가 run된다.

object & array

//object
var obj = {
    greet: 'Hello'
}

console.log(obj.greet); // -> 'Hello'
console.log(obj["greet"]); // -> 'Hello'
var prop = 'greet';
console.log(obj[prop]); // -> 'Hello'

//functions any arrays

var arr = [];

arr.push(function(){
    console.log("Hello world 1");
});
arr.push(function(){
    console.log("Hello world 2");
});
arr.push(function(){
    console.log("Hello world 3");
});

arr[0](); // -> "Hello world 1"
arr[1](); // -> "Hello world 2"
arr[2](); // -> "Hello world 3"

arr.forEach(function(item){
    item();
});
// -> "Hello world 1"
// -> "Hello world 2"
// -> "Hello world 3"

Object.create & Prototypes

js에서 상속을 구현하는 대표적인 방법이다.

var person = {
    firstname: '',
    lastname: '',
    greet:function(){
        return this.firstname + ' '+ this.lastname;
    }
}

var john = Object.create(person);
// john은 person의 프로토타입이 된다.(이때 프로토타입은 인스턴스를 뜻한다.)
john.firstname = 'John';
john.lastname = "Doe";

var jane = Object.create(person);
jane.firstname = 'Jane';
jane.lastname = "Doe";

//john과 jane은 greet메소드를 공유한다.

console.log(john.greet);
console.log(jane.greet);

Event Emitter part 1

클래스에 이벤트관련 메소드를 만들어 사용해보자.

//app.js
var Emitter = require('./emitter');

var emtr = new Emitter();
emtr.on('greet', function(){
    console.log("Hello");
});
emtr.on('greet', function(){
    console.log("Bula");
});

emtr.emit('greet');
// -> Hello;
// -> Bula;

//emitter.js
function Emitter(){
    this.events = {};
}

// 이벤트들이 이런식으로 만들어졌구나 생각하면 된다.
Emitter.prototype.on = function(type, listener){
    this.events[type] = this.events[type] || [];
    this.events[type].push(listener);
}

//이런식으로 실행되는구나 생각하자.
Emitter.prototype.emit = function(type){
    if(this.events[type]){
        this.event[type].forEach(function(listener){
            listener();
        });
    }
}

module.exports = Emitter;

Event Emitter part 2

이번엔 노드 코어에 존재하는 events 모듈을 사용해본다. events모듈역시 물론 복잡하지만 위에 만들어본 Emitter와 비슷한 구조를 갖는다. 조금만 관찰해보자.

function EventEmitter(){
    EventEmitter.init.call(this);
}

module.exports = EventEmitter;

EventEmitter.EventEmitter = EventEmitter;

//생략

EventEmitter.prototype.emit = function emit(type){
    if (!this._events)
        this._events = {};

    handler = this._events[type];

    if(util.isFunction(handler)){
        switch(arguments.length){
            case 1:
                handler.call(this);
                break;
            case 2:
                handler.call(this, arguments[1]);
                break;
            case 3:
                handler.call(this, arguments[1], arguments[2]);
                break;
        }

        listeners = handler.slice();
        len = listeners.length;
        for(i = 0; i < len; i++)
            listeners[i].apply(this, args);
    }

    return true;

}

EventEmitter.prototype.addListener = function addListener(type, listener){
    //...
}

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

이전에 만든 Emitter와 거의 유사하므로 모듈만 바꿔주면 된다.

//app.js

var Emitter = require('event');

var emtr = new Emitter();
emtr.on('greet', function(){
    console.log("Hello");
});
emtr.on('greet', function(){
    console.log("Bula");
});

emtr.emit('greet');
// -> Hello;
// -> Bula;

on에 인자로 들어가는 'click' 등과 같은 문자열을 magic string이라고 한다. 문자열이지만 프로퍼티의 키가 되므로 특별한 기능이나 뜻을 갖게된다. 이러한 문자열은 오타가 났을때 버그를 찾기 어렵기때문에 별로 좋지 않다. 이런 경우를 위해 외부에서 오브젝트를 통해 상수처럼 프로퍼티를 불러오기로 한다.

//config.js
module.exports = {
    events: {
        GREET: 'greet'
    }
}

////////////
//app.js
var Emitter = require('event');
var eventConfig = require('./config').event;
// import { events } from './config'

// var GREET = require('./config').event.GREET;
// import { events : { GREET } } from './config'

var emtr = new Emitter();
emtr.on(eventConfig.GREET, function(){
    console.log("Hello");
});

Inheriting from the Event Emitter

util모듈안에는 inherits이라는 상속을 도와주는 메소드가 있다. 조금 살펴보면 내부적으로 Object.create()를 사용하는것을 알 수 있다.

//util.js

exports.inherits = function(ctor, superCtor){
  ctor.super_ = superCtor;
  ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
      value: ctor,
      enumerable: false,
      writable: true,
      configurable: true
    }
});
};

util모듈의 inherits을 사용해 EventEmitter 생성자를 클래스에 상속해보자.

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

function Greetr(){
  this.greeting = "Hello!";
}
util.inherits(Greetr, EventEmitter);

Greetr.prototype.greet = function(data){
  console.log(this.greeting + data);
  this.emit('greet', data);
}

var greeter1 = new Greetr();

greeter1.on('greet', function(data){
  console.log(data + " is greered");
});

greeter1.greet("Minwoo");
greeter1.emit("greet","Jane");

greeter1Greet클래스의 프로토타입(인스턴스)이다. GreetrEventEmitter을 상속받았다. Greetrgreet메소드를 가지고 있고, EventEmitter의 메소드중 onemit을 사용할 것이다.

상속구조

{ [Function: Greetr]
  super_:
   { [Function: EventEmitter]
     EventEmitter: [Circular],
     usingDomains: false,
     defaultMaxListeners: [Getter/Setter],
     init: [Function],
     listenerCount: [Function] } }
greeter1 -> Greet.prototype -> EventEmitter.prototype -> on, emit
-> greet

call을 사용한 상속

call은 해당함수와 전혀 관계가 없는 곳에서 원하는 함수를 불러사용할수 있다.

var obj = {
  name: "Chun",
  greet: function(params){
    console.log("Hello! " + params + this.name)
  }
}

obj.greet("Mr. "); //-> Hello Mr. Chun
obj.greet.call({name: "js"}, "Node"); //-> Hello Nodejs

같은원리로EventEmitter의 모든 메소드를 this(Greetr의 인스턴스)와 연결해 사용할 수 있다.

var EventEmitter = require('events');
var util = require('util');
function Greetr(){
  EventEmitter.call(this); //super constructor: EventEmitter의 프로퍼티와 메서드를 가져온다.
  this.greeting = "Hello!";
}

ES6 class

es6에서는 조금 더 직관적인 클래스를 만들 수 있다.

class Person{
  constructor(firstname, lastname){
    this.firstname = firstname
    this.lastname = lastname
  }
  greet(){
    console.log("hello" + this.firstname);
    return true;
  }
}

class Rich extends Person{
  constructor({firstname,lastname,money}){
    super(firstname,lastname);
    this.money = money
  }
}

var profile = {
  firstname: "minwoo",
  lastname: "chun",
  money:100
}

var rich = new Rich(profile);

console.log(rich.firstname);
console.log(rich.lastname);
console.log(rich.money);
console.log(rich.greet());

이와 같은 방법으로 node모듈을 상속받는 Greetr 클래스를 만들어보자.

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

class Greetr extends EventEmitter{
  constructor(){
    super();
    this.greeting = "Hello!";
  }

  greet(data){
    thie.emit('greet',data);
  }
}

//생략

module.exports = Greetr;

results matching ""

    No results matching ""