Introduction
We know that functions are special objects in JavaScript. One way to distinguish functions from other reference type(objects) is that every function will have an internal property [[Call]]. In this post we will explore hoisting, function overloading, call(), apply() and bind().
1. First class citizen
Functions are first class citizen in JavaScript. We can assign them to variables, add them to objects, pass them to other functions as arguments, and return them from functions. Therefore, they behave like any other object.
// normal declaration
function substract(a, b){
return a > b;
}
// anonymous function assigned to a variable
var substract = function(a, b){
return a > b;
}
// function passed as argument
[4, 1, 3, 2, 5].sort(substract) // [1, 2, 3, 4, 5]
// function returning other function
function createSub() {
return function substract(a, b){
return a - b
}
}
2. Hoisting
Hoisting is phenomenon that occurs due to the way JavaScript executes (creation and execution phase) To put it in simple terms
1) All the function declarations in a particular scope is made available before the execution of the code.
greet(); // hello world
function greet(){
console.log("hello world");
}
Then order of a function declaration and invoking the function doesn't matter in a particular scope.
2) All the variable declaration in a particular scope exists before the execution of the code, but the value assigned in them is undefined.
console.log(a); // undefined
var a = "hello"
console.log(a); // hello
console.log(greet); // undefined
console.log(greet()); // Error: greet is not a function
var greet = function(){
return "hello world"
}
console.log(greet); // function(){return 'hello world'}
console.log(greet()); // hello world
Here not to get confused with the function as an expression, while in the previous example it was function as an declaration.
3. Overloading
First let us look at how functions in JavaScript work. Functions can accept any number of arguments when being invoked, the program doesn't throw any error in this case.
function greet(name){
console.log("hello" + name);
}
greet("clark"); // hello clark
greet("clark", 12) // hello clark
In the above example, the second parameter is skipped because we don't have any way to access it. But JavaScript provides us with a way to access it using the variable arguments inside the function body.
// arguments variable contains all the parameters of the function
function add(a) {
sum = 0;
for(var i = 0; i < arguments.length - 1; i++) {
sum = sum + arguments[i];
}
return sum
}
console.log(add(1, 2, 3, 4)) // 10
Using this, we can overload functions in JavaScript. Overloading is basically having the same function name with different function signatures.
function greet() {
if(arguments.length != 0) {
console.log("hello" + arguments[0]);
} else {
console.log("hello world");
}
}
greet("clark"); // hello clark
greet(); // hello world
Note that using arguments variable inside functions reduces the code readability, so we must use it with precautions.
4. Methods and this
Methods are just functions as properties of objects.
var obj = {
name: "clark",
greet: function() {
console.log("Hi, I am " + this.name);
}
};
obj.greet() // Hi, I am clark
In every scope there exists, this which is basically the calling object.
function greet() {
console.log("Hi, I am " + this.name);
}
var superman = {
name: "clark",
greet: greet
}
};
var batman = {
name: "bruce",
greet: greet
}
};
superman.greet() // Hi, I am clark
batman.greet() // Hi, I am bruce
We cannot reassign this directly, but there are ways to change the this.
5. call() and apply()
call() and apply() are methods available to functions only, not on any other objects. When invoking call(object, parameter), the first argument is the object which this should point to, and the second parameter is the parameter to be passed to the original function. apply() is same as call(), expect it is more powerful as it accepts a list of parameters as the second parameter, apply(obj, [param1, param2, param3])
function greet(lastname) {
console.log("Hi, I am " + this.name + " " + lastname);
}
var superman = {
name: "clark",
greet: greet
}
};
var batman = {
name: "bruce",
greet: greet
}
};
greet.apply(superman, ["kent"]) // Hi, I am clark kent
greet.apply(batman, ["wayne"]) // Hi, I am bruce wayne
bind()
bind(obj, param1, param2, ...]) is different from the above two, because bind() returns a function rather than invoking the function directly. The obj and parameters passed to binded to function after that.
function greet(lastname) {
console.log("Hi, I am " + this.name + " " + lastname);
}
var superman = {
name: "clark",
greet: greet
}
};
var batman = {
name: "bruce",
greet: greet
}
};
var greetBySuperman1 = greet.bind(superman, "kent") // returns a function
var greetBySuperman2 = greet.bind(superman) // returns a function
greetBySuperman1() // Hi, I am clark kent
// changing the parameter has no effect
greetBySuperman1("luther") // Hi, I am clark kent
/* changing the parameter works here
because there was no parameter binded to it initially */
greetBySuperman2("luther") // Hi, I am clark luther
I have not covered functions as constructors here, and I will be writing a different post about it later.