The absorb() function
From time to time I see people blogging about their most favorite or useful functions. Here’s a little function I wrote called absorb(). Its purpose is quite simple, to have one object “absorb” another, i.e., an object receives all of the properties and methods of another object. The difference from other similar functions is that this one allows you to specify not to overwrite existing properties/methods of the same name. Here’s the function:
function absorb(destination /*:Object*/, source /*:Object*/, dontOverwrite /*:Boolean*/) {
for (var key in source) {
if (!dontOverwrite || typeof destination[key] == "undefined"){
destination[key] = source[key];
}
}
}
I’ve found this function useful in many cases, but the most useful is when implementing methods for native JavaScript objects that may exist only in certain browsers. Reading this post over at Dustin’s reminded me to post this. In the post, he talks about checking for the existence of the foreEach() method on Array.prototype before defining his own version. This is a perfect example of how to use absorb():
absorb(Array.prototype, {
forEach: function () { ... }
}, true);
By setting the last argument as true, absorb() won’t overwrite any existing implementation of forEach() (you can omit the last argument if you want it to overwrite existing methods). You can think of this function as a sort of smart extension mechanism: you can use it to copy properties/methods to an object’s prototype for easy subclassing or you can add properties/methods to an already instantiated object that doesn’t support inheritance (like DOM elements in Internet Explorer). There are plenty of uses for absorb(), have fun!
Update: After seeing the comments from JCurtis and Dustin, both of whom pointed out that having a variable named dontOverwrite is a little confusing, I’ve changed the function from the above to this:
function absorb(destination /*:Object*/, source /*:Object*/, overwrite /*:Boolean*/) {
for (var key in source) {
if (overwrite || typeof destination[key] == "undefined"){
destination[key] = source[key];
}
}
}
This does change the behavior so that the default is to not overwrite any existing properties/methods of the same name. Meaning that the previous example should now be:
absorb(Array.prototype, {
forEach: function () { ... }
});
This is really the way it should work – you should never purposely overwrite existing methods. Thanks guys for the suggestions.
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




8 Comments
It would read better if you called second argument "overwrite".
When a person reads the expression "!dontOverwrite", he actually reads "don’t don’t overwrite", which is harder understand than plain "don’t overwrite" (!overwrite).
JCurtis on November 4th, 2006 at 4:22 am
If I took that suggestion, than that would mean the default value for that last argument would be true and I’d have to add extra logical to check to see if the argument is undefined (which would default to the value of true) or a Boolean and set to false. This allows you to omit the last argument and have it work appropriately.
Nicholas C. Zakas on November 4th, 2006 at 3:15 pm
Perfect. I like this Nicholas. You’ve already proved one place I can already use it
I will also second JCurtis’ comment as well. It might be better in this case to rename the third arg to overwrite, and then just check in the condition with: <code>if (overwrite …) { … }</code>
Dustin Diaz on November 4th, 2006 at 3:16 pm
Hey there, looks like my html wasn’t stripped out after all… just exposed
Dustin Diaz on November 4th, 2006 at 3:17 pm
Dustin – looks like you caught me. The HTML isn’t stripped, it’s HTML encoded.
Shame on me.
Again, the purpose of naming the third argument dontOverwrite is to make sure that you can omit it and have it default to an appropriate value. I hate having optional arguments defaulting to values other than false, 0, or null because it forces me to do a type check on that argument instead of using type cohersion.
Nicholas C. Zakas on November 4th, 2006 at 3:21 pm
All right guys, I’ve been convinced. Check out the updated post above.
Nicholas C. Zakas on November 4th, 2006 at 3:39 pm
It would be better (for call chaining) if absorb() returned destination.
Les on November 6th, 2006 at 11:05 am
Les: Good suggestion. It would then be much easier to do multiple absorptions. Eg:
absorb(
o,
{ foo:function() { },
bar:function() { }
}
).
absorb(
o2,
{ baz:function() { },
thunk:function() { }
}
);
Dustin Diaz on November 6th, 2006 at 5:22 pm
Comments are automatically closed after 14 days.