Tags: array, bug fixes, compatibility requirements, es3, expressions, global names, global object, implementation, incompatibilities, level names, meta programming, prototype, variables, world wide web,
Compatibility Between ES3 and Proposed ES41
Revision 1, 29 November 2007
This note describes the level of compatibility between ECMAScript 3rd Edition ("ES3") and Proposed
ECMAScript 4th Edition ("ES4").
All known incompatibilities between the two editions are divided into groups: true incompatibilities; new
names introduced by ES4 (as properties and global names); and behavior that is implementation-dependent
or unclear in ES3 but is mandated or clarified in ES4. Of the twelve true incompatibilities listed below, at
most two--read-only top-level names and changes to the computation of this in certain calls--have not
already been implemented in one or more ES3 implementations2 in widespread use on the world-wide web,
where the compatibility requirements for the language are likely the most stringent. Of the other, non-true
incompatibilities, most are implemented in one or more of those ES3 implementations.
Additionally, ES4 incorporates some behavior strictly to be backward compatible with ES3, and avoids
making some obvious bug fixes for the same reason. Both groups are outlined later in this paper.
1. True Incompatibilities 2
1.1. Evaluation of a RegExp Literal Results in a new RegExp 2
1.2. RegExp.prototype is a RegExp 2
1.3. The Value of length on Function Objects 3
1.4. Top-level Names are Read-Only, not Meta-Programming Hooks 3
1.5. Language Constructs Use the Global Object and Array Classes 4
1.6. Bindings for catch Variables and Named function Expressions 5
1.7. Computation of this in Calls to Lexically Nested Functions 6
1.8. New Keywords 6
1.9. Order of Conversion Operations in some Comparisons 7
1.10. No Stripping of Unicode "class Cf" Characters 7
1.11. The DontDelete Status of a Variable Cannot be Changed by eval 8
1.12. Canonicalize Does not Call String.prototype.toUpperCase 8
2. New Global Names 9
3. New Property Names 9
3.1. Prototype Methods 9
3.2. Value Properties 10
3.3. Class Methods 10
3.4. The arguments Object 11
3.5. String Indexing 11
4. Implementation Dependencies 12
4.1. Date.parse Must Give Precedence to ISO Dates 12
4.2. Property Enumeration Order 12
5. Compatibility-Preserving Decisions 12
5.1. Automatic Wrapping of "Primitive" Values in Some Contexts 12
5.2. Bug Fixes That Were Not Made 13
1
Based on the 2007-10-23 Proposed ECMAScript 4th Edition Language Overview, the (unpublished) 2007-09-20 Library
Specification draft, and the ES4 wiki and bug tracking system. See http://www.ecmascript.org/.
2
The ES3 implementations are Internet Explorer 6, Firefox 2.0, Opera 9.20, and Safari 3 (beta), all on Windows XP SP2.
1
1. True Incompatibilities
A true incompatibility between ES3 and ES4 is defined as:
Any change to ECMAScript introduced in ES4 where code written for a conforming ES3
implementation would generate a different answer in a conforming ES4 implementation,
provided the answer is not dependent on implementation-dependent or illegal (exception-
throwing) behavior in ES3.
Not included in the definition of true incompatibility are language extensions that are explicitly allowed by
Chapter 16 of the 3rd Edition Specification (E262-3) such as the appearance of new global names, new
properties on predefined objects or their prototypes, or compatible syntax extensions.
1.1. Evaluation of a RegExp Literal Results in a new RegExp
Description
In ES3 a regular expression literal like /a*b/mg denotes a single unique RegExp object that is created the
first time the literal is encountered during evaluation. In ES4 a new RegExp object is created every time
the literal is encountered during evaluation.
Rationale
This is a bug fix. The create-once behavior in ES3 is contrary to programmer expectation.3 If a RegExp
object created by a literal is used for matching, its lastIndex property will be left in a non-initial state
after a match; programmers who expect lastIndex to be reset to zero the next time the literal is evaluated
(because they expect a new RegExp object to be created) will be surprised. The bug bites even advanced
programmers.
Impact
The changed behavior is observable because RegExp objects are mutable and the literal no longer
corresponds to a single, unique object. In practice the change is most observable in that the lastIndex
property of the (new) RegExp object is in an initial state every time the literal is evaluated, as desired.
Implementation precedent
Internet Explorer 6 and Safari 3 create a new RegExp object every time the literal is encountered during
evaluation.
1.2. RegExp.prototype is a RegExp
Description
In both ES3 and ES4 the prototype object for a constructor is of the same type as the objects returned by the
constructor. For example, Array.prototype is of type Array. ES3, however, makes an exception for the
RegExp type: RegExp.prototype is a plain Object. ES4 removes the exception.
Rationale
This is a bug fix. The restriction in ES3 has no clear motivation, and it is not entirely consistent. For
example, RegExp.prototype.toString requires its this object to be a RegExp object, but because
RegExp.prototype is itself not a RegExp, the expression String(RegExp.prototype) will always
throw a TypeError (unless the toString method is replaced by the user program).
Impact
Programs can observe that the class name of RegExp.prototype has changed.
Programs that transfer the RegExp prototype to other constructor functions will find that objects
constructed by those functions have additional properties (in their prototype).
3
Evidence of this may be found at https://bugzilla.mozilla.org/show_bug.cgi?id=98409.
2
Implementation precedent
Internet Explorer 6, Firefox 2, Opera 9.20, and Safari 3 all have a RegExp.prototype object that is itself
a RegExp.
1.3. The Value of length on Function Objects
Description
In ES3 some predefined function objects have length properties whose values deviate from the standard
rule, which requires that the length property represent the number of formal parameters in the function
definition (or in ES4 terms, the number of fixed and optional parameters, not counting this type-bounds
or rest arguments). The standard rule is followed for all function objects in ES4. As a consequence, the
following predefined function objects have new length values:
Function ES4 value ES3 value
Function 0 1
Array 0 1
Math.max 0 2
Math.min 0 2
Array.concat 0 1
Array.push 0 1
Array.unshift 0 1
String.fromCharCode 0 1
String.prototype.concat 0 1
String.prototype.indexOf 2 1
String.prototype.lastIndexOf 2 1
Rationale
This is language cleanup. The irregularities in ES3 are not clearly motivated by utility. Furthermore, since
ES4 allows the formal parameter lists of predefined functions to be expressed with greater clarity (using
optional and rest arguments) than does ES3, following the common rule in ES4 makes more sense.
Eliminating special cases is good for the language in general and also benefits language implementations.
Impact
The change is visible to programs that inspect or act on the values of the length properties of functions.
Implementation precedent
Firefox 2 has String.prototype.concat.length = 0. Internet Explorer 6 has
String.fromCharCode.length = 0, String.prototype.indexOf.length = 2, and
String.prototype.lastIndexOf.length = 2. Opera 9 is known to deviate from ES3 in the value of
length properties on predefined methods not listed in the table.
1.4. Top-level Names are Read-Only, not Meta-Programming Hooks
Description
The following top-level bindings were writable in ES3 but are read-only in ES4.
· The primitive values undefined, NaN, and Infinity
· The following names that were constructor functions in ES3 and are classes in ES4: Object,
Function, Array, String, Boolean, Number, Date, RegExp, Error, EvalError,
RangeError, ReferenceError, SyntaxError, TypeError, and URIError
There are metaprogramming and aspect-oriented programming use cases for allowing the predefined
constructors in ES3 to be changed, and the ES3 specification incorporates language that requires the
implementation to use the current value of Object (E262-3 11.1.6, 12.14, 13), Array (E262-3 11.1.4,
15.4.4.4, 15.4.4.10, 15.4.4.12, 15.5.4.14), or Date (E262-3 15.9.2.1). The user program can override the
Object constructor, say, to add extra properties to every new Object instance without the cooperation of
the user program.
3
Rationale
This is a bug fix. It is silly to be able to redefine undefined, NaN, and Infinity--programmers want to
depend on these values. (Programmers write the more obscure (void 0), (0/0), and (1/0) to avoid
having to depend on these names).
The mutability of constructors in ES3 has three problems. One problem is that the user-defined
constructors are not always called. When the implementation creates new objects of some types E262-3
sometimes mandates the use of the original constructor and prototype values instead of the program
overrides, and inconsistent data may result.
The second problem is that the changed constructors are called when the program might wish them not to
be. When the implementation evaluates object and array initializer expressions--{ x: 10 } and
[10, 20]--it is supposed to create the new objects as if by "new Object" and "new Array". Scripts
cannot always guarantee that data will not be stolen.4 (The next section describes a related problem.)
Finally, the predefined constructors in ES4--in the form of classes--are used as type names in annotations
on variables and parameters, as well as in type dispatched control structures, and allowing the predefined
bindings to be changed would make ES4 code unpredictable at best.
Impact
The change will be visible to programs that try to update these bindings, in two ways: first, the values are
not updated (though no exception is thrown); second, metaprogramming techniques that rely on being able
to change the bindings will no longer work.
Implementation precedent
No ES3 implementations are known to make these top-level names read-only, and the impact of that
change is therefore unknown. However, ES3 implementations do not always respect E262-3 with regard to
invoking user-defined constructors, so there is precedent for blocking the metaprogramming techniques.
The next section describes two important cases in more detail.
1.5. Language Constructs Use the Global Object and Array Classes
Description
ES3 requires object and array initializers, catch clauses, and named function expressions to construct
new objects "as if by new Object" or "as if by new Array". The meaning, if not the intent, of the
requirement is that the appropriate new expression is evaluated in the scope of the language construct
occurrence, with the consequence that any non-global binding of the names Object and Array will be
used by the expression.
ES4 requires the global, standard bindings of Object and Array to be used to construct new objects for
object and array initializers. As described in the next section, ES4 also requires that catch clauses and
named function expressions not allocate objects in the way required by E262-3.
Rationale
This is a bug fix. The purpose of the ES3 requirement is to allow the user program to redefine the Object
and Array constructors globally and have literals create instances of the new types, not to allow the user
program to pick up arbitrary bindings for those names.
Impact
Programs that depend on being able to pick up shadowing bindings may experience changed behavior.
Additionally, as described in the previous section, the global Object and Array are read-only in ES4 and
cannot be redefined. Object and array initializers will always create instances of the predefined classes.
4
See http://getahead.org/blog/joe/2007/03/05/json_is_not_as_safe_as_people_think_it_is.html for a broader study.
4
Implementation precedent
Internet Explorer 6, Opera 9.20, and Safari 3 do not respect either local or global rebindings of Object and
Array, but use the original Object and Array constructors.
1.6. Bindings for catch Variables and Named function Expressions
Description
ES3 specifies that the variable name introduced by a catch clause and the function name introduced by a
named function expression should both be bound by creating a new Object instance which is pushed on
the scope chain and in which a property of the name is created to hold the value. Because the names are
bound by objects that are not activation objects, those objects become the value of this in the invocation
should the name be called as a function (E262-3 11.2.3), and the binding objects will therefore become
visible to the program. If the program adds properties to a binding object, those will become visible to the
program because of the with-like scoping mechanism used to introduce that object.
An example:
function f() {
this.x = 10
}
function g() {
var x = 20
try {
throw f
}
catch (exn) {
exn()
print(x) // prints 10, not 20
}
}
g()
ES4 specifies that these names should both be bound as if by let, that is, by a block scope object. Block
scope objects are treated like activation objects, and this will either be unchanged or the global object in
the invocation. (See also the next section, "this").
Rationale
This is a bug fix. The ES3 behavior is most likely the consequence of reusing the with mechanism for
let-like bindings without fully considering how that would make the private scope objects visible to the
program as part of the function calling machinery.
There is also a security hole lurking in the ES3 behavior. The specification requires the binding object to
be created "as if by `new Object'", so the problem with mutable top-level bindings described in the
previous section applies here as well.
Impact
The change is visible to programs that invoke the values of those bound names as functions and then access
the this value, which will never be the binding object in ES4.
Implementation precedent
Firefox 2 and Internet Explorer 6 implement let-like scoping for catch clauses. Opera 9.20 implements
let-like scoping for named function expressions.
5
1.7. Computation of this in Calls to Lexically Nested Functions
Description
ES4 specifies that when a function that is defined by a FunctionDefinition lexically nested inside some
other function is called directly by naming it in the call, then the caller's value of this is transferred to the
callee. (The called function does not need to be lexically nested inside the calling function, just inside
some function--the called function could be a function containing the caller, or a sibling of such a
function.) ES3 specifies that the value of this would be the global object in that case.
For example, this program prints "local" in ES4 but "global" in ES3:
var x = "global"
var o = { m: function () {
function g() { print(this.x) }
g()
},
x: "local"
}
o.m()
Rationale
This is a bug fix. Reverting the value of this to the global object is not useful behavior, and creates a
hazard where global variables may be set or created unintentionally. The changed behavior especially
supports ES3-style method functions that abstract common functionality as functions, or that use functional
abstraction to clarify the code.
Impact
Code that attempts to get at the global object by invoking a function that just returns this can no longer do
that under the circumstances described above.
Implementation precedent
Unknown.
1.8. New Keywords
Description
ES4 defines as keywords some identifiers that are valid names in ES3. Most of these keywords are only
reserved in some contexts, like override, but others are reserved everywhere, like class, let, and
yield. Some of the keywords are future reserved words in ES3 and should not occur in ES3 programs,
but in practice ES3 implementations allow them to be used as names and programs do.
The consequence of the larger set of keywords is that there are some ES3 programs that are not valid ES4
programs, because they use the ES4 keywords as names in contexts where ES4 does not allow them.
(Note here that eval(s) does not suffer from a compatibility problem because it uses the ES3 keyword set
unless an extra parameter is passed asking for the ES4 keyword set.)
Rationale
This is a consequence of language evolution. The committee felt it was necessary to introduce new
syntactic facilities into ES4 that required the new keywords.
Every evolving programming language struggles with this problem; in ES4 it has been sought to minimize
the problem by making most new keywords contextual, and allowing any keyword to be used in some
contexts such as property names.
Impact
ES4 implementations that accept arbitrary ES3 content must make it possible for the provider of the content
to specify the dialect; the provider must on the other hand specify the dialect (typically by a MIME type).
6
Implementation precedent
Firefox 2 supports the let and yield keywords if the script that uses them is loaded with the MIME type
"application/javascript;version=1.7".
1.9. Order of Conversion Operations in some Comparisons
Description
ES3 specifies the binary comparison operators (, =) in terms of an abstract comparator algorithm
(E262-3 11.8.5) that is called by the (semantic) functions that implement the comparison operators. A
semantic function passes the operator's two operands as arguments when calling the comparator algorithm.
The comparator algorithm subsequently invokes the internal operator ToPrimitive on the two arguments
in the order they are received. The calls to ToPrimitive are significant in that they may have user-
observable effects because they may invoke user-defined valueOf or toString methods on the operand.
Unfortunately, the semantic functions that call the comparator algorithm sometimes pass the operator's left-
hand-side argument followed by its right-hand-side argument, and sometimes the reverse. Thus the
program may observe that (a > b) has right-to-left order of evaluation as observed by the sequence of
methods calls.
ES4 specifies that the left-hand-side argument to the comparison operator is converted to primitive before
the right-hand-side argument is.
Rationale
This is a bug fix. Elsewhere, ES3 is careful about maintaining left-to-right order of evaluation, and the
behavior for the comparison operators is a language bug.
Impact
Programs that depend on right-to-left order of primitive conversion for > and