4

序章

現在、関数の実装に興味がありdeclare()ます。これにより、プロトタイプの継承を使用して JavaScript クラスを宣言できるようになります (または、JavaScript が従来の OOP ではなく、異なるオブジェクト モデルを使用するため)。これまでのところ、誰かの意見と説明を知りたい(可能であれば)いくつかの問題を見つけました。

これは、コンソールで実行できる「再現スクリプト」(問題を再現するために簡略化されたもの)です。

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. definition - 結果クラスのプロトタイプのプロパティとメソッド。

問題


#1の問題はコンストラクターに関するものです。メソッドconfig.definitionがない場合、エラーが発生します。つまり、「一時的な」コンストラクター関数constructorRangeError: Maximum call stack size exceeded

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

無限ループで自分自身を呼び出し始めました。再現するには、行のコメントを外してくださいvar c = new Test.C();

質問: メソッドの存在をテストconfig.definitionし、これを回避するメソッドが指定されていない場合、空の関数を挿入する必要がありますか? パフォーマンスに大きな影響を与えずに他の可能なアプローチはありますか?constructorconstructor


#2の問題aはデバッグに関するものです。b変数をログに記録しようとするとns.(anonymous function){ ... }、コンソールに表示されます。つまり、「動的宣言」の実行中に名前空間とクラス/オブジェクト名が失われました。

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

おそらく、名前のない無名関数の問題であるため、ブラウザは割り当てが発生した最後のシンボルを保存しようとします。関数を動的に作成し、その名前を定義する可能性があることを期待していましたが、orを使用することを提案するこの質問を見つけました。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.

私はあなたの助けの努力に本当に感謝しています. ありがとう。

4

1 に答える 1