pjs icon indicating copy to clipboard operation
pjs copied to clipboard

Incorrect class names for constructed instances

Open juntalis opened this issue 10 years ago • 0 comments

Saw the following comments, and thought I'd volunteer what I learned on the subject:

    // C is the class to be returned.
    //
    // When called, creates and initializes an instance of C, unless
    // `this` is already an instance of C, then just initializes `this`;
    // either way, returns the instance of C that was initialized.
    //
    //  TODO: the Chrome inspector shows all created objects as `C`
    //        rather than `Object`.  Setting the .name property seems to
    //        have no effect.  Is there a way to override this behavior?
    function C() {
      var self = this instanceof C ? this : new Bare;
      self.init.apply(self, arguments);
      return self;
    }

Class instance names are based on the value of Class.prototype.constructor.name. As far as I know, the name property of a function is readonly and cannot be changed after a function has been declared. In the past, I've tried a few hacky workarounds:

    /**
     * Create a new class inheriting from an old one.
     * @param {function} Class - Constructor for the old class
     * @param {string} [name] - Name of the new class.
     * @return {function}
     */
    function inheritClass(Class, name) {
        // Verify that we received a function.
        if(typeof(Class) !== 'function' || Class instanceof Function)
            throw new TypeError('Expected a function!');

        // In order to maintain the same length as the original function, we'll need to improvise.
        var emptySuper = function(){},
        fakeArgs = function(len) {
            var i = 0, params = [];
            for(; i < len; i++) {
                params.push('arg' + i);
            }
            return params.join(', ');
        },
        argsCode = Class.length === 0 ? '' : fakeArgs(Class.length),
        createSubclass = new Function('__super',
        'return function' + (name||Class.name||'') + '(' + argsCode + '){' +
            '__super.apply(this, arguments);' +
        '};'),
        subclass = createSubclass(Class);
        emptySuper.prototype = Class.prototype;
        subclass.prototype = new emptySuper();
        subclass.prototype.constructor = subclass;
        return subclass;
    };

If you do decide to do something similar, you should note the following behavior:

    function RealClass() { this.num++; }
    function FakeClass() { this.num--; }
    RealClass.prototype.num = 0;
    RealClass.prototype.constructor = FakeClass;
    var test = new RealClass();

Inspecting the test variable after running the code above should result in: FakeClass { num=1}, showing that it was RealClass being executed for the construction. Given that understanding, you should be able to set C.prototype.constructor to a generated noop function with the target name, and still have C executed without issue.

Workarounds aside, you might want to try taking a look at the approach used by my.class. By waiting until after the user's declaration, the framework is able use the developer's own constructor function for the newly created class, which means that:

    var Car = my.Class({
        constructor: function Car() {
            if(this instanceof arguments.callee) {
                this.beep().beep().beep();
            }
        },
        beep: function() {
            console.log("beep");
            return this;
        }
    },
    car = new Car();

will work correctly, with car showing up as an instance of Car. (and the correct behavior in the constructor with regards to arguments.callee)

Anyways, lot of information for something sort of trivial, but I hope it helps.

juntalis avatar Dec 18 '14 19:12 juntalis