JavaScript

JavaScript functions

Everything about functions

Theo Ibot

--

Photo by T S on Unsplash

Hi everyone, I’m a front-end developer and in this article, we will speak about JavaScript functions. Enjoy reading.

The main influence in JavaScript code lies in functions. All code software engineers write contains functions. They are like sentences in the article and they have their own power, meaning, and coherence.

To use them correctly and efficiently and build a good app architecture, every JavaScript developer should know how they work under the hood.

Therefore, in this article, I’ve tried to collect all the crucial JavaScript concepts about functions that I know. It isn’t everything but without this, it’s pointless to dig into other language features. So let’s learn these concepts to get our super-power!

You can launch the code snippets from this article inside Chrome DevTools in console or using tab Snippets from Sources.

The definition of functions👀

We define functions everywhere in our projects all the time. At first, a function can be assigned to a variable, and we called it Function Expression or if we start the definition from the built-in word function, we call this Function Declaration. Nothing interesting, saying you. And why we have two methods to do the same thing? The reason is they are slightly different.

function myFuncDeclaration() {} (1)const myFuncExpression = function() {} (2)

We may not use (1) inside loops, conditionals, or try/catch/finally or with statements, but (2) style we can use anywhere.

A good style is to define functions at the start of the code. But sometimes it is necessary to know about this difference:

myFuncDeclaration(); // 'myFuncDeclaration called'
function myFuncDeclaration() {
console.log('myFuncDeclaration called');
}
// Uncaught ReferenceError: Cannot access 'myFuncExpression' before // initialization
myFuncExpression();
const myFuncExpression = function() {
console.log('myFuncExpression called');
}

Function Declaration is hoisted to the top of the containing script or function and they are visible throughout the script or function and we can call it before definition. Function Expression doesn’t work if we call it before we defined it in our code, because of its variable declaration isn’t hoisted.

Also in modern ES6+ standards, we have Arrow Function. This type of definition frequently used for callback functions. We will review the difference below in the section about the body of functions.

const arrowFnExpression = () => {};

Let it work!

Let’s go ahead 🐾, we’ve already defined function with all staff inside. How can we make the function’s code work and how it can influence the other functions? There are several methods for doing this. Here they are:

(function immediatelyCalledFn(a, b, c) {
console.log(a + b + c);
})(1,2,3); // 6
const immediatelyCalledExpr = (function(a, b, c){
console.log(a + b + c);
})(1, 2, 3); //
console.log(immediatelyCalledExpr); // undefined
console.log(immediatelyCalledFn); // error immediatelyCalledFn is not defined

The first one you see above is an immediately invoked function. It invokes once in the same place where it was defined and we cant call it anywhere else in our code.

The second we’ve already seen in previous parts, just using the function name and parentheses:

const myFn(a, b, c) {
return a + b + c;
}
myFn(1, 2, 3); // 6

This call we can use so many times as we need in any part of our code where this function defined.

And the last from frequently used methods for function invocation is the usage of the keyword new. The function, in this case, becomes the Function Constructor and we can create a lot of similar objects using this function. It works like a class in OOP.

const myCreatorFn = function(a, b, c) {
this.calculateCurrentSum = () => {
console.log(a + b + c);
};
this.updateFirst = (num) => {
a *= num;
};
}
const obj1 = new myCreatorFn(1, 2, 3);
const obj2 = new myCreatorFn(4, 5, 6);
obj1.calculateCurrentSum(); // 6
obj2.calculateCurrentSum(); // 15
obj1.updateFirst(3);
obj1.calculateCurrentSum(); // 8
obj2.calculateCurrentSum(); // 15

Here in the example, calculateCurrentSum and updateFirst functions work like a public method of myCreatorFn constructor. We can invoke them everywhere using objects created by this constructor, as we could see, from the code snippet, all objects are different, we can’t mutate one object using another.

So it’s an old way to create Function Constructor in ES6 the code from the previous snippet will look like this:

class myCreatorFn {
constructor(a, b, c) {
this.a = a;
this.b = b;
this.c = c;
}
calculateCurrentSum() {
console.log(this.a + this.b + this.c);
}
updateFirst(num) {
this.a *= num;
}
}
const obj1 = new myCreatorFn(1, 2, 3);
const obj2 = new myCreatorFn(4, 5, 6);
obj1.calculateCurrentSum(); // 6
obj2.calculateCurrentSum(); // 15
obj1.updateFirst(3);
obj1.calculateCurrentSum(); // 8
obj2.calculateCurrentSum(); // 15

In the new apps, it’s a good idea to use ES6 everywhere, but you can also meet an old type of Function Constructor definition.

Function like an object. Static properties.

Function Declaration and Function Expression are the most commonly used methods of a function definition, but despite them, we have the Function constructor, so we can define function this way also:

