Object Oriented Javascript

What do you get at the end of this page?

  • JavaScript Engine
  • What is a paradigm?
  • What are different types of paradigms?
  • Why do we need paradigm?
  • What is OOP?
  • Some of the OOP supported languages
  • How do we achieve OOP in JavaScript. Please explain with examples?
  • Hoisting
  • Closures
  • ES6 features
  • Pass by value vs pass by reference
  • this in ES5 and ES6
  • and many more to come.....

JavaScript Engine



Compiled language — the source file typically will be “compiled” to machine code (or byte code) before being executed.

Interpreted language — the source code will be read and directly executed, line by line.

Let’s say you want to make a Mojito, you go to Google (or Bing if you are a freak) and search for the ingredients list, this is what you came up with:

· The juice of 1 Lime
· White rum
· Mint
· Soda water

There are 2 ways to make the cocktail, the Compiler or the Interpreter way.

The compiler will first, before doing any mixing, organize all the ingredients in front of him, the specific amounts of every single ingredient, only then, will he mix all the ready components of the cocktail.

The interpreter will take his glass and will start by reading the ingredients, line by line. he will go to his refrigerator and will fetch a lemon, cut it and squeeze it directly into the glass, then pour the white rum, etc.

The build (preparation) time of the compiler will be longer than the interpreters. however, the run (mixing) time will be much shorter.



What is a paradigm?

  • A programming paradigm is the way of thinking about or approaching problems.
  • A programming paradigm is a fundamental style of building the structure and elements of a program.
  • It influences the programming language and other things built on top of the language, such as libraries, frameworks, and common styles and patterns of programming.
  • The styles and capabilities of programming languages are defined by their paradigms.

What are different types of paradigms?

  • Imperative
    • Paradigm of computer programming in which the program describes a sequence of steps that change the state of the computer. It explicitly tells the computer "how" to accomplish it not "what" to accomplish
  • Declarative
    • which describes "what" a program should accomplish not "how" to.
  • Object-oriented
    • OOP is a programming paradigm based upon objects (having both data and methods) that aims to incorporate the advantages of modularity and reusability. Objects, which are usually instances of classes, are used to interact with one another to design applications and computer programs.
  • Functional
    • A style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.
  • Logic
    • (Rule-based): Programming by specifying a set of facts and rules. An engine infers the answers to questions.
  • Symbolic
  • Structured
    • Programming with clean, goto-free, nested control structures.
  • Procedural
    • Programming by manipulating the program elements themselves.
  • Event-driven
    • Programming with emitters and listeners of asynchronous actions.
  • Flow-driven
    • Programming processes communicating with each other over predefined channels.
  • Constraint
    • Programming by specifying a set of constraints. An engine finds the values that meet the constraints.
  • Aspect oriented
    • Programming cross-cutting concerns applied transparently.
  • Reflected
    • Programming by manipulating the program elements themselves.

Why do we need paradigm?

Programming language paradigms are just like genre of music. People like different kinds of music according to their favor, and likely, different problem you are solving should be coded in different paradigms. 

For example, you are programming to get the result of "1+1", you should program in Procedural Programming paradigm rather than Object-Oriented paradigm, while OOP could be better than Procedural paradigm in other problems.

Doing everything the same way isn't very productive. There's an old saying that, "when all you have is a hammer, everything starts to look like a nail." I'm guessing you don't want to be the guy who whacks everything with a hammer.

Different models, different approaches, better solutions. It's as simple as that.

What is OOP?

OOP is a programming paradigm based upon objects (having both data and methods) that aims to incorporate the advantages of modularity and reusability. Objects, which are usually instances of classes, are used to interact with one another to design applications and computer programs.

Some of the OOP supported languages

  • JavaScript
  • C#
  • C++
  • Java
  • Ruby
  • Python
  • PHP etc.,

How do we achieve OOP in JavaScript. Please explain with examples?

There are certain features or mechanisms which makes a Language Object Oriented like:
  • Object
  • Classes
  • Inheritance
  • Encapsulation
  • Data Abstraction(hiding of data)
  • Polymorphism
Let’s dive into the details of each one of these and see how they are implemented in JS.

Object: 

An Object is a unique entity which contains properties and methods.
For example,
We can create object as below:

Object literal(Method-1):

var car = {
   "type": "someType",
   "model": "someModel",
   "color": "Pink",
   "horsePower": "Y",
   "canDrive": function() {
      return "We can drive this "+ this.color+" car of model "+this.model;
   }
}
car.canDrive();//"We can drive this Pinkcar of model someModel"

Object constructor(Method-2):

function Car(type, model, color) {
   this.type = type;
   this.model = model;
   this.color = color;
}
Car.prototype.canDrive = function(){
   return "We can drive this "+ this.color+" car of model "+this.model;
} 

var car = new Car("someType", "someModel", "Pink")
car.canDrive();//"We can drive this Pinkcar of model someModel"

With new keyword(Method-3):

var myCar = new Object();
myCar.make = 'Ford';
myCar.model = 'Mustang';
myCar.year = 1969;

Object.is()

determines whether two values are the same value
Object.is(value1, value2);
Boolean indicating whether or not the two arguments are the same value
  • both undefined
  • both null
  • both true or both false
  • both strings of the same length with the same characters in the same order
  • both the same object (meaning both values reference the same object in memory)
  • both numbers and
    • both +0
    • both -0
    • both NaN
    • or both non-zero and both not NaN and both have the same value
difference between Object.is() and === is in their treatment of signed zeroes and NaNs.

let user = { name: "Alexander" }

// this instead copies a reference to the previous object
let newUser = user

Object.prototype.hasOwnProperty
const object1 = {};
object1.property1 = 42;

