Javascript:运算符重载


93

我已经使用JavaScript了几天,现在我想为定义的对象重载运算符。

在Google搜索了一段时间后,您似乎无法正式执行此操作,但仍有一些人声称采取了一些漫长的方法来执行此操作。

基本上,我制作了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

Answers:


102

如您所见,JavaScript不支持运算符重载。您可以执行的最接近的是实现toString(当实例需要被强制为字符串时valueOf将被调用)和(被强制将其强制为一个数字,例如在+用于加法时,或者在许多情况下,使用它进行串联,因为它+尝试在串联之前进行加法运算),这非常有限。两者都不会让您创建Vector2对象。


但是,对于遇到此问题的人想要一个字符串或数字(而不是Vector2),下面是valueOf和的示例toString。这些示例没有演示运算符重载,只是利用了JavaScript的内置处理将其转换为原语:

valueOf

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

或搭配ES2015 class

或仅包含对象,没有构造函数:

toString

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

或搭配ES2015 class

或仅包含对象,没有构造函数:


1
尽管JS本身不支持它,但如今,使用自定义功能扩展JS并转换回普通JS相当普遍,例如,SweetJS旨在解决这个问题。
德米特里·扎伊采夫

1
Date类中的比较运算符是否使用隐式将日期转换为数字valueOf?例如,您可以做date2 > date1,如果date2在之后创建,则为true date1
肖恩·莱顿德雷

1
@SeanLetendre:是的。><>=,和<=(但不是=====!=,或!==)使用摘要关系比较操作,其用途ToPrimitive与提示“数量”。在一个Date对象上,这导致getTime返回的数字(毫秒-自-Epoch值)。
TJ Crowder

23

正如TJ所说,您不能在JavaScript中重载运算符。但是,您可以利用该valueOf函数编写一个hack,该hack看起来比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方法编写了代码...他们不想做的事情。
伊恩·布林德里

15
@IanBrindley OP希望重载运算符,这显然意味着他计划编写此类函数。OP担心的是不得不调用“ add”,这是不自然的。在数学上,我们用一个+符号表示向量加法。这是一个很好的答案,它显示了如何避免为准数字对象调用不自然的函数名称。
Kittsil

1
@Kittsil问题显示我已经在使用添加功能。尽管上面的函数根本不是一个坏函数,但是它并没有解决这个问题,所以我同意Ian的观点。
Lee Brindley

到目前为止,这是唯一可能的方法。我们对+运算符唯一的灵活性是可以返回a Number来替换其中一个操作数。因此,任何适用于Object实例的添加功能都必须始终将对象编码为Number,并最终对其进行解码。
Gershom '18年

请注意,将两个向量相乘时,这将返回意外结果(而不是给出错误)。而且坐标必须是整数。
user202729

8

FYI paper.js通过创建PaperScript来解决此问题,PaperScript是一种自包含的,范围有限的javascript,带有向量的运算符重载,然后将其处理回javascript。

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


6

实际上,有一种JavaScript变体确实支持运算符重载。ExtendScript是Adobe应用程序(例如Photoshop和Illustrator)使用的脚本语言,确实有运算符重载。在其中,您可以编写:

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;

这在“ Adob​​e Extendscript JavaScript工具指南”(此处为当前链接)中有更详细的描述。该语法显然基于ECMAScript标准的草案(现已废弃)。


9
ExtendScript!= JavaScript
Andrio Skur,

1
为什么在延长PaperScript答案的同时降低ExtendScript的答案?恕我直言,这个答案也很好。
xmedeko

4

可以将两个数字组合成一个数字来进行矢量数学运算。让我先展示一个例子,然后再解释它的工作原理:

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必须在+ -33,000,000范围内,因为它们必须分别位于26位之内。


vec_pack的定义在哪里?
恶心

1
@令人作呕的嗯,对不起,好像我忘了补充说...现在已经修复:)
Stuffe

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

我们可以使用类似React的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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.