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.Different models, different approaches, better solutions. It's as simple as that.
What is OOP?
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
Object:
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()
Object.is(value1, value2);
Boolean
indicating whether or not the two arguments are the same value- both
undefined
- both
null
- both
true
or bothfalse
- 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
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
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 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 = {
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
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.
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.
name age marks
Output:
true true true falseSymbol:
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.getOwnPropertySymbols(obj) - to get all symbols
- Reflect.ownKeys(obj) - all keys of an object including symbolic ones
- string
- symbol
- not boolean
- not numeric
- not NaN
- not an object
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
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
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:
The idea is that when we clone an object or merge objects, we usually want all properties to be copied (including symbols like id
).
- 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.Data Abstraction/Hiding of Data:
// 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 InheritanceIn 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.
[[Prototype]]
That is either null or
references another objectobject
, 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:
There are only two limitations:
- The references can’t go in circles. JavaScript will throw an error if we try to assign
__proto__
in a circle. - The value of
__proto__
can be either an object ornull
. 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.
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.keys
, Object.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 ornull
. - 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 themethod
is taken from the prototype,this
still referencesobj
. 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.
https://www.geeksforgeeks.org/introduction-object-oriented-programming-javascript/
https://www.geeksforgeeks.org/commonly-asked-oop-interview-questions/
https://www.learn-js.org/
Method | Description |
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. |
substr(startIndex, noOfElementsToExtract)
substr()
extracts length
characters from a string
, counting from the start
index.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.length
is omitted, substr()
extracts characters to the end of the string.If
length
is undefined
, substr()
extracts characters to the end of the string.If
length
is a negative number, it is treated as 0.start
and length
, NaN is treated as 0.JS splice(startIndex, deleteCount, addThis1, addThis2); |
slice(beginIndex, endIndex)
JS slice(beginIndex, endIndex) |
Size() vs length:
.size()
is not a native JS function of Array
JavaScript: delete on Arrays and objects |
JS algorithms:
https://github.com/trekhleb/javascript-algorithms#readmeEvent bubbling and event capturing
.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
});
CSRF(Cross site request forgery)
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
Regular Expressions:
string.match(regExp) vs regExp.test(string)
Executes the search for a match between a regular expression and a specified string. Returns true or false.
Used to retrieve the matches when matching a string against a regular expression. Returns an array with the matches ornull
if there are none.
null
evaluates to false
,setTimeout or asynchronous code in loops
Closures
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
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
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.Map:
- a collection of keyed data items, just like an
Object
. But the main difference is thatMap
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
ifkey
doesn't exist in map.map.has(key)
-- returnstrue
if thekey
exists,false
otherwise.map.delete(key)
-- removes the value by the key.map.clear()
-- clears the mapmap.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 thatNaN
is considered equal toNaN
. SoNaN
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 regularObject
.Besides that,
Map
has a built-inforEach
method, similar toArray
Set:
- A
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, returnstrue
ifvalue
existed at the moment of the call, otherwisefalse
.set.has(value)
- returnstrue
if the value exists in the set, otherwisefalse
.set.clear()
- removes everything from the set.set.size
- is the elements count.set.keys()
- returns an iterable object for values,set.values()
- same asset.keys
, for compatibility withMap
,set.entries()
- returns an iterable object for entries[value, value]
, exists for compatibility withMap
.
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) }
WeakMap:
- The first difference between
Map
andWeakMap
is that keys must be objects, not primitive values WeakMap
isMap
-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
clear
,size
,keys
,values
…- 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
hasweakMap.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.
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
.
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
isSet
-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 toWeakSet
(not primitives).- An object exists in the set while it is reachable from somewhere else.
- Like
Set
, it supportsadd
,has
anddelete
, but notsize
,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.
There’s a base set of inherently reachable values, that cannot be deleted for obvious reasons.
For instance:
- The currently executing function, its local variables and parameters.
- Other functions on the current chain of nested calls, their local variables and parameters.
- Global variables.
- (there are some other, internal ones as well)
These values are called roots.
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.
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.
- 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.
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
3.Using Object.assign()
Promises:
- To run promises in parallel, create an array of promises and then use
Promise.all(promisesArray)
. - When 2nd functions's is dependent on the 1st fns output then we should use async/ await
- 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));
function addValues(){ sumValue = 0; for(let i=0;i<arguments.length;i++){ sumValue += arguments[i]; } return sumValue;}
calc = (...args) => { sumValue = 0;
for(let i=0;i<args.length;i++){
sumValue += args[i];
}
return sumValue;
};console.log(calc(5,3,4,5));function sum(...args) {
return theArgs.reduce((prev, curr) => {
return prev + curr;
});
}console.log(sum(1, 2, 3));
Proxy:
levelup.gitconnected.com/the-amazing-power-of-javascript-proxies-aa27c6d06bcbArray.from(arrayLikeObject, mapfn)
Array.from()
lets you create Array
s from:
- array-like objects (objects with a
length
property and indexed elements); or - iterable objects (objects such as
Map
andSet
).
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');
},
const expr = 'foo';
const obj = {
get [expr]() { return 'bar'; }
};
console.log(obj.foo); // "bar"
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