119

我已经使用 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 类中的运算符吗?因为这看起来很丑陋。

4

9 回答 9

136

如您所见,JavaScript 不支持运算符重载。您可以实现的最接近的方法是实现toString(当需要将实例强制为字符串时将调用它)和valueOf(将调用它以将其强制为数字,例如在使用+加法时,或者在许多情况下当将其用于连接,因为+尝试在连接之前进行加法),这是非常有限的。结果,它们都不允许您创建Vector2对象。同样,Proxy(在 ES2015 中添加)允许您拦截各种对象操作(包括属性访问),但同样不会让您控制+=onVector实例的结果。


但是,对于想要一个字符串或数字作为结果(而不是 a Vector2)的人来说,这里是valueOfand的示例toString。这些示例演示运算符重载,只是利用 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)

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

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

于 2013-10-27T16:54:01.270 回答
31

正如 TJ 所说,您不能在 JavaScript 中重载运算符。但是,您可以利用该valueOf函数编写一个看起来比add每次都使用函数更好的 hack,但对 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]
于 2014-10-07T05:04:57.993 回答
13

可以将两个数字合并为一个来进行矢量数学运算。在解释它是如何工作的之前,让我先展示一个示例:

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 位以内。

于 2019-08-23T18:39:58.420 回答
12

实际上,有一种 JavaScript 变体确实支持运算符重载。ExtendScript 是 Photoshop 和 Illustrator 等 Adob​​e 应用程序使用的脚本语言,它确实具有运算符重载。在其中,您可以编写:

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 标准的(现已长期放弃的)草案。

于 2019-02-21T21:27:42.783 回答
10

仅供参考,paper.js 通过创建 PaperScript 解决了这个问题,PaperScript 是一个独立的、作用域的 javascript,具有向量的运算符重载,然后将其处理回 javascript。

但是paperscript文件需要特别指定和处理。

于 2015-01-22T21:34:46.200 回答
9

valueOf我们可以使用类似 React 的 Hooks 来评估每次迭代时方法中具有不同值的箭头函数。

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)

@js-basics/vector对 Vector3 使用相同的想法。

于 2020-01-19T16:13:43.647 回答
6

我写了一个库,它利用了一堆邪恶的 hack 在原始 JS 中做到这一点。它允许这样的表达。

  • 复数:

    >> 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 ]

    IEf'(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

于 2020-10-28T15:16:02.150 回答
5

虽然不是这个问题的确切答案,但可以使用 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;
        }
    }
}
于 2020-03-28T15:35:02.937 回答
4

有趣的是实验operator-overloading-js。它仅在定义的上下文(回调函数)中进行重载。

于 2020-01-06T07:53:54.130 回答