15

I'm looking through the MooTools source to try and understand its .implement() and .extend() utilities.

The definition of each refers to a function defined like this:

var enumerables = true;
for (var i in {toString: 1}) enumerables = null;
if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];

Function.prototype.overloadSetter = function(usePlural){
    var self = this;
    return function(a, b){
        if (a == null) return this;
        if (usePlural || typeof a != 'string'){
            for (var k in a) self.call(this, k, a[k]);
            if (enumerables) for (var i = enumerables.length; i--;){
                k = enumerables[i];
                if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
            }
        } else {
            self.call(this, a, b);
        }
        return this;
    };
};

However, I am having a tough time understanding what it does.

Can you explain how this function works and what it does?

Marcel Korpel
  • 21,536
  • 6
  • 60
  • 80
Austin Hyde
  • 26,347
  • 28
  • 96
  • 129

2 Answers2

29

overloadSetter

overloadSetter, together with overloadGetter, are two function decorator methods. The overloadSetter function is used to transform functions that have the signature fn(key, value) to functions that could accept object arguments, ie: fn({key: value}).

In order to do this, overloadSetter must wrap the original function. This wrapper function has the signature fn(a, b) which is a shortcut for fn(key, value). This effectively becomes the new overloaded version of the original function.

First thing this overloaded function does is check whether the passed key argument (a) is of the string type or not. If it's not a string, the function assumes that we're passing an object. So it iterates over each key-value pair in the object and applies the original function to it. If it's a string, on the other hand, it simply applies the function to the values of the a and b arguments.

Example

To illustrate, let's say we have the following function:

var fnOrig = function(key, value){
    console.log(key + ': ' + value); 
};

var fnOver = fnOrig.overloadSetter();

fnOver('fruit', 'banana');
fnOver({'fruit': 'banana', 'vegetable': 'carrot'});

In the first invocation, the fnOver function is invoked with two arguments, a key and a value. When the function checks the type of the a argument value, it'll see that it is a string. Therefore, it will simply invoke the original fnOrig function: fnOrig.call(this, 'fruit', 'banana'). Our console output is 'fruit: banana'.

For the second invocation, the fnOver function is invoked with an object argument. Since we passed an object instead of a string, fnOver will iterate through the members of this object and invoke the fnOrig function for each one of them. Thus, fnOrig will be invoked twice in this case: fnOrig.call(this, 'fruit', 'banana') and fnOrig.call(this, 'vegetable', 'carrot'). Our console output is 'fruit: banana' and 'vegetable: carrot'.

Extras

Inside the wrapper function, you'll see that there's an check for the value of usePlural. This is an argument for the overloadSetter method itself. If you set this value to true, the new function will treat all arguments as object. This means that even if you pass a string key argument, it will still be processed as an object.

The other thing, the enumerables code that preludes the actual method declaration, is there because it fixes an issue with some browsers wherein the native Object methods are not enumerated in for/in loops even if the object itself implements its own version of it.

  • 6
    although this is _somewhat unlikely_ to change, i should point out that this is a private / internal method and writing code that depends on the API may not stand the test of time (works in 1.3 for the time being) – Dimitar Christoff Oct 25 '10 at 13:10
  • undocumented, so no promises. use at your own risk :) – seanmonstar Oct 25 '10 at 17:19
  • 1
    Thanks for the great answer. @Dimitar, seanmonstar: That's OK, I'm not planning on using it, I just wanted to understand how the function worked and what it did. – Austin Hyde Oct 27 '10 at 14:17
  • 1
    I use it often, and it's awesome. Thanks for the in depth explanation. – Ryan Florence Nov 18 '10 at 04:12
3

The part that had me scratching my head for a while was the

var enumerables = true; for (var i in {toString: 1}) enumerables = null;

part, which turns out to be a test for the DontEnum bug that some browsers have. At first glance it seems like it should just set enumerables to null, but with the DontEnum bug toString is suppressed (wrongly, because the object's prototype.toString has the DontEnum flag) and enumerables is left as true.

overloadSetter (or rather the resulting function) then has to check one at a time for the seven properties that the DontEnum bug affects, to see if they exist in the object argument.

LHMathies
  • 2,384
  • 16
  • 21
  • And if you wonder what happens if you try to set the `hasOwnProperty` property in the presence of the DontEnum bug, you're right to: the check for the affected properties will use your supplied value for `hasOwnProperty`, which is almost certainly not what you wanted even if you somehow find a good reason to override the prototype function. – LHMathies Jan 20 '11 at 11:45