The early editions: ES1 to ES5
Learn how JavaScript was standardized from ES1 to ES5, why ES4 was abandoned, and what ES5 added: strict mode, JSON and property descriptors.
- 01Tell the editions apartSay what ES1, ES3 and ES5 each added, and why there was no ES4.
- 02Use ES5's big threeUse strict mode,
JSON.parseandJSON.stringify, andObject.definePropertywith its settings. - 03Read old code by its editionSpot clues such as
new Array,var self = thisandCart.prototype.total, and rewrite them with newer tools.
The early editions: how JavaScript was written down
In 1996, Netscape gave JavaScript to Ecma, a standards group, so that every browser could follow the same rules. Ecma's committee for it is called TC39, and the rulebook it writes is called ECMAScript. Each new version of the rulebook is an edition, and people shorten the names: ES1 is the first edition, ES3 the third, and so on.
Today's JavaScript still contains every early edition. One short program can use a feature from each.
var price = 3;
console.log(price === 3);
console.log(JSON.stringify({ price: price }));
const total = price + 1;
console.log(total);Line 1 uses var and a number, which were in ES1 from 1997. Line 2 uses ===, which arrived in ES3 in 1999, and prints true.
Line 3 uses JSON, from ES5 in 2009, and prints {"price":3}. Line 4 uses const, from ES2015, and line 5 prints 4.
=== from ES3, line 3 uses JSON from ES5, and line 4 uses const from ES2015. Between ES3 and ES5 there were ten years, and a fourth edition that was dropped. ES2, in 1998, only changed wording, so it isn't shown.Read each row of the table as: "this edition came out then, and added this."
| The edition | When | What it added |
|---|---|---|
| ES1 | June 1997 | The first standard, based on Netscape's JavaScript 1.1: var, functions, for loops, typeof and a few methods. No [3, 4] or { price: 3 } yet. |
| ES2 | June 1998 | Only wording changes, to match the ISO version of the standard. |
| ES3 | December 1999 | Regular expressions, try and catch, ===, instanceof, switch, do-while, array and object literals, push, slice and toFixed. |
| ES4 | Dropped in 2008 | Classes with types, packages and namespaces. It was never published. |
| ES5 | December 2009 | Strict mode, JSON, Object.defineProperty, getters and setters, and array methods such as map and filter. |
| ES5.1 | June 2011 | Small fixes, to match the ISO version again. |
| ES2015 | June 2015 | The Harmony features: class, let and const, arrow functions, modules and promises. |
Picture a family that prints its recipes as a book. The first printing has a few basics. The third adds many new dishes. For the fourth, some cousins want to rewrite the whole book, and others refuse. After years of arguing, they print a smaller fifth edition with fixes and a few new recipes. Then they agree to print a small update every year, so nobody waits ten years again.
- In real life: The family's printed recipe book
- In JavaScript: The ECMAScript standard
- In real life: Each new printing, with more recipes
- In JavaScript: ES1, ES3 and ES5
- In real life: A huge fourth printing the family argued about, never printed
- In JavaScript: ES4
- In real life: After that, a small new printing every year
- In JavaScript: ES2015, ES2016 and so on
ECMAScript is the standard that says what JavaScript means. ES1 (1997) wrote the language down, ES3 (1999) made it practical, ES4 was dropped, and ES5 (2009) added strict mode, JSON and property settings. Since ES2015, a new edition comes out every year.
ES1 to ES3: from Netscape's language to a standard
ES1, in June 1997, described JavaScript 1.1, the version in Netscape Navigator 3. It was small. Arrays had only four methods: join, reverse, sort and toString. And there was no short way to write an array or an object.
var prices = new Array(3, 4, 5);
var cart = new Object();
cart.price = prices[0];
var total = 0;
for (var i = 0; i < prices.length; i++) {
total = total + prices[i];
}
console.log(prices.join(", ") + " = " + total);
console.log(typeof cart, cart.price);Line 1 makes an array with new Array(3, 4, 5), because ES1 had no [3, 4, 5]. Line 2 makes an empty object with new Object(), because ES1 had no { } either. Line 3 adds a property to it.
Lines 5 to 7 add up the prices with a for loop. Line 8 prints 3, 4, 5 = 12. Line 9 prints object 3.
console.log is not part of any edition. It comes from the browser or Node.js, so we can use it to see results in every example.
ES1 also reserved words it planned to use later: class, const, import, export, extends, super, try, catch, switch and a few more. A reserved word can't be a variable name.
var class = 3;
console.log(class);Line 1 tries to name a variable class. That is a SyntaxError even in sloppy code, the code without "use strict", so line 2 never runs.
var let = 3;
console.log(let);let wasn't on ES1's list, so old pages used it as a name. Sloppy code must still allow that, and line 2 prints 3. The don't break the web lesson explains why.
class, so no old page could use it as a name, and ES2015 could give it a job safely. let wasn't reserved, so old pages used it as a name, and sloppy code still allows var let = 3.ES2, in June 1998, changed only wording. ES3, in December 1999, described JavaScript 1.2 and added much of what you use every day.
var order = "tea: 3, cake: 4";
var match = /cake: (\d+)/.exec(order);
try {
console.log(Number(match[1]).toFixed(2));
console.log(match instanceof Array);
null.price;
} catch (error) {
console.log(error instanceof TypeError);
}Line 1 stores some text. Line 2 uses a regular expression, a pattern for searching text, written between slashes. The brackets around \d+ capture the digits after cake: , so match[1] is "4".
Line 3 starts a try block. Line 4 formats the number with 2 decimals using toFixed, and prints 4.00. Line 5 checks the result with instanceof, and prints true.
Line 6 reads a property of null, which throws a TypeError. Lines 7 and 8 catch it, and line 8 prints true. Before ES3, there was no way to catch an error: it stopped the whole script.
A replay of the example code, not a view inside a JavaScript engine. The example runs for real.
script
var match = /cake: (\d+)/.exec(order);try { console.log(Number(match[1]).toFixed(2)); console.log(match instanceof Array); null.price;} catch (error) { console.log(error instanceof TypeError);}Press Step to follow each ES3 feature, including the jump from line 6 into the catch block. This replays the example code; it isn't a view inside the JavaScript engine.
console.log(3 == "3");
console.log(3 === "3");
var prices = [3, 4];
console.log(prices.push(5));
console.log(prices.slice(1).join(" "));Line 1 uses ==, from ES1, which turns the string "3" into a number before comparing, so it prints true. Line 2 uses ===, from ES3, which never changes types, so it prints false.
Line 3 uses an array literal, also from ES3. Line 4 calls push, which adds 5 and returns the new length, so it prints 3. Line 5 uses slice(1), which copies from position 1 on, and prints 4 5.
ES4: the edition that was never finished
After ES3, the committee started a much bigger fourth edition. ES4 aimed to make JavaScript better for very large programs. Its draft added:
- classes with
publicandprivateparts, - type annotations, such as
price: int, saying what kind of value a name holds, - packages and namespaces, for grouping names, as in Java or C#.
package shop {
public class Cart {
private var price: int = 3;
public function total(tax: int): int {
return price + tax;
}
}
}Line 1 puts the code in a package called shop. Line 2 makes a public class, and line 3 a private variable whose type is int, a whole number. Line 4 says that total takes an int and gives back an int.
Adobe used ideas from these drafts in ActionScript 3, the language of Flash. But browser makers disagreed. Microsoft and Yahoo said the changes were too big and could break the web, and they worked on a smaller update called ES3.1. In July 2008, the committee agreed to drop ES4.
function add(a: int, b: int): int {
return a + b;
}
console.log(add(3, 1));Line 1 uses ES4-style types. JavaScript never got them, so the script is a SyntaxError, and line 4 never runs.
ES3.1 was renamed ES5, so there is no ES4 at all. Many ES4 ideas came back later, in smaller pieces. Classes arrived in ES2015 and private fields in ES2022.
class Cart {
#price = 3;
total(tax) {
return this.#price + tax;
}
}
console.log(new Cart().total(1));Lines 1 to 6 make a class. Line 2 makes a private field, #price, which only code inside the class can read. Line 7 makes a cart and prints 4.
Type annotations live on in TypeScript, a language from Microsoft that adds types to JavaScript and removes them before the code runs.
ES5: strict mode and JSON
ES5 came out in December 2009, ten years after ES3. It was careful: it added features without changing what old code did. Its best-known addition is strict mode, which you turn on with "use strict". Strict mode turns many silent mistakes into errors.
"use strict";
function setPrice() {
pirce = 4;
}
try {
setPrice();
} catch (error) {
console.log(error.name);
}Line 1 turns on strict mode. Line 3 has a typo: pirce instead of price. In sloppy code, this would quietly make a new global variable. In strict code, line 6's call throws, and line 8 prints ReferenceError.
Old pages don't contain "use strict", so they keep the old rules. Only code that asks for strict mode gets it. The strict mode lesson lists everything it changes.
ES5 also added JSON, built-in tools for JSON, a text format for data that servers and browsers send to each other.
var text = '{"item": "tea", "price": 3}';
var order = JSON.parse(text);
console.log(order.price + 1);
order.price = 4;
console.log(JSON.stringify(order));Line 1 stores JSON text. Line 2 turns it into an object with JSON.parse, so line 3 can use order.price and print 4.
Line 4 changes the price. Line 5 turns the object back into text with JSON.stringify, and prints {"item":"tea","price":4}.
Before ES5, code often read JSON with eval, which runs any text as code. That works for real JSON, but it also runs anything else hidden in the text.
var text = '{"price": (function () { return 4; })()}';
console.log(eval("(" + text + ")").price);
try {
JSON.parse(text);
} catch (error) {
console.log(error.name);
}Line 1 stores text that looks like JSON, but its price is a function call. Line 2 reads it with eval, which runs the function, and prints 4.
Line 4 tries JSON.parse on the same text. JSON allows only plain data, so it throws, and line 6 prints SyntaxError. If the text came from someone else, eval would have run their code.
ES5: property settings and new methods
Every property has a value and three settings. ES5 was the first edition that let your code choose them:
writable: can the value be changed?enumerable: do loops such asfor…insee it?configurable: can the property be deleted, or its settings changed?
Together, the value and the settings are called a property descriptor.
var cart = {};
Object.defineProperty(cart, "price", {
value: 3,
writable: false,
enumerable: true,
});
cart.price = 4;
console.log(cart.price);
console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cart, "price")));Line 1 makes an empty cart. Lines 2 to 6 add price with Object.defineProperty: its value is 3, line 4 makes it not writable, and line 5 makes it enumerable.
Line 7 tries to change it to 4. In sloppy code, the write is silently ignored, so line 8 prints 3. Line 9 reads the descriptor back. configurable is false, because defineProperty sets every setting you leave out to false.
price has writable: false, so the assignment on line 7 is ignored, and line 8 prints 3.A replay of the example code, not a view inside a JavaScript engine. The example runs for real in sloppy mode.
script
Object.defineProperty(cart, "price", { value: 3, writable: false, enumerable: true,});cart.price = 4;console.log(cart.price);console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cart, "price")));Press Step and watch cart.price beside line 3 stay 3 after line 7. This replays the example code; it isn't a view inside the JavaScript engine.
ES5 also made getters standard: a property whose value is worked out by a function each time you read it.
var cart = {
price: 3,
get total() {
return this.price + 1;
},
};
console.log(cart.total);
cart.price = 5;
console.log(cart.total);Lines 3 to 5 make a getter called total. Line 7 reads cart.total, which runs the getter and prints 4. Line 8 changes the price, so line 9 prints 6.
Line 5 ends with a comma, even though nothing follows it. ES3 didn't allow that comma in object literals. ES5 does.
var menu = Object.freeze({ tea: 3 });
menu.tea = 4;
menu.cake = 5;
console.log(menu.tea, menu.cake);
console.log(Object.isFrozen(menu));Line 1 freezes the menu: no property can change, and none can be added. Lines 2 and 3 are silently ignored, so line 4 prints 3 undefined, and line 5 prints true.
ES5 also added many methods that are now everyday tools. They take a function and run it on each item.
var prices = [3, 4, 5];
var doubled = prices.map(function (p) { return p * 2; });
var cheap = prices.filter(function (p) { return p < 5; });
var total = prices.reduce(function (sum, p) { return sum + p; }, 0);
console.log(doubled.join(" "), cheap.join(" "), total);
console.log(prices.indexOf(4), Array.isArray(prices), " tea ".trim());Line 2 doubles each price with map. Line 3 keeps the prices under 5 with filter. Line 4 adds them up with reduce, starting from 0. Line 5 prints 6 8 10 3 4 12.
Line 6 prints 1 true tea: indexOf finds 4 at position 1, Array.isArray confirms the array, and trim removes the spaces. The functions use the function keyword, because arrow functions didn't exist until 2015.
Harmony and ES2015: smaller steps
When ES4 was dropped in 2008, its bigger ideas went into a plan called Harmony. Harmony kept the good parts, such as classes and modules, but built them from what JavaScript already had, and left out types and packages.
Harmony became the sixth edition, ES6. While it was being finished, the committee decided to name editions by year, so ES6 was published in June 2015 as ES2015. Since then, a smaller edition comes out every June.
function Cart(price) {
this.price = price;
}
Cart.prototype.total = function (tax) {
return this.price + tax;
};
var cart = new Cart(3);
console.log(cart.total(1));
console.log(typeof Cart);Lines 1 to 3 make a constructor function, Cart. Lines 4 to 6 add total to Cart.prototype, the object every cart shares its methods from.
Line 7 makes a cart with new. Line 8 prints 4, and line 9 prints function.
class Cart {
constructor(price) {
this.price = price;
}
total(tax) {
return this.price + tax;
}
}
const cart = new Cart(3);
console.log(cart.total(1));
console.log(typeof Cart);Lines 1 to 8 make the same thing with class. Lines 2 to 4 are the constructor, and lines 5 to 7 are the total method.
Line 10 prints 4, as before. Line 11 prints function too: a class is still a function with a prototype, the same parts the ES5 version used. This is the Harmony idea: new syntax, built on the old pieces.
The modern JavaScript lesson goes through what each yearly edition added.
Reading old code by its edition
Old code often shows when it was written. Libraries from the 2000s and early 2010s were written for browsers that only had ES3, so they used patterns that look strange today.
var shop = (function () {
var count = 0;
return {
add: function () {
var self = this;
count = count + 1;
return self === shop ? count : -1;
},
};
})();
console.log(shop.add(), shop.add());
console.log(typeof JSON === "undefined" ? "load a JSON library" : "JSON is built in");Lines 1 and 10 wrap everything in a function that runs at once. Before ES2015 modules, this was the way to keep count private. Line 5 saves this in self, which old code did before arrow functions.
Line 11 calls add twice and prints 1 2. Line 12 checks whether JSON exists, as code did when browsers older than ES5 were still common. Today it prints JSON is built in.
Read each row of the table as: "if you see this in old code, it tells you this."
| The clue in old code | What it tells you |
|---|---|
new Array(3, 4) and new Object() | Written in the style of ES1, before array and object literals were standard. |
var self = this; inside a method | Written before ES2015 arrow functions, which keep this for you. |
(function () { … })(); around a whole file | Written before ES2015 modules, to keep its names private. |
if (typeof JSON === "undefined") | Written for browsers older than ES5, which had no built-in JSON. |
"use strict"; at the top of a function | Written for ES5 or later, by someone who chose strict mode. |
function Cart() {} with Cart.prototype.total = … | Written before ES2015 classes. |
The playground shows one small job, picking the prices under 5 and adding them up, written with the tools of each edition. Pick an edition. The highlighted lines use something that edition added. Guess first: do all four print the same line?
var prices = new Array(3, 4, 5);var picked = "";var total = 0;for (var i = 0; i < prices.length; i++) { if (prices[i] < 5) { picked = picked + prices[i] + " "; total = total + prices[i]; }}console.log(picked + "= " + total);3 4 = 73 4 = 73 4 = 73 4 = 73 4 = 7ES1 is the first edition, so it sets the base: var, new Array, for loops, if and string joining with +. All four programs print the same line.
All four print 3 4 = 7. ES1 needs new Array and string joining with +. ES3 adds the array literal and push. ES5 replaces the loop with filter and reduce. ES2015 makes it short with const, arrow functions and a template literal.
typeof pricenew Array(3, 4)3 === "3"try { … } catch (e) { … }/te+a/.test("teea")[3, 4].push(5)JSON.parse(text)"use strict";[3, 4].map(fn)Object.defineProperty(cart, "price", { value: 3 })class Cart {}(p) => p * 2const total = 7;
Put each piece of code with the edition that first had it: ES1, ES3, ES5 or ES2015.
Common mix-ups
Most mix-ups come from the missing ES4, and from thinking today's basics were there from the start.
- "ES4 was published, then replaced." No. Its draft was dropped in 2008. ES5 comes straight after ES3.
- "
[3, 4]and{ price: 3 }were always there." No. They became standard in ES3. ES1 usednew Arrayandnew Object. - "ES1 had
tryandcatch." No. ES1 only reserved those words. ES3 made them work. - "Strict mode changed how all code runs." No. Only code that says
"use strict"gets it, so old pages kept working. - "JSON was part of JavaScript from the start." No. It became built in with ES5. Before that, code used
evalor a library. - "ES2015 classes replaced prototypes." No. A class is a function with a prototype, built from the same parts.
- "ES6 and ES2015 are different editions." No. They are two names for the same sixth edition.
The table below puts three of these side by side. Read each row as: "people often think this, but this is true."
| The topic | What people think | What is true |
|---|---|---|
| ES4 | It was published, then replaced by ES5. | It was never published. Its draft was dropped in 2008. |
[3, 4] and { price: 3 } | They were in the very first standard. | They became standard in ES3, in 1999. |
class in ES2015 | It added a new kind of object. | A class is still a function with a prototype, like ES5 code. |
Practice exercises
Every program below is a sloppy classic script. For each one, find the feature and ask which edition added it, and what it does. Guess first, then run it.
What does line 1 print?
console.log(3 == "3", 3 === "3", null == undefined, null === undefined);console.log(3 == "3", 3 === "3", null == undefined, null === undefined);3 == "3" converts and is true; 3 === "3" is false. null == undefined is true by a special rule; null === undefined is false. Line 1 prints true false true false.
What happens when you run it? Then fix the name so it prints 3.
var import = 3;
console.log(import);var importCount = 3;
console.log(importCount);import is a reserved word, so the script is a SyntaxError before anything runs. The version shown names the variable importCount, and prints 3.
What does line 3 print?
var order = JSON.parse('{"price": 3, "items": ["tea", "cake"]}');
order.items.push("juice");
console.log(JSON.stringify(order));var order = JSON.parse('{"price": 3, "items": ["tea", "cake"]}');
order.items.push("juice");
console.log(JSON.stringify(order));The parsed object's items is a normal array, so push adds juice to it. JSON.stringify writes keys and strings in double quotes with no spaces, so line 3 prints {"price":3,"items":["tea","cake","juice"]}.
What does line 4 print? Then change line 2 so the program prints 4 1.
var cart = {};
Object.defineProperty(cart, "price", { value: 3 });
cart.price = 4;
console.log(cart.price, Object.keys(cart).length);var cart = {};
Object.defineProperty(cart, "price", { value: 3, writable: true, enumerable: true, configurable: true });
cart.price = 4;
console.log(cart.price, Object.keys(cart).length);With every setting left out, price is read-only and hidden, so line 4 prints 3 0. The version shown sets writable, enumerable and configurable to true, so the write works and it prints 4 1.
Rewrite the program with filter and map instead of the loop. What does it print?
var prices = [3, 4, 5];
var picked = [];
for (var i = 0; i < prices.length; i++) {
if (prices[i] > 3) {
picked.push(prices[i] * 2);
}
}
console.log(picked.join(" "));var prices = [3, 4, 5];
var picked = prices
.filter(function (p) { return p > 3; })
.map(function (p) { return p * 2; });
console.log(picked.join(" "));filter keeps 4 and 5, and map doubles them to 8 and 10. The program prints 8 10, like the loop version, with no counter to manage.
Rewrite Cart as a class. What does your program print?
function Cart(price) {
this.price = price;
}
Cart.prototype.total = function (tax) {
return this.price + tax;
};
var cart = new Cart(4);
console.log(cart.total(2));class Cart {
constructor(price) {
this.price = price;
}
total(tax) {
return this.price + tax;
}
}
const cart = new Cart(4);
console.log(cart.total(2));The constructor stores the price, and total becomes a class method. The cart's price is 4 and the tax is 2, so it prints 6, the same as the ES5 version.
Check your understanding
For each question, ask which edition the feature came from, and whether the code is sloppy or strict. Each answer explains why it's right or wrong.
Question 1 of 7What does comparing 0 with an empty string print?
Read the code, then predictconsole.log(0 == "", 0 === "");Choose an answer to see the explanation.
Question 2 of 7Which edition first had regular expressions and try and catch?
Choose an answer to see the explanation.
Question 3 of 7What happened to ES4?
Choose an answer to see the explanation.
Question 4 of 7What does writing to a read-only property print in sloppy code?
Read the code, then predictvar box = {}; Object.defineProperty(box, "size", { value: 1, writable: false }); box.size = 2; console.log(box.size);Choose an answer to see the explanation.
Question 5 of 7What does strict code print when it writes to a frozen object?
Read the code, then predict"use strict"; var box = Object.freeze({ size: 1 }); try { box.size = 2; } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 6 of 7What does typeof give for an ES2015 class?
Read the code, then predictclass Cart {} console.log(typeof Cart);Choose an answer to see the explanation.
Question 7 of 7Why could ES2015 make class a keyword without breaking old pages?
Choose an answer to see the explanation.
Key takeaways
Every early edition is still inside today's JavaScript. Knowing where each feature came from helps you read old code and understand why the language looks the way it does.
- ECMAScript is the standard for JavaScript, written by Ecma's TC39 committee. Each version is an edition.
- ES1 (1997) was small: no
[3, 4], no{ price: 3 }, notry. It reserved words likeclassfor later. - ES3 (1999) added regular expressions,
tryandcatch,===,instanceof, and array and object literals. - ES4 was dropped in 2008 and never published. Its small fixes became ES5, and its big ideas went into Harmony.
- ES5 (2009) added strict mode,
JSON, property settings withObject.defineProperty, getters, and methods likemapandfilter. - Harmony became ES2015. Its classes are built from the same functions and prototypes as ES5 code.
- Since ES2015, a new edition comes out every June.
Remember the one-liner.
ES1 wrote it down, ES3 made it practical, ES4 was dropped, ES5 made it safe, and Harmony led to a new edition every year.
Coming next: How JavaScript evolves. It explains how TC39 turns an idea into a feature today, one stage at a time.