"Хорошие" практики для реализации функции Declare()

Вступление

В настоящее время меня интересует реализация declare() функция, которая должна позволять мне объявлять классы javascript с использованием наследования прототипа (или что-то в этом роде, потому что javascript использует другую объектную модель, а не классическую ООП). До сих пор я обнаружил некоторые проблемы, по которым я хотел бы узнать чье-то мнение и разъяснения (если это возможно).

Вот "сценарий воспроизведения" (упрощенный для воспроизведения проблем), который можно выполнить в консоли:

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();

Концепция

declare() следует объединить функциональность extend(), inherit() а также mixin() функции. В качестве аргумента требуется config объект со следующими разделами:

  1. object - имя класса объекта (обязательно);
  2. parent - имя класса объекта для наследования (не обязательно);
  3. mixins - объекты / классы, свойства и метод которых необходимо включить в прототип результирующего класса / объекта (не обязательно);
  4. определение - свойства и методы прототипа класса результата.

Проблемы


Вопрос № 1 о конструкторе: если config.definition приходит без constructor метод, то я получаю RangeError: Maximum call stack size exceeded ошибка, которая означает, что моя "временная" функция конструктора

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

Начал называть себя в бесконечном цикле. Для воспроизведения вы можете раскомментировать var c = new Test.C(); линия.

Вопросы: я должен проверить config.definition на constructor метод существования и вставка пустой функции, где нет constructor метод, указанный, чтобы избежать этого? Есть ли другие возможные подходы без существенного влияния на производительность?


Проблема № 2 об отладке: когда я пытаюсь войти a или же b переменные тогда я получаю ns.(anonymous function){ ... } в консоли, что означает, что я потерял пространство имен и имя класса / объекта при выполнении "динамического объявления".

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

Возможно, что проблема в анонимной функции без имени, поэтому браузер пытается сохранить последние символы, где происходит присвоение. Я надеялся, что есть возможность динамически создавать функцию и определять для нее имя, и нашел этот вопрос, который предлагает использовать eval(); или же new Function(...)();,

Вопросы: есть ли возможность сохранить пространство имен и имя класса без каких-либо evUl() магия?

Например, вот что я бы оценил:

namespace('X.Y');

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

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

console.log(test);

Показывает:

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

Я действительно ценю ваши усилия по оказанию помощи. Благодарю.

1 ответ

Решение

Должен ли я проверить config.definition на существование метода конструктора и внедрить пустую функцию, где не указан метод конструктора, чтобы избежать этого? Есть ли другие возможные подходы без существенного влияния на производительность?

Да, внедрение пустой функции в качестве конструктора фактически уменьшит влияние на производительность.

И вместо function(){this.constructor.apply(this, arguments);} обертка, вы должны просто использовать сам конструктор (если вы не уверены, что он не возвращает объект):

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

Есть ли возможность сохранить пространство имен и имя класса без какой-либо магии eval()?

Нет. То, что ваш отладчик / инспектор использует здесь для описания экземпляров, является .name функции конструктора. Вы не можете установить другое с помощью именованной функции, и они не могут содержать точек в своих именах.


№ 3 проблема ваша inherits функция. Вместо

child.prototype = Object.create(parent);

это должно быть

child.prototype = Object.create(parent.prototype);
Другие вопросы по тегам