Javascript: operator overloading

我已经使用 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.

Instead I'm having to do this:

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类中有没有重载运算符的方法。

104678 次浏览

As you've found, JavaScript doesn't support operator overloading. The closest you can come is to implement toString (which will get called when the instance needs to be coerced to being a string) and valueOf (which will get called to coerce it to a number, for instance when using + for addition, or in many cases when using it for concatenation because + tries to do addition before concatenation), which is pretty limited. Neither lets you create a Vector2 object as a result. Similarly, Proxy (added in ES2015) lets you intercept various object operations (including property access), but again won't let you control the result of += on Vector instances.


但是,对于那些希望得到一个字符串或数字(而不是 Vector2)的人来说,这里有一些 valueOftoString的例子。这些例子展示了运算符重载,仅仅利用了 JavaScript 内置的转换为原语的处理:

valueOf

此示例将对象的 val属性的值加倍,以响应被强制为原语,例如通过 +:

function Thing(val) {
this.val = val;
}
Thing.prototype.valueOf = function() {
// Here I'm just doubling it; you'd actually do your longAdd thing
return this.val * 2;
};


var a = new Thing(1);
var b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)

或者用 ES2015的 class:

class Thing {
constructor(val) {
this.val = val;
}
valueOf() {
return this.val * 2;
}
}


const a = new Thing(1);
const b = new Thing(2);
console.log(a + b); // 6 (1 * 2 + 2 * 2)

Or just with objects, no constructors:

var thingPrototype = {
valueOf: function() {
return this.val * 2;
}
};


var a = Object.create(thingPrototype);
a.val = 1;
var b = Object.create(thingPrototype);
b.val = 2;
console.log(a + b); // 6 (1 * 2 + 2 * 2)

toString

此示例将对象的 val属性的值转换为大写,以响应被强制转换为原语,例如通过 +:

function Thing(val) {
this.val = val;
}
Thing.prototype.toString = function() {
return this.val.toUpperCase();
};


var a = new Thing("a");
var b = new Thing("b");
console.log(a + b); // AB

或者用 ES2015的 class:

class Thing {
constructor(val) {
this.val = val;
}
toString() {
return this.val.toUpperCase();
}
}


const a = new Thing("a");
const b = new Thing("b");
console.log(a + b); // AB

或者只有对象,没有构造函数:

var thingPrototype = {
toString: function() {
return this.val.toUpperCase();
}
};


var a = Object.create(thingPrototype);
a.val = "a";
var b = Object.create(thingPrototype);
b.val = "b";
console.log(a + b); // AB

正如 T.J. 所说,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]

Js 通过创建 PaperScript 解决了这个问题,PaperScript 是一个自包含的、有作用域的 javascript,带有向量运算符重载,然后再处理回 javascript。

But the paperscript files need to be specifically specified and processed as such.

事实上,有一种 JavaScript 的变种是 does支持的运算符重载。用于 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 标准的草案(现在已经废弃很久了)。

把两个数字放在一起做向量数学是可能的。在我解释它的工作原理之前,让我先举一个例子:

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");

我使用的事实是,如果你位移两个数 X 倍,然后加减之前移回来,你会得到相同的结果,如果你没有移动他们开始。类似的标量乘法和除法对偏移值的作用是对称的。

一个 JavaScript 数字有52位的整数精度(64位浮点数) ,所以我将把一个数字放在可用的较高的26位中,一个放在较低的位中。因为我想支持有符号的数字,所以代码变得有点混乱。

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 必须在 +-3300万的范围内,因为它们必须适合每个26位。

有趣的是 实验性的操作员重载。它只在定义的上下文(回调函数)中重载。

我们可以使用 React-like Hooks 在每次迭代中使用不同于 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

const Vector2 = (function() {
let index = -1
return function(x, y) {
if (typeof x === 'function') {
const calc = x
index = 0, x = calc()
index = 1, y = calc()
index = -1
}
return Object.assign([x, y], {
valueOf() {
return index == -1 ? this.toString() : this[index]
},
toString() {
return `[${this[0]}, ${this[1]}]`
},
len() {
return Math.sqrt(this[0] ** 2 + this[1] ** 2)
}
})
}
})()


const a = Vector2(1, 2)
const b = Vector2(2, 4)


console.log('a = ' + a) // a = [1, 2]
console.log(`b = ${b}`) // b = [2, 4]


const c = Vector2(() => (2 * a + b) / 2) // [2, 4]
a[0] = 12
const d = Vector2(() => (2 * a + b) / 2) // [13, 4]
const normalized = Vector2(() => d / d.len()) // [0.955..., 0.294...]


console.log(c, d, normalized)

Library @ js- 基础知识/向量 uses the same idea for Vector3.

Whilst not an exact answer to the question, it is possible to implement some of the python __magic__ methods using ES6 Symbols

[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;
}
}
}

我编写了一个库,它利用一些邪恶的技巧在原始 JS 中完成这项工作。它允许这样的表达。

  • Complex numbers:

    >> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))

    <- {r: -2, i: 1}

  • 自动微分:

    f(x) = x^3 - 5x:

    >> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);

    现在将它映射到一些值上:

    >> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)

    <- [ 7, -2, -5, -2, 7 ]

    i.e. f'(x) = 3x^2 - 5.

  • 多项式:

    >> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')

    <- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"

对于您的特定问题,您可以使用这个库定义一个 Vector2函数(或者更短的函数) ,然后编写 x = Vector2()(x + y);

Https://gist.github.com/pyrocto/5a068100abd5ff6dfbe69a73bbc510d7