📜 ⬆️ ⬇️

JavaScript Guide, Part 4: Functions

Today we publish the fourth part of the translation of the JavaScript guide, which is dedicated to functions.

Part 1: first program, language features, standards
Part 2: code style and program structure
Part 3: Variables, Data Types, Expressions, Objects
Part 4: Functions
Part 5: Arrays and Loops
Part 6: Exceptions, Semicolon, Pattern Literals
Part 7: strict mode, this keyword, events, modules, mathematical calculations
Part 8: ES6 feature overview
Part 9: Overview of ES7, ES8, and ES9 Capabilities


')

Javascript functions


Let's talk about the functions in JavaScript, we will do their general overview and consider the details about them, the knowledge of which will allow you to use them effectively.

A function is an independent block of code that, once declared, can be called as many times as necessary. The function may, although not necessarily, take parameters. Functions return a single value.

Functions in JavaScript are objects, more precisely, they are objects of type Function . Their key difference from ordinary objects, giving them the exceptional capabilities they possess, is that functions can be called.

In addition, functions in JavaScript are called “first-class functions,” since they can be assigned to variables, they can be passed to other functions as arguments, and they can be returned from other functions.

First, we will consider the features of working with functions and the corresponding syntactic constructions that existed in the language before the advent of the ES6 standard and are still relevant.

