Objects in javascript

Everything except primitives is an object in JavaScript. An Object has properties and methods.

Creating objects

You can create objects in many ways in JavaScript as mentioned below.
  1. literal object - syntactic sugar for new Object(). Each object gets separate copy of properties/methods. but inheritance is possible using Object.setPrototypeof(myobject,prototypeObject)
  2. Constructor functions (class keyword in ES6 is syntactis sugar for constructor functions) - you need to use new keyword to create an object. As a convention, first letter must be Capital. Important point to note here is that you can share properties or methods using FunctionName.prototype syntax.
  3. using Object.create() - we can share the props among objects using Object.create. In factory functions, each object gets separate copy of methods. We can share same copy of method/prop using object.create which is memory efficient. Object.create creates the prototype chain.
  4. Object.assign
  5. factory functions - function that create and return objects created using any of the methods above

//creating objects using literals
let car1 ={
        make:"honda",
        price:20000,
        features:['abs','alarm']
    }

console.log(car1.make);
//output - honda

console.log("car 1 " + JSON.stringify(car1));
//output - car 1 {"make":"honda","price":20000,"features":["abs","alarm"]}

//create objects using new operator (constructor)
let car2 = new Object();
car2.make = "honda";
car2.price = 20000;
car2.features = ['abs','alarm'];

console.log("car 2 " + JSON.stringify(car2));
//output - car 2 {"make":"honda","price":20000,"features":["abs","alarm"]}

let car3 = new Array();
car3.push(car1);
car3.push(car2);
console.log("car 3 " + JSON.stringify(car3));
/*output - car 3 [{"make":"honda","price":20000,"features":["abs","alarm"]},
 {"make":"honda","price":20000,"features":["abs","alarm"]}]*/


/*similarly we can also create custom constructor and
then create an object using new operator */
let Car =  function(make,price,features){
    this.make=make;
    this.price = price;
    this.features = features;
}

/*
new operator steps
- Creates a blank, plain JavaScript object.
- Adds a property to the new object (__proto__) that links to the constructor function's prototype 
object
- Binds the newly created object instance as the this context (i.e. all references to this in the 
constructor function now refer to the object created in the first step).
- Returns this if the function doesn't return an object.
*/

let car4 = new Car("Honda", 20000, ['abs','alarm']);
console.log("car 4 " + JSON.stringify(car4));
//output - car 4 {"make":"Honda","price":20000,"features":["abs","alarm"]}

//create objects using Object.create(prototypeObject, propertiesObject) method
//Advantage of using this method is that we can specify if the property is writable
//and or enumerable
let propObject = {
    make:{value:"honda",writable:true,enumerable:true},
    price:{value:20000,writable:false,enumerable:true},
    features:{value:['abs','alarm'],writable:false,enumerable:true}
}
let car5 = Object.create(null,propObject);
console.log("car 5 " + JSON.stringify(car5));
//output - car 5 {"make":"Honda","price":20000,"features":["abs","alarm"]}

//below object will not inherit from Object. so it will not have methods like toString()
const dummy1 = Object.create(null);


//Object.assign
let o5 = Object.assign({},o1) // {} is required, otherwise o1 and o5 will refer to same object
o5.p = 11
console.log('o5 -> ' , o5);
console.log('o1 -> ' , o1);


Object Properties

Creating properties

Object.defineProperty can be used to create property.

let user = {
    name: "Sagar"
  };

  //create new property "id" with value 333 and overwriting is not allowed
  Object.defineProperty(user, "id", {writable:false, 
    enumerable:true, configurable:true, value:333
  });

Property aspects

3 important aspects of objects properties are
  • owned or inherited
  • Enumaerable or Non-enumerable
  • string or symbol
There are several methods that exist in JS that behave differently based on above aspects.
  • Querying props - propertyIsEnumerable(), hasOwnProperty(), Object.hasOwn(), in
  • Traversing props - Object.keys, Object.values, Object.entries, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.getOwnPropertyDescriptors , Reflect.ownKeys, for...in, Object.assign, ... spread syntax
Please refer Enumerability and Ownership for more details.

let user = {
    name: "John"
  };
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );

You can access all methods and properties of any object using below code.

//show all properties and methods

for (let key in car5){
    console.log (key, typeof car5[key],car5[key]);
}
// output 
/*
make string honda
price number 20000
features object [ 'abs', 'alarm' ]
*/

Prototypes

  • Object.getProtoypeOf()
  • Object.setPrototypeOf

Copying and cloning objects

By using loop


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 object with the same content clone.name = "Pete"; // changed the data in it

alert( user.name ); // still John in th

Object.assign method

You can also use the Object.assign method to copy objects

let user = {
    name: "John",
    age: 30
  };
  
  let clone = Object.assign({}, user);

To make a “real copy” (a clone) we can use Object.assign for the so-called “shallow copy” (nested objects are copied by reference) or a “deep cloning” function, such as _.cloneDeep(obj) from lodash library. Const objects can be modified. Property flags and descriptors can be used to make object properties constant. Functions that are stored in object properties are called methods. When a function is executed with new, it does the following steps:
  • A new empty object is created and assigned to this.
  • The function body executes. Usually it modifiesthis, adds new properties to it.
  • The value of this is returned.

Comparing 2 objects

  • by using lodash _equal() method
  • by stringifying and then comparing

Destructuring objects


//object desctructuring
let dimensions = {
    width: 1100,
    height: 2200
  };
  
  //extract only width prop from dimensions object
  let {width} = dimensions;

