As a general rule, operator overloading should not be considered as a good programming practice. This feature is overlooked by the JS ExtendScript developers because the situations in which they really benefit from redefining an operator are infrequent and unstable. In most cases, it is far better to explicitly create and invoke the underlying operations through normal object methods.

An operator is nothing but a method. For example, the Addition operator internally corresponds to a prototype['+'] method which is defined in many native JavaScript classes. Number.prototype['+'] implements the regular addition, String.prototype['+'] implements the string concatenation, etc. The process of defining a '+' behavior in a new class is similar to creating a prototype.add method:

MyClass.prototype.add = function(/*MyClass*/ obj)
{
var ret = new MyClass();
//
// Here you implement ret = this.add(obj)
//
return ret;
}
 
// Usage:
var a = new MyClass(/*a_parameters*/);
var b = new MyClass(/*b_parameters*/);
 
var c = a.add(b);
// etc.
 

Now, to plug a + operator in MyClass, we just need to replace the method name ('add') by the operator name ('+'):

MyClass.prototype['+'] = function(/*MyClass*/ obj)
{
var ret = new MyClass();
//
// Here you implement ret = this + obj
// (this is the left operand, obj is the right operand)
//
return ret;
}
 
// Usage:
var a = new MyClass(/*a_parameters*/);
var b = new MyClass(/*b_parameters*/);
 
var c = a + b;  // means: c = a['+'](b)
// etc.
 

In the above code, a + b is interpreted by ExtendScript as a['+'](b), so this syntactically shortens the method call. The + operator is then available to any MyClass object instance.

ExtendScript allows you to extend —or override!— a number of operators:

Operator Expression Default Semantics (ECMA)
Unary + +X Converts X to Number.
Unary – –X Converts X to Number and then negates it.
Addition X + Y Performs string concatenation or numeric addition.
Subtraction X – Y Returns the difference of the numeric operands.
Equiv: X + (–Y).
Multiplication X * Y Returns the product of the numeric operands.
Division X / Y Returns the quotient of the numeric operands.
Modulus X % Y Returns the floating-point remainder of the numeric operands from an implied division.
Left Shift X << N Performs a bitwise left shift operation on X by the amount specified by N.
Signed Right Shift X >> N Performs a sign-filling bitwise right shift operation on X by the amount specified by N.
Unsigned Right Shift X >>> N Performs a zero-filling bitwise right shift operation on X by the amount specified by N.
Equals X == Y Performs an abstract equality comparison and returns a boolean.
Less-than X < Y Performs an abstract relational less-than algorithm and returns a boolean.
Less-than-or-equal X <= Y Performs an abstract relational less-than-or-equal algorithm and returns a boolean.
Strict Equals X === Y Performs a strict equality comparison.
Bitwise NOT ~X Converts X to signed 32-bit integer and performs a bitwise complement.
Bitwise AND X & Y Performs a signed 32-bit integer bitwise AND.
Bitwise OR X | Y Performs a signed 32-bit integer bitwise OR.
Bitwise XOR X ^ Y Performs a signed 32-bit integer bitwise XOR.

Note 1. — You cannot override the following JavaScript operators:
• Assignment and compound assignment operators (=, *=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |=).
• Prefix/Postfix Increment/Decrement operators (++, --).
• Logical operators (!, &&, ||), and the conditional operator ( ? : ).
• Syntactic operators (new, delete, typeof, void, instanceof, in), and the comma operator ( , ).

Note 2. — The > and >= operators can't be overridden directly; instead, they are internally implemented by executing NOT < and NOT <=. Similarly, JavaScript performs the != and !== operators in terms of NOT == and NOT ===.

Note 3. — “The comparison of Strings uses a simple lexicographic ordering on sequences of code unit values. There is no attempt to use the more complex, semantically oriented definitions of character or string equality and collating order defined in the Unicode specification.” (ECMA-262.)

Example 1 — Cumulative Arrays

The JavaScript Array class has no math operator prototype, probably because summing or multiplying arrays is not semantically univocal. Even if one considers numeric arrays only, there are several ways to envision the addition, or the multiplication. For instance A + B could be interpreted as a concatenation, while A * B could be regarded as a matrix product. (Note that since A + B has no default meaning for Array objects, JS will convert this expression into A.toString() + B.toString() —which is not very exciting.)

But suppose that every array in your script describes an ordered set of amounts in the form [x1, x2, ..., xn]. One can define a simple addition routine such as:
[a1, a2, ..., an] + [b1, b2, ..., bn]
= [ (a1+b1), (a2+b2), ..., (an+bn) ].

Providing that your algorithm requires a lot of similar operations, the code could be dramatically simplified by using an Array.prototype['+'] operator as following:

