JavaScript Versions: How JavaScript has changed over the years

Dec 18, 2020 - 17 min read
Christina Kopecky

JavaScript is one of the world’s most popular and widespread programming languages. Since its inception in 1995, the language that has ultimately come to be known as JavaScript has gone through several iterations and versions.

JavaScript was invented by Brendan Eich, and in 1997 and became an ECMA standard. ECMAScript is the official language name. ECMAScript versions include ES1, ES2, ES3, ES5, and ES6.

As we come to end of the year, let’s reflect on all the changes that JavaScript has gone through to better understand how to utilize this language.

We will go over:

Learn how to write modern JavaScript

This is the perfect place to start your journey as a front-end developer. You’ll learn HTML, CSS, and JavaScript in depth.

Become a Front End Developer

Websites prior to JavaScript

Web pages before the existence of JavaScript were very static. Lists, dates, and links were all hard-coded into your HTML, and any sort of dynamic functionality was encoded in the head of the HTML document as a header.

One of the more famous examples of web page design pre-JavaScript that’s still around is the San Francisco FogCam:


The San Francisco FogCam was created almost 30 years ago and is know as one of the oldest webcams in the world. It is the earliest ways we could see a “live” picture on the internet.

As times changed, the website didn’t. It still used the static HTML and CSS created in the 1990s. The page refreshes every 20 seconds using <meta> information in the document head.

JavaScript wasn’t officially released when the FogCam was created, but we can already witness the need for something that can dynamically load images.

Another example of how pre-JavaScript web pages were architected is a the Dole/Kemp ‘96 page built in the late nineties for Senator Bob Dole’s Presidential bid.


This site is still a static website, but it uses HTML routing to go from page to page. Everything was hard-coded on this site. The engineers at Netscape Communicator saw this issue and decided to create a scripting language that allowed animations, form building, and more dynamic interactions.

This is where the inception of JavaScript starts.

JavaScript beginnings

The first iteration of JavaScript was actually not called JavaScript at all. It was called Mocha. This language was created to be a higher-level language for designers and non-programmers alike.

When Mocha was shipped with Netscape Navigator 2.0, its production name became LiveScript, and then in later versions, JavaScript.

First public release of JavaScript was integrated in Netscape Navigator 2.0 (1995)
First public release of JavaScript was integrated in Netscape Navigator 2.0 (1995)

Netscape was certainly ahead of the game with their collaboration with Sun Microsystems in creating JavaScript. As Netscape gained more and more of the browser share, other browsers needed to come up with something to keep up with Netscape’s success.

Due to legal reasons, Microsoft created their own version of JavaScript, called JScript. The main mission of these “dialects” was to increase the user experience and user interaction of web sites.

At first, Netscape won these wars, but with the creation of JScript, Microsoft’s Internet Explorer was increasing their browser share.

This made standardization really difficult. JavaScript inched forward in the scripting language wars due to its similarity to Java syntax. As Java became more popular, JavaScript gained more ground as well.

Note: Java is NOT equal to JavaScript. Java is a compiled language that uses a Virtual Machine or Browser to execute code.

JavaScript is a scripted language that shines in the browser in production and is used in Node.js outside the browser.

JavaScript versions

Version Official Name Description
ES1 ECMAScript 1 (1997) First edition
ES2 ECMAScript 2 (1998) Editorial changes
ES3 ECMAScript 3 (1999) Added regular expressions & try/catch
ES4 ECMAScript 4 Not released
ES5 ECMAScript 5 (2009) Added “strict mode”, JSON support, String.trim(), Array.isArray(), & Array iteration methods.
ES6 ECMAScript 2015 Added let and const, default parameter values, Array.find(), & Array.findIndex()
ES6 ECMAScript 2016 Added exponential operator & Array.prototype.includes
ES6 ECMAScript 2017 Added string padding, Object.entries, Object.values, async functions, & shared memory
ES6 ECMAScript 2018 Added rest / spread properties, asynchronous iteration, Promise.finally(), & RegExp


Netscape Communicator submitted documents to ECMA International, a company that standardizes information and communications systems, in 1997.

ECMA International took Netscape’s JavaScript and Microsoft’s JScript to create a standardization called ECMAScript, a language specification that both languages are based on. ECMA wasn’t able to be called JavaScript because JavaScript was a trademark held by Sun Microsystems (which later became Oracle).

