【问题标题】:"Good" practices to implement declare() function实现 declare() 函数的“好”做法
【发布时间】:2014-04-30 14:30:16
【问题描述】:

简介

目前我对 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() 函数的功能。作为参数,它需要一个带有以下部分的 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(); 行。

问题: 我是否应该在constructor 方法的存在上测试config.definition 并注入一个空函数,其中没有指定constructor 方法以避免这种情况?有没有其他可能的方法而不会对性能产生重大影响?


#2 问题 与调试有关:当我尝试记录 ab 变量时,我在控制台中收到 ns.(anonymous function){ ... },这意味着,我已经在执行“动态声明”时丢失了命名空间和类/对象名称。

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

可能是匿名函数没有名称的问题,因此浏览器尝试保存最后一个符号,其中发生赋值。我希望有可能动态创建函数并为其定义名称,发现this question,建议使用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.

我非常感谢您的帮助。谢谢。

【问题讨论】:

    标签: javascript object definitions


    【解决方案1】:

    我是否应该测试 config.definition 是否存在构造方法并注入一个空函数,而没有指定构造方法来避免这种情况?是否还有其他可能不会对性能产生重大影响的方法?

    是的,注入一个空函数作为构造函数实际上会减少性能影响。

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

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

    是否有可能在没有任何 eval() 魔法的情况下保存命名空间和类名?

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


    #3 问题 是您的inherits 函数。而不是

    child.prototype = Object.create(parent);
    

    应该是

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

    【讨论】:

    • 您好,谢谢。除了#3问题,一切都按我认为应该的那样工作。这很关键吗?
    • 什么是关键,第 3 期?什么有效,你的代码还是我的建议?
    • 好吧,什么有效:constructor 有效 - 它允许调用继承的构造函数。关于问题 #2 我只是不确定这是否可能,而且我读了很多,我发现这几乎是不可能的。关于问题#3,我目前正在调查它。我只是问,你能不能进一步解释一下?
    猜你喜欢
    • 2021-03-20
    • 2021-08-04
    • 1970-01-01
    • 2019-07-15
    • 1970-01-01
    • 1970-01-01
    • 2013-10-13
    • 2011-11-18
    • 1970-01-01
    相关资源
    最近更新 更多