console.log(object1.hasOwnProperty('property1'));
// expected output: true

console.log(object1.hasOwnProperty('toString'));
// expected output: false

console.log(object1.hasOwnProperty('hasOwnProperty'));
// expected output: false

Array is an Object, so you can use hasOwnProperty() method to check whether an index exists
let fruits = ['Apple', 'Banana','Watermelon', 'Orange'];
fruits.hasOwnProperty(3);   // true ('Orange')
fruits.hasOwnProperty(4);   // false - not defined

let example = {};
example.prop = 'exists';

// `hasOwnProperty` will only return true for direct properties:
example.hasOwnProperty('prop');             // returns true
example.hasOwnProperty('toString');         // returns false
example.hasOwnProperty('hasOwnProperty');   // returns false

// The `in` operator will return true for direct or inherited properties:
'prop' in example;                          // returns true
'toString' in example;                      // returns true
'hasOwnProperty' in example;                // returns true

let foo = {
  hasOwnProperty: function() {
    return false;
  },
  bar: 'Here be dragons'
};

foo.hasOwnProperty('bar'); // reimplementation always returns false

let foo = Object.create(null);
foo.prop = 'exists';
foo.hasOwnProperty("prop");  // Uncaught TypeError: foo.hasOwnProperty is not a function

Objects created using Object.create(null) do not inherit from Object.prototype, making hasOwnProperty() inaccessible.

Solution: use Object.hasOwn()

Enumerability:

An enumerable property in JavaScript means that a property can be viewed if it is iterated using the for…in loop or Object.keys() method unless the property's key is a Symbol. All the properties which are created by simple assignment or property initializer are enumerable by default.
const student = {
    registration: '12342',
    name: 'Sandeep',
    age: 27,
    marks: 98
};
  
// prints all the keys in student object
for (const key in student) {
    console.log(key);
}
//registration
name
age
marks
const student = {
    registration: '12342',
    name: 'Sandeep',
    age: 27,
};
  
// This sets the enumerable attribute
// of marks property to false 
  
Object.defineProperty(student, 'marks', {
    value: 98,
    configurable: true,
    writable: false,
    enumerable: false,
});
  
// To print whether enumerable or not
console.log(student.propertyIsEnumerable('registration')); 
console.log(student.propertyIsEnumerable('name'));
console.log(student.propertyIsEnumerable('age'));
console.log(student.propertyIsEnumerable('marks'));
</script>

Output:

true
true
true
false
Symbol:

Symbol type:

strings and symbols are fundamentally different and should not accidentally convert one into another.

symbols are not 100% hidden due to below built-in methods:

Object property keys,

  • string
  • symbol
  • not boolean
  • not numeric
  • not NaN
  • not an object
But we can also have object's keys as number, boolean, object using Map

let id = Symbol();// id is a new symbol
let id = Symbol("id");// symbol description/ symbol name
let id1 = Symbol("id"); let id2 = Symbol("id"); alert(id1 == id2); // false


Most values in JavaScript support implicit conversion to a string. For instance, we can alert almost any value, and it will work. Symbols are special. They don’t auto-convert.


let id = Symbol("id"); alert(id); // TypeError: Cannot convert a Symbol value to a string    

alert(id.toString()); // Symbol(id), now it works

alert(id.description); // id

Symbols allow us to create “hidden” properties of an object, that no other part of code can accidentally access or overwrite.

For instance, if we’re working with user objects, that belong to a third-party code. We’d like to add identifiers to them.

