Object

Objects are used to store keyed collections of various data and more complex entities.

There are two ways we can create an object -

let user = new Object(); // "object constructor" syntax
let user = {};  // "object literal" syntax

Usually, the figure brackets {...} are used. That declaration is called an object literal.

Literals and properties

We can immediately put some properties into {...} as “key: value” pairs:

let user = {     // an object
  name: "John",  // by key "name" store value "John"
  age: 30        // by key "age" store value 30
};

Here, name, age are called Properties, and "John" and 30 is called value. They are seperated by a colon :. Internally properties are always treated as a string, even though the properties are not quoted, they still are strings. You can optionally enclose the property within the single or double quote.

A property can contain any type of value. In the first property name we have string data type value, in the second property age, we have Number data type value. We can also add boolean, array, etc. Any type of data is valid for property.

In the above example, we have assigning literal directly to the property. But we can also add a variable to that property -

let myName = "Santanu Bera";
let obj = {
	name : myName,
	age : "29"
};

In the above example, the property name will contain the value of the variable myName that is "Santanu Bera". In the above case, the value is copied by value. It means the value of the variable myName gets copied to the property name. If the value were array, or another object, then a reference to the variable would get copied to the property.

Adding a Property

After you have declared an object, you may want to add more properties to it, you can do it by dot notation -

object.newProperty = value;

In the above syntax, if the newProperty already exists, then it updates the value, if it doesn't exists, then it adds a new property called newProperty and assign the value value. For example -

let myName = "Santanu Bera";
let obj = {
	name : myName,
	age : "29"
};

obj.height = "6 Feet";
console.log(obj);
// name : "Santanu Bera"
// age : 29
// height : "6 Feet"

Accessign a Property

Property values are accessible using the dot notation:

// get fields of the object:
alert( user.name ); // John
alert( user.age ); // 30

Delete a Property

To remove a property, we can use delete operator:

delete user.age;

Multiword Property

We can also use multiword property names, but then they must be quoted:

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // multiword property name must be quoted
};

Reserved words are allowed as property names

A variable cannot have a name equal to one of language-reserved words like “for”, “let”, “return” etc. But for an object property, there’s no such restriction. Any name is fine:

let obj = {
  for: 1,
  let: 2,
  return: 3
};
alert( obj.for + obj.let + obj.return );  // 6

Special property __proto__

You cannot have a property named __proto__, In JS, it convey different meaning and bound to object. So do not ever create a property with the name __proto__.

Trailing Comma

The last property in the list may end with a comma:

let user = {
  name: "John",
  age: 30,
}

That is called a “trailing” or “hanging” comma. Makes it easier to add/remove/move around properties, because all lines become alike.

Square brackets

For multiword properties, the dot access doesn’t work -

// this would give a syntax error
user.likes birds = true

That’s because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations.

There’s an alternative “square bracket notation” that works with any string:

let user = {};

// set
user["likes birds"] = true;

// get
alert(user["likes birds"]); // true

// delete
delete user["likes birds"];

Now everything is fine. Please note that the string inside the brackets is properly quoted (any type of quotes will do).

Computed Property : Variable as a property

You can use square baracket notation along with variable to create property name. These kind of property is called computed property. For example -

let name = "Santanu";
let obj = {};
obj[name] = "Access Granted";
console.log(obj);
// Santanu : "Access Granted"

But this is not possible with dot notation -

obj.name = "Access Granted";
console.log(obj);
// name : "Access Granted"

You can use computed property directly within object literal -

let name = "Santanu";
let obj = {
	[name] : "Access Granted",
};
console.log(obj);
// Santanu : "Access Granted";

Property value shorthand

In real code we often use existing variables as values for property names.

function makeUser(name, age) {
  return {
    name: name,
    age: age
    // ...other properties
  };
}

let user = makeUser("John", 30);
alert(user.name); // John

In the example above, properties have the same names as variables. The use-case of making a property from a variable is so common, that there’s a special property value shorthand to make it shorter.

Instead of name:name we can just write name, like this:

