Javascriptで関数のオーバーロードを偽造する最良の方法は何ですか?
他の言語のようにJavascriptで関数をオーバーロードすることはできないことを私は知っています。foo(x)
2つの用途があり、foo(x,y,z)
どちらが最良/推奨される方法である関数が必要な場合:
- そもそも別の名前を使う
- 次のようなオプションの引数を使用する
y = y || 'default'
- 引数の数を使用する
- 引数の種類を確認する
- またはどのように?
Javascriptで関数のオーバーロードを偽造する最良の方法は何ですか?
他の言語のようにJavascriptで関数をオーバーロードすることはできないことを私は知っています。foo(x)
2つの用途があり、foo(x,y,z)
どちらが最良/推奨される方法である関数が必要な場合:
y = y || 'default'
パラメータで関数のオーバーロードを行う最善の方法は、引数の長さや型をチェックしないことです。タイプをチェックすると、コードが遅くなるだけで、配列、null、オブジェクトなどを楽しむことができます。
ほとんどの開発者は、メソッドの最後の引数としてオブジェクトを追加します。このオブジェクトは何でも保持できます。
function foo(a, b, opts) {
// ...
if (opts['test']) { } //if test param exists, do something..
}
foo(1, 2, {"method":"add"});
foo(3, 4, {"test":"equals", "bar":"tree"});
その後、メソッドで必要に応じて処理できます。【スイッチ、if-elseなど】
私はよくこれを行います:
C#:
public string CatStrings(string p1) {return p1;}
public string CatStrings(string p1, int p2) {return p1+p2.ToString();}
public string CatStrings(string p1, int p2, bool p3) {return p1+p2.ToString()+p3.ToString();}
CatStrings("one"); // result = one
CatStrings("one",2); // result = one2
CatStrings("one",2,true); // result = one2true
同等の JavaScript:
function CatStrings(p1, p2, p3)
{
var s = p1;
if(typeof p2 !== "undefined") {s += p2;}
if(typeof p3 !== "undefined") {s += p3;}
return s;
};
CatStrings("one"); // result = one
CatStrings("one",2); // result = one2
CatStrings("one",2,true); // result = one2true
この特定の例は、実際には C# よりも JavaScript の方が洗練されています。指定されていないパラメーターは JavaScript では「未定義」であり、if ステートメントで false と評価されます。ただし、関数定義は、p2 と p3 がオプションであるという情報を伝えていません。多くのオーバーロードが必要な場合、jQuery は、jQuery.ajax(options) などのオブジェクトをパラメーターとして使用することを決定しました。これがオーバーロードに対する最も強力で明確に文書化可能なアプローチであることに同意しますが、1 つまたは 2 つ以上の簡単なオプションのパラメーターが必要になることはめったにありません。
編集: Ian の提案に従って IF テストを変更
JavaScriptには、任意のタイプの任意の数のパラメーターを渡すことができるため、実際の関数のオーバーロードはありません。関数内で、渡された引数の数とそれらの型を確認する必要があります。
正解は、JAVASCRIPT にはオーバーロードがありません。
関数内のチェック/切り替えはオーバーロードではありません。
オーバーロードの概念: 一部のプログラミング言語では、関数のオーバーロードまたはメソッドのオーバーロードは、実装が異なる同じ名前の複数のメソッドを作成する機能です。オーバーロードされた関数を呼び出すと、呼び出しのコンテキストに適したその関数の特定の実装が実行され、コンテキストに応じて 1 つの関数呼び出しでさまざまなタスクを実行できます。
たとえば、doTask() と doTask(object O) はオーバーロードされたメソッドです。後者を呼び出すには、オブジェクトをパラメーターとして渡す必要がありますが、前者はパラメーターを必要とせず、空のパラメーター フィールドで呼び出されます。一般的なエラーは、2 番目のメソッドのオブジェクトに既定値を割り当てることです。これは、コンパイラが 2 つのメソッドのどちらを使用するかを認識できないため、あいまいな呼び出しエラーになります。
https://en.wikipedia.org/wiki/Function_overloading
提案された実装はどれもすばらしいものですが、正直なところ、JavaScript のネイティブ実装はありません。
これにより適切にアプローチするには、次の 2 つの方法があります。
多くの柔軟性を残したい場合は、辞書(連想配列)を渡します
オブジェクトを引数として取り、プロトタイプベースの継承を使用して柔軟性を追加します。
以下に示すように、パラメーター型を使用して実際のメソッドのオーバーロードを許可するアプローチを次に示します。
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
編集 (2018) : これは 2011 年に書かれて以来、直接メソッド呼び出しの速度は大幅に向上しましたが、オーバーロードされたメソッドの速度は向上していません。
これは私が推奨するアプローチではありませんが、この種の問題をどのように解決できるかを考える価値のある思考練習です。
これは、さまざまなアプローチのベンチマークです - https://jsperf.com/function-overloading。16.0(beta)の時点でGoogle Chrome の V8では、 (型を考慮して) 関数のオーバーロードが約13 倍遅くなる可能性があることを示しています。
オブジェクトを渡すだけでなく({x: 0, y: 0}
つまり たとえば、Vector.AddVector(vector)、Vector.AddIntegers(x、y、z、...)、Vector.AddArray(integerArray) などです。命名のインスピレーションについては、OpenGL などの C ライブラリを参照できます。
編集'param' in arg
: オブジェクトを渡し、 と の両方を使用してオブジェクトをテストするためのベンチマークを追加しました。arg.hasOwnProperty('param')
関数のオーバーロードは、オブジェクトを渡してプロパティをチェックするよりもはるかに高速です (少なくともこのベンチマークでは)。
設計の観点から、関数のオーバーロードは、オーバーロードされたパラメーターが同じアクションに対応する場合にのみ有効または論理的です。したがって、特定の詳細のみに関係する基本的な方法が存在する必要があるのは当然のことです。そうしないと、不適切な設計の選択を示している可能性があります。したがって、データをそれぞれのオブジェクトに変換することで、関数のオーバーロードの使用を解決することもできます。もちろん、名前を印刷するだけであれば、複雑な設計を行う必要はないため、問題の範囲を考慮する必要がありますが、フレームワークやライブラリの設計では、そのような考えが正当化されます。
私の例は、Rectangle の実装からのものです。したがって、Dimension と Point について言及しています。おそらく Rectangle はandプロトタイプにGetRectangle()
メソッドを追加することができ、関数のオーバーロードの問題は解決されます。そして、プリミティブはどうですか?さて、引数の長さがあります。オブジェクトにはメソッドがあるため、これは有効なテストです。Dimension
Point
GetRectangle()
function Dimension() {}
function Point() {}
var Util = {};
Util.Redirect = function (args, func) {
'use strict';
var REDIRECT_ARGUMENT_COUNT = 2;
if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
return null;
}
for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
var argsIndex = i-REDIRECT_ARGUMENT_COUNT;
var currentArgument = args[argsIndex];
var currentType = arguments[i];
if(typeof(currentType) === 'object') {
currentType = currentType.constructor;
}
if(typeof(currentType) === 'number') {
currentType = 'number';
}
if(typeof(currentType) === 'string' && currentType === '') {
currentType = 'string';
}
if(typeof(currentType) === 'function') {
if(!(currentArgument instanceof currentType)) {
return null;
}
} else {
if(typeof(currentArgument) !== currentType) {
return null;
}
}
}
return [func.apply(this, args)];
}
function FuncPoint(point) {}
function FuncDimension(dimension) {}
function FuncDimensionPoint(dimension, point) {}
function FuncXYWidthHeight(x, y, width, height) { }
function Func() {
Util.Redirect(arguments, FuncPoint, Point);
Util.Redirect(arguments, FuncDimension, Dimension);
Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
}
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
最善の方法は、実際には関数と引数に依存します。それぞれのオプションは、さまざまな状況で優れたアイデアです。通常、いずれかが機能するまで、次の順序でこれらを試します。
y = y || のようなオプションの引数を使用する 'デフォルト'。これはできれば便利ですが、実際には常に機能するとは限りません。たとえば、0/null/undefined が有効な引数である場合などです。
引数の数を使用します。最後のオプションに似ていますが、#1 が機能しない場合に機能する可能性があります。
引数の型をチェックしています。これは、引数の数が同じ場合に機能します。タイプを確実に判別できない場合は、別の名前を使用する必要がある場合があります。
Using different names in the first place. You may need to do this if the other options won't work, aren't practical, or for consistency with other related functions.
foo(x) と foo(x,y,z) を 2 つ使用する関数が必要な場合、どちらが最適な方法でしょうか?
問題は、JavaScript がメソッドのオーバーロードをネイティブにサポートしていないことです。そのため、同じ名前の 2 つ以上の関数を認識または解析した場合、最後に定義された関数を考慮して、以前の関数を上書きします。
ほとんどの場合に適していると思う方法の1つは次のとおりです-
メソッドがあるとしましょう
function foo(x)
{
}
JavaScript では不可能なメソッドをオーバーロードする代わりに、新しいメソッドを定義できます。
fooNew(x,y,z)
{
}
次に、最初の関数を次のように変更します -
function foo(arguments)
{
if(arguments.length==2)
{
return fooNew(arguments[0], arguments[1]);
}
}
そのようなオーバーロードされたメソッドが多数ある場合は、ステートメントswitch
だけでなく使用を検討してください。if-else
ベストプラクティスについてはよくわかりませんが、次のようにします。
/*
* Object Constructor
*/
var foo = function(x) {
this.x = x;
};
/*
* Object Protoype
*/
foo.prototype = {
/*
* f is the name that is going to be used to call the various overloaded versions
*/
f: function() {
/*
* Save 'this' in order to use it inside the overloaded functions
* because there 'this' has a different meaning.
*/
var that = this;
/*
* Define three overloaded functions
*/
var f1 = function(arg1) {
console.log("f1 called with " + arg1);
return arg1 + that.x;
}
var f2 = function(arg1, arg2) {
console.log("f2 called with " + arg1 + " and " + arg2);
return arg1 + arg2 + that.x;
}
var f3 = function(arg1) {
console.log("f3 called with [" + arg1[0] + ", " + arg1[1] + "]");
return arg1[0] + arg1[1];
}
/*
* Use the arguments array-like object to decide which function to execute when calling f(...)
*/
if (arguments.length === 1 && !Array.isArray(arguments[0])) {
return f1(arguments[0]);
} else if (arguments.length === 2) {
return f2(arguments[0], arguments[1]);
} else if (arguments.length === 1 && Array.isArray(arguments[0])) {
return f3(arguments[0]);
}
}
}
/*
* Instantiate an object
*/
var obj = new foo("z");
/*
* Call the overloaded functions using f(...)
*/
console.log(obj.f("x")); // executes f1, returns "xz"
console.log(obj.f("x", "y")); // executes f2, returns "xyz"
console.log(obj.f(["x", "y"])); // executes f3, returns "xy"
私はちょうどこれを試しました、多分それはあなたのニーズに合っています。引数の数に応じて、別の関数にアクセスできます。最初に呼び出すときに初期化します。関数マップはクロージャに隠されています。
TEST = {};
TEST.multiFn = function(){
// function map for our overloads
var fnMap = {};
fnMap[0] = function(){
console.log("nothing here");
return this; // support chaining
}
fnMap[1] = function(arg1){
// CODE here...
console.log("1 arg: "+arg1);
return this;
};
fnMap[2] = function(arg1, arg2){
// CODE here...
console.log("2 args: "+arg1+", "+arg2);
return this;
};
fnMap[3] = function(arg1,arg2,arg3){
// CODE here...
console.log("3 args: "+arg1+", "+arg2+", "+arg3);
return this;
};
console.log("multiFn is now initialized");
// redefine the function using the fnMap in the closure
this.multiFn = function(){
fnMap[arguments.length].apply(this, arguments);
return this;
};
// call the function since this code will only run once
this.multiFn.apply(this, arguments);
return this;
};
試して。
TEST.multiFn("0")
.multiFn()
.multiFn("0","1","2");
JavaScript でオーバーロードを機能させる方法はありません。そこで、typeof()
オーバーロードを偽装するには、複数の関数ではなく、メソッドごとに次のようにすることをお勧めします。
function multiTypeFunc(param)
{
if(typeof param == 'string') {
alert("I got a string type parameter!!");
}else if(typeof param == 'number') {
alert("I got a number type parameter!!");
}else if(typeof param == 'boolean') {
alert("I got a boolean type parameter!!");
}else if(typeof param == 'object') {
alert("I got a object type parameter!!");
}else{
alert("error : the parameter is undefined or null!!");
}
}
幸運を!
これにアプローチするもう 1 つの方法は、特別な変数argumentsを使用することです。これは実装です。
function sum() {
var x = 0;
for (var i = 0; i < arguments.length; ++i) {
x += arguments[i];
}
return x;
}
したがって、このコードを次のように変更できます。
function sum(){
var s = 0;
if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
return s;
}
これをチェックしてください。とてもクールです。 http://ejohn.org/blog/javascript-method-overloading/ Javascript をトリックして、次のような呼び出しを実行できるようにします。
var users = new Users();
users.find(); // Finds all
users.find("John"); // Finds users by name
users.find("John", "Resig"); // Finds users by first and last name
#Forwarding Pattern => JS オーバーロードのベスト プラクティス 名前が 3 番目と 4 番目のポイントから構築された別の関数に転送します。
- 引数の数を使用する
- 引数の型のチェック
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)
#あなたの場合のアプリケーション:
function foo(...args){
return window['foo_' + args.length+'_'+Array.from(args).map((arg)=>typeof arg).join('_')](...args);
}
//------Assuming that `x` , `y` and `z` are String when calling `foo` .
/**-- for : foo(x)*/
function foo_1_string(){
}
/**-- for : foo(x,y,z) ---*/
function foo_3_string_string_string(){
}
#その他の複雑なサンプル :
function foo(...args){
return window['foo_'+args.length+'_'+Array.from(args).map((arg)=>typeof arg).join('_')](...args);
}
/** one argument & this argument is string */
function foo_1_string(){
}
//------------
/** one argument & this argument is object */
function foo_1_object(){
}
//----------
/** two arguments & those arguments are both string */
function foo_2_string_string(){
}
//--------
/** Three arguments & those arguments are : id(number),name(string), callback(function) */
function foo_3_number_string_function(){
let args=arguments;
new Person(args[0],args[1]).onReady(args[3]);
}
//--- And so on ....
John Resig の「addMethod」を使用できます。このメソッドを使用すると、引数の数に基づいてメソッドを「オーバーロード」できます。
// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
var old = object[ name ];
object[ name ] = function(){
if ( fn.length == arguments.length )
return fn.apply( this, arguments );
else if ( typeof old == 'function' )
return old.apply( this, arguments );
};
}
また、キャッシングを使用して関数のバリエーションを保持する、この方法の代替手段も作成しました。違いはここで説明されています
// addMethod - By Stavros Ioannidis
function addMethod(obj, name, fn) {
obj[name] = obj[name] || function() {
// get the cached method with arguments.length arguments
var method = obj[name].cache[arguments.length];
// if method exists call it
if ( !! method)
return method.apply(this, arguments);
else throw new Error("Wrong number of arguments");
};
// initialize obj[name].cache
obj[name].cache = obj[name].cache || {};
// Check if a method with the same number of arguments exists
if ( !! obj[name].cache[fn.length])
throw new Error("Cannot define multiple '" + name +
"' methods with the same number of arguments!");
// cache the method with fn.length arguments
obj[name].cache[fn.length] = function() {
return fn.apply(this, arguments);
};
}
この投稿にはすでに多くの異なるソリューションが含まれているため、別のソリューションを投稿すると思いました。
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function overload() {
var functions = arguments;
var nroffunctionsarguments = [arguments.length];
for (var i = 0; i < arguments.length; i++) {
nroffunctionsarguments[i] = arguments[i].length;
}
var unique = nroffunctionsarguments.filter(onlyUnique);
if (unique.length === arguments.length) {
return function () {
var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);
return functions[indexoffunction].apply(this, arguments);
}
}
else throw new TypeError("There are multiple functions with the same number of parameters");
}
これは、以下に示すように使用できます。
var createVector = overload(
function (length) {
return { x: length / 1.414, y: length / 1.414 };
},
function (a, b) {
return { x: a, y: b };
},
function (a, b,c) {
return { x: a, y: b, z:c};
}
);
console.log(createVector(3, 4));
console.log(createVector(3, 4,5));
console.log(createVector(7.07));
このソリューションは完璧ではありませんが、それがどのように行われるかを示したいだけです。
関数のオーバーロードとは、実装が異なる同じ名前の複数の関数を作成するプログラミング言語の機能です。オーバーロードされた関数が呼び出されると、呼び出しのコンテキストに適したその関数の特定の実装で関数が実行されます。このコンテキストは通常、受け取る引数の量であり、1 つの関数呼び出しがコンテキストに応じて異なる動作をすることを可能にします。
Javascriptには組み込み関数のオーバーロードがありません。ただし、この動作はさまざまな方法でエミュレートできます。便利でシンプルなものを次に示します。
function sayHi(a, b) {
console.log('hi there ' + a);
if (b) { console.log('and ' + b) } // if the parameter is present, execute the block
}
sayHi('Frank', 'Willem');
取得する引数の数がわからないシナリオでは、 3 つのドットである残りの演算子...
を使用できます。残りの引数を配列に変換します。ただし、ブラウザの互換性に注意してください。次に例を示します。
function foo (a, ...b) {
console.log(b);
}
foo(1,2,3,4);
foo(1,2);
デフォルト パラメーターは過負荷ではありませんが、この領域で開発者が直面する問題のいくつかを解決する可能性があります。入力は順序によって厳密に決定されます。古典的なオーバーロードのように、必要に応じて順序を変更することはできません。
function transformer(
firstNumber = 1,
secondNumber = new Date().getFullYear(),
transform = function multiply(firstNumber, secondNumber) {
return firstNumber * secondNumber;
}
) {
return transform(firstNumber, secondNumber);
}
console.info(transformer());
console.info(transformer(8));
console.info(transformer(2, 6));
console.info(transformer(undefined, 65));
function add(firstNumber, secondNumber) {
return firstNumber + secondNumber;
}
console.info(transformer(undefined, undefined, add));
console.info(transformer(3, undefined, add));
結果 (2020 年):
2020
16160
12
65
2021
2023
詳細: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters
この問題を解決するためにover.jsを作成したのは、非常に洗練された方法です。できるよ:
var obj = {
/**
* Says something in the console.
*
* say(msg) - Says something once.
* say(msg, times) - Says something many times.
*/
say: Over(
function(msg$string){
console.info(msg$string);
},
function(msg$string, times$number){
for (var i = 0; i < times$number; i++) this.say(msg$string);
}
)
};
これは古い質問ですが、別のエントリが必要だと思います (誰も読んでいないと思いますが)。即時呼び出し関数式 (IIFE) をクロージャーやインライン関数と組み合わせて使用すると、関数のオーバーロードが可能になります。次の (不自然な) 例を考えてみましょう。
var foo;
// original 'foo' definition
foo = function(a) {
console.log("a: " + a);
}
// define 'foo' to accept two arguments
foo = (function() {
// store a reference to the previous definition of 'foo'
var old = foo;
// use inline function so that you can refer to it internally
return function newFoo(a,b) {
// check that the arguments.length == the number of arguments
// defined for 'newFoo'
if (arguments.length == newFoo.length) {
console.log("a: " + a);
console.log("b: " + b);
// else if 'old' is a function, apply it to the arguments
} else if (({}).toString.call(old) === '[object Function]') {
old.apply(null, arguments);
}
}
})();
foo(1);
> a: 1
foo(1,2);
> a: 1
> b: 2
foo(1,2,3)
> a: 1
つまり、IIFE を使用するとローカル スコープが作成old
され、関数の初期定義への参照を格納するプライベート変数を定義できるようになりますfoo
。newFoo
次に、この関数は、2 つの引数が渡された場合に 2 つの引数の両方の内容をログに記録するインライン関数を返し、a
またはif の場合に関数をb
呼び出します。このパターンを何度でも繰り返して、1 つの変数に複数の異なる関数定義を与えることができます。old
arguments.length !== 2
オーバーロードのようなアプローチの有用な例を共有したいと思います。
function Clear(control)
{
var o = typeof control !== "undefined" ? control : document.body;
var children = o.childNodes;
while (o.childNodes.length > 0)
o.removeChild(o.firstChild);
}
使用法: Clear(); // すべてのドキュメントをクリアします
クリア(myDiv); // myDiv が参照するパネルをクリアします
JavaScript は型指定されていない言語であり、パラメーターの数に関してメソッド/関数をオーバーロードすることだけが理にかなっていると思います。したがって、パラメーターが定義されているかどうかを確認することをお勧めします。
myFunction = function(a, b, c) {
if (b === undefined && c === undefined ){
// do x...
}
else {
// do y...
}
};
2017 年 7 月現在、次の手法が一般的です。関数内で型チェックも実行できることに注意してください。
function f(...rest){ // rest is an array
console.log(rest.length);
for (v of rest) if (typeof(v)=="number")console.log(v);
}
f(1,2,3); // 3 1 2 3
だから、javascript忍者の秘密で見つけたこのやり方が本当に好きだった
function addMethod(object,name,fn){
var old = object[name];
object[name] = function(){
if (fn.length == arguments.length){
return fn.apply(this,arguments);
} else if(typeof old == 'function'){
return old.apply(this,arguments);
}
}
}
次に addMethod を使用して、オーバーロードされた関数を任意のオブジェクトに追加します。私にとってこのコードの主な混乱は、 fn.length == arguments.length の使用でした-これは、 fn.length が予想されるパラメーターの数であるのに対し、arguments.length は実際に呼び出されるパラメーターの数であるため機能します関数。無名関数に引数がない理由は、javascript で任意の数の引数を渡すことができ、言語が寛容であるためです。
どこでも使用できるため、これが気に入りました。この関数を作成し、必要なコードベースでメソッドを使用するだけです.
また、複雑なコードを書き始めると読みにくくなる、とてつもなく大きな if/switch ステートメントを使用することも回避されます (受け入れられた答えはこれになります)。
短所に関しては、コードは最初は少しあいまいだと思います...しかし、他のものはわかりませんか?
クラスのようなコード機能を Javascript に提供するライブラリに取り組んでいます。現在、コンストラクター、継承、パラメーターの数とパラメーターの種類、ミックスイン、静的プロパティ、およびシングルトンによるメソッドのオーバーロードをサポートしています。
そのライブラリを使用したメソッドのオーバーロードの例を参照してください。
eutsiv.define('My.Class', {
constructor: function() {
this.y = 2;
},
x: 3,
sum: function() {
return this.x + this.y;
},
overloads: {
value: [
function() { return this.x + ', ' + this.y },
function(p1) { this.x = p1; },
function(p1, p2) { this.x = p1; this.y = p2; } // will set x and y
]
}
});
var test = new My.Class({ x: 5 }); // create the object
test.value(); // will return '5, 2'
test.sum(); // will return 7
test.value(13); // will set x to 13
test.value(); // will return '13, 2'
test.sum(); // will return 15
test.value(10, 20); // will set x to 10 and y to 20
test.value(); // will return '10, 20'
test.sum(); // will return 30
フィードバック、バグ修正、ドキュメント、テストの改善は大歓迎です!