7

I was reading Ch 5.5 of the book in title. I have still have trouble in seeing how "We can compose objects out of sets of parts" using the eventuality function in the chapter.

Are objects to be composed by a event system with the "on" and "fire" functions ?

Code from the section of the book below:

var eventuality = function (that) {
    var registry = {};
    that.fire = function (event) {
// Fire an event on an object. The event can be either
// a string containing the name of the event or an
// object containing a type property containing the
// name of the event. Handlers registered by the 'on'
// method that match the event name will be invoked.
        var array,
            func,
            handler,
            i,
            type = typeof event === 'string' ?
                    event : event.type;
// If an array of handlers exist for this event, then
// loop through it and execute the handlers in order.
        if (registry.hasOwnProperty(type)) {
            array = registry[type];
            for (i = 0; i < array.length; i += 1) {
                handler = array[i];
// A handler record contains a method and an optional
// array of parameters. If the method is a name, look
// up the function.
                func = handler.method;
                if (typeof func === 'string') {
                    func = this[func];
                }
// Invoke a handler. If the record contained
// parameters, then pass them. Otherwise, pass the
// event object.
                func.apply(this,
                    handler.parameters || [event]);
            }
        }
        return this;
    };
    that.on = function (type, method, parameters) {
// Register an event. Make a handler record. Put it
// in a handler array, making one if it doesn't yet
// exist for this type.
        var handler = {
            method: method,
            parameters: parameters
        };
        if (registry.hasOwnProperty(type)) {
            registry[type].push(handler);
        } else {
            registry[type] = [handler];
        }
        return this;
    };
    return that;
}
isNaN1247
  • 17,793
  • 12
  • 71
  • 118
user670800
  • 1,107
  • 1
  • 11
  • 16

5 Answers5

7

What Mr. Crockford means here is that you can implement specific functionality such as the on and fire functions that add event processing to any object by calling the function object that creates them (eventuality in this case) with that object as parameter.

The "part" here is an "event processing part" embodied in the eventuality function object. You could imagine different parts that add other functions. The idea here is that you can use this system to add this functionality to individual objects where you need it. This concept is called a Mixin(1).

Also read the final paragraph of chapter 5:

In this way a constructor could assemble objects from a set of parts. JavaScript's loose typing is a big benefit here because we are not burdened with a type system that is concerned about the lineage of classes.

(1) Thank you Zecc.

Community
  • 1
  • 1
jilles de wit
  • 7,060
  • 3
  • 26
  • 50
  • 3
    Instead of writing my own answer, could I suggest you edit yours to include a link to http://en.wikipedia.org/wiki/Mixin or some other better explanation of the concept? – Zecc May 30 '11 at 09:10
1

As I understand it, the point of this section of the book is to illustrate the power of JavaScript - in that you could build an object with all the various 'powerful components of JavaScript' easily.

As he says

For example, we can make a function that can add simple event processing features to any object. It adds an on method, a fire method, and a private event registry

isNaN1247
  • 17,793
  • 12
  • 71
  • 118
0

The following is my own test result. If you copy the code and paste them to a file named 'test.js', then run it on the command line by 'node test.js' (must have already installed node), you will get the same result. My effort is to show you how to make use of eventuality() by tracing the flow with self-explanatory comments.

The only place I don't understand is the line; "fund = this[func]" (with "???" as the comment). It seems that "func = registry[func]" makes more sense to me, since the registry object is where the handler is registered, not the eventuality function object (i.e., the 'this').

var eventuality = function(that) {
  var registry = {};
  that.fire = function(event) {
    var type = typeof event === 'string' ? event : event.type;
    console.log('fire(): fired on event, "' + type + '"');

    if (registry.hasOwnProperty(type)) {
      var array = registry[type];
      for (var i = 0; i < array.length; ++i) {
        var handler = array[i];
        console.log('fire(): handler found at loop ' + i);
        var func = handler.method;
        var pars = handler.parameters;
        if (typeof func === 'string') {
          console.log('fire(): the found func is a string');
          func = this[func]; // ???
        } else {
          // Invoke the handler with parameters.
          console.log('fire(): the found method is NOT a string');
          func.apply(this, [pars]);
        }
      }
    }
    return this;
  };

  that.on = function(type, method, parameters) {
    // Register an event. Make a handler record. Put it in a handler array, making
    // one if it doesn't yet exist for this type.
    var handler = {
      method: method,
      parameters: parameters
    };
    if (registry.hasOwnProperty(type)) {
      // If already registered:
      registry[type].push(handler);
    } else {
      // If it's first time:
      console.log('on(): "' + type + '" event registered');
      registry[type] = [handler];
    }
    return this;
  }

  return that;
};

var dog_is_hungry = {
  type: 'is_hungry'
};
var dog_needs_to_poo = {
  type: 'needs_to_poo'
};
var dog_methods = {
  feed: function() {
    console.log('Event processed by the handler, dog_methods.feed(): ');
    for (var i = 0; i < arguments.length; ++i) {
      console.log('    Feed the dog with the "' + arguments[i].food + '"');
    }
  },
  walk: function() {
    console.log('Event processed by the handler, dog_methods.walk(): ');
    for (var i = 0; i < arguments.length; ++i) {
      console.log('    Walk the dog to the "' + arguments[i].place + '"');
    }
  }
};
var myDog = {
  name: 'Lucky',
  age: 2
}

