web-dev-qa-db-ja.com

JavaScriptで算術演算子をオーバーロードしますか?

これは、このJavaScriptの「クラス」定義を考えると、この質問の言い回しについて考えることができる最良の方法です。

var Quota = function(hours, minutes, seconds){
    if (arguments.length === 3) {
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;

        this.totalMilliseconds = Math.floor((hours * 3600000)) + Math.floor((minutes * 60000)) + Math.floor((seconds * 1000));
    }
    else if (arguments.length === 1) {
        this.totalMilliseconds = hours;

        this.hours = Math.floor(this.totalMilliseconds / 3600000);
        this.minutes = Math.floor((this.totalMilliseconds % 3600000) / 60000);
        this.seconds = Math.floor(((this.totalMilliseconds % 3600000) % 60000) / 1000);
    }

    this.padL = function(val){
        return (val.toString().length === 1) ? "0" + val : val;
    };

    this.toString = function(){
        return this.padL(this.hours) + ":" + this.padL(this.minutes) + ":" + this.padL(this.seconds);
    };

    this.valueOf = function(){
        return this.totalMilliseconds;
    };
};

および次のテストセットアップコード:

var q1 = new Quota(23, 58, 50);
var q2 = new Quota(0, 1, 0);
var q3 = new Quota(0, 0, 10);

console.log("Quota 01 is " + q1.toString());    // Prints "Quota 01 is 23:58:50"
console.log("Quota 02 is " + q2.toString());    // Prints "Quota 02 is 00:01:00"
console.log("Quota 03 is " + q3.toString());    // Prints "Quota 03 is 00:00:10"

次のように加算演算子を使用して、q4Quotaオブジェクトとして暗黙的に作成する方法はありますか...

var q4 = q1 + q2 + q3;
console.log("Quota 04 is " + q4.toString());    // Prints "Quota 04 is 86400000"

に頼るのではなく...

var q4 = new Quota(q1 + q2 + q3);
console.log("Quota 04 is " + q4.toString());    // Prints "Quota 04 is 24:00:00"

そうでない場合、算術演算子を使用してカスタム数値JavaScriptオブジェクトを作成可能にするためのこの領域のベストプラクティスの推奨事項は何ですか?

66
Peter McG

私が知っている限りでは、Javascript(少なくとも現在のように)は演算子のオーバーロードをサポートしていません。

私が提案できる最善の方法は、他のいくつかから新しいクォータオブジェクトを作成するためのクラスメソッドです。ここに私が意味することの簡単な例があります:

// define an example "class"
var NumClass = function(value){
    this.value = value;
}
NumClass.prototype.toInteger = function(){
    return this.value;
}

// Add a static method that creates a new object from several others
NumClass.createFromObjects = function(){
    var newValue = 0;
    for (var i=0; i<arguments.length; i++){
        newValue += arguments[i].toInteger();
    }
    return new this(newValue)
}

次のように使用します:

var n1 = new NumClass(1);
var n2 = new NumClass(2);
var n3 = new NumClass(3);

var combined = NumClass.createFromObjects(n1, n2, n3);
33
Dan

残念だけど違う。

フォールバックの場合、戻り値を配置した場合、メソッドチェーンを使用できます

var q4 = q1.plus(p2).plus(q3);
20
Justin Love

誰もが私の別の答えに賛成票を投じたため、実際に意図したとおりに機能する概念実証コードを投稿したかったのです。

これは、chromeおよびIEでテストされています。

//Operator Overloading

var myClass = function () {

//Privates

var intValue = Number(0),
    stringValue = String('');

//Publics
this.valueOf = function () {
    if (this instanceof myClass) return intValue;
    return stringValue;
}

this.cast = function (type, call) {
    if (!type) return;
    if (!call) return type.bind(this);
    return call.bind(new type(this)).call(this);
}

}

//Derived class
var anotherClass = function () {

//Store the base reference
this.constructor = myClass.apply(this);

var myString = 'Test',
    myInt = 1;

this.valueOf = function () {
    if (this instanceof myClass) return myInt;
    return myString;
}

}


//Tests

var test = new myClass(),
anotherTest = new anotherClass(),
composed = test + anotherTest,
yaComposed = test.cast(Number, function () {
    return this + anotherTest
}),
yaCComposed = anotherTest.cast(Number, function () {
    return this + test;
}),
t = test.cast(anotherClass, function () {
    return this + anotherTest
}),
tt = anotherTest.cast(myClass, function () {
    return this + test;
});

debugger;

誰かが技術的な説明をするほど親切なら、なぜこれが十分ではないのか私はそれを聞いてうれしいです!

14
Jay

2番目の提案:

var q4 = Quota.add(q1, q2, q3);
7
Justin Love

オブジェクトを暗黙的に整数または文字列に変換できます。

JavaScriptが数値または文字列を必要とする場合にのみ、オブジェクトは暗黙的に変換されます。前者の場合、変換には3つのステップが必要です。

1.- valueOf()を呼び出します。結果がプリミティブではない(オブジェクトではない)場合は、それを使用して数値に変換します。

2.-それ以外の場合は、toString()を呼び出します。結果がプリミティブである場合は、それを使用して数値に変換します。

3.-そうでない場合は、TypeErrorをスローします。ステップ1の例:

3 * { valueOf: function () { return 5 } }

JavaScriptが文字列に変換される場合、ステップ1と2が入れ替わります。toString()が最初に試行され、valueOf()が2番目に試行されます。

http://www.2ality.com/2013/04/quirk-implicit-conversion.html

5
Luis Naves

私は最近この記事に出会いました: http://www.2ality.com/2011/12/fake-operator-overloading.html