let user = { // belongs to another code name: "John" }; let id = Symbol("id"); user[id] = 1; alert( user[id] ); // we can access the data using the symbol as the key

Benefit of using Symbol("id") over a string "id"

As user objects belongs to another code, and that code also works with them, we shouldn’t just add any fields to it. That’s unsafe. But a symbol cannot be accessed accidentally, the third-party code probably won’t even see it, so it’s probably all right to do.

Also, imagine that another script wants to have its own identifier inside user, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other.

Then that script can create its own Symbol("id"), like this:

let id = Symbol("id"); user[id] = "Their id value";

But if we used a string "id" instead of a symbol for the same purpose, then there would be a conflict:

let user = { name: "John" }; // Our script uses "id" property user.id = "Our id value"; // ...Another script also wants "id" for its purposes... user.id = "Their id value" // Boom! overwritten by another script!

In an object literal:

let id = Symbol("id");
let user = { name: "John", [id]: 123 // not "id": 123 };

Symbols are skipped by for...in and Object.keys() but Object.assign(targetOb, sourceOb) copies both strings and  symbols
let user = { name: "John", age: 30, [id]: 123 }; for (let key in user) alert(key); // name, age (no symbols) // the direct access by the symbol works 
alert( "Direct: " + user[id] );
let clone = Object.assign({}, user);
alert(clone[id]);//123
.

The idea is that when we clone an object or merge objects, we usually want all properties to be copied (including symbols like id).

Global symbols:  Symbol.for(key)   Symbol.for(name)
// read from the global registry let id = Symbol.for("id"); // if the symbol did not exist, it is created // read it again (maybe from another part of the code) let idAgain = Symbol.for("id"); // the same symbol alert( id === idAgain ); // true

Get symbol by name/key
let sym = Symbol.for("name"); let sym2 = Symbol.for("id");

Get name/key by symbol
alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id

let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
alert( Symbol.keyFor(globalSymbol) ); // name, global symbol alert( Symbol.keyFor(localSymbol) ); // undefined, not global
alert( localSymbol.description ); // name


System Symbols:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive





Classes:

Classes are blueprint of an Object.A class can have many Object, because class is a template while Object are instances of the class or the concrete implementation.
Before we move further into implementation, we should know unlike other Object Oriented Language their is no classes in JavaScript we have only Object. To be more precise, JavaScript is a prototype based object oriented language, which means it doesn’t have classes rather it define behaviors using constructor function and then reuse it using prototype.
Note :- Even the classes provided by ECMA2015 are objects.
JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript. JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.

ES5:

// Defining class in a Traditional Way.
function Person(name, age) {
 this.name = name;
  this.age = age;
  this.getName = function() {
   console.log("Name: "+this.name);
  }
}
Person.prototype.getDetails = function() {
   console.log("Getting Details from Person: "+this.name, this.age);
    return this.name +" "+ this.age;
}

var person = new Person("Divya", "20+");
console.log(person);
console.log(person.getDetails());

ES6:

//Define class using ES6
class Person {
//Defininig the constructor to initialize the property
 constructor(name, age) {
   this.name = name;
    this.age = age;
    this.getName = function() {
     console.log("Name: "+this.name);
    }
  }
  getDetails() {
   console.log("Getting Details from Person: "+this.name, this.age);
    return this.name +" "+ this.age;
  }
}

//Creating person object
var person = new Person("Divya", "20+");
console.log(person);
console.log(person.getDetails());

Inheritance:

It is a concept in which some property and methods of an Object is being used by another Object. Unlike most of the OOP languages where classes inherit classes, JavaScript Object inherits Object i.e. certain features (property and methods)of one object can be reused by other Objects.

ES5:

// Defining class in a Traditional Way.
function Person(name, age) {
 this.name = name;
  this.age = age;
  this.getName = function() {
   console.log("Name: "+this.name);
  }
}
Person.prototype.getDetails = function() {
   console.log("Getting Details from Person: "+this.name, this.age);
    return this.name +" "+ this.age;
}
function Employee(name, age, salary) {
 //To inherit instance properties of super
 Person.call(this, name, age);
  this.salary = salary;
  this.getSalary = function() {
   console.log("Salary: "+this.salary);
  }
}
//Note: New methods must be added to the SubType after the inheritance because inheritance overwrites the existing prototype of SubType
// If we have sup_prototype before sub_prototype, then we would get the getDetails() from Employee(sub_type) otherwise from Person(sup_type)
//We call this Method over riding
//Refer to this block as sup_prototype
Employee.prototype = Object.create(Person.prototype);
//Refer to this block as sub_prototype
Employee.prototype.getDetails = function() { console.log("Getting Details from Employee: "+ this.name +" "+this.salary+" "+this.age); return this.name + this.age + this.salary; } var person = new Person("Divya", "20+"); console.log(person); console.log(person.getDetails()); var employee = new Employee("Sai", "15+", 10000000); console.log(employee); console.log(employee.getDetails());

ES6:

//Define class using ES6
class Person {
//Defininig the constructor to initialize the property
 constructor(name, age) {
   this.name = name;
    this.age = age;
    this.getName = function() {
     console.log("Name: "+this.name);
    }
  }
  getDetails() {
   console.log("Getting Details from Person: "+this.name, this.age);
    return this.name +" "+ this.age;
  }
}
class Employee extends Person{
 constructor(name, age, salary) {
   super(name, age);
   this.salary = salary;  
    this.getSalary = function() {
     console.log("Salary: "+this.salary);
    }
  }
  getDetails() {
   console.log("Getting Details from Employee: "+ this.name +" "+this.salary+" "+this.age);
   return this.name + this.age + this.salary;
  }
}

//Creating person object
var person = new Person("Divya", "20+");
console.log(person);
console.log(person.getDetails());

//Creating employee object
var employee = new Employee("Sai", "15+", 10000000); console.log(employee); console.log(employee.getDetails());

Encapsulation:

The process of wrapping property and function within a single unit is known as encapsulation.
Let’s understand encapsulation with an example.

// Defining class in a Traditional Way.
function Person(name, age) {
 this.name = name;
  this.age = age;
  this.getName = function() {
   console.log("Name: "+this.name);
  }
}
Person.prototype.getDetails = function() {
   console.log("Getting Details from Person: "+this.name, this.age);
    return this.name +" "+ this.age;
}

var person = new Person("Divya", "20+");
console.log(person);
console.log(person.getDetails());
In the above example we simply create a person Object using the constructor with some properties and methods.
Some times encapsulation refers to Data abstraction or hiding of data.

Data Abstraction/Hiding of Data:

Means representing essential features hiding the background detail. Most of the OOP languages provide access modifiers to restrict the scope of a variable, but there are no such access modifiers in JavaScript but there are certain ways by which we can restrict the scope of variable within the Class/Object like below.

// Defining class in a Traditional Way.
function Person(name, age) {
//These name, age and getNameNoAccess are 
not visible outside the scope of the object
var name = name; var age = age; var getNameNoAccess = function() { console.log("cannotAccessName: "+name); } this.getName = function() { console.log("canAccessName: "+name); } } Person.prototype.getDetails = function() { console.log("Getting Details from Person: "+this.name, this.age); return this.name +" "+ this.age; } var person = new Person("Divya", "20+"); console.log(person); console.log(person.getDetails());
console.log(person.getNameNoAccess());//undefined
console.log(person.getName());//it consoles
console.log(person.name);//undefined

Polymorphism:

  • The ability to call the same method on different objects and have each of them respond in their own way is called polymorphism.
  • In OOP, we think of objects that are linked through inheritance has the same methods (override methods) and that the method being called up, is the method associated with the object and not the type of referance.
  • This should not be a problem in Java Script as references (variables) in JavaScript is not type-set. We can assign any type of data to a variable in Javascript, and Javascript will know the object a variable refer to if it exists.
  • For example, all employees are people, but all people are not employees. Which is to say that people will be the super class, and employee the sub class. People may have ages and weights, but they do not have salaries. Employees are people so they will inherently have an age and weight, but also because they are employees they will have a salary.
function Person(age, weight) {
    this.age=age;
    this.weight=weight;
    this.getInfo=function() {
      return "I am " + this.age + " years old " +
        "and weighs " + this.weight +" kilo.";
    }
  }
  function Employee(age, weight, salary){
    this.salary=salary;
    this.age=age;
    this.weight=weight;
    this.getInfo=function() {
      return "I am " + this.age + " years old " +
        "and weighs " + this.weight +" kilo " +
        "and earns " + this.salary + " dollar.";
    }
  }
  Employee.prototype= new Person();
  Employee.prototype.constructor=Employee;
// The argument, 'obj', can be of any kind
// which method, getInfo(), to be executed depend on the object
// that 'obj' refer to.
  function showInfo(obj) {
    document.write(obj.getInfo()+"<br>");
  }

  var person = new Person(50,90);
  var employee = new Employee(43,80,50000);
  showInfo(person);
  showInfo(employee)
//I am 50 years old and weighs 90 kilo.<br>
//I am 43 years old and weighs 80 kilo and earns 50000 dollar.<br>
Prototypal Inheritance

In programming, we often want to take something and extend it.

For instance, we have a user object with its properties and methods, and want to make admin and guest as slightly modified variants of it. We’d like to reuse what we have in user, not copy/reimplement its methods, just build a new object on top of it.

Prototypal inheritance is a language feature that helps in that.

In JavaScript, objects have a special hidden property [[Prototype]]
That is either null or references another object

When we read a property from object, and it’s missing, JavaScript automatically takes it from the prototype. In programming, this is called “prototypal inheritance”.

The property [[Prototype]] is internal and hidden, but there are many ways to set it.

One of them is to use the special name __proto__, like this:

let animal = {
  eats: true,
  walk() {
    alert("Animal walk");
  }
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

let longEar = {
  earLength: 10,
  __proto__: rabbit
};

// walk is taken from the prototype chain
longEar.walk(); // Animal walk
alert(longEar.jumps); // true (from rabbit)


There are only two limitations:

  1. The references can’t go in circles. JavaScript will throw an error if we try to assign __proto__ in a circle.
  2. The value of __proto__ can be either an object or null. Other types are ignored.

Also it may be obvious, but still: there can be only one [[Prototype]]. An object may not inherit from two others.

__proto__ is a historical getter/setter for [[Prototype]]

It’s a common mistake of novice developers not to know the difference between these two.

Please note that __proto__ is not the same as the internal [[Prototype]] property. It’s a getter/setter for [[Prototype]]. Later we’ll see situations where it matters, for now let’s just keep it in mind, as we build our understanding of JavaScript language.

The __proto__ property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use Object.getPrototypeOf/Object.setPrototypeOf functions instead that get/set the prototype. We’ll also cover these functions later.

By the specification, __proto__ must only be supported by browsers. In fact though, all environments including server-side support __proto__, so we’re quite safe using it.

As the __proto__ notation is a bit more intuitively obvious, we use it in the examples.

let user = { name: "John", surname: "Smith", set fullName(value) { [this.name, this.surname] = value.split(" "); }, get fullName() { return `${this.name} ${this.surname}`; } }; let admin = { __proto__: user, isAdmin: true }; alert(admin.fullName); // John Smith (*) // setter triggers! admin.fullName = "Alice Cooper"; // (**) alert(admin.fullName); // Alice Cooper, state of admin modified alert(user.fullName); // John Smith, state of user protected

/ animal has methods let animal = { walk() { if (!this.isSleeping) { alert(`I walk`); } }, sleep() { this.isSleeping = true; } }; let rabbit = { name: "White Rabbit", __proto__: animal }; // modifies rabbit.isSleeping rabbit.sleep(); alert(rabbit.isSleeping); // true alert(animal.isSleeping); // undefined (no such property in the prototype)



let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// Object.keys only returns own keys
alert(Object.keys(rabbit)); // jumps

// for..in loops over both own and inherited keys
for(let prop in rabbit) alert(prop); // jumps, then eats

If that’s not what we want, and we’d like to exclude inherited properties, there’s a built-in method obj.hasOwnProperty(key): it returns true if obj has its own (not inherited) property named key.

So we can filter out inherited properties (or do something else with them):

let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

for(let prop in rabbit) {
  let isOwn = rabbit.hasOwnProperty(prop);

  if (isOwn) {
    alert(`Our: ${prop}`); // Our: jumps
  } else {
    alert(`Inherited: ${prop}`); // Inherited: eats
  }
}


Where is the method rabbit.hasOwnProperty coming from? We did not define it. Looking at the chain we can see that the method is provided by Object.prototype.hasOwnProperty. In other words, it’s inherited.

…But why does hasOwnProperty not appear in the for..in loop like eats and jumps do, if for..in lists inherited properties?

The answer is simple: it’s not enumerable. Just like all other properties of Object.prototype, it has enumerable:false flag. And for..in only lists enumerable properties. That’s why it and the rest of the Object.prototype properties are not listed.

NOTE: Almost all other key/value-getting methods, such as Object.keysObject.values and so on ignore inherited properties.

They only operate on the object itself. Properties from the prototype are not taken into account.

Summary

  • In JavaScript, all objects have a hidden [[Prototype]] property that’s either another object or null.
  • We can use obj.__proto__ to access it (a historical getter/setter, there are other ways, to be covered soon).
  • The object referenced by [[Prototype]] is called a “prototype”.
  • If we want to read a property of obj or call a method, and it doesn’t exist, then JavaScript tries to find it in the prototype.
  • Write/delete operations act directly on the object, they don’t use the prototype (assuming it’s a data property, not a setter).
  • If we call obj.method(), and the method is taken from the prototype, this still references obj. So methods always work with the current object even if they are inherited.
  • The for..in loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself.





Along with the below links also refer to my blog

https://www.geeksforgeeks.org/introduction-object-oriented-programming-javascript/

https://www.geeksforgeeks.org/commonly-asked-oop-interview-questions/
https://www.learn-js.org/


Built – in Functions
JavaScript comes along with useful set-of built- functions. These methods can be used to manipulate string Numbers and Dates
Following are important JavaScript functions
MethodDescription
charAt()Returns the character at the specified index
concat()Combines the text of two strings and returns a new string.
forEach()Calls a function for each element in the array
indexOf()Returns the index within the calling string object of the first occurrence of the specified value or -1 if not found.
length()Returns the length of the string
pop()Removes the last element form an array and returns the new length of the array.
push()Adds one or more elements to the end of an array and returns the new length of the array
reverse()Reverses the order of the elements of an array – the first becomes the last, and the last becomes the first
sort()Sorts the elements of an array
substr()Returns the characters in a string beginning at the specified location through the specified number of characters
toLowerCase()Returns the calling string value converted to lower case.
toString()Returns the string representation of the number’s value
toUppercase()Returns the calling string value converted to uppercase.
shift()    removes the first element in an array
unshift()    adds one or more elements at the beginning of an array
substr(startIndex, noOfElementsToExtract)
substr() extracts length characters from a string, counting from the start index.
If start is a positive number, the index starts counting at the start of the string. Its value is capped at str.length.
If start is a negative number, the index starts counting from the end of the string. Its value is capped at -str.length.
Note: In Microsoft JScript, negative values of the start argument are not considered to refer to the end of the string.
If length is omitted, substr() extracts characters to the end of the string.
If length is undefinedsubstr() extracts characters to the end of the string.
If length is a negative number, it is treated as 0.
For both start and length, NaN is treated as 0.
substr(startIndex, deleteCount)



substring(startIndex, endIndex-notIncluding)




splice(startIndex, deletecount, elem1, eleme2); elem1,elem2==>elementsTOBeAdded
javascript array.splice(startIndex, deleteCount, addThis1, addThis2);
JS splice(startIndex, deleteCount, addThis1, addThis2);

















slice(beginIndex, endIndex)
Javascript slice
JS slice(beginIndex, endIndex)

Size() vs length:

.size() is not a native JS function of Array
length should be used. 
Javascript: How delete works on arrays and objects
JavaScript: delete on Arrays and objects


JS algorithms:

https://github.com/trekhleb/javascript-algorithms#readme

Event bubbling and event capturing

let's say you have a page where elements are being created dynamically, but you want to process a click on those elements. You can't bind an event handler directly to them before they exist, but binding individual handlers to them when you create them is a bit of a pain. Instead, bind an event handler to a container object: click events will bubble up from the individual elements to the container but you can still tell which element was clicked - jQuery makes this easy if you are using the appropriate syntax of .on(), or .delegate() (or even .live() if you have a really old version of jQuery) because it sets this to the clicked element.
<div id="someContainer"></div>

$("#someContainer").on("click", ".dynamicElement", function() {
    // this is the element, do something with it
});
This says that on click of an element with the class "dynamicElement" that is a child of the "someContainer" div do something. It will work regardless of whether the "dynamicElement" element existed at page load, was added later in response to some other user action, or perhaps loaded with Ajax.

CSRF(Cross site request forgery)

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.
In this situation, someone includes an image that isn’t really an image (for example in an unfiltered chat or forum), instead it really is a request to your bank’s server to withdraw money:
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
Now, if you are logged into your bank account and your cookies are still valid (and there is no other validation), you will transfer money as soon as you load the HTML that contains this image. 

Regular Expressions:

string.match(regExp) vs regExp.test(string)

regexObject.testString )
Executes the search for a match between a regular expression and a specified string. Returns true or false.
string.matchRegExp )
Used to retrieve the matches when matching a string against a regular expression. Returns an array with the matches or null if there are none.
Since null evaluates to false,
Power of null:
so, the best approach is 
var a = null;
if(a && a <= 0) {}
if(a && a >= 0) {}


setTimeout or asynchronous code in loops

Closures

Practical example of Closure

const FactoryFunction = string => {
  const capitalizeString = () => string.toUpperCase();
  const printString = () => console.log(`----${capitalizeString()}----`);
  return { printString };
};

const taco = FactoryFunction('taco');

printString(); // ERROR!!
capitalizeString(); // ERROR!!
taco.capitalizeString(); // ERROR!!
taco.printString(); // this prints "----TACO----"

The big deal here is that even though we can’t access the capitalizeString() function, printString() can. That is closure.

capitalizeString is a private function and printString is public

The concept of closure is the idea that functions retain their scope even if they are passed around and called outside of that scope. In this case, printString has access to everything inside of FactoryFunction, even if it gets called outside of that function.
const counterCreator = () => {
  let count = 0;
  return () => {
    console.log(count);
    count++;
  };
};

const counter = counterCreator();

counter(); // 0
counter(); // 1
counter(); // 2
counter(); // 3
As above, the function counter is a closure. It has access to the variable count and can both print and increment it, but there is no other way for our program to access that variable.
Lexical environment:

Map:

  • a collection of keyed data items, just like an Object. But the main difference is that Map allows keys of any type.
  • new Map() -- creates the map.
  • map.set(key, value) -- stores the value by the key.
  • map.get(key) -- returns the value by the key, undefined if key doesn't exist in map.
  • map.has(key) -- returns true if the key exists, false otherwise.
  • map.delete(key) -- removes the value by the key.
  • map.clear() -- clears the map
  • map.size -- returns the current element count.
  • To test values for equivalence, Map uses the algorithm SameValueZero. It is roughly the same as strict equality ===, but the difference is that NaN is considered equal to NaN. So NaN can be used as the key as well.

  • The iteration goes in the same order as the values were inserted. Map preserves this order, unlike a regular Object.

    Besides that, Map has a built-in forEach method, similar to Array

let map = new Map(); map.set('1', 'str1'); // a string key map.set(1, 'num1'); // a numeric key map.set(true, 'bool1'); // a boolean key
CHAINING:

map.set('1', 'str1') .set(1, 'num1') .set(true, 'bool1');
// remember the regular Object? it would convert keys to string // Map keeps the type, so these two are different: alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3

let john = { name: "John" }; // for every user, let's store their visits count let visitsCountMap = new Map(); // john is the key for the map visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123


Map from object:
// array of [key, value] pairs let map = new Map([ ['1', 'str1'], [1, 'num1'], [true, 'bool1'] ]);

let map = new Map(Object.entries({ name: "John", age: 30 }));

recipeMap.forEach( (value, key, map) => { alert(`${key}: ${value}`); // cucumber: 500 etc });

Iteration over Map:
  • map.keys() - returns an iterable for keys,
  • map.values() - returns an iterable for values,
  • map.entries() - returns an iterable for entries [key, value], it's used by default in for..of

Set:

  • Set is a collection of values, where each value may occur only once. Avoid duplicates
  • new Set(iterable) - creates the set, optionally from an array of values (any iterable will do).
  • set.add(value) - adds a value, returns the set itself.
  • set.delete(value) - removes the value, returns true if value existed at the moment of the call, otherwise false.
  • set.has(value) - returns true if the value exists in the set, otherwise false.
  • set.clear() - removes everything from the set.
  • set.size - is the elements count.
  • set.keys() - returns an iterable object for values,
  • set.values() - same as set.keys, for compatibility with Map,
  • set.entries() - returns an iterable object for entries [value, value], exists for compatibility with Map.
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" }; let mary = { name: "Mary" }; // visits, some users come multiple times set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); // set keeps only unique values alert( set.size ); // 3 for (let user of set) { alert(user.name); // John (then Pete and Mary) }
let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); // the same with forEach: set.forEach((value, valueAgain, set) => { alert(value); });

WeakMap:

  • The first difference between Map and WeakMap is that keys must be objects, not primitive values
  • WeakMap is Map-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means.
  • main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector.

    That comes at the cost of not having support for clearsizekeysvalues

  • if we use an object as the key in it, and there are no other references to that object – it will be removed from memory (and from the map) automatically
  • WeakMap has
    • weakMap.get(key)
    • weakMap.set(key, value)
    • weakMap.delete(key)
    • weakMap.has(key)
    • no keys()
    • no values()
    • no entries()
    • doesn't support iteration
  • Why such a limitation? That’s for technical reasons. If an object has lost all other references (like john in the code above), then it is to be garbage-collected automatically. But technically it’s not exactly specified when the cleanup happens.
  • The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically, the current element count of a WeakMap is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported.
  • The main area of application for WeakMap is an additional data storage.

  • If we’re working with an object that “belongs” to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive – then WeakMap is exactly what’s needed.

  • We put the data to a WeakMap, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well
  • weakMap.set(john, "secret documents");
    // if john dies, secret documents will be destroyed automatically
  • For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don’t want to store their visit count anymore.





let weakMap = new WeakMap(); let obj = {}; weakMap.set(obj, "ok"); // works fine (object key) //can't use string as a key
weakMap.set("test", "Whoops!");//Error, bcs test is not an object

Ex2:
let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); john = null; // overwrite the reference // john is removed from memory!
Ex 3:
// 📁 visitsCount.js let visitsCountMap = new Map(); // map: user => visits count // increase the visits count function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); }
// 📁 main.js let john = { name: "John" }; countUser(john); // count his visits // later john leaves us john = null;

Now, john object should be garbage collected, but remains in memory, as it’s a key in visitsCountMap.

We need to clean visitsCountMap when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures.

We can avoid it by switching to WeakMap instead

// 📁 visitsCount.js let visitsCountMap = new WeakMap(); // weakmap: user => visits count // increase the visits count function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); }

we don’t have to clean visitsCountMap. After john object becomes unreachable, by all means except as a key of WeakMap, it gets removed from memory, along with the information by that key from WeakMap.

Cache:
// 📁 cache.js let cache = new Map(); // calculate and remember the result function process(obj) { if (!cache.has(obj)) { let result = /* calculations of the result for */ obj; cache.set(obj, result); } return cache.get(obj); } // Now we use process() in another file: // 📁 main.js let obj = {/* let's say we have an object */}; let result1 = process(obj); // calculated // ...later, from another place of the code... let result2 = process(obj); // remembered result taken from cache // ...later, when the object is not needed any more: obj = null; alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!

For multiple calls of process(obj) with the same object, it only calculates the result the first time, and then just takes it from cache. The downside is that we need to clean cache when the object is not needed any more.

If we replace Map with WeakMap, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected.

// 📁 cache.js
let cache = new WeakMap();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculate the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

// 📁 main.js
let obj = {/* some object */};

let result1 = process(obj);
let result2 = process(obj);

// ...later, when the object is not needed any more:
obj = null;

// Can't get cache.size, as it's a WeakMap,
// but it's 0 or soon be 0
// When obj gets garbage collected, cached data will be removed as well




WeakSet:

  • WeakSet is Set-like collection that stores only objects and removes them once they become inaccessible by other means.

  • It is analogous to Set, but we may only add objects to WeakSet (not primitives).

  • An object exists in the set while it is reachable from somewhere else.
  • Like Set, it supports add, has and delete, but not size, keys() and no iterations.
let visitedSet = new WeakSet();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

visitedSet.add(john); // John visited us
visitedSet.add(pete); // Then Pete
visitedSet.add(john); // John again

// visitedSet has 2 users now

// check if John visited?
alert(visitedSet.has(john)); // true

// check if Mary visited?
alert(visitedSet.has(mary)); // false

john = null;

// visitedSet will be cleaned automatically

The most notable limitation of WeakMap and WeakSet is the absence of iterations, and the inability to get all current content. That may appear inconvenient, but does not prevent WeakMap/WeakSet from doing their main job – be an “additional” storage of data for objects which are stored/managed at another place.

Garbage Collection:

  • Memory management in JavaScript is performed automatically and invisibly to us. We create primitives, objects, functions… All that takes memory
  • The main concept of memory management in JavaScript is reachability.

  • “reachable” values are those that are accessible or usable somehow. They are guaranteed to be stored in memory.
  • There’s a background process in the JavaScript engine that is called garbage collector. It monitors all objects and removes those that have become unreachable.
  • Garbage collection is performed automatically. We cannot force or prevent it.
  • Objects are retained in memory while they are reachable.
  • Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
  • The basic garbage collection algorithm is called “mark-and-sweep”.

    The following “garbage collection” steps are regularly performed:

    • The garbage collector takes roots and “marks” (remembers) them.
    • Then it visits and “marks” all references from them.
    • Then it visits marked objects and marks their references. All visited objects are remembered, so as not to visit the same object twice in the future.
    • …And so on until every reachable (from the roots) references are visited.
    • All objects except marked ones are removed.
  1. There’s a base set of inherently reachable values, that cannot be deleted for obvious reasons.

    For instance:

    1. The currently executing function, its local variables and parameters.
    2. Other functions on the current chain of nested calls, their local variables and parameters.
    3. Global variables.
    4. (there are some other, internal ones as well)

    These values are called roots.

  2. Any other value is considered reachable if it’s reachable from a root by a reference or by a chain of references.

    For instance, if there’s an object in a global variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow.

// user has a reference to the object let user = { name: "John" };
user = null;




John becomes unreachable. There’s no way to access it, no references to it. Garbage collector will junk the data and free the memory.
let admin = user;

user = null;
Then the object is still reachable via admin global variable, so it’s in memory. If we overwrite admin too, then it can be removed.

JSON.parse

  • parses a JSON string, constructing the JavaScript value or object described by the string.
  • An optional reviver function can be provided to perform a transformation on the resulting object before it is returned.If a function, this prescribes how the value originally produced by parsing is transformed, before being returned.
  • turns a string of JSON text into a JavaScript object

  • JSON.parse(text, reviver)
  •     Throws a SyntaxError exception if the string to parse is not valid JSON.
  • doesn't allow single quotes; throw a syntax error
  • doesn't allow trailing commas; throw a syntax error
JSON.parse('{}');              // {}
JSON.parse('true');            // true
JSON.parse('"foo"');           // "foo"
JSON.parse('[1, 5, "false"]'); // [1, 5, "false"]
JSON.parse('null');            // null
JSON.parse('{"p": 5}', (key, value) =>
  typeof value === 'number'
    ? value * 2 // return value * 2 for numbers
    : value     // return everything else unchanged
);

// { p: 10 }
jhkJSON.parse('{"1": 1, "2": 2, "3": {"4": 4, "5": {"6": 6}}}', (key, value) => {
  console.log(key); // log the current property name, the last is "".
  return value;     // return the unchanged property value.
});

// 1
// 2
// 4
// 6
// 5
// 3
// ""
//Throw syntax error
JSON.parse('[1, 2, 3, 4, ]');
JSON.parse('{"foo" : 1, }');
JSON.parse("{'foo': 1}");

Shallow cloning of objects:

  • A shallow copy means that only the actual object gets copied. If the copied object contains nested objects — these nested objects aren't get cloned.
  • A shallow copy successfully copies primitive types like numbers and strings, but any object reference will not be recursively copied, but instead the new, copied object will reference the same initial object.
  • With primitive data types, once variables are assigned, they cannot be copied over. Therefore, changing the value of the variable never changes the underlying primitive type. This means it is impossible to change the values of these types once they are assigned to a variable — a concept known as immutability. However, they can be combined together to derive new values.

  • Objects, on the other hand, are mutable data types. In this article, we will explore ways of modifying or mutating objects in JavaScript. This entails performing either shallow or deep cloning or copying with respect to general object behavior.

  • 4 ways to shallow copy an object
    • Using spread operator
    • Using rest
    • Using Object.assign(targetObject, sourceObject, new properties )
    • Using JSON.parse(JSON.stringify()) - But there will be some data loss . please refer to the above JSON.parse

      1.Using Spread operator

    • allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.


Ex1 spread: 
let numberStore = [0, 1, 2];
let newNumber = 12;
numberStore = [...numberStore, newNumber];
function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];

