Objects

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

An object can be created with figure brackets {...} with an optional list of properties. A property is a “key: value” pair, where key is a string (also called a “property name”), and value can be anything.

Imagine an object as a cabinet with signed files. Every piece of data is stored in its file by the key. It’s easy to find a file by its name or add/remove a file.

cabinet

An empty object (“empty cabinet”) can be created using one of two syntaxes:

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

Usually, the figure brackets {...} are used.

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
  }

A property has a key (also known as “name” or “identifier”) before the colon ":" and a value to the right of it.

In the user object, there are two properties:

  1. The first property has the "name" and the value "John".
  2. The second one has the name "age" and the value 30.

The resulting user object can be imagined as a cabinet with two signed files labeled “name” and “age”.

user cabinet

We can add, remove and read files from it at any time.

Property values are accessible using the dot notation:

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

The value can be of any type.

user.isAdmin = true;
user is admin

To remove a property, we can use the delete operator.

delete user.age;
delete user age

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
  }
likes birds

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

JavaScript doesn’t understand that. It thinks that we address user.likes, and then gives a syntax error when comes across unexpected birds.

The dot requires the key to be a valid variable identifier. That implies: contains no spaces, doesn’t start with a digit and doesn’t include special characters ($ and _ are allowed).

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).

Square brackets also provide a way to obtain the property name as the result of any expression - as opposed to a literal string - like from a variable as follows:

let key = "likes birds";

// same as user["likes birds"] = true;
user[key] = true;

Here, the variable key may be calculated at run-time or depend on the user input. And then we use it to access the property. That gives us a great deal of flexibility.

For instance:

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

let key = prompt("What do you want to know about the user?", "name");

// access by variable
alert( user[key] ); // John (if enter "name")

The dot notation cannot be used similarly:

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

let key = "name";
alert( user.key ) // undefined

Computed properties

We can use square brackets in an object literal, when creating an object. That’s called computed properties.

let fruit = prompt("Which fruit to buy?", "apple");

let bag = {
    [fruit]: 5, // the name of the property is taken from the variable fruit
  };

alert( bag.apple ); // 5 if fruit="apple"

The meaning of a computed property is simple: [fruit] means that the property name should be taken from fruit.

So, if a visitor enters "apple", bag will become {apple: 5}.

Essentially, that works the same as:

let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};

// take property name from the fruit variable
bag[fruit] = 5;

We can use more complex expressions inside square brackets:

let fruit = 'apple';
let bag = {
    [fruit + 'Computers']: 5 // bag.appleComputers = 5
  }

Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write.

So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.

Property value shorthand

In real code, existing variables as values for property names is often used.

For instance:

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

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

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
  };

Property names limitations

Object properties can be named language-reserved words like “for”, “let”, “return” etc.

// these properties are all right
let obj = {
    for: 1,
    let: 2,
    return: 3
  };

alert( obj.for + obj.let + obj.return ); // 6

In short, there are no limitations on property names. They can be any strings or symbols.

Other types are automatically converted to strings.

For instance, a number 0 becomes a string "0" when used as a property key:

let obj = {
    0: "test" // same as "0": "test"
  };

// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)

There’s a minor gotcha with a special property named __proto__. We can’t set it to a non-object value:

let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended

The assignment to a primitive 5 is ignored.

There is a way to fix this.

Property existence test, “in” operator

A notable feature of objects in JavaScript, compared to many other languages, is that it’s possible to access any property. There will be no error if the property doesn’t exist!

Reading a non-existing property just returns undefined. So we can easily test whether the property exists:

let user = {};

alert( user.noSuchProperty === undefined ); // true means " no such property"

There’s also a special operator "in" for that.

The syntax is:

"key" in object

For instance:

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

alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist

Note that on the left side of in there must be a property name. That’s usually a quoted string.

If we omit quotes, that means a variable should contain the actual name to be tested. For instance:

let user = { age: 30 };

let key = "age";
alert( key in user ); // true, property "age" exists

Most of the time the comparison with undefined works fine. But there’s a special case when it fails, but "in" works correctly.

It’s when an object property exists, but stores undefined:

let obj = {
    test: undefined
};

alert( obj.test ); // it's undefined, so - no such property?

alert( "test" in obj ); // true, the property does exist!

The property obj.test technically exists. So the in operator works right.

The “for..in” loop

To walk over all keys of an object, there exists a special form of the loop: for..in. This is a completely different thing from the for(;;) construct.

for (key in object) {
  // executes the body for each key among object properties
}

For instance, let’s output all properties of user:

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
  }

Note that all “for” constructs allow us to declare the looping variable inside the loop, like let key here.

Also, we could use another variable name here instead of key. For instance, "for (let prop in obj)" is also widely used.

Ordered like an object

If we loop over an object, the integer properties are sorted, others appear in creation order.

As an example, let’s consider an object with the phone codes:

let codes = {
  "49": "Germany",
  "41": "Switzerland",
  "44": "Great Britain",
  // ..,
  "1": "USA"
};

for (let code in codes) {
  alert(code); // 1, 41, 44, 49
}

The object may be used to suggest a list of options to the user. If we’re making a site mainly for a German audience then we probably want 49 to be the first.

But if we run the code, we see a totally different picture:

  • USA (1) goes first
  • then Switzerland (41) and so on.

The phone codes go in the ascending sorted order, because they are integers. So we see 1, 41, 44, 49.

If the keys are non-integer, then they are listed in the creation order:

let user = {
  name: "John",
  surname: "Smith"
};
user.age = 25; // add one more

// non-integer properties are listed in the creation order
for (let prop in user) {
  alert( prop ); // name, surname, age
}

So, to fix the issue with the phone codes, we can “cheat” by making the codes non-integer. Adding a plus "+" sign before each code is enough.

let codes = {
  "+49": "Germany",
  "+41": "Switzerland",
  "+44": "Great Britain",
  // ..,
  "+1": "USA"
};

for (let code in codes) {
  alert( +code ); // 40,  41, 44, 1
}

Tasks

Hello, object

Write the code, one line for each action:

  1. Create an empty object user.
  2. Add the property name with the value John.
  3. Add the property surname with the value Smith.
  4. Change the value of the name to Pete.
  5. Remove the property name from the object.
let user = {
  name: "John",
  surname: "Smith"
};
user.name = "Pete"
delete user.name

Check for emptiness

Write the function isEmpty(obj) which returns true if the object has no properties, false otherwise.

function isEmpty(obj) {
  for (let property in obj) {
    return false;
  }
  return true;
}

Sum object properties

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
}

let sum = 0
for (let properties in salaries) {
  sum += salaries[properties];
};

alert(sum); // 390

Multiply numeric property values by 2

Create a function multiplyNumeric(obj) that multiplies all numeric property values of obj by 2.

For instance:

// before the call
let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};

multiplyNumeric(menu);

// after the call
menu = {
  width: 400,
  height: 600,
  title: "My menu"
};
function multiplyNumeric(obj) {
  for (let properties in obj) {
    if (!isNaN(obj[properties])){
  // could also be
  // if (typeof obj[properties] == 'number') {
      obj[properties] *= 2;
    }
  }
}

For another take on this look at MDN article

Some more Arrays stuff

Wes bos JavaScript Array Cardio Practice Part 2