ECMAScript 1-4

ECMAScript 1 (ES1) was released in 1997 with ES2 following the following year. Not much changed between the two versions.


ES3 was released in 1999 and added support for many new things that have become a standard part of the language today:

  • Strict Equality: Beginning with ES3, the strict equality operator ( === ) became an option to use in addition to the equality operator ( == ). The difference between the two is a matter of comparing the type and number. Strict equality considers values that are the same but a different type to be unequal.
const compareTypeAndValue = (num, str) => {
 return num === str;
console.log(compareTypeAndValue(8, '8')); //false
console.log(compareTypeAndValue(8, 8)); //true
  • Regular Expressions: Two types of regular expressions became available in ES3: literal and constructor.

  • Literal Expressions: Literal regular expressions are expressed in between two backslashes. The actual expression is in between the slashes and the global, ignore case, and multi-line flags can be turned on or off after the last backslash.

  • Constructor Expressions: Constructor regular expressions are those expressions that are created as an instance of the RegExp object. The actual regular expression is the first argument that is passed into the RegExp constructor. The second, if needed, are the flags you would like to use.
const regex = new RegExp(‘[^abc]’, ‘gim’);
  • Switch Statement: The switch statement is a control flow statement that basically chains together many if conditions without having to use else if statements. The switch statement uses a parameter and compares that parameter to each of the case statements. If that parameter matches a case, it performs the logic in that block.
const fizzBuzz = (num) => {
    switch(num) {
     case 1:
     case 2:
     case 3:
     case 4:
     case 5:
     case 6:
     case 7:
     case 8:
     case 9:
     case 10:
  • Try/Catch Handling: A try/catch handler will throw an error if the try block for whatever reason fails. Below, the try fails because obj was never defined. The catch block is executed and a new Error object exception is thrown.
const isValidKey = (val1) => {
try {
  let obj;
  return obj.hasOwnProperty(val1);
} catch (err) {
    throw new Error("not valid key");

ES3 was the last major update to the ECMAScript specification for almost a decade, until 2009 with ES5.


TC39 is a Royalty-Free Task Group at ECMA International whose main job is to standardize ECMAScript. When it came time to update and release a standard for ES4, the task group really couldn’t agree on a specification. As a result, ES4 was dog-eared as a version, but never fully released as an actual standard.

ECMAScript 5 updates in detail

ES5 and ES6 are the latest specifications released that have had the greatest number of changes.

Ten years after the release of ES3, in 2009, a new version of ECMAScript was released. This standard was the biggest change to JavaScript since its founding. Some of the new features include:

“use strict”

Prior to ES5, undeclared variables (those variables that don’t use the var keyword when initially introduced), were allowed to be used. When the “use strict” feature is turned on, a reference error is thrown.

"use strict"
x = 5; // ReferenceError: x is not defined

New Array Methods

There are several new array methods that were introduced in ES5 that made life much easier when working with arrays. The new array methods are shown here in alphabetical order:


The every() array method checks to see if every single element in the array satisfies the condition you have passed it.

var arr = [6, 4, 5, 6, 7, 7];
arr.every(function(element) {
 return element % 2 === 0; //checks to see if even
}); // false


Think of the filter method as a for loop that has an if statement. If the element passes the test, you push that element to a new array. This is how filter() works under the hood.

Like map(), another array method mentioned in this section, filter() returns a new array with the values that pass the test.

var arr = [6, 4, 5, 6, 7, 7];
arr.filter(function(element) {
 return element/2 > 3;


A method that is very similar to a for loop. For every element found in the array, the forEach() method executes a callback function on it.

var arr = [6, 4, 5, 6, 7, 7];
arr.forEach(function(element) {
 console.log(element * 2);

indexOf() and lastIndexOf()

If you need to search for a particular element in an array, you can do that with indexOf() and lastIndexOf(). indexOf() returns the first index of the search parameter if it’s found, otherwise it returns a -1.

In lastIndexOf(), it gives us the last index of the search element in the array. Again, if not found, it’ll return -1.

var arr = [6, 4, 5, 6, 7, 7];
console.log(arr.indexOf(4)); // 1
console.log(arr.indexOf(2)); // -1
console.log(arr.indexOf(7)); // 4
console.log(arr.lastIndexOf(7)); // 5


This method checks to see if the object passed to it is an array or not. Returns a boolean.

var arr = [6, 4, 5, 6, 7, 7];
var str = "Hello";


The map() method is very similar to the forEach() method with the exception that it returns a brand new array. This allows for manipulation of data without compromising the original array.

The callback function must have a return statement. This is the new value that will be going into that particular index of the new array.

var arr = [6, 4, 5, 6, 7, 7]; {
 return element * 2;

reduce() and reduceRight()

Each of these reduce methods applies a callback function to each element in the array. What is special about the reduce() and reduceRight() methods is that it reduces the array to a single element.

The reduceRight() method is just like reduce() except that it iterates from right to left instead of left to right.

var arr = [6, 4, 5, 6, 7, 7];
var reduced = arr.reduce(function(curr, next) {
 return curr + next;
}, 0);
var reducedRight = arr.reduceRight(function(curr, next) {
 return curr + next;
}, 0)


The some() method is almost exactly like the every() method, with the exception that it checks to see if at least one element satisfies the condition you have set for it.

var arr = [6, 4, 5, 6, 7, 7];
arr.some(function(element) {
 return element % 2 === 0; //checks to see if even
}); //true


The ability to parse and stringify JavaScript Object Notation (JSON) became a possibility in the ES5 standard. The JSON format is used to basically transmit some sort of structured data over a network connection, usually a web application and an API.

When we transmit the data from one application, it has to be in the form of a string. We use JSON.stringify() to transform JavaScript objects into strings.

We then use JSON.parse() on the other side, to transform the data after transmission back to a JavaScript object so we can use it.

var arr = [6, 4, 5, 6, 7, 7];
var obj = {
 author: "Christina Kopecky",
 title: "How to parse JSON objects",
 published: false
console.log("======== ARR EXAMPLE ==========");
console.log("orig arr=====>", arr);
console.log("stringified arr=====>", JSON.stringify(arr));
console.log("proof of type=====>", typeof JSON.stringify(arr));
console.log("parsed string=====>", JSON.parse(JSON.stringify(arr)));
console.log("proof of type=====>", typeof JSON.parse(JSON.stringify(arr)), "\n\n");
console.log("======== OBJ EXAMPLE ==========");
console.log("orig obj=====>", obj);
console.log("stringified obj=====>", JSON.stringify(obj));
console.log("proof of type=====>", typeof JSON.stringify(obj));
console.log("parsed string=====>", JSON.parse(JSON.stringify(obj)));
console.log("proof of type=====>", typeof JSON.parse(JSON.stringify(obj)), "\n\n");

New Date Methods

There were two new Date object methods that were introduced in ES5 that are functionally equivalent. They both return the current time in milliseconds since January 1, 1970. They are and new Date().valueOf().

console.log(new Date().valueOf());

The biggest difference between the two methods is that valueOf() is a method on an instance of the Date object and is a static function of the Date object.

Note: Internet Explorer may not support, so if that is a concern for you, you may need to deal with that in your code.

getters and setters

In ES5, we are introduced to the idea of accessor properties. These are functions whose sole purpose is to get or set a value. They look like standard properties when you call them:

let character = {
 first_name: "Darth",
 last_name: "Vader",
 get fullName() {
   return `${this.first_name} ${this.last_name}`;
 set fullName(str) {
   [this.first_name, this.last_name] = str.split(" ");
console.log(character.fullName); // Darth Vader
character.fullName = "Luke Skywalker"

The ES5 standard really started to pave the way for making JavaScript code more readable. With the introduction of new array methods, the ability to parse and stringify JSON, and making code creation more strict, it really helped make JavaScript easier to understand.

Keep the learning going.

Learn modern JavaScript without scrubbing through videos or documentation. Educative’s text-based learning paths are easy to skim and feature live coding environments, making learning quick and efficient.

Become a Front End Developer


ES6 updates in detail

Seven years passed between the finished version of ES5 to the release of ES6. It became a standard in June of 2015.


One of the biggest changes from ES5 is that ES6 JavaScript is not able to be compiled directly in browsers. We need to use a transpiler called Babel.js to produce compatible JavaScript that the older browsers can read.

Babel allows you to use ES6 features and syntax in your project and then translates it to ES5 so that you can use it in production.

To use Babel when your project is built, you’ll need to add a package.json to your project. This is where all of your dependencies for your project will be held.

Be sure that you have Node and npm installed (or Node and Yarn installed if you prefer to use Yarn) and then type in the npm init or the yarn init command in your terminal. Answer the questions that come up and the package.json will be pre-filled with those values.

Use npm/yarn to add babel to your dependencies with the command:

npm install --save-dev babel-cli
// or
yarn add babel-cli --dev

You’ll use the scripts field in your package.json to set your build command with Babel. The actual command will differ based upon the folder you are building from and where you want to build to.

Finally, in the root folder of your project (where the package.json resides), create a .babelrc file. This is a Babel configuration file that will tell Babel to transform your code to ES5. Install the preset with:

npm install --save-dev babel-preset-env
// or
yarn add babel-preset-env --dev

And then define it in your .babelrc file:

	“presets”: [“env”]

Now you can run Babel by running your build command. Your destination folder should now look exactly like your origin folder, except that the contents of the destination folder is ES5 code instead of ES6.

If you happen to use a JavaScript library or framework like create-react-app, more than likely the Babel is already configured for you and you won’t need to worry about it. This is for projects that are created from scratch.

Big Arrow (Fat Arrow) Functions

Before this new standard, JavaScript used the function keyword to create functions. Now, we can use the big arrow, =>, to write functions. It can make code look a little more elegant as we can create one-liner fat arrow functions.

//pre ES-6
function add(num1, num2) {
 return num1 + num2;
//ES6 (implicit return)
const addImplicit = (num1, num2) => num1 + num2;
console.log(add(3, 4));
console.log(addImplicit(3, 4));

The ES6 one-liner has an implicit return. We don’t need the return keyword if the function is only one line. This also means there is no need for curly braces. If the function is more than one line, we would need curly braces and a return statement:

//ES6 (explicit return)
const addExplicitReturn = (num1, num2) => {
 let sum = num1 + num2;
 return sum;
console.log(addExplicitReturn(3, 4));

It’s also important to note that when you are using classes, the arrow function is bound to the “this” keyword so there is no need to actually use the bind() method to bind the function to the class.

If using the function keyword, the method needs to be bound to the class with the bind() method.


Classes act as syntactic sugar on top of JavaScript’s Prototypes. Instead of Prototypal Inheritance, they use Classical Inheritance with the extends keyword. Overall, it just cuts down on the amount of code, and spruces it up a bit.

class StarWarsCharacter{
 constructor(attributes) { =;
   this.age = attributes.age;
   this.homePlanet = attributes.homePlanet;
 getCharacter = () => `${} is ${this.age} years old and is from ${this.homePlanet}.`;
const luke = new StarWarsCharacter({ name: "Luke Skywalker", age: 23, homePlanet: "Tatooine"});
class Heroes extends StarWarsCharacter {
 constructor(attributes) {
   this.favoriteVehicle = attributes.favoriteVehicle;
  getFavoriteVehicle = () => `${} is ${this.age} and their favorite vehicle is the ${this.favoriteVehicle}`;  
const hans = new Heroes({ name: "Hans Solo", age: 35, favoriteVehicle: "Millennium Falcon"});


Object destructuring is a great way to reduce code clutter to make it more palatable. It allows us to “unpack” an object and use that unpacked value as the variable we refer to later in the code.

const state = {
 name: "Luke Skywalker",
 age: 22,
 dark_side: false
console.log("before destructuring");
console.log(; // notice the prefixed object name prior to each property
console.log(state.age);  // destructuring gets rid of this
const { name, age, dark_side } = state;
console.log("after destructuring");
console.log(name); // we can access using just the property name now!

In the “before destructuring” section, we have to use the object name in addition to the property we want to access that property. We can destructure it by pulling out the properties, put it in a set of curly braces and set it to the object name.

Be sure to use the const keyword in front of the curly braces. It allows for us to access these properties as variables instead of using dot notation on the actual object itself.

Array destructuring is done in a very similar fashion, but with square brackets instead of curly braces.

const arr_state = [ "Luke Skywalker", 22, false];
console.log("before destructuring");
console.log(arr_state[0]); // notice the index number in bracket notation
console.log(arr_state[1]);  // destructuring gets rid of this
const [ name, age, dark_side ] = arr_state; // assign a variable to each of the indexes in the array
console.log("after destructuring");
console.log(name); // we can access using just the variable name we created now!

let and const

In ES6, we have some new variable keywords that have essentially replaced the var keyword. Prior to ES6, JavaScript only had functional and global scope. With the addition of let and const, we now have block scope.

let x = 5;
function blockExample() {
 let x = 2 //this is function scope;
 if(x >= 3) {
   let x = 10; // this is block scope
   console.log(x, "inside if block");''
 } else {
   let x = 1;
   console.log(x, "inside else block")
 console.log(x, "inside function");
console.log(x, "global example");

The let keyword can be reassigned as needed. When used in the same scope, a redeclaration of the same variable will throw a syntax error.

This is an improvement upon the var keyword where you could redeclare variables with another value. This proved problematic when we had the same variable name with different values that produced unintended bugs.

// pre-ES6: 
var x = 5;
var x = 120; //produces no errors

// ES6: 
let x = 5;
let x = 120; // produces a syntax error

The const keyword is useful when you have a variable that you have no intention of reassigning. It will throw an error if you try to reassign your const variable to another value.


Promises are a way to handle Asynchronous JavaScript programming in a better way. Before, asynchronous calls were made by using callback functions, which could make code convoluted and confusing really quickly.

Note: Promises wrap asynchronous logic in a net package to make code more readable.

console.log("before promise")
let promise = new Promise((resolve, reject) => {
  let resolvedFlag = false;
//this is just a flag so we can intentionally throw the response to test logic
  console.log("this is eventually going to be an API call");
  resolvedFlag = true; //flip resolved to true once all console logs are done
  if(resolvedFlag) { //if resolved is true invoke the resolve function   
resolve("Promise resolved THIS IS THE RESPONSE");
  } else { // else invoke the reject function with a new Error object with message
    reject(new Error("Promise failed"));
    console.log("after promise");
promise.then(resp => {
  console.log(resp); //promise response

rest and spread operators

The rest and spread operators are essentially the same syntax, but serve a different purpose. The rest operator is used before a function parameter to indicate multiple arguments should be assigned to that parameter.

function restExample(a, ...b) {
 console.log(a); // 1
 console.log(b); // [2, 3, 4, 5, 6]
restExample(1, 2, 3, 4, 5, 6);

The spread operator uses the same syntax, but is instead used by arrays. It essentially takes the contents of the array, copies it so that it can be spread into the new structure. We can use the spread operator as a way to add something to an array without having to use push() or unshift().

function spreadExample(arr) {
 let newArr = [2, 4, 6, 8];
 console.log("arr", arr);
 let combinedArr = [...newArr, ...arr]; //this pushes the contents of newArr and the contens of arr into a one-dimensional combined array.
 let arrWithOtherContents = ["a", ...newArr, {b: "c", d: "e"}, true, ...arr];
 console.log("combined", combinedArr);
console.log(spreadExample([1, 3, 5, 7, 9]))

The spread operator works great when you need to work with an array but don’t want to manipulate the actual contents of the array. You can use the spread operator to create essentially a copy to work with.

Template Literals

In ES6, we no longer need to concatenate strings, spaces, and variables together to form a larger string. We use template literals to create expressions that allow us to have variables embedded in our strings.

let name = "Jane";
let holiday = "Christmas";
console.log(name + "'s favorite holiday is " + holiday);
console.log(`${name}'s favorite holiday is ${holiday}`);

What’s next for JavaScript?

Since ES6 came out, there have been yearly updates to the standardization. You can keep an eye on ECMA International’s ECMA-262 standards to see what’s new in JavaScript. They have the standard available to read online for free in a PDF format.

To continue your JavaScript learning, you should check out:

  • Map
  • Set
  • Generators
  • async/await

To get started on your JavaScript journey, checkout Educative’s curated learning path Become a Front End Developer. With no prior knowledge needed, you will gain a mastery of HTML, CSS, and JavaScript, allowing you to put together beautiful, functional websites and web apps yourself.

Happy learning!

Continue reading about JavaScript

WRITTEN BYChristina Kopecky

Join a community of 270,000 monthly readers. A free, bi-monthly email with a roundup of Educative's top articles and coding tips.