Recommendations for the Fourth Edition of the ECMAScript Programming Language Specification

Douglas Crockford
www.crockford.com

2007-06-21

The ECMAScript Programming Language (aka JavaScript) went from prototype to world wide acceptance in a remarkably short time. This led to premature standardization, locking some mistakes and inconveniences into the language specification.

Despite these flaws, the language has become one of the world's most popular programming languages. It has been embedded in a large class of applications, and its role as the language of the browser has expanded significantly with the advent of Ajax.

In the earlier years of JavaScript's development, one of its worst attributes was its instability due to frequent redefinition. One of modern JavaScript's best features is its stability. It hasn't changed in seven years. This is what allowed Ajax to happen. Ajax was impossible when the language was in flux. The browser is unlike most programming environments in that the execution environment is not dictated by the supplier of a program; it is determined by every individual who runs the program. The requirement to run in a multitude of browsers, with widely varying levels of quality and standards compliance, which can each have multiple revision levels still in the field, makes programming for the browser quite difficult.

We want to correct some of the errors in the design and specification of the language but without introducing instability. The changes considered here are corrections, deprecations, and some new features. New features must be of high value, and introduce no new syntax. This makes it possible for Ajax libraries running in older browsers (such as IE 6 and IE 7) to fill in features that will be present in more futuristic browsers.

New Features

Most of the new features are implemented as new members of existing objects. This makes it possible for a program to look for the presence of a feature without triggering a syntax error.

Function Reflection

ECMAScript has powerful reflection capabilities, but there is a blind spot with respect to functions. It is not possible to determine the name of a function or the names of its arguments without parsing the function's source text.

Functions should have a name property that returns the name of the function as a string. The name of anonymous functions is the empty string.

Functions should also have an arguments array, which contains the names of the arguments. So, given the example

function foo(bar, bas, bat) {...}

foo.name is 'foo' and foo.arguments is ['bar', 'bas', 'bat'].

typeOf

The typeof prefix operator returns a string based on the type of its parameter. Unfortunately, it provides the wrong result if the operand is null or an array.

The new typeOf global function is intended to replace the defective typeof operator. It produces the same result as typeof, except that it returns 'null' for null and 'array' for arrays.

It can be implemented in JavaScript:

function typeOf(value) {
    var s = typeof value;
    if (s === 'object') {
        if (value) {
            if (typeof value.length === 'number' &&
                    !(value.propertyIsEnumerable('length')) &&
                    typeof value.splice === 'function') {              
                s = 'array';
            }
        } else {
            s = 'null';
        }
    }
    return s;
}

Object Methods

Don't Enum

There should be a way of adding members to objects, particularly prototype objects, without them being enumerated by the for...in statement.

object.dontEnum(memberName)

This is a one-way operation: It cannot be undone.

Array of Keys

The keys method produces an array containing all of the enumerable keys of the object, in no particular order.

object.arrayOfKeys()

Array of Values

The values method produces an array containing the values from an object. By default, the array is filled with the enumerable values of the object. If a keys array parameter is supplied, then it provides the names of the members whose values should be included in the result, in the order determined by the keys.

object.arrayOfValues(keys)

toJSONString

object.toJSONString(memberName)

Beget Object

The begetObject method returns a new empty object whose [[Prototype]] is the original object.

object.begetObject()

It can be implemented as

Object.prototype.begetObject = function () {
    function F() {}
    F.prototype = this;
    return new F();
};

isEmpty

The isEmpty method returns true if the object (ignoring its prototype chain) has no properties.

It can be implemented as

Object.prototype.isEmpty = function () {
    var i;
    if (typeof this === 'object' || typeof this === 'function') {
        for (i in this) {
            if (this.hasOwnProperty(i)) {
                return false;
            }
        }
    }
    return true;
};

Array Methods

Most of these methods come from Mozilla's JavaScript 1.6.

array.toJSONString()

array.indexOf(value)

array.lastIndexOf(value)

array.every(func, thisObject)

array.filter(func, thisObject)

array.forEach(func, thisObject)

array.map(func, thisObject)

array.some(func, thisObject)

String Methods

string.parseJSON()

string.toJSONString()

string.trim()

String.prototype.trim = function () {
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}; 

string.eval(context)

The eval method replaces the current eval global function. The string is compiled as an expression and executed in its own environment, completely isolated from the current environment except that an optional context object can be supplied which provides global variable names and their values.

Example:

result = "a + b * c".eval({a: 1, b: 2, c: 3}); // result is 7 

Date Methods

date.toJSONString()

date.toISOString()

Corrections

Reserved Words

The ECMAScript Specification is unnecessarily restrictive in the limitations on the use of reserved words. These restrictions can be significantly relaxed.

Identifiers in the Object Literal Notation

The ECMAScript Specification does not allow the use of reserved words as identifiers in the key/value pairs in the literal object notation. There is no reason to have this restriction.

The following statement is currently illegal, producing syntax errors. It should be legal.

var object = {function: false, goto: "jail", var: 27};

Member Identifiers in Dot Notation

The ECMAScript Specification does not allow the use of reserved words in the dot notation. There is no reason to have this restriction.

The following statement is currently illegal, producing syntax errors. It should be legal.

if (object.function) {
    object.goto = object.var;
}

Reserved Word Reduction

Currently, the reserved word list is much too long. It not only includes ECMAScript's keywords, it also includes all of Java's reserved words, even including the words that Java does not use.

The list should be trimmed way back to include only the words that need to be reserved to ensure reliable source parsing.

Object Literal Notation

The use of commas should be made more regular.

Object

Allow a comma between the last value and the closing brace.

Array

Allow a comma between the last value and the closing bracket without increasing the length.

arguments

Make the arguments array a true array.

Inner Functions and this

The this variable is used to point at the container object of a function, providing a way for the function to access the object's members. The ECMAScript Specification requires that, for inner functions, this be set to point to the global object. This is bad for a couple of reasons.

First, it is often useful for an inner function to act on the same object that the outer method does. But because this is not retained when calling the inner function, this must be passed in as an explicit parameter. Second, accidents can easily occur in which global vars are unintentionally modified.

I recommend that when calling a function that the value of this be retained from the outer context except when new or a method invocation determine the value of this.

!!

The !! prefix operator is the smallest way of converting a value to a boolean It is already in the language. By recognizing it, it can be optimized.

Tail Recursion Optimization

If a function returns the result of recursively calling itself, then that call will be reformed as a loop. This optimization can significantly reduce the rate of memory consumption of recursive algorithms.

Deprecation

The following features should be depreciated. The Standard should offer advice that use of these features is not recommended, and that they may become illegal in a later revision of the standard.

Primitive type wrappers: new Object(value), new String(), new Number(), new Boolean().

The with statement.

Semicolon insertion.

arguments.callee.

typeof.

The eval() function.