In JavaScript, a function is really a unique feature. In general, you can use it as a global module. Define it and use it anywhere you want. But JavaScript functions are much more than that. Let’s find out what they are.
Functions
A JavaScript function is a group of statements that can be run as a single unit. It has the following features:
- can have parameters
- returns a value
- each invocation has a context value – can be accessed by “this “
- A function can be assigned to the property of an object, which is called a method. The invocation context of a method is an object. Therefore, the “this” keyword inside a method refers to the object itself.
Functions can also be designed to create objects. They are called constructors.
Functions as Objects
In JavaScript, a function is also an object. You can freely assign functions to variables or properties in objects. Interestingly, you can even add properties or methods to functions (rarely used, though).
var calculator = {
x: 0,
y: 0,
add: function () { return this.x + this.y; }
};
calculator.x = 10;
calculator.y = 20;
alert(calculator.add());
Declaring Functions
Functions are defined by using the keyword “function” followed by a name, parameters, and statements. When functions are declared as part of an expression, the name of the function is usually omitted.
$(document).ready(function () {
var nums = [1, 2, 3, 4, 5];
alert(nums.reduce(function(r, v) { return r+v; }, 0)); // 15
});
Invoking Functions
There are a couple of ways to invoke or call functions:
- as a function
- as a method
- as a constructor
- indirectly using the “call()” or “apply()” methods
Constructor Invocation
When you call a function with the “new” keyword, a new object is created. Constructors initialize the properties of objects.
var now = new Date();
alert(now.toString());
The following example shows how a constructor is used to create a custom object.
function Calculator(x, y) {
this.op1 = x;
this.op2 = y;
}
Calculator.prototype.add = function () {
return this.op1 + this.op2;
}
$(document).ready(function () {
var calculator = new Calculator(10, 20);
alert(calculator.add());
});
Indirect Invocation
Indirect invocation is used when you want to call a function as if it is a method of an object. You know each function has its own invocation context and can be accessed by “this. ” What if you want to call a function with the specific object context? That is for what indirect invocation is.
“Function.prototype” defines the “call()” and “apply()” methods.
- apply(thisArg, argsArray)
- call(thisArg, arg1, arg2, …)
Two methods do the same task. The difference is how to pass arguments to a function. You need to use an array to the “apply()” method.
function calculateTax(rate, deduction) {
var tax = (this.amount - deduction) * rate;
return tax > 0 ? tax : 0.0;
}
When you call the “calculateTax()” function directly, you will get an error because the invocation context does not have an amount property.
function calculateTax(rate, deduction) {
var tax = (this.amount - deduction) * rate/100;
return tax > 0 ? tax : 0.0;
}
function Product(price) {
this.amount = price;
}
$(document).ready(function () {
var food = new Product(10);
var truck = new Product(40000);
alert(calculateTax.apply(food, [10, 100])); // 0
alert(calculateTax.call(truck, 10, 10000)); // 3000
});
You can think of indirect invocation as a temporary method.
var truck = new Product(40000);
truck.calTax = calculateTax; // temporary method
alert(truck.calTax(10, 10000)); // 3000
delete truck.calTax; // remove it
Optional Parameters
You can invoke functions with fewer arguments than declared parameters. The important part is how to check whether the argument is passed or not.
When the arguments are not passed, matching parameters are set to “undefined. “
function add(x, y) {
if (y === undefined) {
y = 0;
}
if (x === undefined) {
x = 0;
}
return x + y;
}
alert(add(10,20)); // 30
alert(add(10)); // 10
alert(add()); // 0
Variable Length Arguments and Variadic Functions
You can even pass more arguments than the number of parameters. And then how can we access the arguments? Actually, all arguments can be accessed through the “arguments” array-like object (It is not an array, which can be a language design mistake).
By using this “arguments” variable, you can create a function that can accept any number of arguments. This kind of function is called a variadic function.
function add() {
var result = 0;
for (var i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}
alert(add(1,2,3,4,5,6,7,8,9,10)); // 55
Return Values
In JavaScript, functions always return values. There is no “void” type. If you do not return a value in a function, the return value is “undefined. “
function dummy() {
};
var result = dummy();
alert( typeof result ); // "undefined"
Hoisting
“Hoisting” means to lift someone or something to a higher place. JavaScript lifts (hoists) variables and functions to the top.
Scope
Variables in most programming languages are scoped in blocks { }. JavaScript does not have a block scope. Instead, it has a function scope.
{
var i = 10;
}
alert(i);
It returns “10” even though “i” is defined in a block.
But if the variable is defined in a function, it is local to the function.
function doSomething(){
var i = 10;
}
alert(i);
You will get an error “i is undefined.”
Variable Hoisting
Now you know variables are local in a function. But JavaScript engine hoists all local variables to the top. What does it mean?
(function () {
alert(i); // undefined or 10 ???
var i = 10;
})();
You will see the alert box with “undefined.” What does it mean? What has happened here?
Hoisting only lifts the declaration, not the value. The previous code is much like the following:
(function () {
var i;
alert(i); // undefined
i = 10;
})();
This feature can make your code quite interesting when the local variable is mixed with the global one.
var i = 5; // global
(function () {
alert(i); // undefined
var i = 10;
alert(i); // 10
})();
You might think the first alert(i) will show “5”. But it displays “undefined” because the local variable declaration is hoisted.
Note that only the declaration is hoisted; the value is not hoisted.
Function Hoisting without var
Functions are also hoisted in a global or function scope. You can call the function even before it is defined.
(function () {
alert(add(2, 3)); // 5
function add(x, y) {
return x + y;
}
})();
Function Hoisting with var
But if functions are assigned to variables using “var, “only declarations are hoisted. The function implementation is not hoisted.
alert(add(2, 3)); // error
var add = function (x, y) {
return x + y;
}
alert(add(3, 4)); // 7
Closure
Function Closure is one of the most widely referred JavaScript techniques. It sounds complex, but once you have understood the concept, it becomes quite useful. Also, it is not as difficult as you might think.
Nested Functions
Functions can be nested. Variables in JavaScript are scoped in functions, and variables in outer functions can be accessed in inner functions. (Just like global variables in functions).
function outer() {
var x = 10;
inner();
function inner() {
var y = 20;
alert(x + y);
}
}
outer(); // call inner() and shows 30
There’s nothing new here.
Closure
Let’s change how to call the function a little bit.
function outer() {
var x = 10;
return function inner() {
var y = 20;
alert(x + y);
}
}
var innerFunc = outer();
innerFunc(); // 30
The “innerFunc” variable refers to the inner function. When it is called, only “inner()” is invoked. The interesting thing is that “inner()” tries to access “x,” which is defined in “outer().” But you already executed “outer(),” which does not exist anymore.
But the result is still 30.
“Closure” means that the variable in the outer functions stays alive as long as the inner functions are alive.
Modules
You might wonder why “Closure” is an important concept in JavaScript in any way. I think people do not use nested functions often, not to mention closures.
One example of closure is a “Module. “A module is like a class on other languages. It hides its state and exposes its interfaces.
var Calculator = function (x, y) {
// private members
var _x = x,
_y = y,
_add = function () { return _x + _y; },
_multiply = function () { return _x * _y; };
// public interface as an object
return {
add: _add,
multiply: _multiply
};
}
var calc = new Calculator(3, 4);
alert(calc.add()); // 7
calc = new Calculator(5, 10);
alert(calc.multiply()); // 50
By using closures, you can simulate private members (variables in outer functions) and public interfaces (inner functions).