function makeUser(name, age) {
  return {
    name, // same as name: name
    age   // same as age: age
    // ...
  };
}

We can use both normal properties and shorthands in the same object:

let user = {
  name,  // same as name:name
  age: 30
};

Existence check

To know if a property exists within a object, we can compare with undefined value -

let obj = {
	name : "Santanu",
	age : 30,
};
console.log(obj.address == undefined); // true
console.log(obj.name == undefined); // false

So, if you try to access any property that doesn't exists, it returns undefined, otherwise it returns it's value. But there is a problem here, what if a property exists but it contains undefined value.

let obj = {
	name : undefined,
};
console.log(obj.name == undefined); // true

In the above example, the property name exists, but it still returns true. In these kind of scinario, you can use in operator to check if the operator exists.

// Syntax --
"propertyName" in ObjectName

// Example --
let obj = {
	name : undefined,
	"my name" : "Santanu Bera";
};
// Exits
console.log("name" in obj); // true

// Doesn't Exists 
console.log("address" in obj); // false

Remember that the property name must be quoted. This example works multiword property too -

console.log("my name" in obj); // true

For In Loop

To walk over all keys of an object, there exists a special form of the loop: for..in

for(key in object) {
  // executes the body for each key among object properties
}
let user = {
  name: "John",
  age: 30,
  isAdmin: true
};

for(let key in user) {
  // keys
  alert( key );  // name, age, isAdmin
  // values for the keys
  alert( user[key] ); // John, 30, true
}

Copying By Reference

A variable stores not the object itself, but its “address in memory”, in other words “a reference” to it.

let user = {
  name: "John"
};

Here, the object is stored somewhere in memory. And the variable user has a “reference” to it. When an object variable is copied – the reference is copied, the object is not duplicated. For instance:

let user = { name: "John" };

let admin = user; // copy the reference

Now we have two variables, each one with the reference to the same object:

let user = { name: 'John' };

let admin = user;

admin.name = 'Pete'; // changed by the "admin" reference

alert(user.name); // 'Pete', changes are seen from the "user" reference

Comparison by reference

The equality == and strict equality === operators for objects work exactly the same. Two objects are equal only if they are the same object.

let a = {};
let b = a; // copy the reference

alert( a == b ); // true, both variables reference the same object
alert( a === b ); // true

And here two independent objects are not equal, even though both are empty:

let a = {};
let b = {}; // two independent objects

alert( a == b ); // false

Const Object

An object declared as const can be changed.

const user = {
  name: "John"
};

user.age = 25; // (*)

alert(user.age); // 25

It might seem that the line (*) would cause an error, but no, there’s totally no problem. That’s because const fixes the value of user itself. And here user stores the reference to the same object all the time. The line (*) goes inside the object, it doesn’t reassign user.

The const would give an error if we try to set user to something else, for instance:

const user = {
  name: "John"
};

// Error (can't reassign user)
user = {
  name: "Pete"
};

Cloning Object : Object.assign

So, copying an object variable creates one more reference to the same object. But what if we need to duplicate an object? Create an independent copy, a clone? That’s also doable, but a little bit more difficult, because there’s no built-in method for that in JavaScript. Actually, that’s rarely needed. Copying by reference is good most of the time. But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.

let user = {
  name: "John",
  age: 30
};

let clone = {}; // the new empty object

// let's copy all user properties into it
for (let key in user) {
  clone[key] = user[key];
}

// now clone is a fully independent clone
clone.name = "Pete"; // changed the data in it

alert( user.name ); // still John in the original object

Also we can use the method Object.assign for that.

Object.assign(dest[, src1, src2, src3...])

Arguments dest, and src1, ..., srcN (can be as many as needed) are objects. It copies the properties of all objects src1, ..., srcN into dest. In other words, properties of all arguments starting from the 2nd are copied into the 1st. Then it returns dest. For example -

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

// copies all properties from permissions1 and permissions2 into user
Object.assign(user, permissions1, permissions2);

// now user = { name: "John", canView: true, canEdit: true }

If the receiving object (user) already has the same named property, it will be overwritten:

let user = { name: "John" };

// overwrite name, add isAdmin
Object.assign(user, { name: "Pete", isAdmin: true });

// now user = { name: "Pete", isAdmin: true }

We also can use Object.assign to replace the loop for simple cloning:

let user = {
  name: "John",
  age: 30
};

let clone = Object.assign({}, user);

It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

Deep Cloning

Until now we assumed that all properties of user are primitive. But properties can be references to other objects. What to do with them? For example -

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

alert( user.sizes.height ); // 182

Now if you use loop or Object.assign to clone the object, in the new object, the properties sizes will be copied by reference, as it is an object. So the sizes properties of source object and destination object will share the same reference. If you change in the source, it will be affected in destination object.

let targetObject = Object.assign({}, user);
user.sizes.width = 100;
alert(targetObject.sizes.width); // 100

So Object.assign actually clone upto the topmost level. It cannot copy in the deep level.

To fix that, we should use the cloning loop that examines each value of user[key] and, if it’s an object, then replicate its structure as well. That is called a Deep Cloning.

There’s a standard algorithm for deep cloning that handles the case above and more complex cases, called the Structured cloning algorithm. In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library lodash, the method is called _.cloneDeep(obj).

Methods in an Object

An object can also contain methods -

let user = {
  name: "John",
  age: 30
};

user.sayHi = function() {
  alert("Hello!");
};

user.sayHi(); // Hello!

Of course, we could use a pre-declared function as a method, like this:

let user = {
  // ...
};

// first, declare
function sayHi() {
  alert("Hello!");
};

// then add as a method
user.sayHi = sayHi;

user.sayHi(); // Hello!

Method shorthand

// these objects do the same

let user = {
  sayHi: function() {
    alert("Hello");
  }
};

// method shorthand looks better, right?
let user = {
  sayHi() { // same as "sayHi: function()"
    alert("Hello");
  }
};

As demonstrated, we can omit "function" and just write sayHi().

this

It’s common that an object method needs to access the information stored in the object to do its job. For instance, the code inside user.sayHi() may need the name of the user.

To access the object, a method can use the this keyword.

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(this.name);
  }

};

user.sayHi(); // John

In the above example, the keyword this refers to the same object by which it is called. As we are using user.sayHi(), in this case this refers to the object user.

Technically, it’s also possible to access the object without this, by referencing it via the outer variable:

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(user.name); // "user" instead of "this"
  }

};

But this way of accessing object from inside the method is not recommended. In few cases, it will result unpredictable behaviour. So always use this.

Internals: Reference Type

An intricate method call can lose this, for instance:

let user = {
  name: "John",
  hi() { alert(this.name); },
  bye() { alert("Bye"); }
};

user.hi(); // John (the simple call works)

// now let's call user.hi or user.bye depending on the name
(user.name == "John" ? user.hi : user.bye)(); // Error!

You can see that the last call results in an error, because the value of "this" inside the call becomes undefined.

If we want to understand why it happens, let’s get under the hood of how obj.method() call works.

Looking closely, we may notice two operations in obj.method() statement:

  1. First, the dot '.' retrieves the property obj.method.
  2. Then parentheses () execute it.

So, how does the information about this get passed from the first part to the second one? If we put these operations on separate lines, then this will be lost for sure:

let user = {
  name: "John",
  hi() { alert(this.name); }
}

// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined

Here hi = user.hi puts the function into the variable, and then on the last line it is completely standalone, and so there’s no this.

To make user.hi() calls work, JavaScript uses a trick – the dot '.' returns not a function, but a value of the special Reference Type.

The Reference Type is a “specification type”. We can’t explicitly use it, but it is used internally by the language. The value of Reference Type is a three-value combination (base, name, strict), where:

The result of a property access user.hi is not a function, but a value of Reference Type. For user.hi in strict mode it is:

// Reference Type value
(user, "hi", true)

When parentheses () are called on the Reference Type, they receive the full information about the object and its method, and can set the right this (=user in this case).

