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 nullproducing"object"is an example of this. - New abilities are mostly added beside the old one rather than in its place;
letandconstexisting alongsidevaris this pattern. - A built-in ability’s existence is tested with a
typeofprobe; 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.