//array destructuring
let arr = ["sagar", "salunke"]
let [firstName, surname] = arr;
let [a, b, c] = "abc"; // ["a", "b", "c"]

let [p1, p2, ...p3] = ["X", "Y", "Z", "and more";

More on Objects


// Objects - create and destroy  
// serialization
// equality
// Immutable Object
// Copying or cloning (shallow and deep)
// Comparing 2 objects
// Props - add delete props, attributes, enumerate props, isOwned meaning


// simple way to create a object using literal expression
let user = {
  name:"sagar",
  email: "[email protected]",
  isMarried : true,
  mobile : "0400000000",
  "address" : {
    city:"brisbane",
    postcode : 4000
  },
  skills : ["js","ts", ".net", "java", "selenium", "python", "Azure","aws"],

  //this is mandatory. otherwise you will get ref error
  showInfo: function(){console.log(this.name,this.email)},
  salary:(function(){
    var __salary = 90000;

    //IIFE can be used to create private variables in object literal

    //return the members that you want to expose
    return {      
      getTaxBracket: function()
      {
        return __salary>90000 ? 1:2;
      }
    };
  }())
}


//destroy object - just set it to null - garbage collector will take care after that
//user = null


if(user===null)
console.log("User is pointing to null")


//access prop
console.log("User Mobile ", user.mobile)

//call object method
console.log("User Info ", user.showInfo())

//serialiazation & Deserialiazation
// convert object to string - serialaization
let serialiazedUser =  JSON.stringify(user)
console.log("String representation -> ",serialiazedUser)

// convert string to object - deserialaization
console.log("Object representation -> ", JSON.parse(serialiazedUser))

// equality
let user1 = user 
if (user1==user){
  console.log("user1 and user are same objects")
}
let user3 = {}
if (user1!=user3){
  console.log("user1 and user3 are different objects")
}



// Immutable objects
// Undefined, Null, Boolean, Number, BigInt, String, Symbol - these are immutable - passed by value
// Objects are mutable by default - we can add/remove props of object
// We can make certain props immutable by setting writable attribute to false

//To prevent any types of modifications in a object, freeze the object
var obj = { foo: 'bar' };
Object.freeze(obj);
delete obj.foo  //silently ignored
console.log("Frozen object ", obj) 


// clone 
// shallow copy means nested objects will not be copied. Nested object will be shared 

//shallow copying using spread operator and Object.assign
const clonedUser = { ...user };
console.log("Cloned user", clonedUser);

const clonedUser2 = Object.assign({}, user);

// deep copy using JSON - not recommended as you may lose data
/*
Object literal notation vs JSON
The object literal notation is not the same as the JavaScript Object Notation (JSON). 
- In JSON, The property name must be double-quoted, and the definition cannot be a shorthand.
- In JSON the values can only be strings, numbers, arrays, true, false, null, or another (JSON) object.
- A function value can not be assigned to a value in JSON.
- Objects like Date will be a string after JSON.parse().
- JSON.parse() will reject computed property names and an error will be thrown.
*/
const clonedUser3 = JSON.parse(JSON.stringify(user));

//using lodash
var deepCopy = _.cloneDeep(obj);


// comparing 2 primitives or objects
// == (type conversion happens - typeof operator) vs === (no type conversion happens)
// _.isEqual method of lodash compares object values recursively
//private props
console.log("Salary Bracket ", user.salary.getTaxBracket())
console.log("x ", user.x) //undefined
console.log("Salary ", user.salary.__salary) //undefined

//Shallow vs deep copy of objects
let user1 = {
  name: "sagar",
  id: 12,
  address: {
    city: "brisbane",
    postcode: 4000
  }
};

let user2 = user1; //not a copy
//true
console.log("user1===user2? ", user1 === user2);

let user3 = { ...user1 };
//false
console.log("user1===user3? ", user1 === user3, user3);

//true - address is a reference to nested object,
//so it is copied in shallow manner
console.log(
  "user1.address===user3.address? ",
  user1.address === user3.address,
  user3
);

//Shallow vs deep copy of Arrays
let a1 = [{ a: 1 }, { b: 2 }];

//not a copy
let a2 = a1;

//true
console.log("a1===a2? ", a1 === a2, a2);

let a3 = [...a1];

//false
console.log("a1===a3? ", a1 === a3, a3);

//true - Shallow copy
console.log("a1[0]===a3[0]? ", a1[0] === a3[0], a3);



// props

//add new prop to user
user.landline = "0203020"

//remove prop
delete user.landline

// enumerate all props
console.log("Enumerating object")

for (var i in user) {
  console.log(i);
}

for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

//convert object into map of key,value
new Map(Object.entries(user));

//Prop attributes - enumerable, configurable, writable
//Object.defineProperty(obj, prop, descriptor)

Object.defineProperty(user, 'citizenship', {
  enumerable: false,
  configurable: false,
  writable: true,
  value: 'AUS'
});


console.log("new prop citizenship -> ", user.citizenship)

// check if prop is available on object and prototype chain
console.log("is citizenship prop in user object -> ",'citizenship' in user)
console.log("is toString prop in user object -> ",'toString' in user) //true
console.log("is toString owned prop in user object -> ", user.hasOwnProperty("toString")) //false



//destroy props
Object.getOwnPropertyNames(user).forEach(function (prop) {
  delete user[prop];
});

// for enumerable and non-enumerable properties
for (const prop of Object.getOwnPropertyNames(user)) {
  delete user[prop];
}

Web development and Automation testing

solutions delivered!!