I am going to start with version 5 as this was a game changer, bringing JSON, new String and Array functions and various syntactical improvements. Prior to that things were a little, well…meh with different browsers adding their own functions and enhancements. ES5 sought to codify these differences into a single standard version of Javascript. This is a bit of a mammoth post so hold on tight it’s going to be a wild ride…maybe.
Some would argue that this should have been turned on by default. Setting this option saves a lot of headaches and catches early bugs that would otherwise have slipped through the net.
Syntactic changes to the language
Using reserved words as property names
I’m not sure why you should use property names that are also reserved words.
var obj = {new:'object',}; //The property name is a reserved word ooh err!
Trailing or dangling commas
This can be used in object, array and function definitions. An advantage of using dangling commas is clean diffs. Only the line changed is shown in the diff tool as opposed to two. Personally I don’t like the look of that extra comma.
var array = ['a',,,,,]; // multiple trailing commas allowed!
console.log(array); //[ 'a', <4 empty items> ]
// Adding more than one comma results in an error
var object = {
propa:'a',
func: function() {
console.log('I have been called');
},
}
// This works too!
function add(a,b,) {
console.log(a+b);
}
console.log(object); //{ propa: 'a', func: [Function: func] }
Multi-line strings
A backslash can be used to continue a string on multiple lines.
var str = 'hello \
this string \
is on multiple lines';
console.log(str);//hello this string is on multiple lines
The Google style guide recommends concatenating instead of escaping multi-line strings.
This function allows you to create an object given an object prototype and an optional list of additional properties to be applied to the new object. In the example below there is a Fruit object defined with two properties and a single function which displays the name and colour of the fruit. Next Object.create
is called using the Fruit
class and implementing the name
and colour
properties. When info
is called the interpreter checks the current(lemon) object for the info
function which it cannot find. The next step is check the object prototype and hey presto it finds a definition in the Fruit
object so it gets called. So you can see this pattern is a powerful mechanism for providing a way to inherit functionality from other objects.
var Fruit = {
name: 'Fruit',
colour: 'Fruity',
info: function() {
// Capitalize the first letter of the name
var name = this.name.charAt(0).toUpperCase() + this.name.slice(1);
return name+' is the colour '+this.colour+'.';
}
}
var lemon = Object.create(Fruit,{
name:{value:'lemon'},
colour:{value: 'yellow'}
});
console.log(lemon.info()); //Lemon is the colour yellow.
Using the example above if we call:
var fruit = Object.getPrototypeOf(lemon);
console.log(fruit.info());
What do we get back? Remember what Object.create
is essentially doing is assigning an object to a prototype property. So in this case the Fruit object would be returned
var fruit = Object.getPrototypeOf(lemon);
console.log(fruit.info()); //Fruit is the colour of fruit.
console.log(fruit === Fruit);//true
An interesting thing to note is that the property descriptors have different default values depending on how the property is created. For example if creating an object using object notation all descriptors are set to true. Conversely when creating a property on an object using defineProperty
any unassigned descriptors are explicitly set to false.
//Create a car object
//Note: writable,enumerable,configurable true by default
var car = {
name: 'Escort RS2000',
make: 'Ford'
}
//Create a new prop on the car object to return the name,make
// capacity and sound
//writable,enumerable,configurable false by default
Object.defineProperty(car, 'show', {
value: function() {
return this.make+' '+this.name+' '+this.capacity+'cc'+' '+this.sound;
}
});
//Create a 'sound' prop which can be changed and removed
Object.defineProperty(car, 'sound', {
value: 'Beep!',
writable: true,
enumerable: true,
configurable: true
}
);
//Create a engine capacity prop that can be updated but not removed
Object.defineProperty(car, 'capacity', {
value: 1993,
writable: true
});
console.log('1.'+car.show()); //Ford Escort RS2000 1993cc Beep!
car.name='Focus Ghia'; //Can be changed
car.capacity = 1600; //Can be changed
car.sound = 'Beep beep!'; //Can be changed
console.log('2.'+car.show()); //Ford Focus Ghia 1600cc Beep beep!
delete car.sound; //Can be removed
delete car.capacity; //Cannot be removed
console.log('3.'+car.show()); //Ford Focus Ghia 1600cc undefined
//Let's try changing the show function
car.show = function() { return 'HONK!' };//Cannot be changed
console.log('4.'+car.show()); //Ford Focus Ghia 1600cc undefined
//Create the car object
//Note: writable,enumerable,configurable true by default for the
//defined props
var car = {
name: 'Escort RS2000',
make: 'Ford'
}
// Define some properties on the car object
Object.defineProperties(car, {
show: {
value: function() {
return this.make+' '+this.name+' '+this.capacity+'cc'+' '+this.sound;
}
},
sound: {
value: 'Beep!',
writable: true,
enumerable: true,
configurable: true
},
capacity: {
value: 1993,
writable: true
}
});
console.log('1.'+car.show()); //Ford Escort RS2000 1993cc Beep!
This function returns only those property descriptors assigned to the object and not those in the prototype chain.
var Fruit = {
name: 'Lemon'
}
/*
{
value: 'Lemon',
writable: true,
enumerable: true,
configurable: true
}
*/
console.log(Object.getOwnPropertyDescriptor(Fruit,'name'));
var Apple = Object.create(Fruit,{
name: {value:'Apple'}
});
/*
{
value: 'Apple',
writable: false,
enumerable: false,
configurable: false
}
*/
console.log(Object.getOwnPropertyDescriptor(Apple,'name'));
This function is similar to getOwnPropertyDescriptor except it returns the properties owned directly by the object.
// Create a Fruit object
var Fruit = {
name: 'Lemon',
citrus:true
}
console.log(Object.getOwnPropertyNames(Fruit)); // [ 'name', 'citrus' ]
// Create an apple object
var Apple = Object.create(Fruit,{
name: {value:'Apple'}
});
console.log(Object.getOwnPropertyNames(Apple));// [ 'name' ]
Here again we have a Fruit
object but this time it has two properties, name
and citrus
. Calling getOwnPropertyNames
as expected returns these two properties. Next we have an Apple class that defines its own name property. Calling getOwnPropertyNames
will only return the name
property and not those from the linked Fruit
object. Note that name
property is returned even though it is non-enumerable by default.
Returns an array of the objects own property names. If the property descriptor is set as non enumerable then it will not be returned. If the object is an array then the list of indices will be returned.
// Create an array
var arr = ['a','b','c'];
console.log(Object.keys(arr));// [ '0', '1', '2' ]
// Create a Fruit object
var Fruit = {
name: 'Lemon',
citrus:true
}
console.log(Object.keys(Fruit));// [ 'name', 'citrus' ]
// Create an apple object, which can be changed
var Apple = Object.create(Fruit,{
name: {value:'Apple',configurable:true}
});
console.log(Object.keys(Apple));// []
Object.defineProperty(Apple, 'name', {
enumerable:true
});
// Now the 'name' property is visible
console.log(Object.keys(Apple));// [ 'name' ]
This function locks down the object, stopping new properties from being added. Existing properties can be updated though if the correct descriptors are set.
// Create a Fruit object
var Fruit = {
name: 'Lemon',
citrus:true
}
// Create an apple object
var Apple = Object.create(Fruit,{
name: {value:'Apple',configurable:true}
});
Object.preventExtensions(Apple);
// An existing property can be updated
Object.defineProperty(Apple, 'name', {
value:'Golden delicious'
});
console.log(Apple.name);// Golden delicious
// A new property cannot be added
// TypeError: Cannot define property colour, object is not extensible
Object.defineProperty(Apple, 'colour', {
value:'Green'
});
This function goes hand in hand with preventExtensions
. Determines whether new properties can be added.
var Lemon = {
name: 'Lemon',
citrus:true
}
console.log(Object.isExtensible(Lemon)); //true
Object.preventExtensions(Lemon);
console.log(Object.isExtensible(Lemon)); //false
This function will prevent new properties being added as well as stopping existing properties being removed or updated i.e. configurable is set to false.
// Create using Object.create(). Remember all descriptors
// will be set to false when creating an object this way
// writable = false
var Lemon = Object.create({
name: {
value: 'Lemon'
}
});
Object.seal(Lemon);
// Try updating the name
// TypeError: Cannot define property name, object is not extensible
Object.defineProperty(Lemon, 'name', {
value:'Lemon McLemonface'
});
// Objects created this way have their descriptor set to true by default
// writable = true
var Lime = {
name: 'Lime',
}
Object.seal(Lime);
Object.defineProperty(Lime, 'name', {
value:'Lime McLimeface'
});
console.log(Lime);// { name: 'Lime McLimeface' }
Checks if the object is sealed. No code example necessary here as it is pretty self explanatory.
Freeze is pretty hardcore. Doing this means the object cannot be modified in any way. This means adding/updating properties, changing property descriptors or changing the object’s prototype. Under the hood calling freeze explicitly sets the configurable and writable descriptors to false. This can be seen in the example below.
var CitrusFruit = {
citrus: true
}
var Lime = {
name: 'Lime'
}
Object.setPrototypeOf(Lime, CitrusFruit);
//name is writable:true,enumerable:true,configurable:true
Object.freeze(Lime);
//name is writable:false,enumerable:true,configurable:false
Object.setPrototypeOf(Lime, {});//TypeError: [object Object] is not extensible
Checks if the object is frozen. No code example necessary here as it is pretty self explanatory.
The bind function passes the this
context to the given function.
const car = {
name: 'Escort',
getName: function() {
return this.name;
}
}
console.log(car.getName()); //Escort
const notBoundFn = car.getName;
console.log(notBoundFn()); //undefined
// bind to the correct context
const boundFn = car.getName.bind(car);
console.log(boundFn()); //Escort
Does what it says on the tin. Is the given object an array?
Determine if all elements in an array pass a given test
var cars = [
{
name:'Escort',
colour:'Blue',
},
{
name:'Panda',
colour:'Blue',
},
{
name:'Golf',
colour:'Blue',
}
];
var result = cars.every(function(car){
return car.colour === 'Blue';
});
console.log(result); //true
Return a new array constructed using the condition set in the filter function.
var cars = [
{
name:'Escort',
price:2000,
},
{
name:'Panda',
price:200,
},
{
name:'Lada',
price:100,
}
];
var cheapCars = cars.filter(function(car){
return car.price < 500;
});
console.log(cheapCars); //[{name: 'Panda', price: 200}, {name: 'Lada', price: 100}]
Array.prototype.forEach()
Loop through each element in an array. Note break
and continue
are not allowed in a forEach
loop.
var cars = [
{
name:'Escort',
price:2000,
},
{
name:'Panda',
price:200,
},
{
name:'Lada',
price:100,
}
];
var names = [];
cars.forEach(function(car){
names.push(car.name);
});
console.log(names); //[ 'Escort', 'Panda', 'Lada' ]
Return the index of the given object. -1 will be returned in the object is not found. The second argument set the starting index for the search to begin. The default is zero.
var index;
var escort = {
name:'Escort',
price:2000,
};
var panda = {
name:'Panda',
price:200,
};
var lada = {
name:'Lada',
price:100,
};
var cars = [escort,panda,lada];
index = cars.indexOf(null);
console.log(index); //-1
index = cars.indexOf(escort);
console.log(index); //0
index = cars.indexOf(escort,1);
console.log(index); //-1
index = cars.indexOf(lada,2);
console.log(index); //2
Searches the array backwards from an optional starting index. Stops when the first occurrence of the object is found.
var cars = ['Panda','Escort','Escort','Lada']
var index = cars.lastIndexOf('Escort');
console.log(index); //2
var index = cars.lastIndexOf('Escort', 1);
console.log(index); //1
The map function is very useful. It allows you to create a new array by calling a function for every item in the array and returning the result.
Here we have an array of car objects and we want to calculate the age of each car and add it to the car object. We could use the forEach
loop but a more succinct way is to use the map
function.
cars = [
{
name:'Escort',
date:1980
},
{
name:'Ferrari',
date:1999
},
{
name:'VW Transporter',
date:2005
}
];
var cars2 = cars.map( function(_car) {
let age = new Date().getFullYear() - _car.date
return Object.create(_car,{age:{value:age}});
})
console.log(cars2[0].name); // Escort
console.log(cars2[0].date); // 1980
console.log(cars2[0].age); // 40
These esoteric functions allow a list of items to be reduced down to a single value. A simple example would be to add the values of an array together.
let monthly_car_sales = [1,10,1,10,10,4,1,1,1,1,1,1];
let start = 0;
let yearly_sales = monthly_car_sales.reduce(function(_prev,_current)
{
return _prev + _current;
},start);
console.log(yearly_sales); //42
reduceRight sone the same as reduce
except the array is traversed from right to left. Simples!
Applies a function which should return true or false. If one or more entries return true then the result of calling some
will return true. In the following example we have a list of quarterly sales figures. The business states that in order to have hit targets at least one quarter should have had sales of more than 900.
let sales = [10,500,1000,700];
let hit_target = sales.some(function(_sales){
return _sales > 900;
});
console.log(hit_target); //true
Returns the number of milliseconds since January 1st, 1970.
Returns the date as a string that conforms to ISO standard 8601.
var dt = new Date('2020-01-01 09:00').toISOString()
console.log(dt); //2020-01-01T09:00:00.000Z
Parse and JSON string and return a valid JSON object. An optional argument can be used to change the returned result. If doing this based on a test be sure to return the original value otherwise the property will be deleted from the JSON object.
var jsonStr = 'badjson';
JSON.parse(jsonStr); //SyntaxError: Unexpected token b in JSON at position 0
// Adjust all strings to be upper case
jsonStr = JSON.parse('{"good":"json"}',function(key,value){
if ( typeof value==='string')
return value.toUpperCase();
return value; // Remember to return this
}); //{ good: 'JSON' }
Convert a JSON object into a string. An optional replace can to be used to change the output. You can add a function which works in the same way as the function in the parse
function or use an array which will whitelist what is returned.
var json = {userId:'123-456',name:'Joe Satriani'};
var str = JSON.stringify(json,null,2); // indent by 2 spaces
/*
{
"userId": -333,
"name": "Joe Satriani"
}
*/
console.log(str);
// Do not show the name
str = JSON.stringify(json,function(key,value) {
if ( key === 'name') {
return value.replace(/[a-zA-Z0-9]/g, '*');
}
return value;
});
console.log(str); //{"userId":-333,"name":"*** ********"}
//Only show the name
str = JSON.stringify(json,['name']);
console.log(str); //{"name":"Joe Satriani"}
Boolean, Number, String and Date `toJSON()` functions
‘set’ and ‘get’ keywords
Next we have property setter and getters. These handy constructs allow you to pretend that a function is a simple property. Note that when using the set
keyword the function must have one, and only one, property.
Here we have created a car object with two static properties, name
and make
. Then we have defined a setter property called makeAndName
which returns the two properties concatenated together. The setter property takes a single parameter, the value of which is then split and then assigned to the make and name props.
var car = {
name: 'Escort RS2000',
make: 'Ford',
get makeAndName() {
return this.make +' '+ this.name;
},
set makeAndName(value) {
let makeAndName = value.split(' ');
this.make = makeAndName[0];
this.name = makeAndName[1];
}
}
console.log(car.makeAndName); //Ford Escort RS2000
car.makeAndName = 'Citroen Picasso';
console.log(car.makeAndName); //Citroen Picasso
It is also possible to define a setter/getter when using the defineProperty
function.