javascript – 实现declare()函数的“好”实践

介绍

目前我对declare()函数的实现感到好奇,这应该允许我使用原型继承(或者它的一种,因为javascript使用不同的对象模型,而不是经典的OOP)来声明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()函数的功能.作为参数,它需要一个包含以下部分的配置对象:

> object – 对象类名(必需);
> parent – 要继承的对象类名(不是必需的);
> mixins – 对象/类,需要将哪些属性和方法包含在结果类/对象的原型中(不是必需的);
> definition – 结果类原型属性和方法.

问题

#1问题是关于构造函数:如果config.definition没有构造函数方法,那么我得到RangeError:最大调用堆栈大小超出错误,这意味着,我的“临时”构造函数

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

开始无休止地呼唤自己.要重现,您可以取消注释var c = new Test.C();线.

问题:我应该在构造函数方法存在时测试config.definition并注入一个空函数,其中没有指定构造函数方法来避免这种情况吗?是否有其他可能的方法没有显着的性能影响?

#2问题是关于调试:当我尝试记录a或b变量时,我在控制台中得到ns.(匿名函数){…},这意味着我丢失了名称空间和类/对象名称,同时执行“动态声明”.

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

可能是匿名函数中没有名称的问题,因此浏览器尝试保存最后一个符号,其中发生了赋值.我希望,有可能动态创建函数并为其定义名称,并找到this question,建议使用eval();或新函数(…)();.

问题:是否有可能在没有任何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.

我真的很感谢你的帮助.谢谢.

最佳答案

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?

是的,注入一个空函数作为构造函数实际上会降低性能影响.

而不是函数(){this.constructor.apply(this,arguments);}包装器你应该只使用构造函数本身(除非你不确定它不返回一个对象):

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

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

否.您的调试器/检查器在此处用于描述实例的是.name of the constructor function.您不能通过使用命名函数来设置其他函数,并且它们的名称中不能包含点.

#3问题是你的继承函数.代替

child.prototype = Object.create(parent);

它需要

child.prototype = Object.create(parent.prototype);
点赞