JavaScript:演算子のオーバーロード


93

私はJavaScriptを数日間使用していて、定義したオブジェクトの演算子をオーバーロードしたいところまで来ました。

グーグルでこれを検索した後、正式にこれを行うことはできないようですが、このアクションを実行するための長い時間のかかる方法を主張している人もいます。

基本的に私はVector2クラスを作成し、次のことができるようにしたいと考えています。

var x = new Vector2(10,10);
var y = new Vector2(10,10);

x += y; //This does not result in x being a vector with 20,20 as its x & y values.

代わりに私はこれをしなければなりません:

var x = new Vector2(10,10);
var y = new Vector2(10,10);

x = x.add(y); //This results in x being a vector with 20,20 as its x & y values. 

Vector2クラスのオペレーターをオーバーロードするために使用できるアプローチはありますか?これは単に醜く見えます。



1
オペレーターのオーバーロードライブラリに遭遇しました。:それを試していないとも、それがどのように動作するかよく知っていないgoogle.com/...
fishinear

回答:


102

ご存じのとおり、JavaScriptは演算子のオーバーロードをサポートしていません。あなたが来ることができる最も近いのは、実装することですtoString(これは、インスタンスが文字列になるように強制する必要があるときに呼び出されます)およびvalueOf(たとえば+、加算に使用するとき、または多くの場合、それを数値に強制するために呼び出されます連結の+前に加算を行おうとするため、それを連結に使用します)。これはかなり制限されています。またVector2、結果としてオブジェクトを作成することもできません。


(の代わりにVector2)結果として文字列または数値が必要なこの質問に来た人のために、valueOfとの例を次に示しtoStringます。これらの例、プリミティブに変換するJavaScriptの組み込み処理を利用するだけで、オペレーターのオーバーロードを示していません

valueOf

この例ではval、プリミティブに強制的に変更されたことに応じて、オブジェクトのプロパティの値を2倍にします。例+

またはES2015の場合class

または、オブジェクトのみで、コンストラクタはありません:

toString

次の例ではval、プリミティブに強制的に変換されたことに応じて、オブジェクトのプロパティの値を大文字に変換します+

またはES2015の場合class

または、オブジェクトのみで、コンストラクタはありません:


1
JS固有ではサポートされていませんが、最近では、JSをカスタム機能で拡張し、単純なJSにトランスパイルするのが一般的です。たとえば、SweetJSはこの問題に正確に対処することを目的としています。
Dmitri Zaitsev 2016

1
Dateクラスの比較演算子は、日付を暗黙的に数値に変換しvalueOfますか?たとえば、あなたが行うdate2 > date1ことができ、date2がの後に作成された場合はtrueになりますdate1
Sean Letendre 2017

1
@SeanLetendre:はい。><>=、及び<=(ただし=====!=、または!==)使用抽象リレーショナル比較使用する操作、ToPrimitiveヒント「番号」です。上のDateオブジェクト数の結果は、そのgetTime戻り値(ミリ秒-ので-エポック値)。
TJクラウダー2017

23

TJが言ったように、JavaScriptでオペレーターをオーバーロードすることはできません。ただし、valueOf関数を利用して、add毎回のように関数を使用するよりも見栄えが良いが、xとyが0とMAX_VALUEの間にあるベクトルに制約を課すハックを作成できます。これがコードです:

var MAX_VALUE = 1000000;

var Vector = function(a, b) {
    var self = this;
    //initialize the vector based on parameters
    if (typeof(b) == "undefined") {
        //if the b value is not passed in, assume a is the hash of a vector
        self.y = a % MAX_VALUE;
        self.x = (a - self.y) / MAX_VALUE;
    } else {
        //if b value is passed in, assume the x and the y coordinates are the constructors
        self.x = a;
        self.y = b;
    }

    //return a hash of the vector
    this.valueOf = function() {
        return self.x * MAX_VALUE + self.y;
    };
};

var V = function(a, b) {
    return new Vector(a, b);
};

次に、次のような方程式を記述できます。

var a = V(1, 2);            //a -> [1, 2]
var b = V(2, 4);            //b -> [2, 4]
var c = V((2 * a + b) / 2); //c -> [2, 4]

7
基本的に、OPのaddメソッドのコードを記述したばかりです...彼らがやりたくないこと。
Ian Brindley、2015年

15
@IanBrindley OPは演算子をオーバーロードしたかったので、そのような関数を作成することを計画していたことは明らかです。OPの懸念は、「追加」を呼び出す必要があることでしたが、これは不自然です。数学的には、ベクトルの加算を+符号で表します。これは、疑似数値オブジェクトに対して不自然な関数名を呼び出さないようにする方法を示す非常に良い答えです。
Kittsil、2016年

1
@Kittsil質問は、私がすでにadd関数を使用していることを示しています。上記の関数は悪い関数ではありませんが、問題には対応していなかったため、Ianに同意します。
Lee Brindley

