66

私のキーワードに関連する他の多くの問題が存在するため、適切な答えを探すのは難しいことがわかったので、ここでこれを尋ねます。

ご存知のように、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. オブジェクトに新しいプロパティを追加することによってオブジェクトを変更するこの種の良い習慣はありますか?
4

10 に答える 10

131

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

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


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

方法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:一般的な「extend」関数を作成します:

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のアプローチが可能になり、オブジェクトまたは任意のプロジェクトを他のオブジェクトに追加できます。


方法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」メソッドがアタッチされた関数を生成できます。


方法7:「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 

この手法の大きな利点は、関数に新しいプロパティを非常に簡単に追加できることと、完全にOOであるだけでなくDRYも可能になることです。また、それはかなりメモリフレンドリーです。ただし、欠点は、将来性があまりないことです。将来のブラウザが関数プロトタイプにネイティブの「extend」関数を追加する場合、これによりコードが破損する可能性があります。


方法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つとして「extend」関数を含めると、後でそれを実行して新しいプロパティを追加できます。


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

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

個人的には、次の構文のシングルトンクラスが好きです。

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

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

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());
于 2013-12-22T21:01:08.390 に答える
29

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

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

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

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

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

それはあなたのオブジェクトです、あなたは好きなプロパティを追加することができます。オブジェクトの要点は、操作可能なプロパティがあることです。プロパティやメソッドの追加、削除、更新など、オブジェクトの変更を伴わないオブジェクトの使用方法を実際に想像することはできません。

そうは言っても、私には関数にプロパティを追加することは実際には意味がありません。オブジェクトmyMethodに他のプロパティを追加するのがより一般的です(関数が正しく呼び出された場合、を介して他のプロパティにアクセスできます。キーワード)。somethingmyMethodsomethingthis

関数をコンストラクターとして使用している場合は、通常、関連するプロトタイプにメソッドを追加し、各インスタンスに(メソッド以外の)プロパティを追加するのが理にかなっていますが、必要に応じて、どちらかまたは両方を行うことができます。(「メソッド」は本質的に、関数を参照するための単なるプロパティであることに注意してください。)

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

MDNでこれらのようないくつかの記事を読むことから利益を得るかもしれません:

于 2011-12-21T11:08:37.023 に答える
19

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

はいはい*

プロパティを関数にアタッチすることで、スコープをクリーンアップし、読みやすさを向上させ、論理的なまとまりを追加します。追加の利点は、関数と変数の間の関係を文書化することです。スコープに変数を追加するよりもはるかに優れた設計だと思います 関数のインスタンスにプロパティをアタッチするいくつかの例

こことここにいくつかの楽しい例を作成しました。 ここ とここ


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

于 2014-07-18T05:35:20.007 に答える
4

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

このJSFiddletranslatorは、追加のユーティリティを備えた関数のカスタムプロパティを備えた関数を使用する方法を示しています。

/**
 * 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');

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

于 2014-05-09T08:22:46.307 に答える
2

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

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

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

于 2013-11-29T03:49:16.990 に答える
0

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

Function.prototype.SomeNewProperty = function () {//Do something awesome here...}
于 2015-02-22T17:03:58.990 に答える
0

プロパティまたはメソッドを関数オブジェクトに追加することはまったく問題ありません。それはかなり頻繁に行われます。jQuery/$オブジェクトはその一例です。かなりの数のメソッドが付加された関数です。

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

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

于 2016-11-29T18:39:52.767 に答える
0

これは複数の答えを持つ可能性のある難しい質問であることに同意するので、別の例を作成することを好みます。

Arrayジェネレーターによって入力されたJavaScriptがあるとします。

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

あれは

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

次に、これを新しい配列にマップします。同じ長さで、関数を適用して、ネイティブmap関数プロパティを使用できるようにします。

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

avalue=value+1とreturnを実行したので、配列は次のようになります。

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

Objectさて、今のようなJavaScriptを持っているはずです

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の新しいメソッドとして定義する必要があります。prototypeObject

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])])

于 2017-10-27T08:40:32.727 に答える
0

ジョン・スレガーズへの追加の可能性素晴らしい答え

ジョン・スレガーズの後、それは可能ではありません:

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

方法の追加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);

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

于 2018-11-06T00:35:14.367 に答える
0

test = (function() {
  var a = function() {
    console.log("test is ok");
  };
  a.prop = "property is ok";
  a.method = function(x, y) {
    return x + y;
  }
  return a
})()

test();
console.log(test.prop);
console.log(test.method(3, 4));

または、ゲッターとセッターを使用する必要があります

var person = {
  firstName: 'Jimmy',
  lastName: 'Smith',
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  },
  set fullName(name) {
    var words = name.toString().split(' ');
    this.firstName = words[0] || '';
    this.lastName = words[1] || '';
  }
}
console.log(person.firstName);
console.log(person.lastName);
console.log(person.fullName);
person.fullName = "Tom Jones";
console.log(person.fullName);

于 2020-03-18T21:40:44.563 に答える