const argumentsStr = "test";
const functionDefStr = 'console.log(argumentsStr);';
const myFuncObject = new Function(argumentsStr, functionDefStr);
myFuncObject(); // test

It must be appropriate for the case when the functionDefStr came from the database in string format. I’ve never met it in the production code.

Also from the last definition, we have seen, that function in JavaScript is a special type of object which we can create using a Function constructor. As we know objects have their properties hence function definitions can have theirs. These properties called static properties.

It’s convenient to use them if we need the value persists across invocations of the function and only this function uses this value. The second reason to use static properties is if we need to get some information before function invocation and make a decision about the function call on this base. Exactly this approach is used in Singleton design pattern to get always the same single instance of the class.

As an example, let’s consider we have a function which gets some data from outside every 5 minutes and if data is different from previous, we should call some update function and finish listen to data after we get ten updates:

function showUpdate(data) {
console.log(data );
}
getData.previousData = [];// invoke function every 5 minutes
const interval = setInterval(getData, 300000);
function getData() {// mock random data, integers from 1 to 15
const newDataFromDb = Math.floor(Math.random() * 15) + 1;
// check how many differen pices of data we have
// stop invokations when it's equal to 10
if (getData.previousData.length === 10) {
getData.previousData = [];
clearInterval(interval);
}
// check if current new data
// show them if they're really new
if (!getData.previousData.includes(newDataFromDb)){
getData.previousData.push(newDataFromDb);
showUpdate(newDataFromDb);
}
}

In modern JavaScript and in TypeScript word static used to define static methods and variables inside the classes which work the same as Function Constructor under the hood.

class myClass {
static staticVariable = 'static variable';
static staticFn(data) {
return 'Method called with ' + data;
}
}
console.log(myClass.staticFn('readers')); // Method called with readers
console.log(myClass.staticVariable); // static variable

What hides between curly brackets?

So, while we are implementing a function definition we also have a list of parameters between curly brackets — local variables and a list of arguments between the round brackets. Also inside the function’s body, we often use two magic keywords return and this. So let’s lay it out in order.

Local variables have a piece of information encapsulated inside a function. We can get them outside using return. The code doesn’t execute after the return statement; the function gives the value of returned expression outside. If nothing goes after the word return function returns undefined, if the function doesn’t contain return, all lines inside the function body will be executed and the function returns undefined. The demonstration:

function showReturned() {
const data = 'any data';
return data;
console.log(data); // it won't be called
}
console.log(showReturned()); // 'any data'
console.log(data); // error: data isn't defined

That’s interesting what we can do with data and encapsulation. When I think about it, I usually memories about getters and setters implementation. Uh, stop, I will show it below, let’s proceed in an orderly fashion.

The next object we should review is arguments. They comprise data from outside this function. We can strictly define the sequence of these arguments and get any passed data, using built-in word arguments, which defined an array-like object with the length property. Since arguments aren’t a real array, the documentation has the note:

Note: If you’re writing ES6 compatible code, then rest parameters should be preferred.

The code below shows the difference:

// args - variable, which contain all rest arguments after num
function restFromDevisionSpread(num, ...args) {
console.log(...args);
const filteredData = [...args].filter((arg) => (arg%num === 0));
return filteredData;
}
restFromDevisionSpread(3, 8, 12, 6, 7); // [12, 6]
function restFromDevisionArguments(num) {
const removed = arguments.shift();
const filteredData = arguments.filter((arg) => arg%num === 0);
return filteredData;
}
restFromDevisionArguments(3, 8, 12, 6, 7); // error: shift is not a function

And one more thing about arguments, they can contain any type of variable including objects and functions. The function which is an argument of the other function and invoked inside the function it passed into called Callback Function. They used with timeouts, AJAX-requests, in events handling, promises, observables. Let’s take a look at a simple example:

const myFn1 = function(n) {
console.log('some data was received: ' + n);
}
const myFn2 = function(callback) {
let someData = null;
setTimeout(() => {
someData = 'data from parent function';
callback(someData);
}, 2000);
console.log(someData);
}
myFn2(myFn1);
// we will se in console
// null
// some data was received: data from parent function

We can see that callback inside the timeout got the data, but inside myFn2 we can’t see this data. So it is how callbacks work.

Here I should remind you that it is the method which should be used very carefully. We should avoid nested callbacks to save good readability in our code. Don’t use callback functions inside callback functions! Keep your code flat and clean!

The last but really important magic word inside the curly brackets is the word “this”. It isn’t just a word, it’s a very powerful and useful thing in JavaScript and it’s one of those features that confuses a lot of developers. Let’s figure out what it contains and what can we do with this.

