まず、標準の関数プロパティ(引数、名前、呼び出し元、長さ)は上書きできないことを理解することが重要です。したがって、その名前のプロパティを追加することを忘れてください。
関数に独自のカスタムプロパティを追加することは、すべてのブラウザで機能するさまざまな方法で行うことができます。
独自のカスタムプロパティを関数に追加する
方法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());