【问题标题】:Overloading Arithmetic Operators in JavaScript?在 JavaScript 中重载算术运算符?
【发布时间】:2010-12-10 16:45:47
【问题描述】:

鉴于这个 JavaScript“类”定义,这是我能想到的表达这个问题的最佳方式:

var Quota = function(hours, minutes, seconds){
    if (arguments.length === 3) {
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;

        this.totalMilliseconds = Math.floor((hours * 3600000)) + Math.floor((minutes * 60000)) + Math.floor((seconds * 1000));
    }
    else if (arguments.length === 1) {
        this.totalMilliseconds = hours;

        this.hours = Math.floor(this.totalMilliseconds / 3600000);
        this.minutes = Math.floor((this.totalMilliseconds % 3600000) / 60000);
        this.seconds = Math.floor(((this.totalMilliseconds % 3600000) % 60000) / 1000);
    }

    this.padL = function(val){
        return (val.toString().length === 1) ? "0" + val : val;
    };

    this.toString = function(){
        return this.padL(this.hours) + ":" + this.padL(this.minutes) + ":" + this.padL(this.seconds);
    };

    this.valueOf = function(){
        return this.totalMilliseconds;
    };
};

以及以下测试设置代码:

var q1 = new Quota(23, 58, 50);
var q2 = new Quota(0, 1, 0);
var q3 = new Quota(0, 0, 10);

console.log("Quota 01 is " + q1.toString());    // Prints "Quota 01 is 23:58:50"
console.log("Quota 02 is " + q2.toString());    // Prints "Quota 02 is 00:01:00"
console.log("Quota 03 is " + q3.toString());    // Prints "Quota 03 is 00:00:10"

有没有什么方法可以使用加法运算符将q4 隐式创建为Quota 对象,如下所示...

var q4 = q1 + q2 + q3;
console.log("Quota 04 is " + q4.toString());    // Prints "Quota 04 is 86400000"

而不是诉诸...

var q4 = new Quota(q1 + q2 + q3);
console.log("Quota 04 is " + q4.toString());    // Prints "Quota 04 is 24:00:00"

如果不是,在这个领域中,使自定义数字 JavaScript 对象可通过算术运算符组合的最佳实践建议是什么?

【问题讨论】:

标签: javascript operator-overloading


【解决方案1】:

您可以将对象隐式转换为整数或字符串。

只有当 JavaScript 需要一个数字或字符串时,对象才会被隐式转换。在前一种情况下,转换需要三个步骤:

1.- 致电valueOf()。如果结果是原始的(不是对象),则使用它并将其转换为数字。

2.- 否则,请致电toString()。如果结果是原始的,则使用它并将其转换为数字。

3.- 否则,抛出 TypeError。 第 1 步的示例:

3 * { valueOf: function () { return 5 } }

如果 JavaScript 转换为字符串,则交换第 1 步和第 2 步:首先尝试 toString(),然后尝试 valueOf()。

http://www.2ality.com/2013/04/quirk-implicit-conversion.html

