web-dev-qa-db-ja.com

関数にカスタムプロパティを追加する

キーワードに関連する他の多くの問題が存在するため、適切な回答を検索することは困難であることが判明したので、ここで質問します。

ご存じのように、javascriptの関数はオブジェクトであり、独自のプロパティとメソッド(より適切には、Function.prototypeから継承された関数の要素)があります。

1つの関数(メソッド)にカスタムプロパティを追加することを検討していました。「理由」をスキップしましょう。パートし、コードに直行します。

var something = {
    myMethod: function () {
        if (something.myMethod.someProperty === undefined) {
            something.myMethod.someProperty = "test";
        }
        console.log(something.myMethod);
    }
}

FirebugのDOMエクスプローラーで検査すると、プロパティは期待どおりに定義されます。ただし、私は自分がJavaScriptの専門家だとは思わないので、次の質問があります。

  1. この方法は「適切」で標準に準拠していると考えられますか? Firefoxで動作しますが、Webブラウザーで期待どおりに動作するものが多くあり、決して標準ではありません。
  2. この種のオブジェクトに新しいプロパティを追加して変更するのは良い習慣ですか?
50
Przemek

あなたが質問に非常に意味のある答えを出すのは少し難しいです。なぜなら、あなたは「ここに私の解決策があります、大丈夫ですか?」と言ったからです。解決しようとしている問題を説明せずに(「理由」を説明しないと明示的に言った場合でも)。コードは実行される有効なJavaScriptのように見えますが、最適な方法ではないようにも見えます。

あなたが実際に達成したいことを説明すると、コードを構造化するより良い方法についていくつかの良い提案を得ることができます。それでも、私はあなたにある種の答えを与えます:

この方法は「適切」で標準に準拠していると見なすことができますか? Firefoxで動作しますが、Webブラウザーで期待どおりに動作するものが多くあり、決して標準ではありません。

関数は(あなたが言ったように)オブジェクトなので、プロパティを追加することができます。これは、すべてのブラウザーがサポートするJavaScriptの中核部分であるという点で、実際には標準的な問題ではありません。

この種のオブジェクトに新しいプロパティを追加して変更するのは良い習慣ですか?

オブジェクトです。好きなプロパティを追加できます。オブジェクトの重要なポイントは、オブジェクトに操作可能なプロパティがあることです。プロパティやメソッドの追加、削除、更新など、オブジェクトの変更を伴わないオブジェクトの使用方法を思い描くことはできません。

そうは言っても、プロパティをmyMethod関数に追加するのは理にかなっていないため、他のプロパティをsomethingオブジェクト(myMethod関数は、正しく呼び出された場合、somethingキーワードを介してthisの他のプロパティにアクセスできます)。

関数をconstructorとして使用している場合、通常はmethods関連付けられたプロトタイプに追加し、(インスタンスではない)プロパティを各インスタンスに追加しますが、必要に応じて他の方法のいずれかまたは両方を実行できます。 (「メソッド」は本質的に、たまたま関数を参照する単なるプロパティであることに注意してください。)

示した特定のコードはプロパティを追加しません。somePropertyプロパティalreadyが存在するかどうかをテストし、存在する場合は新しいプロパティを割り当てます。値。

MDNで次のような記事を読むとメリットが得られる場合があります。

20
nnnnnn

まず、標準関数のプロパティ(引数、名前、呼び出し元、長さ)を上書きできないことを理解することが重要です。そのため、その名前のプロパティを追加することを忘れてください。

関数への独自のカスタムプロパティの追加は、すべてのブラウザーで機能するさまざまな方法で実行できます。


関数に独自のカスタムプロパティを追加する

方法1:関数の実行中にプロパティを追加します:

var doSomething = function() {
    doSomething.name = 'Tom';
    doSomething.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : 
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

ウェイ1(代替構文):

function doSomething() {
    doSomething.name = 'Tom';
    doSomething.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : doSomething
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 

ウェイ1(2番目の代替構文):

var doSomething = function f() {
    f.name = 'Tom';
    f.name2 = 'John';
    return 'Beep';
};

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : f
doSomething.name2 : undefined
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 

この戦略の問題は、プロパティを割り当てるために少なくとも1回関数を実行する必要があることです。多くの機能にとって、それは明らかにあなたが望むものではありません。それでは、他のオプションを考えてみましょう。


方法2:関数を定義した後にプロパティを追加する:

function doSomething() {
    return 'Beep';
};

doSomething.name = 'Tom';
doSomething.name2 = 'John';

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : doSomething
doSomething.name2 : John
doSomething() : Beep
doSomething.name : doSomething
doSomething.name2 : John 

これで、プロパティにアクセスする前に関数を実行する必要がなくなりました。ただし、不利な点は、プロパティが関数から切り離されていると感じることです。


方法3:関数を匿名関数でラップします。

var doSomething = (function(args) {
    var f = function() {
        return 'Beep';
    };
    for (i in args) {
        f[i] = args[i];
    }
    return f;
}({
    'name': 'Tom',
    'name2': 'John'
}));

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

関数を匿名関数にラップすると、属性をオブジェクトに収集し、ループを使用して匿名関数内にそれらの属性を1つずつ追加できます。そうすることで、あなたの属性はあなたの機能により関連していると感じます。この手法は、既存のオブジェクトから属性をコピーする必要がある場合にも非常に役立ちます。ただし、欠点は、関数を定義するときに複数の属性を同時にしか追加できないことです。また、関数にプロパティを追加することが頻繁に行いたい場合、DRYコードになりません。


方法4:関数に「extend」関数を追加します。これにより、オブジェクトのプロパティが1つずつそれ自体に追加されます。

var doSomething = function() {
    return 'Beep';
};

doSomething.extend = function(args) {
    for (i in args) {
        this[i] = args[i];
    }
    return this;
}

doSomething.extend({
    'name': 'Tom',
    'name2': 'John'
});

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

このように、いつでも複数のプロパティを拡張したり、別のプロジェクトからプロパティをコピーしたりできます。繰り返しますが、コードはDRYではありません。


方法5:汎用の「拡張」機能を作成します。

var extend = function(obj, args) {
    if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            obj[i] = args[i];
        }
    }
    return obj;
}

var doSomething = extend(
    function() {
        return 'Beep';
    }, {
        'name': 'Tom',
        'name2': 'John'
    }
);

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

遺伝的拡張機能により、より多くのDRYアプローチが可能になり、オブジェクトまたはプロジェクトを他のオブジェクトに追加できます。


Way 6: extendableFunctionオブジェクトを作成し、それを使用して拡張関数を関数にアタッチします。

var extendableFunction = (function() {
    var extend = function(args) {
        if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
            for (i in args) {
                this[i] = args[i];
            }
        }
        return this;
    };
    var ef = function(v, obj) {
        v.extend = extend;
        return v.extend(obj);
    };

    ef.create = function(v, args) {
        return new this(v, args);
    };
    return ef;
})();

var doSomething = extendableFunction.create(
    function() {
        return 'Beep';
    }, {
        'name': 'Tom',
        'name2': 'John'
    }
);

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

この手法では、汎用の「extend」関数を使用するのではなく、「extend」メソッドが付加された関数を生成できます。


Way 7: Functionプロトタイプに「extend」関数を追加します。

Function.prototype.extend = function(args) {
    if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
        for (i in args) {
            this[i] = args[i];
        }
    }
    return this;
};

var doSomething = function() {
    return 'Beep';
}.extend({
    name : 'Tom',
    name2 : 'John'
});

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : 
doSomething.name2 : John
doSomething() : Beep
doSomething.name : 
doSomething.name2 : John 

この手法の大きな利点は、関数に新しいプロパティを追加するのが非常に簡単になり、DRYだけでなく完全にオブジェクト指向であるということです。また、かなりメモリに優しいです。将来のブラウザではなく、関数プロトタイプにネイティブの「拡張」関数が追加されると、コードが破損する可能性があります。


Way 8:関数を1回再帰的に実行してから返します:

var doSomething = (function f(arg1) {
    if(f.name2 === undefined) {
        f.name = 'Tom';
        f.name2 = 'John';
        f.extend = function(args) {
            if (Array.isArray(args) || (args !== null && typeof args === 'object')) {
                for (i in args) {
                    this[i] = args[i];
                }
            }
            return this;
        };
        return f;
    } else {
        return 'Beep';
    }
})();

console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);
console.log('doSomething() : ' + doSomething());
console.log('doSomething.name : ' + doSomething.name);
console.log('doSomething.name2 : ' + doSomething.name2);

出力:

doSomething.name : f
doSomething.name2 : John
doSomething() : Beep
doSomething.name : f
doSomething.name2 : John 

関数を1回実行し、そのプロパティの1つが設定されているかどうかをテストします。設定されていない場合は、プロパティを設定して自分自身を返します。設定されている場合、関数を実行します。プロパティの1つとして「拡張」関数を含める場合、後でそれを実行して新しいプロパティを追加できます。


独自のカスタムプロパティをオブジェクトに追加する

これらすべてのオプションにもかかわらず、関数にプロパティを追加しないことをお勧めします。オブジェクトにプロパティを追加することをお勧めします!

個人的には、次の構文を持つシングルトンクラスを好みます。

