Javascript:运算符重载

Lee*_*ley 72 javascript operator-overloading operators

我已经使用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.
Run Code Online (Sandbox Code Playgroud)

相反,我必须这样做:

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. 
Run Code Online (Sandbox Code Playgroud)

我可以采用一种方法来重载Vector2类中的运算符吗?因为这看起来很丑陋.

T.J*_*der 84

正如您所发现的,JavaScript不支持运算符重载.你可以来的最接近的是实现toString(当实例需要被强制为字符串时valueOf将被调用)和(将被调用以将其强制转换为数字,例如当+用于添加时,或者在许多情况下使用它进行连接,因为+在连接之前尝试进行添加),这是非常有限的.也不允许您创建Vector2对象作为结果.


对于那些想要一个字符串或数字作为结果(而不是一个Vector2)的人来说,这里有valueOf和的例子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)
Run Code Online (Sandbox Code Playgroud)

或者使用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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

或者使用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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

  • 虽然 JS 本身不支持它,但现在用自定义功能扩展 JS 并转译回普通 JS 是很常见的,例如,[SweetJS](http://sweetjs.org/) 的目标正是解决这个问题问题。 (3认同)

mrv*_*mir 19

正如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);
};
Run Code Online (Sandbox Code Playgroud)

然后你可以写这样的方程式:

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]
Run Code Online (Sandbox Code Playgroud)

  • @IanBrindley OP希望重载一个运算符,这显然意味着他计划编写这样一个函数.OP的担忧是不得不称之为"添加",这是不自然的; 在数学上,我们用"+"符号表示向量加法.这是一个非常好的答案,显示如何避免为准数字对象调用不自然的函数名称. (9认同)
  • 你基本上只是编写了OP的`add`方法的代码......他们不想做的事情. (6认同)
  • @Kittsil问题表明我已经在使用添加函数。虽然上面的函数根本不是一个坏函数,但它没有解决问题,所以我同意 Ian 的观点。 (3认同)
  • 请注意,当两个向量相乘时,这将返回意外结果(而不是给出错误)。坐标也必须是整数。 (2认同)

J. *_*son 12

实际上,有一种 JavaScript 变体确实支持运算符重载。ExtendScript 是 Adob​​e 应用程序(如 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;
Run Code Online (Sandbox Code Playgroud)

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

  • 扩展脚本!= JavaScript (19认同)
  • 为什么 ExtendScript 答案被否决,而 PaperScript 答案被赞成?恕我直言,这个答案也很好。 (6认同)

Stu*_*ffe 12

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

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");
Run Code Online (Sandbox Code Playgroud)

我使用的事实是,如果您将两个数字移位 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我能看到的唯一缺点是 x 和 y 必须在 +-3300 万的范围内,因为它们每个都必须在 26 位以内。


Jam*_*gan 7

虽然不是问题的确切答案,但可以使用 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Jos*_*man 6

FYI paper.js通过创建PaperScript解决了这个问题,PaperScript是一个包含运算符重载向量的自包含的scoped javascript,然后将其处理回javascript.

但是需要明确指定和处理起稿文件.

  • 这个评论回答了我的问题。我正在阅读 paper.js 代码,想知道他们如何重载 JS 运算符来进行对象数学运算。谢谢! (2认同)

FTO*_*TOH 6

我们可以使用类似 React 的 HooksvalueOf在每次迭代中使用来自方法的不同值来评​​估箭头函数。

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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


Mik*_*tay 6

我写了一个库,利用一堆邪恶的黑客在原始 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 ]

    IE 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


xme*_*eko 5

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

  • 对于任何对其工作原理感兴趣的人,它会解析函数的字符串表示形式,并在运行时构建一个新函数,用函数调用替换运算符。 (3认同)