console.log(sum(...numbers));

For function calls:
myFunction(...iterableObj);// pass all elements of iterableObj as arguments to function myFunction

For array literals or strings:
[...iterableObj, '4', 4, 'five']//combine 2 arrays by inserting all elements from iterableObj

For object literals:
let objClone = {...obj};
const objA = { 
    name: 'Alexander', 
    age: 26, 
}

const objB = { 
    Licensed: true, 
    location: "Ikeja" 
}

const mergedObj = {...objA, ...objB}
console.log(mergedObj)


2.Using rest 

const { ...clone } = object;
const hero = {
name: 'Batman',
city: 'Gotham'
};
const { ...heroClone } = hero;
heroClone; // { name: 'Batman', city: 'Gotham' }
hero === heroClone; // => false
const { city, ...heroClone } = hero;
heroClone; // { name: 'Batman' }

const { city, ...heroClone } = {
...hero,
realName: 'Bruce Wayne'
};
heroClone; // { name: 'Batman', realName: 'Bruce Wayne' }

3.Using Object.assign()

Object.assign(target, ...sources)
const hero = {
name: 'Batman',
city: 'Gotham'
};
const heroClone = Object.assign({}, hero, { name: 'Batman Clone' });
heroClone; // { name: 'Batman Clone', city: 'Gotham' }