var keyValueStore = (function() {
    return {
        'data' : {},
        'get' : function(key) { return keyValueStore.data[key]; },
        'set' : function(key, value) { keyValueStore.data[key] = value; },
        'delete' : function(key) { delete keyValueStore.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in keyValueStore.data) l++;
            return l;
        }
    }
})();

この構文の利点は、パブリック変数とプライベート変数の両方を使用できることです。たとえば、これは 'data'変数をprivateにする方法です。

var keyValueStore = (function() {
    var data = {};

    return {
        'get' : function(key) { return data[key]; },
        'set' : function(key, value) { data[key] = value; },
        'delete' : function(key) { delete data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in data) l++;
            return l;
        }
    }
})();

しかし、複数のデータストアインスタンスが必要ですか?問題ない!

var keyValueStore = (function() {
    var count = -1;

    return (function kvs() {
        count++; 
        return {
            'data' : {},
            'create' : function() { return new kvs(); },
            'count' : function() { return count; },
            'get' : function(key) { return this.data[key]; },
            'set' : function(key, value) { this.data[key] = value; },
            'delete' : function(key) { delete this.data[key]; },
            'getLength' : function() {
                var l = 0;
                for (p in this.data) l++;
                return l;
            }
        }
    })();
})();

最後に、インスタンスとシングルトンのプロパティを分離し、インスタンスのパブリックメソッドにプロトタイプを使用できます。その結果、次の構文になります。