Array.prototype['+'] = function(/*operand*/v)
{
// If the right operand is not an Array, we return undefined
// to let JavaScript behave defaultly
if( v.constructor != Array ) return;
 
var i = this.length,
    j = v.length,
    // clone this and concat if necessary
    // the overflowing part of v:
    r = this.concat(v.slice(i));
 
if( i > j ) i = j;
while( i-- ) r[i] += v[i];
 
return r;
}
 
// Sample code (all operations are commutative)
var a = [1,2,3];
alert( a + [4,5,6] ); // [5,7,9]
 
// Our implementation also supports
// heterogeneous lengths:
alert( a + [4,5] ); // [5,7,3]
alert( a + [4,5,6,7] ); // [5,7,9,7]
alert( a + [] ); // [1,2,3]
 

When defining or overriding an operator you must keep in mind these important rules:

Unary Operators. — Unary + (+X), unary – (–X) and bitwise NOT (~X) can be overridden. In this case, the first argument passed to aClass.prototype[OPERATOR] is undefined and the unique operand is this. If you define both unary + and binary + behavior, make sure you provide an implementation for each situation in the function body.

Operator Precedence and Associativity. — ExtendScript applies the rules defined in the JavaScript Operator Precedence & Associativity Chart. For example, X + Y * Z is always interpreted as X['+'](Y['*'](Z)), and X * Y * Z is always interpreted as (X['*'](Y))['*'](Z) (left-to-right).

Commutativity and External Binary Operations. — JavaScript does not assume that an operator is commutative. X•Y is often different from Y•X (concatenation, division, etc.). In general this is not a problem, because internal binary operations mechanism lets you clearly distinguish the left operand (this) from the right operand (first argument passed to the method). However, external binary operations can introduce a more complex procedure: if objectA.prototype['•'](objectB) is not implemented, ExtendScript tries to invoke objectB.prototype['•'](objectA, true). The second argument (true) is then a boolean flag that indicates to objectB that the operands are provided in reversed order, so that the operation to perform is A•B (and not B•A).

Example 2 — Scalar Operations on Arrays

Let's consider an example to clarify the last point. Suppose we've defined a scalar multiplication in our Array linear algebra:

Array.prototype['*'] = function(/*operand*/k, /*reversed*/rev)
{
if( k.constructor != Number ) return;
 
// Here we implement an "external" binary op.:
// ARRAY * NUMBER
 
var i = this.length,
    r = this.concat(); // clone this
 
while( i-- ) r[i] *= k;
return r;
}
 
// Sample code:
var a = [1,2,3];
 
// array * number is directly implemented
// (reversed == false)
alert( a * 5 ); // [5,10,15]
 
// number * array is indirectly implemented
// it's equivalent to array['*'](number, true)
// (reversed == true)
alert( 5 * a ); // [5,10,15] (works also!)
 

We are lucky! Since the scalar multiplication is commutative, both a * 5 and 5 * a are perfectly computed, returning the same result. In the second case (5 * a), ExtendScript internally invokes a['*'](5,true) —provided that there is no support for Number.prototype['*'](Array).

Now let's implement a scalar division using the same approach. There is a critical difference: a/5 means something —i. e. a*(1/5),— but 5/a should be rejected until we have not defined Array inversion. So we must use the reversed flag to check the order of the operands, and restrict the operation to the Array/Number syntax.

Array.prototype['*'] = function(/*operand*/k)
// Scalar multiplication is OK : Array * Number == Number * Array
{
if( k.constructor != Number ) return;
 
var i = this.length,
    r = this.concat();
 
while( i-- ) r[i] *= k;
return r;
}
 
Array.prototype['/'] = function(/*operand*/k, /*reversed*/rev)
// Scalar division : Array / Number only!
{
if( k.constructor != Number ) return;
if( rev ) return;  // Hey! We do not implement Number / Array !
return this*(1/k);  // Using already defined Array.prototype['*']
}
 
// Sample code:
var a = [10,20,30];
 
// array / number is implemented
alert( a / 10 ); // [1,2,3]
 
// number / array is not implemented
alert( 10 / a ); // NaN
 

Note. — When working on Array objects, never forget to clone the data in a new array when you want to return an independent object. Unlike strings, which are automatically cloned by JavaScript when required, arrays are always assigned “by reference”. If you write something like arr2 = arr1, the arr2 variable will still reference the actual elements of arr1 and not a copy of them. Therefore, modifying arr2[i] will also modify arr1[i] (which is the same “thing.”) An usual way to make a copy of arr1 is: arr2 = arr1.concat(), or arr2 = arr1.slice(). You then obtain a shallow copy, but this is sufficient for one-dimensional arrays that only contain primitive data.

Example 3 — A Complex Class