Originally this is a property of an execution context and it refers to an object or in strict mode can be any value. The value of this can’t be changed using the assignment operator(=), but we can bind any value to this, using bind, call and apply build-in methods. Let consider what contains this by default and then we will implement some code to change this default value using mentioned build-in functions.

Outside of any non-arrow functions, it contains a global object of the environment where js-code launched (in a browser it’s the window, in node — global). Inside a non-arrow function, it refers to the nearest parent scope (Local or Global) where this function defined. Let’s launch these code snippets in our browser.

Functions inside Global scope:

'use strict';
console.log(this, ' - Global scope');
function myFn() {
console.log(this, ' - Local scope');
}
const myArrowFn = () => {
console.log(this, ' - Arrow function, so Global scope');
}
myFn();
myArrowFn();
/** We will see in console:
Window - Global scope
undefined - Local scope
Window - Arrow function, so Global scope
**/

Functions inside Local scope of some object:

let person = {
name: 'Sezar',
sayName: function() {
console.log(this.name, '- Local scope contained properties of object - person');
},
sayArrowName: () => {
console.log((this.name || 'empty'), '- Arrow function, so Global scope doesn\'t have property name');
}
};
person.sayName();
person.sayArrowName();
/** We will see in console:
Sezar - Local scope contained properties of object - person
empty - Arrow function, so Global scope doesn't have property name
**/

The first method to update the value of this variable is to use bind() method. Let’s look at the next code snippet:

class Student { 
constructor(name, specialization) {
this.name = name;
this.specialization = specialization;
}
}
const studentFunction = function(grade, gender) {
console.log('Name: ', this.name, '\n');
console.log('Specialization: ', this.specialization, '\n');
console.log('Grade: ', grade, ', Gender: ', gender);
}
const student1 = new Student('Sezar', 'Artist');
const student2 = new Student('John', 'Data scientist');
const showInfo1 = studentFunction.bind(student1, 3, 'M');
const showInfo2 = studentFunction.bind(student2, 3, 'M');
// Name: Sezar
// Specialization: Artist
// Grade: 3 , Gender: M
showInfo1();
// Name: John
// Specialization: Data scientist
// Grade: 3 , Gender: M
showInfo2();
// Name:
// Specialization: undefined
// Grade: 3 , Gender: M
studentFunction(3, 'M');

Pay attention that if we try to invoke studentFunction itself we won’t get any data. Method bind creates separated functions with a new bonded scope.

Let’s do the same using call and apply:

console.log('----call----');
studentFunction.call(student1, 4, 'M');
studentFunction.call(student2, 5, 'F');
console.log('----apply----');
studentFunction.apply(student1, [2, 'F']);
studentFunction.apply(student2, [5, 'M']);

In the console, we will see an appropriate data for student1 and studend2. The difference between bind is that this method doesn’t invoke the function, but call and apply does. Also, pay attention that in function’s arguments list in the apply method we have a pseudo-array.

And about keyword this that’s all for the current part, but it isn’t everything and it’s a very wide topic to speak.

Interaction between functions (closure)

We’ve already spoken about the connection between functions when mentioned Callback Functions. But we haven’t thought about the situation when a function defined inside the other function.

In JavaScript, the inner function defined inside the outer function gets the reference to the Local scope of this outer function and can use all variables from this outer function. There is a general term in programming for this ability of function — closure. All functions in JavaScript are closures because they have at least reference to the Global environment.

This trait of JavaScript function helps to give access and the ability to change the data only restricted parts of the program. It’s the method to encapsulate data and save them on different steps of program invocation.

To use closure, we should define a function inside the other function, use inside inner function local variables from the outer function and return reference to inner function from the outer function.

Let’s look at the next code snippet:

function Counter() {
let _num = null; // encapsulated variable
return {
set number(num) {
_num = num;
},
get number() {
return _num;
},
increase() {
if (_num === null) {
console.error("Set initial value to increase it!");
} else {
_num++;
}
} // increase
}
}
const counter1 = new Counter();
const counter2 = new Counter();
counter1.increase(); // Set initial value to increase it!
counter1.number = 5;
console.log(counter1.number); // 5
counter1.increase();
console.log(counter1.number); // 6
counter1.increase();
console.log(counter1.number); // 7
console.log(counter2.number); // null

Access to the variable _num we have only by using setter, getter, and increase, this variable is stored in the closure for these methods, when they were initialized. Setter, getter, and increase have actual information about the value of _num and can change it. Objects counter1 and counter2 have separated scope and there own encapsulated _num variable.

I hope that the closure becomes more understandable for you after this part.

Conclusion

Functions in JavaScript played the main role and have a lot of abilities to make the life of programmers easier or bring a lot of headaches. I wish you to use them smartly. Keep your code clean, maintainable, and readable. Thank you for reading! ❤️

--

--