Any other operation like assignment hi = user.hi discards the reference type as a whole, takes the value of user.hi (a function) and passes it on. So any further operation “loses” this.

So, as the result, the value of this is only passed right way if the function is called directly using a dot obj.method() or square brackets obj['method']() syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as func.bind().

Arrow functions have no “this”

Arrow functions are special: they don’t have their “own” this. If we reference this from such a function, it’s taken from the outer “normal” function. For instance, here arrow() uses this from the outer user.sayHi() method:

// The following example works
// as "this" inside the arrow is taken from outer function sayHi()
let user = {
  firstName: "Ilya",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // Ilya

That’s a special feature of arrow functions, it’s useful when we actually do not want to have a separate this, but rather to take it from the outer context.

Object to primitive conversion

When an object is used in the context where a primitive is required, for instance, in an alert or mathematical operations, it’s converted to a primitive value using the ToPrimitive algorithm. That algorithm allows us to customize the conversion using a special object method. Depending on the context, the conversion has a so-called “hint”. There are three hints -

string

When an operation expects a string, for object-to-string conversions, like alert:

// output
alert(obj);

// using object as a property key
anotherObj[obj] = 123;

number

When an operation expects a number, for object-to-number conversions, like maths:

// explicit conversion
let num = Number(obj);

// maths (except binary plus)
let n = +obj; // unary plus
let delta = date1 - date2;

// less/greater comparison
let greater = user1 > user2;

default

Occurs in rare cases when the operator is “not sure” what type to expect.

For instance, binary plus + can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using == with a string, number or a symbol.

// binary plus
let total = car1 + car2;

// obj == string/number/symbol
if (user == 1) { ... };

The greater/less operator <> can work with both strings and numbers too. Still, it uses “number” hint, not “default”. That’s for historical reasons.

In practice, all built-in objects except for one case (Date object, we’ll learn it later) implement "default" conversion the same way as "number". And probably we should do the same.

Please note – there are only three hints. It’s that simple. There is no “boolean” hint (all objects are true in boolean context) or anything else. And if we treat "default" and "number" the same, like most built-ins do, then there are only two conversions.

To do the conversion, JavaScript tries to find and call three object methods:

  1. Call obj[Symbol.toPrimitive](hint) if the method exists,
  2. Otherwise if hint is "string", try obj.toString() and obj.valueOf(), whatever exists.
  3. Otherwise if hint is "number" or "default", try obj.valueOf() and obj.toString(), whatever exists.

Symbol.toPrimitive

Let’s start from the first method. There’s a built-in symbol named Symbol.toPrimitive that should be used to name the conversion method, like this:

obj[Symbol.toPrimitive] = function(hint) {
  // return a primitive value
  // hint = one of "string", "number", "default"
}

For instance, here user object implements it:

let user = {
  name: "John",
  money: 1000,

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money;
  }
};

// conversions demo:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500

As we can see from the code, user becomes a self-descriptive string or a money amount depending on the conversion. The single method user[Symbol.toPrimitive] handles all conversion cases.

toString/valueOf

Methods toString and valueOf come from ancient times. They are not symbols (symbols did not exist that long ago), but rather “regular” string-named methods. They provide an alternative “old-style” way to implement the conversion.

If there’s no Symbol.toPrimitive then JavaScript tries to find them and try in the order:

For instance, here user does the same as above using a combination of toString and valueOf:

