Javascript: objects

·

9 min read

Introduction

objects are an unordered collection of properties where each property has a name and a value. objects are one of the most fundamental data types in javascript. In fact, almost everything in javascript can behave as an object even though they are not typical objects. Arrays are a special kind of object, functions can be thought of as an object, and even the primitive data types such as strings, numbers, and boolean have methods even though they are not typical objects.

Object creation

you can create an object in the following ways.

  1. object literal

  2. object.create()

  3. new object()

Attributes of properties:

Each property of an object has three Attributes which are

  1. writable
    specifies whether the property value can be set

  2. enumerable
    specifies whether the property name is returned when used with for(in) loop

  3. configurable
    specifies whether the property can be deleted and whether its attributes can be
    altered

object literal

The easiest way to create an object is to include an object literal. It has the following syntax.

let x = { name: john, age: 25, country: England}

It is enclosed by curly braces with properties and values separated by a colon. The property name can be of any javascript identifier or a string and values can be of any javascript expression. So you can even have objects, functions, and Arrays inside an object. object properties are typically strings but since ES6 object properties can be Symbols as well.

new object()

The new operator creates and initializes a new object. The new keyword is followed by a function invocation. A function used in this way is known as a constructor which initializes the newly created object. Many built-in types in javascript have a constructor. Look at the following examples.

let x = new Object() // creates an empty object {}
let y = new Array()   // creates an empty array []
let z = new Date()    // creates a date object representing the current time

Object.create()

It creates a new object with its first argument as its prototype which means it inherits the properties from the first argument. The second argument is optional which specifies the own properties of the newly created object.

let x = {a:1,b:2}
let o = Objec.create(x)
o.a //=>1 inherited property
o.b //=>2 inherited prperty

prototype

Almost every object in javascript has a second object associated with it known as a prototype. It inherits properties from this object. objects created using a new keyword and a constructor invocation uses the value of the prototype property of the constructor function as their prototype. Like Array() constructor function has a prototype property. when you create an array using this Array constructor function,it inherits all the properties from Array().prototype. A date object created using the Date() constructor function inherits properties from Date().prototype. In this way, objects have this chain of prototypes that inherits properties.

querying objects

object properties can be queried in the following two ways.

object.identifier
object[expression]

The dot operator is used when you know the property name beforehand. The square bracket is useful when you don't know the property name beforehand and it is known at the runtime. The expression here in the square brackets is evaluated and then converted into a string. This kind of object where indexed by strings is known as an Associative array. In contrast, arrays are a special kind of object but indexed by numbers.

Inheritance

objects in javascript have their properties and inherited properties. so when you query an object, it first looks up its own properties. If it doesn't find it then the prototype object of that object is queried. Still, if it doesn't find the property then it queries the prototype of the prototype. This way it traverses the chain of prototypes until it finds a null. Look at the following example.

let o = {}                         // empty object
o.x = 1                            // o has own property x
let student = object.create(o)     // student inherits from o
student.y = 2                      // student has own property y
let p = object.create(student)     // p inherits from  student and o
p.x + p.y                          // =>3 x and y are inherited properties

Similarly when you try to assign a property say for example property x to object o. If the object already has its property x, it simply changes the value of the x to the value you provide otherwise it creates new own property. If O has an inherited property x then it hides the inherited property x and creates its property x.

let a = {x:1,y:2}
let p = Object.create(a) // p inherits from a
p.x // => 1 inherited property
p.x = 5 // creates its own property x
p.x // => 5 own property
a.x // =>1 unchanged

property access errors

There is no error when you query a property that does not exist. It simply returns undefined. However, every if you try to query an object which does not exist it throws a type error. NULL and undefined are the two values which do not have any properties and throw a type error if you try to access them.

let x = {y:1}   // x has a property
x.p            // undefined
x.p.y         // type error because x.p is undefined

To avoid this you can use short-circuiting operators such as logical AND and conditional operator(?.).

Deleting properties

The delete operator is used to delete the properties of an object. It deletes the property not just the value. It evaluates to true if it succeeds. So when you query a deleted property it does not exist. The delete operator expects one operand.

let person = {name: sam, age: 25}   // an object called person
delete person.age                  // evaluates to true
person.age                         // undefined

The delete operator removes only the own properties, not the inherited properties. It also cannot remove if the configurable attribute of the property is set to false.

