重载JavaScript中的算术运算符?


72

给定这个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"

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

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对象可组合的最佳实践建议是什么?


看一下SweetJS可以做到的。
德米特里·扎伊采夫


Answers:


39

据我所知,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);


1
我有点m愧,从负操作数向其模运算符返回负值的语言将不支持运算符重载。这时世界上每个人都必须将%实施为((a%b)+ b)%b
David Bandel

22

抱歉不行。

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

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

5
如果您的环境支持,您也可以使用one(two)(three)
curring

4
@elliottcable Good + clever思维,但是即使乘法也可以,但即使那样,我也无法在典型的程序员思维方式中很好地进行交流。我仍然会去one.times(two).times(three);
Nobbynob Littlun 2013年

在CoffeeScript中,您也可以放下一些括号:)
Carl Smith

14

由于每个人都否决了我的其他答案,因此我想发布概念证明代码,该代码实际上可以按预期工作。

这已在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;

如果有人乐于提供技术说明,为什么这还不够好,我很乐意听到!


1
如果派生的需要,则可以将其传递给演员中的新类型...
Jay

6
杰伊,您能说明一下如何使用MyNumber类进行算术运算(举个例子)吗?
trusktr

它是否仅由于类型持有单个int值而起作用?
greenoldman '02


7

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

仅当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


6

Paper.js做到了这一点,例如,通过加点(docs):

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

但是它使用自己的自定义脚本解析器来实现


您能用一小段示例代码来解释这个技巧吗?谢谢。我看过示例代码。
Et7f3XIV

+20将20加到成员x和y。2号线
Et7f3XIV

2
此代码段使用提供的自定义解析器进行解释-这不是标准的javascript。
伊扎基

我在Paper.js示例中没有看到对此解析器的调用。就像一个非常聪明的评估者吗?
Et7f3XIV


5

我编写了一个脚本,该脚本在JavaScript中进行运算符重载。进行工作并非直截了当,因此有一些怪癖。我将在项目页面上交叉发布警告,否则,您可以在底部找到链接:

  • 计算结果必须传递给新对象,因此,必须代替新点(p1 + p2 + p3),再执行新点(p1 + p2 + p3),(假设您的用户定义对象被命名为“ point”)。

  • 仅支持+,-,*和/,不支持第五个算术运算符%。强制转换为字符串(“” + p1)和比较(p1 == p2)不能按预期方式进行。如果需要,应为此目的构建新功能,例如(p1.val == p2.val)。

  • 最终,计算答案所需的计算资源与项数成倍增加。因此,默认情况下,每个默认值在一个计算链中仅允许6个项(尽管可以增加)。对于更长的计算链,请拆分计算,例如:新点(新点(p1 + p2 + p3 + p4 + p5 + p6)+新点(p7 + p8 + p9 + p10 + p11 + p12))

Github的页面


2

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

我绝对有一种方法可以用非常小的脚本来概述,您不必是John Resig就可以理解...

在此之前,我还将指出,在JavaScript中,构造函数的工作方式是检查数组或迭代“参数”文字。

例如,在我的“类”的构造函数中,我将迭代这些装饰,确定基础装饰的类型并对其进行智能处理。

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

例如-> new 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/

以及其他东西。


6
您在此处显示的不是操作符重载。此外,在你的榜样toString()的回报'0'stringTest。您应该使用它valueOf()来返回数字替代品(更多信息请参见:fullylygood.com/2010/3/…)。但这仅是值的替代,不是操作员重载功能。即使使用您的方法,我也无法实现Vector会减去字段的类,.x并且.y当我这样做时:vectorC = vectorA - vectorB。为此,您需要操作员重载,这在ES5中是不可能的。
pepkin88

我发现,结合使用以上策略和function.bind,您可以控制调用该方法的版本,并且可以正常工作,尽管它不如其他语言那么灵活...例如,您可以.cast函数接受一个Object并在给定Object的上下文中调用另一种类型的方法....尽管如此,这与其他语言不同,但我仍然认为它可以工作:P
Jay

@Jay我在浏览器和Node.js中尝试了此操作,它抱怨没有定义Class,所以它不起作用…………等等,我看到:Class来自MooTools。您可以使用jQuery进行这项工作吗?甚至更好,根本没有任何库,只有JavaScript?
trusktr

2

除了已经说过的内容:覆盖.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.
};

然后可以使用handlers数组将返回的数字反序列化回函数。此外,还可以从最终值(func1 + func2-func3)中提取位值,因此您可以有效地了解添加了哪些功能以及删除了哪些功能。

您可以在github上查看源代码并在此处演示

完整的解释存在这文章(它是AS3,艰难的,因为它的ECMAScript中它会为JS工作,要么)。


-2

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

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。


10
是否可以在MIT许可下免费使用?我认为您不了解本网站的全部内容。
阿兰·穆赫兰

3
@AranMulholland你呢?当前SE许可证CC BY-SA(一直),并且他们正计划转移到某种MIT的meta.stackexchange.com/questions/272956/...
马里奥特鲁科

1
如果我正确理解,这不会使运算符过载,而只是委托给它的原始实现。正如您指出的那样,使用非常有限。
德米特里·扎伊采夫
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.