在 JavaScript 中重载算术运算符? ?

考虑到 JavaScript 的“ class”定义,这是我能想到的表达这个问题的最佳方式:

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

71416 次浏览

Unfortunately no.

For fallbacks, if you arranged the return values, you could use method chaining

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

Second suggestion:

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

As far as I'm aware, Javascript (at least as it exists now) doesn't support operator overloading.

The best I can suggest is a class method for making new quota objects from several others. Here's a quick example of what I mean:

// 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)
}

and use it like:

var n1 = new NumClass(1);
var n2 = new NumClass(2);
var n3 = new NumClass(3);


var combined = NumClass.createFromObjects(n1, n2, n3);

I am not sure why people continue to answer this question with no!

There is absolutely a way which I will outline with a very very small script which your don't have to be John Resig to understand...

Before I do so I will also state that in JavaScript the way your constructor would have worked is by checking for arrays or iterating the 'arguments' literal.

e.g. In my constructor of my 'class' I would iterate the arugments, determine the type of the underlying arugments and process it intelligently.

This means that if you passed an array I would iterate the arugments to find an array and then iterate the array to do further processing depending on the type the element in the array.

E.g. -> new someClass([ instanceA, instanceB, instanceC])

However you guys are seeking a more "C" style approach to operator overloading which can actually be achived contrary to populare belief.

Here is a class which I have created using MooTools which does honor operator overloading. In plain old JavaScript you would just utilize the same toString method only attach it to the prototype of instance directly.

My main reason for displaying this approach is because of the text I continually read which states this functionality is "impossible" to emulate. Nothing is impossible only sufficently difficult and I will display this below...

 //////


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;


//////

The most recent display of this nomenclature was observed at XDate's documentation page: http://arshaw.com/xdate/

In this case I believe it was actually even easer, he could have used the prototype of the Date object to achive the same.

None the less the method I have given as an example which should portray this style of utilization for others.

Edit:

I have a complete implementation here:

http://netjs.codeplex.com/

Along with other goodies.

I recently came upon this article: http://www.2ality.com/2011/12/fake-operator-overloading.html .

It describes how you can redefine the valueOf method on objects to do something like operator overloading in javascript. It seems like you can only really perform mutator operations on the objects being operated on, so it wouldn't do what you want. Its interesting nonetheless tho.

Since everyone down voted my other answer I wanted to post proof of concept code which does in fact work as intended.

This has been tested in chrome and 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;

If someone would be so kind as to give a technical explanation WHY this is not good enough I would be happy to hear it out!

For some limited use cases you can have operator "overloading" effects:

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!

The above code is free to use under the MIT license. YMMV.

In addition to what already have been said: overriding .valueOf() may help to produce quite powerful operator overloading. In proof-of-concept Fingers.js lib you can add event listeners in .NET style:

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;

Core idea is to replace temporarily valueOf when on() is called:

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.
};

Number returned can be then de-serialized back into function using handlers array. What's more it's possible extract bit values from final value (func1 + func2 - func3) so effectively you can understand what functions where added, and what functions were removed.

You can check out source on github and play with demo here.

Complete explanation exists in this article (it's for AS3, tough since it's ecmascript it will work for JS either).

You can implicitly convert to integer or string, your objects.

Objects are only implicitly converted if JavaScript expects a number or a string. In the former case, the conversion takes three steps:

1.- Call valueOf(). If the result is primitive (not an object) then use it and convert it to a number.

2.- Otherwise, call toString(). If the result is primitive, use it and convert it to a number.

3.- Otherwise, throw a TypeError. Example for step 1:

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

If JavaScript converts to string, steps 1 and 2 are swapped: toString() is tried first, valueOf() second.

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

Paper.js does it, for example with point addition (docs):

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

But it does it using its own custom script parser.

I made a script that does operator overloading in JavaScript. It wasn't straight forward to make work, so there are a few quirks though. I will cross post the caveats here from the project page, otherwise you can find the link at the bottom:

  • Calculation results must be passed to a new object, so instead of (p1 + p2 + p3) you have to do new point(p1 + p2 + p3), (given your user defined object is named "point").

  • Only +, -, * and / are supported, the fifth arithmetic opperator % is not. Coercion to strings (""+p1) and comparisons (p1 == p2) will not work as expected. New functions should be built for these purposes if needed, like (p1.val == p2.val).

  • Finally the computational resources needed to calculate the answer increases quadratically with the number of terms. Therefore only 6 terms is allowed in one calculation chain per default (although this can be increased). For longer calculation chains than that, split the calculations up, like: new point(new point(p1 + p2 + p3 + p4 + p5 + p6) + new point(p7 + p8 + p9 + p10 + p11 + p12))

The Github page.