Javascript has been a very popular language for a long time and is widely used around the community.
Here is a summary of some useful advanced concepts.
Let’s get started.
Stop the habit of wishful thinking and start the habit of thoughtful wishes with Justly.
Hoisting means to pull something up, javascript also does the same.
JavaScript has a mechanism that moves all declarations of variables and functions to the top of their scope before code execution.
You can use
var
orfunction
before declaring it. It’s useful when you know requirements, but don’t know that how to implement it?
Hoisting performs differently for var
, let
, and const
.
x = 5;
console.log(x); //outputs 5
var x;
It runs successfully and logs 5
as output as a declaration of x
moved on top of that block.
Also, another example of var
console.log(x); //outputs undefined
var x = 5;
It logs undefined
as we have initialized (assigned a value) x
at the time of declaration, that’s after printing its value.
Hoisting performs the same in the case of let
and const
.
Consider the same example as var
// let
x = 5;
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x;
// const
console.log(x); // ReferenceError: Cannot access 'x' before initialization
const x = 5;
let
and const
are not declared or initialized before output, it gives a Reference Error
like the above code.
Hoisting can be used for functions too
myFunc("John");
function myFunc(name) {
console.log("My name is " + name); //My name is John
}
We all are using var
, let
, and const
to initialize variables in javascript commonly. Let’s know the difference between them.
Before the ES6 module, var
was used everywhere to declare variables. But due to some cons of var
, Javascript has defined another way that is let
. And const
are used to maintain constant values.
We can differentiate them in the following manners.
var
have global
(If they have declared globally) or functional
(if they have declared inside functions) scope.var x = "hello from x"; //globally scoped
function newFunction() {
var y = "hello from y"; //scoped in function
}
let
is block-scoped. Block means anything that resides in curly braces {}
. It can be function
or if condition
or switch
case.let x = "hello from x";
let count = 3;
if (count > 2) {
let x = "hello from x inside block";
console.log(x);// "hello from x inside block"
}
console.log(x) // x is not defined
const
is block-scoped same as let
.We can define const
as belowconst x = "hello from x";
var
can be updated or redeclared like belowvar x = "hello from x";
x = "hello from x again";
// or
var x = "hello from x";
var x = "hello from x again";
let
can be updated but not redeclaredlet x = "hello from x";
x = "hello from x again";
// but not
let x = "hello from x";
let x = "hello from x again"; // error: Identifier 'x' has already been declared
const
can not be updated or declaredconst X= "HELLO FROM X"
X = "HELLO AGAIN" //error: Assignment to constant variable.
// and
const X = "HELLO FROM X";
const X = "HELLO FROM X AGAIN"; // error: Identifier 'X' has already been declared
let
instead of varConsider the following example using var
var x = "hello from x";
var count = 3;
if (count > 2) {
var x = "hello from x inside condition";
}
console.log(x) // "hello from x inside condition"
In the above example, if you intentionally want to change the value of x
, then there will be no issue with the code.
It becomes a problem when you are working with large code, and don’t know that var x
has been already declared, it will cause lots of bugs in code.
here is the same example using let
,
let x = "hello from x";
let count = 3;
if (count > 2) {
let x = "hello from x inside condition";
}
console.log(x) // "hello from x"
Since let is block-scoped, both variables are treated as different variables. For this reason let
is a better choice than var
if variables using let
are not declared more than one time in the block.
In a simple javascript function, we can add any number of parameters in the function definition if we know them.
function myFunc(var1, var2)
But if we don’t know the number of parameters for the functions, then we can use the rest parameters
. They can be written by prefixing …
like below,
function sum(...args) {
let sum = 0;
for (let arg of args) {
sum += arg;
}
return sum;
}
// can pass any number of arguments
sum(1,2,3); //6
sum(1,2,3,4,5,6); //21
sum(1,2,3,4,5,6,7,8,9,10); //55
We know that variables are only accessible within scopes, but what if we want block-scoped variables outside that scope??
Closures are functions that are defined inside other functions and execute locally scoped variables and allow them to access outside of scope.
function myFunc() {
let message = 'hello';
function insideMyFunc() {
console.log(message);
}
insideMyFunc();
}
myFunc();
The output will be hello
in that case.
function myFunc() {
let message = 'hello';
function insideMyFunc() {
console.log(message);
}
return insideMyFunc;
}
var myFunc1 = myFunc();
myFunc1();
In this case, you can not execute myFunc()
directly as it returns insideMyFunc
. You have to assign it to another var that is myFunc1
and can execute myFunc1()
later.
output: hello
function myFunc(x) {
function insideMyFunc(y) {
console.log(x + " " + y);
}
return insideMyFunc;
}
var func1 = myFunc("Hi");
var func2 = myFunc("Hello");
func1("there");
func2("there");
myFunc
takes Hi
as x
and returns insideMyFunc
to func1
. func1
takes there
as y
and finally prints Hi there
.
The output will look like this,
output of func1 : Hi there
output of func2 : Hello there
Symbols are used to define unique names even if they are the same.
let x = Symbol("id");
let y = Symbol("id");
console.log(x == y) //false
They are new primitive types like Number
, String
, and Boolean
serving object properties. It is used to create a totally unique identifier.
Suppose you are using a third-party library that returns some random user object, and you want to add unique id
on that object, but that should not be accessible outside of the object, then you can use Symbol
.
Here is an example of using a symbol and the way it can be accessible,
let user = { name: "lorem" };
let id = Symbol("id");
user[id] = 1;
console.log(JSON.stringify(user)); //{"name":"lorem"}
console.log(user); //{ name: 'lorem', [Symbol(id)]: 1 }
console.log(user[id]); // 1
console.log(user.id); // undefined
console.log(Object.getOwnPropertyNames(user)) //[ 'name' ]
for (let key in user){
console.log(key) // name
}
You can explore more about symbols from this article.
Javascript defines two protocols iterables and iterators for iterations of data structure over for…of
loop. Instead of for
, for…of
reduces the complexity of the indices when using nested loops.
Data structures like array
, string
and Sets
have symbol.iterator()
method. They are known as iterables.
const array = [1,2,3];
// or it can be written as for (let x of array){
for (let x of array[Symbol.iterator]()){
console.log(x);
}
The output of it should be,
1
2
3
They are objects returned by symbol.iterator()
method.
They use next()
method which returns the next item in the sequence. It contains mainly two properties,
const array = [1,2,3];
let arrIterator = array[Symbol.iterator]();
console.log(arrIterator.next()); // {value: 1, done: false}
console.log(arrIterator.next()); // {value: 2, done: false}
console.log(arrIterator.next()); // {value: 3, done: false}
console.log(arrIterator.next()); // {value: undefined, done: true}
Above is the default iterator, but they also can be defined manually like below
function iterator(index = 0, end, step = 1) {
const rangeIterator = {
next() {
if (index < end) {
let result = { value: index, done: false }
index += step;
return result;
}
return { value: undefined, done: true }
}
};
return rangeIterator;
}
The example simply ranges over the index
till the end
increasing by step
value. Once iteration will over, it will set done
to true and will terminate the loop. It can be accessed as below
const it = iterator(1, 5, 1);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 2 3 4
result = it.next();
}
Generators return multiple values using yield
. They conform iterators
and iterables
protocol, but unlike them, we don’t have to maintain the internal state of variables using generators.
Generator functions can be written using function*
. They do not initially execute their code. Instead, they return a special type of iterator. When we call the generator’s built-in next()
method, the Generator function executes until it encounters the yield
keyword.
Here is an example of a generator function,
function* iterator(index = 0, end, step = 1) {
while (index < end) {
yield index;
index += step;
}
}
// can call generator like below
const generator = iterator(1,5,1);
for (let value of generator) {
console.log(value);
}
The output of it is,
1
2
3
4
We’re Grateful to have you with us on this journey!
Suggestions and feedback are more than welcome!
Please reach us at Canopas Twitter handle @canopas_eng with your content or feedback. Your input enriches our content and fuels our motivation to create more valuable and informative articles for you.
Whether you need...