Promises:


  1. To run promises in parallel, create an array of promises and then use Promise.all(promisesArray).
  2. When 2nd functions's is dependent on the 1st fns output then we should use async/ await

ES6:
Arrow functions:
  • Lexical this binding
  • Arrow functions with the body have no implicit return statement.
  • It automatically binds the this to the surrounding code context.
  • It is less verbose than the traditional javascript code.
  • It doesn’t have a prototype property.
  • Arrow functions don’t have inbuilt arguments property, unlike traditional Javascript functions. But we can achieve it with the help of rest parameters.
  • Arrow functions are more convenient for callbacks, promises, or for methods like map, reduce, and forEach.
  • Avoids duplicate parameters by throwing Uncaught SyntaxError: Duplicate parameter name not allowed in this context

  • Traditional functions in the context of this
    • const details = {
      name: 'Arfat',
      friends: ['Bob', 'Alex'],
      getFriends: function () {
      this.friends.forEach(function (friend) {
      console.log(this.name + " is friends with " + friend);
      }, this);
      }
      }
      details.getFriends();
  • const details = {
    name: 'Arfat',
    friends: ['Bob', 'Alex'],
    getFriends: function () {
    this.friends.forEach(friend => {
    console.log(this.name + " is friends with " + friend);
    }
    );
    }
    }
    details.getFriends();
    // Arfat is friends with Bob
    // Arfat is friends with Alex
  • Can't be used as a constructor
  • const Person = name =>  {
    this.name = name;
    }
    const person1 = new Person('Arfat'); // Will throw an error; Person is not a constructor
  • Can't change this binding
  • const add = (a, b) => a + b;
    const add5 = add.bind(null, 5);
    add5(7); // 12

  • console.log(addValues(1,2,3,4,5));