This is what a function declaration looks like.

 function doSomething(foo) { // - } 

Nowadays, these functions are called "normal", distinguishing them from the "arrow" functions that appeared in ES6.

The function can be assigned to a variable or a constant. Such a construction is called a functional expression (function expression).

 const doSomething = function(foo) { // - } 

You can see that in the above example, the function is assigned to a constant, but it itself does not have a name. Such functions are called anonymous. You can assign names to similar functions. In this case, we are talking about a named function expression (named function expression).

 const doSomething = function doSomFn(foo) { // - } 

The use of such expressions increases the convenience of debugging (in error messages where the stack is traced, you can see the name of the function). The function name in a functional expression may also be needed in order that the function could call itself, which cannot be dispensed with when implementing recursive algorithms.

In the ES6 standard, there are arrow functions (arrow function), which are especially convenient to use in the form of so-called "inline functions" (inline functions) - as arguments passed to other functions (callbacks).

 const doSomething = foo => { // - } 

The switch functions, in addition to the fact that the structures used to declare them, are more compact than when using ordinary functions, differ from them in some important features, which we will discuss below.

Function Parameters


Parameters are variables that are set at the stage of the function declaration and will contain the values ​​passed to it (these values ​​are called arguments). Functions in JavaScript may either not have parameters, or have one or more parameters.

 const doSomething = () => { // - } const doSomethingElse = foo => { // - } const doSomethingElseAgain = (foo, bar) => { // - } 

Here are some examples of switch functions.

Beginning with the ES6 standard, functions may have so-called default parameters.

 const doSomething = (foo = 1, bar = 'hey') => { // - } 

They are standard values ​​that are set to the parameters of functions in the event that when it is invoked, the values ​​of some parameters are not specified. For example, the function shown above can be called both with the transfer of all two parameters it takes, as well as in other ways.

 doSomething(3) doSomething() 

In ES8, it is possible to put a comma after the last argument of a function (this is called trailing comma). This feature allows you to improve the convenience of editing code when using version control systems during program development. Details about this can be found here and here .

The arguments passed to the functions can be represented as arrays. In order to parse these arguments, you can use an operator that looks like three points (this is the so-called "extension operator" or "spread operator"). Here's what it looks like.

 const doSomething = (foo = 1, bar = 'hey') => { // - } const args = [2, 'ho!'] doSomething(...args) 

If functions need to take a lot of parameters, then remembering the order of their sequence can be difficult. In such cases, objects with parameters and possibilities for destructuring ES6 objects are used.

 const doSomething = ({ foo = 1, bar = 'hey' }) => { // - console.log(foo) // 2 console.log(bar) // 'ho!' } const args = { foo: 2, bar: 'ho!' } doSomething(args) 

This method allows, describing parameters in the form of an object's properties and passing an object to a function, to gain access to parameters by their names without using additional constructions. You can read more about this technique here .

Values ​​returned from functions


All functions return some value. If the return command is not explicitly specified, the function will return undefined .

 const doSomething = (foo = 1, bar = 'hey') => { // - } console.log(doSomething()) 

The execution of the function is completed either after all the code it contains is executed, or after the return keyword is found in the code. When this keyword is encountered in a function, its work is completed, and control is transferred to the place from which the function was called.

If after the return keyword you specify a certain value, then this value is returned to the place where the function is called as a result of the execution of this function.

 const doSomething = () => { return 'test' } const result = doSomething() // result === 'test' 

Only one value can be returned from the function. In order to be able to return multiple values, you can return them either as an object, using an object literal, or as an array, and when you call a function, use the structure of destructuring assignment. The names of the parameters are saved. In this case, if you need to work with an object or an array returned from a function, namely in the form of an object or an array, you can do without destructuring assignment.

 const doSomething = () => { return ['Roger', 6] } const [ name, age ] = doSomething() console.log(name, age) //Roger 6 

The const [ name, age ] = doSomething() can be read as follows: “declare the name and age constants and assign them the values ​​of the array elements that the function returns”.
Here's how the same thing looks like using an object.

 const doSomething = () => { return {name: 'Roger', age: 6} } const { name, age } = doSomething() console.log(name, age) //Roger 6 

Nested functions


Functions can be declared inside other functions.

 const doSomething = () => { const doSomethingElse = () => {} doSomethingElse() return 'test' } doSomething() 

The scope of a nested function is limited to a function external to it; it cannot be called from outside.

Object methods


When functions are used as properties of objects, such functions are called object methods.

 const car = { brand: 'Ford', model: 'Fiesta', start: function() {   console.log(`Started`) } } car.start() 

Keyword this


If we compare the pointer and the usual functions used as methods of objects, we can find their important distinction, which consists in the sense of the keyword this . Consider an example.

 const car = { brand: 'Ford', model: 'Fiesta', start: function() {   console.log(`Started ${this.brand} ${this.model}`) }, stop: () => {   console.log(`Stopped ${this.brand} ${this.model}`) } } car.start() //Started Ford Fiesta car.stop() //Stopped undefined undefined 

As you can see, calling the start() method leads to the expected result, but the stop() method obviously does not work correctly.

This is due to the fact that the this keyword behaves differently when used in the switch and normal functions. Namely, the keyword this in the arrow function contains a link to the context that includes the function. In this case, when it comes to the browser, this context is the window object.

Here is how the execution of such code in the browser console

 const test = { fn: function() {   console.log(this) }, arrFn: () => {   console.log(this) } } test.fn() test.arrFn() 


Features of this keyword in normal and switch functions

As you can see, the call to this in a normal function means the call to the object, and this in the arrow function points to the window .

All this means that the arrow functions are not suitable for the role of methods of objects and constructors (if you try to use the arrow function as a constructor, you will get a TypeError error).

Immediate Functional Expressions


Immediately invoked functional expression (Immediately Invoked Function Expression, IIFE) is a function that is automatically called immediately after its declaration.

 ;(function () { console.log('executed') })() 

Semicolon before IIFE is optional, but its use allows you to insure against errors associated with the automatic placement of semicolons.

In the above example, the word “ executed ” will go to the console, after which IIFE will exit. IIFE, just like other functions, can return the results of their work.

 const something = (function () { return 'IIFE' })() console.log(something) 

After executing this simple example, the line IIFE will IIFE in the console, which will be in the constant something after executing the immediately called functional expression. It may seem that there is no special benefit from such a construction. However, if some complex calculations are performed in IIFE, which need to be performed only once, after which the corresponding mechanisms are unnecessary - the utility of IIFE turns out to be obvious. Namely, with such an approach, after the execution of IIFE, only the result returned by the function will be available in the program. In addition, you can remember that functions can return other functions and objects. We are talking about closures, we will talk about them below.

Elevation of functions


Before executing the JavaScript code, it is reorganized. We already talked about the mechanism of raising (hoisting) variables declared using the var keyword. A similar mechanism works when working with functions. Namely, we are talking about the fact that the function declarations in the course of processing the code are moved to the upper part of their scope before its execution. As a result, for example, it turns out that you can call a function before its declaration.

 doSomething() //did something function doSomething() { console.log('did something') } 

If you move a function call so that it goes after its declaration, nothing will change.

If in a similar situation to use a functional expression, then a similar code will generate an error.

 doSomething() //TypeError var doSomething = function () { console.log('did something') } 

In this case, it turns out that although the declaration of the doSomething variable rises to the top of the scope, this does not apply to an assignment operation.
If instead of var in a similar situation to use the keywords let or const , such code will not work either, however, the system will TypeError another error message ( ReferenceError and not TypeError ), since when using let and const declarations of variables and constants are not raised.

Arrow functions


Now we will talk more about the switch functions with which we have already met. They can be considered one of the most significant innovations of the ES6 standard; they differ from ordinary functions not only in appearance, but also in behavioral features. Today they are used extremely widely. Perhaps, there is not a single modern project where they would not be used in the overwhelming majority of cases. We can say that their appearance has forever changed the appearance of the JS-code and the features of its work.

From a purely external point of view, the syntax of declaring switch functions is more compact than the syntax of ordinary functions. Here is the declaration of the usual function.

 const myFunction = function () { //... } 

Here is the declaration of the switch function, which, in general, if one does not take into account the features of the switch functions, is similar to the previous one.

 const myFunction = () => { //... } 

If the body of the arrow function contains only one command, the result of which is returned by this function, it can be written without braces and without the keyword return . For example, such a function returns the sum of the arguments passed to it.

 const myFunction = (a,b) => a + b console.log(myFunction(1,2)) //3 

As you can see, the parameters of the switch functions, as in the case of ordinary functions, are described in brackets. At the same time, if such a function takes only one parameter, it can be specified without brackets. For example, here is a function that returns the result of dividing the number passed to it by 2.

 const myFunction = a => a / 2 console.log(myFunction(8)) //4 

As a result, it turns out that arrow functions are very convenient to use in situations in which small functions are needed.

▍ Implicit return of function results


We have already dealt with this feature of the switch functions, but it is so important that it should be discussed in more detail. The point is that single-line arrow functions support implicit return of the results of their work. We have already seen an example of returning a primitive value from a single-line arrow function. What if such a function should return an object? In this case, the curly brackets of the object literal can confuse the system, so parentheses are used in the body of the function.

 const myFunction = () => ({value: 'test'}) const obj = myFunction() console.log(obj.value) //test 

▍The keyword this and arrow functions


Above, when we looked at the features of the this , we compared ordinary and arrow functions. This section is intended to draw your attention to the importance of their differences. The keyword this , in itself, can cause certain difficulties, since it depends on the context of the code execution and on whether strict mode is enabled or not.

As we have seen, when using the this in the method of an object represented by a normal function, this points to the object that owns the method. In this case, they talk about binding the keyword this to a value that represents the context of the execution of the function. In particular, if the function is called as an object method, then the this keyword is bound to this object.

In the case of pointer functions, it turns out that the this binding is not performed in them, they use the this from the scopes containing them. As a result, they are not recommended for use as object methods.

The same problem arises when using functions as event handlers for DOM elements. For example, the HTML button element is used to describe the buttons. The click event is triggered when the mouse is clicked. In order to respond to this event in the code, you must first get a link to the corresponding element, and then assign it a click event handler as a function. As such a handler, you can use both a regular function and an arrow function. But, if in the event handler you need to refer to the element for which it is called (that is, to this ), the pointer function will not work here, since the value of this available in it points to the window object. To test this in practice, create an HTML page, the code for which is shown below, and click on the buttons.

 <!DOCTYPE html> <html> <body>   <button id="fn">Function</button>   <button id="arrowFn">Arrow function</button>   <script>     const f = document.getElementById("fn")     f.addEventListener('click', function () {         alert(this === f)     })     const af = document.getElementById("arrowFn")     af.addEventListener('click', () => {         alert(this === window)     })   </script> </body> </html> 

In this case, clicking on these buttons will display windows containing true . However, in the click event handler of the button with the fn identifier, this checked for the button itself, and in the button with the arrowFn identifier, the equality of this and the window object is checked.

As a result, if in the event handler of the HTML element you need to refer to this , the switch function for processing such a handler will not work.

Closures


Closures are an important concept in JavaScript. In fact, if you wrote JS functions, then you also used closures. Closures are used in some design patterns, in case you need to organize strict control of access to certain data or functions.

When a function is invoked, it has access to everything that is in its external scope. But to what is declared inside the function, there is no access from the outside. That is, if a variable (or another function) was declared in the function, they are inaccessible to the external code neither during the execution of the function, nor after its completion. However, if another function is returned from the function, this new function will have access to all that was declared in the original function. In this case, all this will be hidden from the external code in the closure.

Consider an example. Here is the function that takes the name of the dog, after which it displays it in the console.

 const bark = dog => { const say = `${dog} barked!` ;(() => console.log(say))() } bark(`Roger`) // Roger barked! 

The value returned by this function does not interest us yet, the text is output to the console using IIFE, which in this case does not play a special role, however, it will help us see the connection between this function and its variant, in which, instead of calling a function that outputs text to the console, we will return this function from the rewritten bark() function.

 const prepareBark = dog => { const say = `${dog} barked!` return () => console.log(say) } const bark = prepareBark(`Roger`) bark() // Roger barked! 

The result of the code in two cases is the same. But in the second case, what was passed to the original function when it was called (the name of the dog, Roger ) is stored in the closure, after which it is used by another function returned from the original one.

We will conduct another experiment - we will create, using the original function, two new ones for different dogs.

 const prepareBark = dog => { const say = `${dog} barked!` return () => {   console.log(say) } } const rogerBark = prepareBark(`Roger`) const sydBark = prepareBark(`Syd`) rogerBark() sydBark() 

This code will output the following.

 Roger barked! Syd barked! 

It turns out that the value of the say constant is bound to a function that is returned from the prepareBark() function.

Note that the say , when you call the prepareBark() again, gets a new value, and the value recorded in the say when you first call prepareBark() does not change. The point is that each time this function is called, a new closure is created.

Results


Today we talked about normal and switch functions, about the features of their declaration and use, about how the keyword this behaves in different situations, and about closures. Next time we will discuss arrays and loops.

Dear readers! How do you feel about the arrow functions in JavaScript?

Source: https://habr.com/ru/post/430382/


All Articles