【讨论】:

    【解决方案2】:

    Paper.js 做到了,例如加点 (docs):

    var point = new Point(5, 10);
    var result = point + 20;
    console.log(result); // {x: 25, y: 30}
    

    但它使用自己的custom script parser

    【讨论】:

    • 你能用一个小示例代码解释一下这个技巧吗?谢谢。我看过示例代码。
    • +20 加 20 到成员 x 和 y。第 2 行
    • 这个 sn-p 是使用提供的自定义解析器解释的 - 这不是标准的 javascript。
    • 我没有在 Paper.js 示例中看到对此解析器的调用。是不是像一个非常智能的评估?
    【解决方案3】:

    我制作了一个在 JavaScript 中执行运算符重载的脚本。制作工作并不简单,所以有一些怪癖。我将从项目页面交叉发布警告,否则您可以在底部找到链接:

    • 计算结果必须传递给一个新对象,所以你必须做新点(p1 + p2 + p3)而不是(p1 + p2 + p3),(假设你的用户定义的对象被命名为“点")。

    • 仅支持 +、-、* 和 /,不支持第五个算术运算符 %。 对字符串 (""+p1) 和比较 (p1 == p2) 的强制将无法按预期工作。如果需要,应为这些目的构建新函数,例如 (p1.val == p2.val)。

    • 最终,计算答案所需的计算资源随着项数的增加呈二次方增加。因此,每个默认值在一个计算链中只允许 6 个术语(尽管可以增加)。对于比这更长的计算链,将计算拆分,例如: new point(new point(p1 + p2 + p3 + p4 + p5 + p6) + new point(p7 + p8 + p9 + p10 + p11 + p12))

    Github page

    【讨论】:

      【解决方案4】:

      对于一些有限的用例,您可以使用运算符“重载”效果:

      function MyIntyClass() {
          this.valueOf = function() { return Math.random(); }
      }
      var a = new MyIntyClass();
      var b = new MyIntyClass();
      a < b
      false
      
      a + b
      0.6169137847609818
      
      [a, b].sort() // O(n^2) ?
      [myClass, myClass]
      
      function MyStringyClass() {
          this.valueOf = function() { return 'abcdefg'[Math.floor(Math.random()*7)]; }
      }
      c = new MyStringyClass();
      'Hello, ' + c + '!'
      Hello, f!
      

      以上代码可在 MIT 许可下免费使用。 YMMV。

      【讨论】:

      • 在 MIT 许可下免费使用?我认为您不了解此网站的全部内容。
      • @AranMulholland 你呢?当前的 SE 许可证是 CC BY-SA(一直是),他们正计划迁移到某种 MIT meta.stackexchange.com/questions/272956/…
      • 如果我理解正确,这不会使运算符过载,而只是委托给它的原始实现。正如你所指出的,使用非常有限。
      【解决方案5】:

      除了已经说过的:覆盖 .valueOf() 可能有助于产生非常强大的运算符重载。在概念验证 Fingers.js lib 中,您可以添加 .NET 样式的事件侦听器:

      function hi() { console.log("hi") }
      function stackoverflow() { console.log("stackoverflow") }
      function bye() { console.log("bye") }
      
      on(yourButton).click += hi + stackoverflow;
      on(yourButton).click -= hi - bye;
      

      核心思想是在调用on()时临时替换valueOf:

      const extendedValueOf = function () {
          if (handlers.length >= 16) {
              throw new Error("Max 16 functions can be added/removed at once using on(..) syntax");
          }
      
          handlers.push(this); // save current function
      
          return 1 << ((handlers.length - 1) * 2); // serialize it as a number.
      };
      

      然后可以使用处理程序数组将返回的数字反序列化回函数。更重要的是,它可以从最终值(func1 + func2 - func3)中提取位值,因此您可以有效地了解添加了哪些函数,以及删除了哪些函数。

      您可以在github 上查看源代码并与demo here 一起玩。

      完整的解释存在于这个article 中(它适用于 AS3,很难,因为它是 ecmascript,它也适用于 JS)。

      【讨论】:

        【解决方案6】:

        我不知道为什么人们继续不回答这个问题!

        绝对有一种方法,我会用一个非常非常小的脚本来概述它,你不必是 John Resig 也能理解......

        在我这样做之前,我还要说明,在 JavaScript 中,构造函数的工作方式是检查数组或迭代“参数”字面量。

        例如在我的“类”的构造函数中,我会迭代参数,确定底层参数的类型并智能地处理它。

        这意味着,如果您传递了一个数组,我将迭代参数以找到一个数组,然后根据数组中元素的类型迭代该数组以进行进一步处理。

        例如-> 新的 someClass([instanceA,instanceB,instanceC])

        然而,你们正在寻求一种更“C”风格的运算符重载方法,实际上可以实现与流行的看法相反。

        这是我使用 MooTools 创建的一个类,它尊重运算符重载。在普通的旧 JavaScript 中,您只需使用相同的 toString 方法,只需将其直接附加到实例的原型。

        我展示这种方法的主要原因是因为我不断阅读的文字说明此功能“不可能”模拟。没有什么是不可能的,只有足够困难,我将在下面展示这个......

         //////
        
        debugger;
        
        //Make a counter to prove I am overloading operators
        var counter = 0;
        
        //A test class with a overriden operator
        var TestClass = new Class({
            Implements: [Options, Events],
            stringValue: 'test',
            intValue: 0,
            initialize: function (options) {
                if (options && options instanceof TestClass) {
                    //Copy or compose
                    this.intValue += options.intValue;
                    this.stringValue += options.stringValue;
                } else {
                    this.intValue = counter++;
                }
            },
            toString: function () {
                debugger;
                //Make a reference to myself
                var self = this;
                //Determine the logic which will handle overloads for like instances
                if (self instanceof TestClass) return self.intValue;
                //If this is not a like instance or we do not want to overload return the string value or a default.
                return self.stringValue;
            }
        });
        
        //Export the class
        window.TestClass = TestClass;
        
        //make an instance
        var myTest = new TestClass();
        
        //make another instance
        var other = new TestClass();
        
        //Make a value which is composed of the two utilizing the operator overload
        var composed = myTest + other;
        
        //Make a value which is composed of a string and a single value
        var stringTest = '' + myTest;
        
        //////
        

        在 XDate 的文档页面上观察到此命名法的最新显示: http://arshaw.com/xdate/

        在这种情况下,我相信它实际上更容易,他可以使用 Date 对象的原型来实现相同的目标。

        尽管如此,我作为示例给出的方法应该为其他人描绘这种使用方式。

        编辑:

        我这里有一个完整的实现:

        http://netjs.codeplex.com/

        还有其他好东西。

        【讨论】:

        • 这里显示的不是运算符重载。此外,在您的示例中,toString()'0' 返回到stringTest。您应该使用 valueOf() 来返回一个数字替代品(更多信息在这里:adequatelygood.com/2010/3/…)。但这只是一个值替代,这不是运算符重载功能。即使使用您的方法,我也无法实现类Vector,当我这样做时,它会减去字段.x.yvectorC = vectorA - vectorB。这样做需要运算符重载,这在 ES5 中是不可能的。
        • 我发现将上述策略与 function.bind 结合使用,您可以控制调用哪个版本的方法并且它确实有效,尽管它不像其他语言那样灵活...例如您可以创建一个 .cast 函数,它接受一个 Object 并调用另一个类型的方法,并将上下文设置为给定的 Object.... 尽管如此,这与其他语言不同,但我仍然认为它可以工作:P
        • @Jay 我在我的浏览器和 Node.js 中尝试了这个,它抱怨 Class 没有定义,所以它不起作用............哦等等,我请参阅:类来自 MooTools。你能用 jQuery 做这个吗?或者甚至更好,根本没有任何库,只有 JavaScript?
        【解决方案7】:

        由于每个人都对我的其他答案投了反对票,因此我想发布实际上按预期工作的概念验证代码。

        这已经在 chrome 和 IE 中测试过了。

        //Operator Overloading
        
        var myClass = function () {
        
        //Privates
        
        var intValue = Number(0),
            stringValue = String('');
        
        //Publics
        this.valueOf = function () {
            if (this instanceof myClass) return intValue;
            return stringValue;
        }
        
        this.cast = function (type, call) {
            if (!type) return;
            if (!call) return type.bind(this);
            return call.bind(new type(this)).call(this);
        }
        
        }
        
        //Derived class
        var anotherClass = function () {
        
        //Store the base reference
        this.constructor = myClass.apply(this);
        
        var myString = 'Test',
            myInt = 1;
        
        this.valueOf = function () {
            if (this instanceof myClass) return myInt;
            return myString;
        }
        
        }
        
        
        //Tests
        
        var test = new myClass(),
        anotherTest = new anotherClass(),
        composed = test + anotherTest,
        yaComposed = test.cast(Number, function () {
            return this + anotherTest
        }),
        yaCComposed = anotherTest.cast(Number, function () {
            return this + test;
        }),
        t = test.cast(anotherClass, function () {
            return this + anotherTest
        }),
        tt = anotherTest.cast(myClass, function () {
            return this + test;
        });
        
        debugger;
        

        如果有人愿意给出技术解释为什么这还不够好,我会很高兴听到它!

        【讨论】:

        • 如果派生的 .... 需要,可以覆盖将其传递给强制转换中的新类型。
        • Jay,你能说明这将如何与 MyNumber 类一起进行算术运算(举个例子)吗?
        • 它是否因为该类型拥有单个 int 值而起作用?
        【解决方案8】:

        我最近看到了这篇文章:http://www.2ality.com/2011/12/fake-operator-overloading.html

        它描述了如何重新定义对象的 valueOf 方法以执行类似 JavaScript 中的运算符重载的操作。似乎您只能对正在操作的对象真正执行 mutator 操作,因此它不会做您想要的。尽管如此,它还是很有趣。

        【讨论】:

          【解决方案9】:

          据我所知,Javascript(至少现在存在)不支持运算符重载。

          我能建议的最好的方法是从其他几个创建新配额对象的类方法。这是我的意思的一个简单示例:

          // define an example "class"
          var NumClass = function(value){
              this.value = value;
          }
          NumClass.prototype.toInteger = function(){
              return this.value;
          }
          
          // Add a static method that creates a new object from several others
          NumClass.createFromObjects = function(){
              var newValue = 0;
              for (var i=0; i<arguments.length; i++){
                  newValue += arguments[i].toInteger();
              }
              return new this(newValue)
          }
          

          并像这样使用它:

          var n1 = new NumClass(1);
          var n2 = new NumClass(2);
          var n3 = new NumClass(3);
          
          var combined = NumClass.createFromObjects(n1, n2, n3);
          

          【讨论】:

          【解决方案10】:

          第二个建议:

          var q4 = Quota.add(q1, q2, q3);
          

          【讨论】:

            【解决方案11】:

            很遗憾没有。

            对于后备,如果你安排了返回值,你可以使用方法链

            var q4 = q1.plus(p2).plus(q3);
            

            【讨论】:

            • 如果您的环境支持,您还可以使用柯里化来获得更漂亮的 API:one(two)(three)
            • @elliottcable 好+聪明的想法,但是虽然乘法可能没问题,但即使这样,我认为它在典型的程序员思维方式中也不能很好地沟通。我还是会选择one.times(two).times(three);
            • 在 CoffeeScript 中,你也可以去掉一些括号 :)
            猜你喜欢
            • 1970-01-01
            • 2020-03-10
            • 1970-01-01
            • 2016-07-15
            • 1970-01-01
            相关资源
            最近更新 更多