Proxy:

levelup.gitconnected.com/the-amazing-power-of-javascript-proxies-aa27c6d06bcb

Array.from(arrayLikeObject, mapfn)

Array.from() lets you create Arrays from:

  • array-like objects (objects with a length property and indexed elements); or
  • iterable objects (objects such as Map and Set).

Divide an array in half

const list = [1, 2, 3, 4, 5, 6]
const half = Math.ceil(list.length / 2);    

const firstHalf = list.slice(0, half-1)
const secondHalf = list.slice(-half)
//[1, 2, 3, 4, 5]=> [ 1, 2 ] [3,4,5]
















Javascript getters and setters:


  • t gives simpler syntax
  • It allows equal syntax for properties and methods
  • It can secure better data quality
  • It is useful for doing things behind-the-scenes
  • It can have an identifier which is either a number or a string;
  • Getters give you a way to define a property of an object, but they do not calculate the property's value until it is accessed. A getter defers the cost of calculating the value until the value is needed. If it is never needed, you never pay the cost.

  • An additional optimization technique to lazify or delay the calculation of a property value and cache it for later access are smart (or "memoized") getters. The value is calculated the first time the getter is called, and is then cached so subsequent accesses return the cached value without recalculating it. This is useful in the following situations:

    • If the calculation of a property value is expensive (takes much RAM or CPU time, spawns worker threads, retrieves remote file, etc).
    • If the value isn't needed just now. It will be used later, or in some case it's not used at all.
    • If it's used, it will be accessed several times, and there is no need to re-calculate that value will never be changed or shouldn't be re-calculated.

    Note: This means that you shouldn’t write a lazy getter for a property whose value you expect to change, because if the getter is lazy then it will not recalculate the value.