オブジェクトのvalueOfメソッドを再定義して、javascriptの演算子のオーバーロードなどを行う方法について説明します。操作対象のオブジェクトに対してのみミューテーター操作を実際に実行できるように思えるので、望んでいることはできません。それにも関わらず興味深い。

5
B T

Paper.jsは、たとえばポイントの追加( docs )でそれを行います:

var point = new Point(5, 10);
var result = point + 20;
console.log(result); // {x: 25, y: 30}

ただし、独自の カスタムスクリプトパーサー を使用して実行します。

4
Izhaki

JavaScriptで演算子のオーバーロードを行うスクリプトを作成しました。仕事をするのは簡単ではなかったので、いくつかの癖があります。ここでプロジェクトページから警告をクロスポストします。それ以外の場合は、下部にリンクがあります。

  • 計算結果は新しいオブジェクトに渡す必要があるため、(p1 + p2 + p3)の代わりに、新しいpoint(p1 + p2 + p3)を実行する必要があります(ユーザー定義オブジェクトの名前が「point」の場合)。

  • +、-、*、および/のみがサポートされ、5番目の算術演算子%はサポートされていません。文字列への強制( "" + p1)および比較(p1 == p2)は期待どおりに機能しません。 (p1.val == p2.val)のように、必要に応じてこれらの目的のために新しい関数を構築する必要があります。

  • 最後に、答えを計算するために必要な計算リソースは、項の数とともに二次的に増加します。したがって、デフォルトごとに1つの計算チェーンで許可されるのは6項のみです(ただし、これは増やすことができます)。それより長い計算チェーンの場合、次のように計算を分割します:new point(new point(p1 + p2 + p3 + p4 + p5 + p6)+ new point(p7 + p8 + p9 + p10 + p11 + p12))

Githubページ

3
Stuffe

なぜ人々がこの質問に「いいえ」で答え続けるのか分かりません!

あなたが理解するためにジョン・レジグである必要はない非常に小さなスクリプトで概要を説明する方法が絶対にあります...

その前に、JavaScriptでコンストラクターが機能する方法は、配列をチェックするか、「引数」リテラルを反復することであると述べます。

例えば私の「クラス」のコンストラクターでは、Arugmentsを反復処理し、基礎となるArugmentsのタイプを決定し、インテリジェントに処理します。

これは、配列を渡した場合、配列を見つけるためにarugmentsを反復してから、配列内の要素のタイプに応じて、配列を反復してさらに処理することを意味します。

例えば。 ->新しいsomeClass([instanceA、instanceB、instanceC])

しかし、あなたは、一般的な信念に反して実際に達成できる演算子のオーバーロードに対して、より「C」スタイルのアプローチを求めています。

演算子のオーバーロードを尊重するMooToolsを使用して作成したクラスを次に示します。単純で古いJavaScriptでは、同じtoStringメソッドを利用するのは、インスタンスのプロトタイプに直接アタッチするだけです。

このアプローチを表示する私の主な理由は、この機能がエミュレートすることは「不可能」であると私が絶えず読むテキストのためです。不可能なことは十分に難しいだけではありません。これを下に表示します...

 //////

debugger;

//Make a counter to prove I am overloading operators
var counter = 0;

//A test class with a overriden operator
var TestClass = new Class({
    Implements: [Options, Events],
    stringValue: 'test',
    intValue: 0,
    initialize: function (options) {
        if (options && options instanceof TestClass) {
            //Copy or compose
            this.intValue += options.intValue;
            this.stringValue += options.stringValue;
        } else {
            this.intValue = counter++;
        }
    },
    toString: function () {
        debugger;
        //Make a reference to myself
        var self = this;
        //Determine the logic which will handle overloads for like instances
        if (self instanceof TestClass) return self.intValue;
        //If this is not a like instance or we do not want to overload return the string value or a default.
        return self.stringValue;
    }
});

//Export the class
window.TestClass = TestClass;

//make an instance
var myTest = new TestClass();

//make another instance
var other = new TestClass();

//Make a value which is composed of the two utilizing the operator overload
var composed = myTest + other;

//Make a value which is composed of a string and a single value
var stringTest = '' + myTest;

//////

この命名法の最新の表示は、XDateのドキュメントページで確認されました。 http://arshaw.com/xdate/

この場合、実際にはもっと簡単だったと思うので、Dateオブジェクトのプロトタイプを使用して同じことを実現できたでしょう。

それにもかかわらず、この利用スタイルを他の人に示すべき例として私が与えた方法はありません。

編集:

ここに完全な実装があります:

http://netjs.codeplex.com/

他のグッズと一緒に。

2
Jay

すでに述べたことに加えて、.valueOf()をオーバーライドすると、非常に強力な演算子のオーバーロードを生成するのに役立つ場合があります。概念実証 Fingers.js libで、.NETスタイルのイベントリスナーを追加できます。

function hi() { console.log("hi") }
function stackoverflow() { console.log("stackoverflow") }
function bye() { console.log("bye") }

on(yourButton).click += hi + stackoverflow;
on(yourButton).click -= hi - bye;

コアとなるアイデアは、on()が呼び出されたときにvalueOfを一時的に置き換えることです。

const extendedValueOf = function () {
    if (handlers.length >= 16) {
        throw new Error("Max 16 functions can be added/removed at once using on(..) syntax");
    }

    handlers.Push(this); // save current function

    return 1 << ((handlers.length - 1) * 2); // serialize it as a number.
};

返された数値は、ハンドラー配列を使用して逆シリアル化して関数に戻すことができます。さらに、最終値(func1 + func2-func3)からビット値を抽出できるため、追加された関数と削除された関数を効果的に把握できます。

github でソースをチェックアウトし、 demo here で再生できます。

これには完全な説明があります 記事 (AS3の場合、JSの場合も動作するecmascriptなので厳しいです)。

1
average Joe