Skip to content
academia.sh

Lesson 03 / 21

Language Versions and Standardization

How ECMAScript editions are versioned, proposal stages, the traces backward compatibility leaves in the language, and feature detection.

Contents

The previous lesson used module mode, the const declaration, and the for...of loop. None of these existed in the language’s first form; each entered the standard with a specific edition. Whether a feature “is in the language” and whether it “works in the environment you have” are separate questions, and their answers come from separate places.

This lesson covers how the standard is versioned, which stages a proposal passes through, and what lasting traces the concern for compatibility leaves in the language. It closes with a practical technique: testing whether an ability exists without looking at a version name.

Versioning the Standard

The language’s definition is the document numbered ECMA-262. The document is versioned as an edition; an edition is a complete text containing every feature accepted into the standard up to that point. Editions add on top of previous editions; a feature that has been published is not removed in a later edition.

Editions moved to a yearly schedule starting with ECMAScript 2015, and from that edition on are referred to by their publication year. This is a technical naming convention: the sentence “as of ES2015, block-scoped declarations exist” says which text defines them. An edition’s name says nothing about where that feature can be used; that question belongs to the environment.

Version names will be used in this course only where a behavior’s origin needs to be shown. For instance, the sentence “block scope arrived with ES2015” explains why the difference between var and let exists.

How a Feature Enters the Language

Proposals go through a staged process in the technical committee that maintains the language. The stages are numbered zero to four, and each stage has an entry criterion:

  • Stage 0 — idea. An unformatted proposal submitted to the committee.
  • Stage 1 — proposal. The problem to be solved, example usage, and possible difficulties are written down. The committee has found the problem worth taking up.
  • Stage 2 — draft. The syntax and semantics are written formally, in the standard’s language. Changes made after this stage are at the level of detail.
  • Stage 3 — candidate. The text is complete; the turn has come to implementation and usage feedback. At least one engine implements the feature.
  • Stage 4 — finished. Independent implementations and conformance tests are ready. The proposal enters the next edition.

The process has two consequences. First, a feature may work in some environments before it enters the standard; second, a proposal that has not reached stage 4 can still change. The observation “this syntax works this way right now” does not show that the syntax is permanent.

The Cost of Backward Compatibility

The strongest constraint the standard carries is that previously published pages keep working. This constraint leads even behaviors accepted as wrong to go uncorrected. It is one of the most visible examples of the backward compatibility concept defined in the Programming Fundamentals course’s Choosing a Language lesson.

console.log(typeof null);
console.log([] + {});
object
[object Object]

The first line’s result is not the outcome of a definition but of a coding choice made in the language’s first implementation. null is not an object; but having typeof null produce "null" would break code depending on this expression’s result. The second line is the type-coercion rules converting an array and an object to text and concatenating them; it will be worked through step by step in the Type Conversion and Coercion lesson.

These behaviors should be learned not as “oddities” but as rules. Each is written explicitly in the standard and can be traced step by step. This course’s Values and Types topic reads these steps.

The second consequence of the compatibility constraint is that new abilities mostly arrive as additions: the old behavior is not removed, a new path is opened beside it. let and const arriving alongside the var declaration is an example of this pattern; the old one keeps working, the new one offers a different scope rule. The difference between the two declaration kinds is the next lesson’s subject.

Feature Detection

The question “does this environment have this ability” is not answered by looking at a version name. How much of which edition an environment implements is learned by asking the environment itself. For methods added to built-in objects, the probe is direct:

console.log(typeof Array.prototype.map);
console.log(typeof Array.prototype.at);
console.log(typeof Object.groupBy);

In the environment the examples were run in, all three are found:

function
function
function

The same probe produces undefined in an environment that lacks the ability. This is the basis of writing fallback code:

const temperatures = [21.5, 19.75, 23, 18.25];

function lastElement(array) {
  if (typeof array.at === "function") {
    return array.at(-1);
  }
  return array[array.length - 1];
}

console.log(lastElement(temperatures));
18.25

Doing the probe with typeof matters: looking directly at an undeclared name throws a ReferenceError, while typeof produces "undefined" without error. Object properties carry no such risk, but a uniform habit prevents the wrong kind of probe.

Filling a Missing Ability

Detection reports an absence; it does not fix it. Code that stands in for a missing ability is called a polyfill. A polyfill should be defined only if the ability is missing, and it must not overwrite an existing implementation.

const builtinExists = typeof Array.prototype.at === "function";
console.log("at built-in:", builtinExists);

if (typeof Array.prototype.last !== "function") {
  Object.defineProperty(Array.prototype, "last", {
    value: function () { return this[this.length - 1]; },
    writable: true, configurable: true, enumerable: false,
  });
}
console.log([3, 7, 9].last());

const keys = [];
for (const k in [1, 2]) keys.push(k);
console.log("for...in keys:", keys);
at built-in: true
9
for...in keys: [ '0', '1' ]

The third line shows why the polyfill is not written as a plain assignment. A property added to the prototype by plain assignment becomes enumerable and gets caught up in for...in traversal; the definition here gives enumerable: false, so the array’s own keys stay unchanged.

Two rules follow. First, if a built-in ability exists, it is left untouched — a polyfill whose behavior differs from the standard breaks the assumptions of later code. Second, adding a non-standard name (like last) carries a risk of colliding with the standard; if the same name later enters it, the two definitions come face to face.

Why Syntax Cannot Be Detected

A method probe only works for things that exist at runtime. New syntax cannot be tested this way, because the entire file is parsed before it runs. As seen in the first lesson, a parsing error runs none of the file’s lines. In an environment that does not recognize a piece of syntax, the file containing that syntax is rejected in its entirety; a check written inside it never gets the chance to run either.

This is why there are two separate tool classes for closing a missing ability:

  • A transpiler converts new syntax into old syntax. It solves the parsing problem by changing the source.
  • A polyfill defines a missing built-in method at runtime. It cannot change syntax; it only adds missing objects and methods.

The two solve different problems and cannot substitute for each other. This course uses neither; its examples are written with syntax that can be run directly.

Reading the Standard Itself

The standard’s text is not a teaching book; it is an exact definitional document. Once its structure is learned, it gives an indisputable answer to the question “why did this expression produce this result.” In the text, every operator and every built-in method reduces to a call of an abstract operation defined by numbered steps. The addition operator, for example, first calls an abstract operation that converts both operands to a primitive value; whether the result is a numeric sum or a text concatenation is decided based on that step’s result.

This course’s Values and Types topic will follow these steps. The goal is not to memorize the result, but to be able to follow the steps that produce it. Once followed once, the result of an expression not yet encountered can be found by the same method.

Summary

  • The language’s definition is the ECMA-262 document; editions are cumulative and published features are not removed.
  • Proposals go through five stages numbered zero to four; a proposal that reaches stage 4 enters the next edition.
  • Backward compatibility leads even behaviors accepted as wrong to be preserved; typeof null producing "object" is an example of this.
  • New abilities are mostly added beside the old one rather than in its place; let and const existing alongside var is this pattern.
  • A built-in ability’s existence is tested with a typeof probe; syntax, being evaluated at the parsing stage, cannot be tested at runtime.

Next Step

The declaration kinds standing side by side was this lesson’s closing observation. The next lesson compares these three declarations: the difference between var, let, and const is not merely a matter of writing style — it determines which region a name is seen in, when it becomes accessible, and whether it can be rebound. The measurement script will be brought into a form where this difference can be observed.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close