Geometrical scripts use and re-use point transformations (translation, rotation, scaling . . .) that generally can be described as operations in a two-dimensional real vector space. Rather than prototyping Array operators, a possible way to handle [x,y] points in your process is to create a Complex class. Each complex number z = x + iy represents a point in the coordinate system (complex plane).

The code below offers various methods and operators that make really easy to compute such operations:

var Complex = (function()
{
// UTILITIES
// ---------------------------
var mSqr = Math.sqrt,
    mCos = Math.cos,
    mSin = Math.sin,
    mAtan2 = Math.atan2,
    kDeg = 180/Math.PI,
    kRad = Math.PI/180,
    m2 = function(){return this.x*this.x+this.y*this.y;},
    eq = function(z){return this.x==z.x && this.y==z.y;},
    cx = function(z){return z instanceof cstr;},
    df = function(z){return cx(z)?z:{x:+(z||0),y:0};};
 
// CONSTRUCTOR
// ---------------------------
var cstr = function Complex(re,im)
    {
    this.x = +re;
    this.y = +im;
    };
 
// INTERFACE (INCLUDING OPERATORS)
// ---------------------------
cstr.prototype = {
    toString: function()
        {
        return [this.x,this.y].toString();
        },
    valueOf: function()
        {
        return (this.y)?NaN:this.x;
        },
    // MAGNITUDE : |Z|
    mag: function()
        {
        return mSqr(m2.call(this));
        },
    // INVERSION : 1/Z
    inv: function()
        {
        if( this==0 ) return NaN;
        return (~this)*(1/(m2.call(this)));
        },
    // ARGUMENT (PHASE) in radians
    arg: function()
        {
        if( this==0 ) return NaN;
        return mAtan2(this.y,this.x);
        },
    // ARGUMENT (PHASE) in degrees
    deg: function()
        {
        return kDeg*this.arg();
        },
    // ADDITION
    "+": function(z)
        { // Supports: +Z | Z+X | X+Z | Z+Z'
        var xy = df(z);
        return new cstr(
            this.x + xy.x,
            this.y + xy.y
            );
        },
    // MULTIPLICATION
    "*": function(z)
        { // Supports: Z*X | X*Z | Z*Z'
        var xy = df(z);
        return new cstr(
            this.x*xy.x - this.y*xy.y,
            this.x*xy.y + this.y*xy.x
            );
        },
    // SUBTRACTION
    "-": function(z,rev)
        { // Supports: -Z | Z-X | X-Z | Z-Z'
        if( !z ) return new cstr(-this.x,-this.y);
        return (rev)?(z+(-this)):(this+(-z));
        },
    // DIVISION 
    "/": function(z, rev)
        { // Supports: Z/X | X/Z | Z/Z'
        if( cx(z) ) return this*(z.inv());
        return (rev)?(z*this.inv()):this*(1/z);
        },
    // EQUALITY
    "==": function(z)
        { // Supports: Z==X | X==Z | Z==Z'
        return eq.call(this,df(z));
        },
    // CONJUGATE FORM
    "~": function()
        { // Supports: ~Z
        return new cstr(this.x,-this.y);
        },
    // INTEGER POWER
    "^": function(n,rev)
        { // Supports: Z^N
        if( rev || n!==~~n ) return NaN;
        if( n==0 ) return cstr.unity;
        if( n<0 ) return 1/(this^(-n));
        var r = +this; // clone
        while( --n ) r*=this;
        return r;
        },
    // ROTATION
    "<<": function(/*degrees*/aDeg,rev)
        { // counterclockwise rotation about [0,0]
        if( rev ) return NaN;
        if( !aDeg ) return +this;
        var a = aDeg*kRad,
            z = new cstr(mCos(a),mSin(a));
        return this*z;
        },
    ">>": function(/*degrees*/aDeg,rev)
        { // clockwise rotation about [0,0]
        if( rev ) return NaN;
        return this<<(-aDeg);
        },
    };
 
// CONSTANTS
// ---------------------------
cstr.zero = new cstr(0,0);
cstr.unity = new cstr(1,0);
cstr.i = new cstr(0,1);
 
return cstr;
})();
 
 
 
// Sample code
// ---------------------------
var z = new Complex(4,3),
    i = Complex.i;
 
// magnitude and phase
var m = z.mag();
alert( m ); // 5
alert( z.deg() ); // about 37°
 
// basic operations
alert( 2*z - 4*i ); // [8,2]
alert( -(z*~z)/(5*i) ); // [0,5]
 
// comparisons
alert( (i^2) == -1 ); // true
alert( 0 == Complex.zero ); // true
alert( (m*m)/z == ~z ); // true
 
// rotation
z <<= 30;
alert( z ); // about [2,4.6]
 

As you can see, once the Complex class is available in your code, you can handle complex numbers as easily and profitably as simple Number objects!