let user = {
  name: "John",
  money: 1000,

  // for hint="string"
  toString() {
    return `{name: "${this.name}"}`;
  },

  // for hint="number" or "default"
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

In the absence of Symbol.toPrimitive and valueOf, toString will handle all primitive conversions.

let user = {
  name: "John",

  toString() {
    return this.name;
  }
};

alert(user); // toString -> John
alert(user + 500); // toString -> John500

In the above example, we didn't implement valueOf method to convert Object to Number type. In these scinario, toString() is called.

Constructor Function : new

The regular {...} syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on. That can be done using constructor functions and the "new" operator.

Constructor functions technically are regular functions. There are two conventions though:

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

When a function is executed as new User(...), it does the following steps:

In other words, new User(...) does something like:

function User(name) {
  // this = {};  (implicitly)

  // add properties to this
  this.name = name;
  this.isAdmin = false;

  // return this;  (implicitly)
}

So the result of new User("Jack") is the same object as:

let user = {
  name: "Jack",
  isAdmin: false
};

Now if we want to create other users, we can call new User("Ann"), new User("Alice") and so on. Much shorter than using literals every time, and also easy to read.

That’s the main purpose of constructors – to implement reusable object creation code.

Make the constructor not reusable in future

If we have many lines of code all about creation of a single complex object, we can wrap them in constructor function, like this:

let user = new function() {
  this.name = "John";
  this.isAdmin = false;

  // ...other code for user creation
  // maybe complex logic and statements
  // local variables etc
};

The constructor can’t be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse.

Omitting parentheses

By the way, we can omit parentheses after new, if it has no arguments:

let user = new User; // <-- no parentheses
// same as
let user = new User();

We can also add methods to Function Constructor

Of course, we can add to this not only properties, but methods as well. For instance, new User(name) below creates an object with the given name and the method sayHi:

function User(name) {
  this.name = name;

  this.sayHi = function() {
    alert( "My name is: " + this.name );
  };
}

let john = new User("John");

john.sayHi(); // My name is: John

/*
john = {
   name: "John",
   sayHi: function() { ... }
}
*/

new.target

Inside a function, we can check whether it was called with new or without it, using a special new.target property.

It is empty for regular calls and equals the function if called with new:

function User() {
  alert(new.target);
}

// without "new":
User(); // undefined

// with "new":
new User(); // function User { ... }

That can be used to allow both new and regular calls to work the same. That is, create the same object:

function User(name) {
  if (!new.target) { // if you run me without new
    return new User(name); // ...I will add new for you
  }

  this.name = name;
}

let john = User("John"); // redirects call to new User
alert(john.name); // John

This approach is sometimes used in libraries to make the syntax more flexible. So that people may call the function with or without new, and it still works.

Global Object : Window

When JavaScript was created, there was an idea of a “global object” that provides all global variables and functions. It was planned that multiple in-browser scripts would use that single global object and share variables through it. Since then, JavaScript greatly evolved, and that idea of linking code through global variables became much less appealing. In modern JavaScript, the concept of modules took its place. But the global object still remains in the specification.

In a browser it is named “window”, for Node.JS it is “global”, for other environments it may have another name.

It does two things:

…But the global object does not have variables declared with let/const!

let user = "John";
alert(user); // John

alert(window.user); // undefined, don't have let
alert("user" in window); // false

Use of Window

The usage of window is not very often though but still there are some situation where you can use Window object -

Accessing Global Variable

To access exactly the global variable if the function has the local one with the same name.

var user = "Global";

function sayHi() {
  var user = "Local";

  alert(window.user); // Global
}

sayHi();

If you used user instead of window.user, then you would have gotten "Local", local value. Using Window, you now have access to the Global object. The trick doesn’t work with let variables.

Check for Existance

To check if a certain global variable or a builtin exists. For instance, we want to check whether a global function XMLHttpRequest exists. We can’t write if (XMLHttpRequest), because if there’s no XMLHttpRequest, there will be an error (variable not defined). But we can read it from window.XMLHttpRequest:

if (window.XMLHttpRequest) {
  alert('XMLHttpRequest exists!')
}

If there is no such global function then window.XMLHttpRequest is just a non-existing object property. That’s undefined, no error, so it works.

“this” and global object

Sometimes, the value of this is exactly the global object. That’s rarely used, but some scripts rely on that.

In the browser, the value of this in the global area is window:

// outside of functions
alert( this === window ); // true

When a function with this is called in non-strict mode, it gets the global object as this:

// not in strict mode (!)
function f() {
  alert(this); // [object Window]
}

f(); // called without an object

By specification, this in this case must be the global object, even in non-browser environments like Node.JS. That’s for compatibility with old scripts, in strict mode this would be undefined.