var myDogEHM = eventuality(myDog); // EHM - events handling manager
console.log('My dog is named ' + myDogEHM.name);
console.log('My dog is aged ' + myDogEHM.age);

// Register the event-handlers
myDogEHM.on(dog_is_hungry.type, dog_methods.feed, {
  food: 'rice and meat'
});
myDogEHM.on(dog_needs_to_poo.type, dog_methods.walk, {
  place: 'park'
});

// Events to be handled
myDogEHM.fire(dog_is_hungry);
myDogEHM.fire(dog_needs_to_poo);

The following is the output:

My dog is named Lucky
My dog is aged 2
on(): "is_hungry"
event registered
on(): "needs_to_poo"
event registered
fire(): fired on event, "is_hungry"
fire(): handler found at loop 0
fire(): the found method is NOT a string
Event processed by the handler, dog_methods.feed():
  Feed the dog with the "rice and meat"
fire(): fired on event, "needs_to_poo"
fire(): handler found at loop 0
fire(): the found method is NOT a string
Event processed by the handler, dog_methods.walk():
  Walk the dog to the "park"
mplungjan
  • 169,008
  • 28
  • 173
  • 236
Daniel C. Deng
  • 1,573
  • 13
  • 8
0

I have further modified Daniel C Deng's example, to further explain the use of this[func]. This code runs in the JavaScript console in Chrome.

var eventuality = function(that) {
  var registry = {};
  that.fire = function(event) {
    var type = typeof event === 'string' ? event : event.type;
    console.log('fire(): fired on event, "' + type + '"');

    if (registry.hasOwnProperty(type)) {
      var array = registry[type];
      for (var i = 0; i < array.length; ++i) {
        var handler = array[i];
        console.log('fire(): handler found at loop ' + i);
        var func = handler.method;
        var pars = handler.parameters;
        if (typeof func === 'string') {
          console.log('fire(): the found func is a string');
          func = dog_methods_2[func];
          //javascript turn string into object reference.
          //https://stackoverflow.com/questions/10953303/javascript-interpret-string-as-object-reference
        }

        // Invoke the handler with parameters.
        //console.log('fire(): the found method is NOT a string');
        func.apply(this, [pars]);

      }
    }
    return this;
  };

  that.on = function(type, method, parameters) {
    // Register an event. Make a handler record. Put it in a handler array, making
    // one if it doesn't yet exist for this type.
    var handler = {
      method: method,
      parameters: parameters
    };
    if (registry.hasOwnProperty(type)) {
      // If already registered:
      registry[type].push(handler);
    } else {
      // If it's first time:
      console.log('on(): "' + type + '" event registered');
      registry[type] = [handler];
    }
    return this;
  }

  return that;
};

var dog_is_hungry = {
  type: 'is_hungry'
};
var dog_needs_to_poo = {
  type: 'needs_to_poo'
};
var dog_is_thirsty = {
  type: 'needs_to_drink'
};
var dog_methods = {
  feed: function() {
    console.log('Event processed by the handler, dog_methods.feed(): ');
    for (var i = 0; i < arguments.length; ++i) {
      console.log('    Feed the dog with the "' + arguments[i].food + '"');
    }
  },
  walk: function() {
    console.log('Event processed by the handler, dog_methods.walk(): ');
    for (var i = 0; i < arguments.length; ++i) {
      console.log('    Walk the dog to the "' + arguments[i].place + '"');
    }
  },
  strings: ["drink"]
};
var dog_methods_2 = {
  drink: function() {
    console.log("    The dog drinks " + arguments[0].drink + ".");
  }
}
var myDog = {
  name: 'Lucky',
  age: 2
}

var myDogEHM = eventuality(myDog); // EHM - events handling manager
console.log('My dog is named ' + myDogEHM.name);
console.log('My dog is aged ' + myDogEHM.age);

// Register the event-handlers
myDogEHM.on(dog_is_hungry.type, dog_methods.feed, {
  food: 'rice and meat'
});
myDogEHM.on(dog_needs_to_poo.type, dog_methods.walk, {
  place: 'park'
});

// Events to be handled
myDogEHM.fire(dog_is_hungry);
myDogEHM.fire(dog_needs_to_poo);

myDogEHM.on(dog_is_thirsty.type, dog_methods.strings[0], {
  drink: 'water'
});
myDogEHM.fire(dog_is_thirsty);

This is the output:

My dog is named Lucky
My dog is aged 2
on(): "is_hungry" event registered
on(): "needs_to_poo" event registered
fire(): fired on event, "is_hungry"
fire(): handler found at loop 0
Event processed by the handler, dog_methods.feed(): 
    Feed the dog with the "rice and meat"
fire(): fired on event, "needs_to_poo"
fire(): handler found at loop 0
Event processed by the handler, dog_methods.walk(): 
    Walk the dog to the "park"
on(): "needs_to_drink" event registered
fire(): fired on event, "needs_to_drink"
fire(): handler found at loop 0
fire(): the found func is a string
    The dog drinks water.
mplungjan
  • 169,008
  • 28
  • 173
  • 236
Sharon
  • 86
  • 1
  • 2
0

either way is possible. i'm a huge fan of the author and he's like a hero to me. anyway, one of the best feature javascript offers us is the dynamic object. we can create object(s) as when do we desire it.