Question

Introduction

Currently I'm being curious about implementation of declare() function, that should allow me to declare javascript classes with usage of prototypal inheritance (or a sort of it, because javascript using different object model, not classic OOP). So far, I found some issues, on which I would like to know somebody's opinion and clarification (if possible).

Here is a "reproduction script" (simplified to reproduce issues), that can be executed in console:

function namespace(){
    if(arguments.length > 1){
        var m, map, result;

        for(m = 0; map = arguments[m], m < arguments.length; m++){
            result = namespace(map);
        }

        return result;
    }

    var scope = window,
        parts = arguments[0].split('.'),
        part, p;

    for(p = 0; part = parts[p], p < parts.length; p++){

        if(typeof scope[part] === 'undefined'){
            scope[part] = {};
        }

        scope = scope[part];
    }

    return scope;
}

function inherit(child, parent){
    child.prototype = Object.create(parent);
    child.prototype.constructor = child;
    child.prototype.$parent = parent.prototype;
}

function mixin(target, source){
    var value;

    target = target || {};

    if(typeof source == 'object'){
        for(var property in source){    
            target[property] = source[property];
        }
    }

    return target;
}

function extend(){
    var mixins = Array.prototype.slice.call(arguments, 0),
        object = mixins.shift() || {},
        length = mixins.length,
        m, mixin;

    for(m = 0; mixin = mixins[m], m < length; mixin(object, mixin), m++);

    return object;
}

function declare(config){
    var map  = config.object.split('.'),
        name = map.pop(),
        ns   = namespace(map.join('.'));

    ns[name] = function(){
        this.constructor.apply(this, arguments);
    };

    if(config.parent){
        if(typeof config.parent == 'string'){
            config.parent = namespace(config.parent);
        }

        inherit(ns[name], config.parent);
    }

    if(config.mixins){
        extend.apply(null, [ ns[name].prototype ].concat(config.mixins));
    }

    if(config.definition){
        mixin(ns[name].prototype, config.definition);
    }
}

declare({
    object: 'Test.A',
    definition: {
        constructor: function(){
            this.a = 1;
        },

        test: function(){
            return this.a;
        }
    }
});

declare({
    object: 'Test.B',
    parent: 'Test.A',
    definition: {
        constructor: function(){
            this.$parent.constructor.call(this);
            this.b = 1;
        },

        test: function(){
            return this.$parent.test.call(this) + this.b;
        }
    }
});

declare({
    object: 'Test.C',
    definition: {
        x: 1
    }
});

var a = new Test.A(),
    b = new Test.B();

console.log('a.test() = ' + a.test());
console.log('b.test() = ' + b.test());

// var c = new Test.C();

A concept

declare() should merge functionality of extend(), inherit() and mixin() functions. As an argument it takes a config object with following sections:

  1. object - object class name (required);
  2. parent - object class name to inherit (not required);
  3. mixins - objects / classes, which properties and method needs to be included to the prototype of result class / object (not required);
  4. definition - result class prototype properties and methods.

The issues


#1 issue is about constructor: if config.definition comes without constructor method, then I'm getting RangeError: Maximum call stack size exceeded error, which means, that my "temporary" constructor function

ns[name] = function(){
    this.constructor.apply(this, arguments);
};

Started calling itself in endless loop. To reproduce you may uncomment var c = new Test.C(); line.

Questions: Should I test config.definition on constructor method existence and inject an empty function, where there is no constructor method specified to avoid this? Is there any other possible approaches without significant performance impact?


#2 issue is about debugging: when I'm trying to log a or b variables then I'm getting ns.(anonymous function){ ... } in console, which means, that I've lost namespace and class / object name, while performed a "dynamic declaration".

ns[name] = function(){ ... };

Possibly, that the problem in anonymous function without name, so browser tries to save last symbols, where an assignment occurs. I was hoping, that there is a possibility to create function dynamicly and define name for it, and found this question, which suggests to use eval(); or new Function(...)();.

Questions: Is there any possibility to save namespace and classname without any evUl() magic?

For example, here is what I would appreciate:

namespace('X.Y');

X.Y.Z = function(){ this.a = 1 };

var test = new X.Y.Z();

console.log(test);

Shows:

X.Y.Z {a: 1}
^^^^^
Literaly, what I want to achieve.

I do really appreciate your help efforts. Thanks.

Was it helpful?

Solution

Should I test config.definition on constructor method existence and inject an empty function, where there is no constructor method specified to avoid this? Is there any other possible approaches without significant performance impact?

Yes, injecting an empty function as the constructor would actually reduce performance impact.

And instead of a function(){this.constructor.apply(this, arguments);} wrapper you should just use the constructor itself (unless you're not sure that it doesn't return an object):

ns[name] = config.definition && config.definition.constructor || function(){};

Is there any possibility to save namespace and classname without any eval() magic?

No. What your debugger/inspector uses here for describing instances is the .name of the constructor function. You cannot set that other by using a named function, and those cannot contain dots in their names.


#3 issue is your inherits function. Instead of

child.prototype = Object.create(parent);

it needs to be

child.prototype = Object.create(parent.prototype);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top