5

Disclaimer

  • This thread is supposed to serve as a help for other people encountering similar problems as well as checking whether there are better solutions. I will attach my own solution, but ideas and improvements (besides making it more generic) are welcome.
  • I know that generally, extending the built-in objects is a bad idea. So an assumption for this thread is that there is a good reason and no way around it.

Scenario

As a developer, I want to add a method someMethod to all Javascript objects, wherein the implementation is different for Object, Number and String.

I want the solution to meet the following acceptance criteria:

  • A) The solution works in a browser
    • A1) The solution works in strict mode in case the script is used within a strict context
    • A2) The solution works in non-strict mode because 'use strict'; will be removed during compression in, e.g., the YUI Compressor[1]
  • B) The solution works in node.js
    • B1) The solution works in strict mode (reason see A1)
    • B2) The solution works in non-strict mode for the same reason as in B2, plus strict mode in node.js cannot be activated on function level[2]
  • C) I want other objects to be allowed to override this method
  • D) If possible, I want to have control over whether or not the method shows up in a for .. in loop to avoid conflicts with other libraries
  • E) The solution shall actually modify the prototypes.

[1] Minfication removes strict directives
[2] Any way to force strict mode in node?

2 Answers 2

3

My own solution

While trying to figure this out, I have encountered a few problems causing one or another acceptance criterium to break (e.g. a problem described in [1]). After some time I came up with the following solution which seems to work for me. This can be written in a more generic way, of course.

(function () {
    'use strict';

    var methodName = 'someMethod',
        /** Sample method implementations */
        __someMethod = {
            'object': function () {
                var _this = this.valueOf();

                return ['Object'].concat( Array.prototype.slice.call( arguments ) );
            },

            'number': function () {
                var _this = this.valueOf();

                return ['Number'].concat( Array.prototype.slice.call( arguments ) );
            },

            'string': function () {
                var _this = this.valueOf();

                return ['String'].concat( Array.prototype.slice.call( arguments ) );
            },

            'boolean': function () {
                var _this = this.valueOf();

                return ['Boolean', _this];
            }
        };

    if( Object.defineProperty ) {
        Object.defineProperty( Number.prototype, methodName, {
            value: __someMethod['number'],
            writable: true
        } );

        Object.defineProperty( String.prototype, methodName, {
            value: __someMethod['string'],
            writable: true
        } );

        Object.defineProperty( Boolean.prototype, methodName, {
            value: __someMethod['boolean'],
            writable: true
        } );

        Object.defineProperty( Object.prototype, methodName, {
            value: __someMethod['object'],
            writable: true
        } );
    } else {
        Number.prototype[methodName] = __someMethod['number'];
        String.prototype[methodName] = __someMethod['string'];
        Boolean.prototype[methodName] = __someMethod['boolean'];
        Object.prototype[methodName] = __someMethod['object'];
    }
})(); 

Edit: I updated the solution to add the solution for the problem mentioned in [1]. Namely it's the line (e.g.) var _this = this.valueOf();. The reason for this becomes clear if using

'number': function (other) {
    return this === other;
}

In this case, you will get

var someNumber = 42;
console.log( someNumber.someMethod( 42 ) ); // false

This, of course, isn't what we'd want (again, the reason is stated in [1]). So you should use _this instead of this:

'number': function (other) {
    var _this = this.valueOf();
    return _this === other;
}

// ...

var someNumber = 42;
console.log( someNumber.someMethod( 42 ) ); // true

[1] Why does `typeof this` return "object"?

Sign up to request clarification or add additional context in comments.

5 Comments

I'm not understanding the point. You're simply adding the method to the .prototype, and making the property non-enumerable and non-configurable if possible. What am I missing?
I'm sorry – I did miss one thing; namely what was mentioned in [1]. Other than that, no you're not missing anything. But I had many versions of something like this before that was broken in the browser or nodejs due to all the possibilites mentioned.
Huh, OK. In any case, is there a reason you're adding a wrapping function when Object.defineProperty isn't supported, instead of just doing Number.prototype[methodName] = __someMethod['number'] ?
Yes and no. There used to be one, built on a wrong believe for a second and never to be changed again (that this would refer to the wrong object). I'll adapt the solution (see, this is why I posted it here :) ).
Trying to do the same for Boolean.prototype I have found that the approach needs to be changed a little. Instead of doing var _this = Number( this ), which won't work with Boolean, var _this = this.valueOf() needs to be used. I'll update the answer.
1

Creating a wrapper object (note this is just an example, it is not very robust):

var $ = (function(){
  function $(obj){
    if(!(this instanceof $))
        return new $(obj);

    this.method = function(method){
        var objtype = typeof obj;
        var methodName = method + objtype[0].toUpperCase() + objtype.substr(1);
        typeof _$[methodName] == 'function' && _$[methodName].call(obj);
    }
  }

  var _$ = {};

  _$.formatNumber = function(){
    console.log('Formatting number: ' + this);
  }

  _$.formatString = function(){
    console.log('Formatting str: "' + this + '"');
  }

  _$.formatObject = function(){
    console.log('Formatting object: ');
    console.log(JSON.stringify(this));
  }

  return $;
})();

Usage:

var num = 5;
var str = 'test';
var obj = {num: num, str: str};

var $num = $(num);
$num.method('format');

$(str).method('format');
$(obj).method('format');

Demo

1 Comment

Yes, it works – but it misses the point of the question: To modify the prototype. I guess I should've been more clear and add it as a criterium, even though it was sort of in the assumptions. (edit: done).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.