get notifier() {
  delete this.notifier;
  return this.notifier = document.getElementById('bookmarked-notification-anchor');
},
Using a computed property name:
const expr = 'foo';

const obj = {
  get [expr]() { return 'bar'; }
};

console.log(obj.foo); // "bar"
To define static getters:
class MyConstants {
  static get foo() {
    return 'foo';
  }
}

console.log(MyConstants.foo); // 'foo'
MyConstants.foo = 'bar';
console.log(MyConstants.foo); // 'foo', a static getter's value cannot be changed
var a = { get x() { } };
var b = { get "y"() { } };
var c = { get 2() { } };

var e = { set x(v) { } };
var f = { set "y"(v) { } };
var g = { set 2(v) { } };


 

  • It must not appear in an object literal with another get e.g. the following is forbidden
    { 
      get x() { }, get x() { } 
    }
    
  • It must not appear with a data entry for the same property e.g. the following is forbidden
    { 
      x: ..., get x() { } 
    }



const obj = {
  log: ['example','test'],
  get latest() {
    if (this.log.length === 0) return undefined;
    return this.log[this.log.length - 1];
  }
}
console.log(obj.latest); // "test"
delete obj.latest;// to delete getter
const o = {a: 0};

Object.defineProperty(o, 'b', { get: function() { return this.a + 1; } });

console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)

No comments:

Post a Comment