Testing properties

we can check whether a property exists in an object in the following ways.

  1. in operator
    The in-operator expects two operands. The left of it should be a property name and the right of it should be an object. It returnss true if the object has its own property or inherited property.
let o = {x:1} 
"x" in o      // true for own property
"y" in o      // false
"toString" in o   // true for inherited property
  1. hasOwnproperty()
    This method returns true only if the property mentioned is its property. It returns false even though it has an inherited property with the mentioned property name.
let p = {name: rohit}
p.hasOwnProperty("name")    // true for own properties
p.hasownProperty("toString") // false for inherited properties
  1. isPropertyEnumerable()
    This method returns true only if the mentioned property is its own property and its enumerable attribute is set to true. By default the properties that we add manually are enumerable unless we set it to false explicitly.
let car = {cost: 900000, type: "sedan"} 
car.isPropertyEnumerable("type")        //=> true for own properties and are enumerable
car.isPropertyEnumerable("toString")  // => false for inherited property
Object.prototype.isPropertyEnumerable("toString") // false for non enumerable

Enumerating properties

There are a couple of ways to enumerate the properties of an object.

  1. for(in)
    It returns only enumerable properties(own and inherited) but works with any object.

  2. for(of)
    This was introduced in ES6 and only works with iterable objects like arrays, strings, and maps. But objects are not iterable by default. To get the properties of objects using for(of) we can use the following ways.

    1. Object.keys()
      it returns an array of names of enumerable own properties. It doesn't include inherited properties, properties whose name is a symbol, or non-enumerable properties.

    2. Object.getOwnPropertyNames()
      It does the work the same as the object.keys() plus it includes non-enumerable own properties as well.

    3. Object.getOwnPropertySymbols() It returns its properties whose names are symbols both enumerable and non-enumerable.

    4. Reflect.ownKeys()
      It returns all the own properties of an object which can be enumerable, non-enumerable, string names, and symbol names.

Extended object literal syntax

  1. shorthand properties

    suppose you have values stored in x and y. If you want to create properties with the same names as variables x and y it would look like the following example.

     let x = 2,y=4
     let o = {
              x : x,
              y:  y,
     }
    

    In ES6 and later you can omit the colon and one copy of the variable name.

     let x = 2, y=4
     let o = {x,y}
    
  2. computed property names

    If you want to add a property that is not hard-coded or which is not known beforehand or which can change at runtime you would be using the square bracket operator.

     let propertyName = "name"
     function computePropertyName() { return "age"}
     let student = {}
     o[propertyName] = "rohit"
     o[computePropertyName()] = "20"
    

    since ES6 you can use the computed syntax in object literal which looks clear and omits the extra line of object creation.

     let propertyName = "name"
     function computePropertyName() { return "age"}
     let o = {
          o[propertyName] = "rohit",
         o[computePropertyName()] = "20"
     }
    
  3. Spread operator

    In ES2018 and later you can copy the properties of an object into another object using the spread operator(...). It only spreads its own properties, not the inherited properties. It works with any iterable object like arrays, sets, strings, and maps. But objects that you create are not iterable by default. So the below one is only possible inside an object. It does not work if you try to spread an object inside the array.

     let o1 = {name: "raina", age: 23}
     let o2 = {...o1} // object spread is possible only inside an object.
     o2.name  // raina
    
     let str = "abc"
     let arr = [1,2,3,4,...str] // => [1,2,3,4,a,b,c] string is iterable
     let arr2 = [1,2,3,4,...o] // error o is not iterable
    

    If the target object already has the same property then the value of it will be the one that comes last.

     let o1 = {name: "raina", age: 23}
     let o2 = {name: "rohit", ...o1}
     o2.name  // raina
    

shorthand methods

when a function is defined as a property of an object then it is called as method.

Before ES6 the syntax to define a function inside an object literal is by using a function expression.

let obj = {
    x:1,
    y: 2,
    sum: function (){ return this.x + this.y}
}
obj.sum() //=> 3

since ES6 a new syntax has been introduced where the function keyword and colon are omitted. String literals, computed property names, symbols are also allowed as function names.

let obj = {
    x: 1,
    y: 2,
    sum() { return this.x + this.y}
}

Source:

  1. JavaScript: The Definitive Guide, 7th Edition by David Flanagan