現時点では、これが唯一の可能な方法です。+演算子の唯一の柔軟性Numberは、オペランドの1つを置き換えるものとしてa を返す機能です。したがって、Objectインスタンスを機能させる追加機能では、常にオブジェクトをとしてエンコードし、Number最終的にそれをデコードする必要があります。
Gershom 2018年

2つのベクトルを乗算すると、これは(エラーを返すのではなく)予期しない結果を返すことに注意してください。また、座標は整数でなければなりません。
user202729

8

FYI paper.jsは、ベクトルのオペレーターオーバーロードを備えた自己完結型のスコープ付きJavaScriptであるPaperScriptを作成し、それを処理してJavaScriptに戻すことにより、この問題を解決します。

ただし、paperscriptファイルは具体的に指定し、そのように処理する必要があります。


6

実際には、JavaScriptの一つの変形がありませんサポート演算子のオーバーロードは。PhotoshopやIllustratorなどのAdobeアプリケーションで使用されるスクリプト言語であるExtendScriptには、オペレーターのオーバーロードがあります。その中で、あなたは書くことができます:

Vector2.prototype["+"] = function( b )
{
  return new Vector2( this.x + b.x, this.y + b.y );
}

var a = new Vector2(1,1);
var b = new Vector2(2,2);
var c = a + b;

これについては、「Adobe Extendscript JavaScriptツールガイド」(現在のリンクはこちら)で詳しく説明しています。構文は、明らかにECMAScript標準の(現在は廃止された)ドラフトに基づいています。


9
ExtendScript!= JavaScript
Andrio Skur

1
PaperScriptの回答が賛成投票されているのに、ExtendScriptの回答が反対投票されているのはなぜですか?私見この答えも良いです。
xmedeko

4

2つの数値を1つにパックしてベクトル演算を行うことができます。どのように機能するかを説明する前に、まず例を示しましょう。

let a = vec_pack([2,4]);
let b = vec_pack([1,2]);

let c = a+b; // Vector addition
let d = c-b; // Vector subtraction
let e = d*2; // Scalar multiplication
let f = e/2; // Scalar division

console.log(vec_unpack(c)); // [3, 6]
console.log(vec_unpack(d)); // [2, 4]
console.log(vec_unpack(e)); // [4, 8]
console.log(vec_unpack(f)); // [2, 4]

if(a === f) console.log("Equality works");
if(a > b) console.log("Y value takes priority");

2つの数値をX回ビットシフトし、それらを加算または減算してから元に戻すと、最初からシフトしていない場合と同じ結果が得られるという事実を利用しています。同様に、スカラーの乗算と除算は、シフトされた値に対して対称的に機能します。

JavaScriptの数値には52ビットの整数精度(64ビット浮動小数点数)があるため、1つの数値をより高い使用可能な26ビットにパックし、もう1つを低いビットにパックします。符号付きの数値をサポートしたかったので、コードは少し面倒になっています。

function vec_pack(vec){
    return vec[1] * 67108864 + (vec[0] < 0 ? 33554432 | vec[0] : vec[0]);
}

function vec_unpack(number){
    switch(((number & 33554432) !== 0) * 1 + (number < 0) * 2){
        case(0):
            return [(number % 33554432),Math.trunc(number / 67108864)];
        break;
        case(1):
            return [(number % 33554432)-33554432,Math.trunc(number / 67108864)+1];
        break;
        case(2):
            return [(((number+33554432) % 33554432) + 33554432) % 33554432,Math.round(number / 67108864)];
        break;
        case(3):
            return [(number % 33554432),Math.trunc(number / 67108864)];
        break;
    }
}

これでわかる唯一の欠点は、xとyがそれぞれ26ビット以内に収まる必要があるため、xとyの範囲が+ -3300万でなければならないことです。


vec_packの定義はどこですか?
嫌な

1
@嫌なうーん、申し訳ありません。それを追加するのを忘れていたようです...これは修正されました:)
スタッフ

3

質問に対する正確な回答ではありませんが、ES6シンボルを使用して一部のpython __magic__メソッドを実装することは可能です

[Symbol.toPrimitive]()この方法は、あなたが電話を暗示することはできませんVector.add()が、あなたのような構文を使用できるようになりますDecimal() + int

class AnswerToLifeAndUniverseAndEverything {
    [Symbol.toPrimitive](hint) {
        if (hint === 'string') {
            return 'Like, 42, man';
        } else if (hint === 'number') {
            return 42;
        } else {
            // when pushed, most classes (except Date)
            // default to returning a number primitive
            return 42;
        }
    }
}

2

興味深いのは、実験的なライブラリoperator-overloading-jsです。定義されたコンテキスト(コールバック関数)でのみオーバーロードします。


2

Reactのようなフックを使用してvalueOf、各反復でメソッドからの異なる値で矢印関数を評価できます。

const a = Vector2(1, 2) // [1, 2]
const b = Vector2(2, 4) // [2, 4]    
const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
// There arrow function will iterate twice
// 1 iteration: method valueOf return X component
// 2 iteration: method valueOf return Y component

ライブラリ@ js-basics / vectorは、Vector3と同じ考え方を使用しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.