var keyValueStore = (function() {
    var count = 0; // Singleton private properties

    var kvs = function() {
        count++; // Instance private properties
        this.data = {};  // Instance public properties
    };

    kvs.prototype = { // Instance public properties
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

この構文を使用すると、次のことができます。

  • オブジェクトの複数のインスタンス
  • プライベート変数
  • クラス変数

このように使用します:

kvs = keyValueStore.create();
kvs.set('Tom', "Baker");
kvs.set('Daisy', "Hostess");
var profession_of_daisy = kvs.get('Daisy');
kvs.delete('Daisy');
console.log(keyValueStore.count());
95
John Slegers

ここでは「ネクロマンシング」ですが、すべての素晴らしい質問には簡単な答えが必要だと思います。

はいおよびはい*

プロパティを関数にアタッチすることにより、スコープをクリーンアップし、読みやすさを向上させ、論理的結合を追加します。追加の利点は、関数と変数の関係を文書化することです。これは優れた設計だと思います。スコープに変数を追加するよりもはるかに優れています some examples of attaching properties to instances of functions

こことここでいくつかの楽しい例を作成しました。 [〜#〜] here [〜#〜]AND HERE


* これはあまり頻繁には表示されないことに注意してください。ほとんどの開発者はおそらくそれが可能であることに気づいていないでしょう。一部の人々はパフォーマンスのあらゆる低下に夢中です... 「JavaScriptエンジンはオブジェクトの「形状」に基づいて最適化します...」 何とか何とか... utオブジェクトに対して持っているルールに従うことができ、うまくいくでしょう。

17
Shanimal

関数にプロパティをアタッチすることは、() operatorをオーバーロードする美しい(ほぼ緩慢/ハックっぽい)方法で、通常は ファンクターを実装するために使用されます :本当に重要な仕事を1つ持つオブジェクトタイプと、他のすべての機能(ある場合)は、単なるヘルパーです。これらのファンクターは、基本的に、状態がパブリックである「ステートフル」関数として解釈することもできます(たとえば、ほとんどのインライン関数には、プライベートステート、つまりローカルスコープからのステートがあります)。

このJSFiddle は、追加のユーティリティでtranslator関数のカスタムプロパティを持つ関数を使用する方法を示します。

/**
 * Creates a new translator function with some utility methods attached to it.
 */
var createTranslator = function(dict) {
    var translator = function(Word) {
        return dict[Word];
    };

    translator.isWordDefined = function(Word) {
        return dict.hasOwnProperty(Word);
    };

    // Add more utilities to translator here...

    return translator;
};


// create dictionary
var en2deDictionary = {
    'banana': 'Banane',
    'Apple': 'Apfel'
};

// simple use case:
var translator = createTranslator(en2deDictionary);
var pre = $('<pre>');
$("body").append(pre);

pre.append(translator('banana') + '\n');
pre.append(translator('Apple') + '\n');
pre.append(translator.isWordDefined('w00t') + '\n');

ご覧のとおり、これは翻訳を唯一の目的とする翻訳者に最適です。もちろん、これらのタイプのオブジェクトにはもっと多くの例がありますが、古典的なUserAnimalCarなど、多様な機能を持つタイプほど一般的ではありませんなどのタイプ。これらの種類には、ごくまれにカスタムプロパティを追加したいだけです。通常、これらをより完全なクラスとして定義し、thisおよびprototypeを介してそれらのパブリックプロパティに到達できるようにします。

2
Domi

私はこれに何年も遅れていることに気付きますが、この例を追加すると思った-requirejsはdefine()関数に「AMD」というプロパティを設定します。これはUMDパターンが定義を検出するために使用するので非常に便利です()スコープ内にある関数は、実際にはAMD define()関数です。

RequireJSソース: http://requirejs.org/docs/release/2.1.9/comments/require.js

この使用法を示すUMDパターン: https://github.com/umdjs/umd/blob/master/amdWeb.js

1
user456176

これは、複数の回答を得ることができる難しい質問であることに同意するため、別の例を作成することを好みます。

ジェネレーターによって生成されたJavaScript Arrayがあるとします:

var arr = [...new Array(10).keys()];

あれは

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

次に、これを新しい配列にマッピングします。同じ長さで、何らかの関数を適用します。したがって、ネイティブのmap関数プロパティを使用できます。

arr = arr.map((value,index) => ++value)

value=value+1およびreturnなので、配列は次のようになります

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

OK、JavaScript Objectのようになりました

var obj=new Object()

それは前の配列のように定義されました(なんらかの理由で):

arr.forEach((value,index) => obj[value]=value)

つまり.

{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}

この時点では、mapに対して定義されていないため、同じObjectメソッドを適用できません。したがって、prototypeの新しいObjectとして定義する必要があります。 :

Object.defineProperty(Object.prototype, 'mapObject', {
      value: function(f, ctx) {
          ctx = ctx || this;
          var self = this, result = {};
          Object.keys(self).forEach(function(k) {
              result[k] = f.call(ctx, self[k], k, self);
          });
          return result;
      }
    });

この時点で、以前の配列のようにできました:

obj=obj.mapObject((value,key) => ++value )

私たちが持っているように:

{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}

値のみを更新したことがわかります。

[...Object.keys(obj)]
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

そして、出力配列に戻すことができます。

[...Object.keys(obj).map(k=>obj[k])]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

ここに仕事があります:

// Array.map
var arr = [...new Array(10).keys()];
console.log("array", arr)
arr = arr.map((value, index) => ++value)
console.log("mapped array", arr)
// new property
Object.defineProperty(Object.prototype, 'mapObject', {
  value: function(f, ctx) {
    ctx = ctx || this;
    var self = this,
      result = {};
    Object.keys(self).forEach(function(k) {
      result[k] = f.call(ctx, self[k], k, self);
    });
    return result;
  }
});

// Object.mapObject
var obj = new Object()
arr = [...new Array(10).keys()];
arr.forEach((value, index) => obj[value] = value)
console.log("object", obj)
obj = obj.mapObject((value, key) => ++value)
console.log("mapped object", obj)
console.log("object keys", [...Object.keys(obj)])
console.log("object values", [...Object.keys(obj).map(k => obj[k])])
0
loretoparisi

関数オブジェクトにプロパティまたはメソッドを追加することは完全に受け入れられます。かなり頻繁に行われます。 jQuery/$オブジェクトはこの例です。かなりの数のメソッドが付加された関数です。

プロパティがコンストラクタに追加されると、それらは「静的」プロパティと呼ばれ、クラスのインスタンスなしで呼び出すことができます。例えばObject.create。

私はコメントを書くのに十分な担当者がいないので、ここで言います:一般に組み込みオブジェクトのプロトタイプを拡張することは、特にコードが他の人のコードと遊ぶ必要がある場合、悪い習慣だと考えられました。追跡が困難な予測不可能な結果が生じる可能性があります。

0
AdamW

関数にカスタムプロパティを追加する場合は、それらのプロパティをFunction.prototypeに追加するだけです。例えば:

Function.prototype.SomeNewProperty = function () {//Do something awesome here...}
0
Nitij

John Slegersの素晴らしい回答に追加可能

ジョン・スレガーズの後:

方法2:関数を定義した後にプロパティを追加する

Way 2.5の追加

function doSomething() {
    doSomething.prop = "Bundy";
    doSomething.doSomethingElse = function() {
        alert("Why Hello There! ;)");

    };

    let num = 3;
    while(num > 0) {
        alert(num);
        num--;  
    }
}

sayHi();
sayHi.doSomethingElse();
alert(doSomething.prop);

var ref = doSomething;

ref();
ref.doSomethingElse();
alert(ref.prop);

完全を期すために、「変数」プロパティと関数プロパティの両方を、関数宣言で直接入力します。したがって、「切断」されないようにします。関数の内部のデフォルトの動作(単純なループ)をそのままにして、機能することを示します。いや?

0
brat