Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make sure ES3 programs will run in an ES5 engine?

So ECMAScript 5 introduces some incompatibilities with ECMAScript 3.


Example:

Many articles have been written stating that this === null || this === undefined is possible in ES5 strict mode:

"use strict";
(function () {
    alert(this); // null
}).call(null);

But, what the standard really suggests is that ES5 engines also allow this in non-strict mode:

15.3.4.3 ... The thisArg value is passed without modification as the this value. This is a change from Edition 3, where a undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the this value.

Currently, IE9 is the only browser that actually implements ES5 this way, and it turns out that this may break current scripts. Great.


Annix E of ES5 spec lists dozens of other incompatibilities.

So what's the best way to make sure that our well-tried ES3 scripts will continue running flawlessly? Some kind of automated test-suite? Will we have to test it all manually?

like image 229
user123444555621 Avatar asked Oct 22 '10 12:10

user123444555621


People also ask

What is ES5?

ES5 is a shortcut for ECMAScript 5. ECMAScript 5 is also known as JavaScript 5. ECMAScript 5 is also known as ECMAScript 2009.

What is the strict mode in JavaScript and how can it be enabled?

The purpose of "use strict" is to indicate that the code should be executed in "strict mode". With strict mode, you can not, for example, use undeclared variables. The numbers in the table specify the first browser version that fully supports the directive. You can use strict mode in all your programs.

What is JS strict mode?

JavaScript's strict mode is a way to opt in to a restricted variant of JavaScript, thereby implicitly opting-out of "sloppy mode". Strict mode isn't just a subset: it intentionally has different semantics from normal code.

Why this is undefined in strict mode?

In strict mode, it is now undefined . When a function was called with call or apply , if the value was a primitive value, this one was boxed into an object (or the global object for undefined and null ). In strict mode, the value is passed directly without conversion or replacement.


2 Answers

For the record, the questioner's interpretation of ES5 15.3.4.3 is incorrect. Any call to a non-strict function in ES5 should be observably the same as in ES3. The global object is still passed into any non-strict function that is called with null or undefined as the this value.

The missing piece of the analysis is 10.4.3 "Entering Function Code":

The following steps are performed when control enters the execution context for function code contained in a function object F, a caller provided thisArg, and a caller provided argumentsList:

  1. If the function code is strict code, set the ThisBinding to thisArg.
  2. Else if thisArg is null or undefined, set the ThisBinding to the global object.
  3. ...

ES3 specified that the caller was responsible for substituting the global object for a null or undefined this value. ES5 specifies that the callee has that responsiblity (if it isn't a strict mode function). For non-strict code this is not an observable difference. The specification change only makes a difference when the callee is a strict function.

like image 154
Allen Wirfs-Brock Avatar answered Sep 18 '22 11:09

Allen Wirfs-Brock


Automated test suite is certainly a good idea.

Since more and more implementations implement ES5 now, running the test suite for your script/library/application in newer browsers is a good way to ensure compatibility.

I have an ES5 compatibility table, listing level of support of some of the more popular implementations. Its not exhaustive, but it shows overall direction — that latest IE, WebKit, Chrome and Firefox all have quite good ES5 support. For a complete conformance test, you can always run official ES5 test suite (which I have online for convenience right here).

If there's no test suite (which should really exist, as it is very useful for few other reasons), you can just run script/library/application in one of the newer (ES5 conforming) implementations and see what works and what fails.

Consulting Annex E is another way to go. Note that even though the list seems quite large, it's not as bad as it seems. One of the goals of ES5 was to make transition from ES3 more or less painless, moving more radical changes into the realm of opt-in strict mode.

Many compatibility changes from that list are likely to go unnoticed. Take for example, change in 15.1.1, where global undefined, NaN and Infinity are now read-only. Considering that sane applications do not reassign these global properties — except by mistake — this change is more of a pleasant "error-catcher" rather than "app-breaker".

Another liekly innocent change is in 15.10.2.12, where whitespace character class (\s) now also matches <BOM> (U+FEFF) character. Considering all the deviations in current implementations (even in regards to ES3), this change is likely to go unnoticed in majority of applications.

However, there are also more dangerous changes, such as that in parseInt and how it no longer treats strings beginning with 0 as octal values. parseInt('010') should not produce 8 anymore (although some implementations chose to deliberately violate that behavior). And still, relying on parseInt without second "radix" argument was never a good practice. So if your application always specifies radix, there's nothing to worry about.

So consult Annex E, test your script in newer implementations (preferably, multiple ones), and follow best practices. That's a good way to ensure compatibility.

like image 45
kangax Avatar answered Sep 19 '22 11:09

kangax