JavaScript functions. Expressive JavaScript: Javascript Functions Returning Multiple Values ​​from a Function

People think that computer science is an art for geniuses. In reality, it's the other way around - just a lot of people making things that stand on top of each other, as if making a wall of small pebbles.

Donald Knuth

You've already seen calls to functions like alert . Functions are the bread and butter of JavaScript programming. The idea of ​​wrapping a piece of program and calling it as a variable is very popular. It is a tool for structuring large programs, reducing repetition, naming subroutines, and isolating subroutines from each other.

The most obvious use of functions is to create a new dictionary. Inventing words for ordinary human prose is bad form. This is necessary in a programming language.

The average adult Russian speaker knows approximately 10,000 words. A rare programming language contains 10,000 built-in commands. And the vocabulary of a programming language is more clearly defined, so it is less flexible than a human one. Therefore, we usually have to add our own words to it to avoid unnecessary repetition.

Function Definition A function definition is a normal variable definition, where the value that the variable receives is a function. For example, the following code defines a variable square, which refers to a function that calculates the square of a given number:

Var square = function(x) ( return x * x; ); console.log(square(12)); // → 144

A function is created by an expression starting with the function keyword. Functions have a set of parameters (in this case, only x), and a body containing the instructions that must be executed when the function is called. The body of a function is always enclosed in curly braces, even if it consists of a single statement.

A function may have several parameters or none at all. In the following example, makeNoise does not have a list of parameters, but power has two:

Var makeNoise = function() ( console.log("Shit!"); ); makeNoise(); // → Khrya! var power = function(base, exponent) ( var result = 1; for (var count = 0; count< exponent; count++) result *= base; return result; }; console.log(power(2, 10)); // → 1024

Some functions return a value, like power and square, others don't, like makeNoise, which only produces a side effect. The return statement specifies the value returned by the function. When program processing reaches this instruction, it immediately exits the function and returns this value to the place in the code from which the function was called. return without an expression returns undefined .

Parameters and scope Function parameters are the same variables, but their initial values ​​are set when the function is called, and not in its code.

An important property of functions is that variables created within a function (including parameters) are local to that function. This means that in the power example, the result variable will be created every time the function is called, and these individual incarnations of it will have nothing to do with each other.

This variable locality only applies to parameters and variables created within functions. Variables defined outside of any function are called global because they are visible throughout the program. You can also access such variables inside a function, unless you declare a local variable with the same name.

The following code illustrates this. It defines and calls two functions that assign a value to the variable x. The first declares it as local, thereby changing only the local variable. The second one does not declare, so working with x inside the function refers to the global variable x defined at the beginning of the example.

Var x = "outside"; var f1 = function() ( var x = "inside f1"; ); f1(); console.log(x); // → outside var f2 = function() ( x = "inside f2"; ); f2(); console.log(x); // → inside f2

This behavior helps prevent accidental interactions between functions. If all variables were used anywhere in a program, it would be very difficult to ensure that one variable was not used for different purposes. And if you were to reuse a variable, you would encounter strange effects when third-party code corrupts the values ​​of your variable. By treating function-local variables so that they exist only within the function, the language makes it possible to work with functions as if they were separate little universes, allowing you to not worry about the entire code.

Nested scoping JavaScript distinguishes more than just between global and local variables. Functions can be defined within functions, resulting in multiple levels of locality.

For example, the following rather meaningless function contains two more inside:

Var landscape = function() ( var result = ""; var flat = function(size) ( for (var count = 0; count< size; count++) result += "_"; }; var mountain = function(size) { result += "/"; for (var count = 0; count < size; count++) result += """; result += "\\"; }; flat(3); mountain(4); flat(6); mountain(1); flat(1); return result; }; console.log(landscape()); // → ___/""""\______/"\_

The flat and mountain functions see the result variable because they are inside the function that defines it. But they can't see each other's count variables because the variables of one function are outside the scope of the other. And the environment outside the landscape function does not see any of the variables defined inside this function.

In short, within each local scope, you can see all the scopes that contain it. The set of variables available within a function is determined by the location where the function is declared in the program. All variables from the blocks surrounding the function definition are visible - including those defined at the top level in the main program. This approach to scopes is called lexical.

People who have studied other programming languages ​​may think that any block enclosed in curly braces creates its own local environment. But in JavaScript, only functions create scope. You can use freestanding blocks:

Var something = 1; ( var something = 2; // Do something with the something variable... ) // Exited the block...

But something inside the block is the same variable as outside. Although such blocks are allowed, it only makes sense to use them for if statements and loops.

If this seems strange to you, you are not the only one who thinks so. In version JavaScript 1.7 appeared keyword let, which works like var but creates variables that are local to any given block, not just the function.

Functions as Values ​​Function names are usually used as a name for a piece of program. Such a variable is set once and does not change. So it's easy to confuse a function and its name.

But these are two different things. A function call can be used like a simple variable - for example, used in any expression. It is possible to store a function call in a new variable, pass it as a parameter to another function, and so on. Also, the variable storing the function call remains a regular variable and its value can be changed:

Var launchMissiles = function(value) ( ​​missileSystem.launch("or!"); ); if (safeMode) launchMissiles = function(value) (/* cancel */);

In Chapter 5, we'll discuss the wonderful things you can do by passing function calls to other functions.

Declaring Functions There is a shorter version of the expression “var square = function...”. The function keyword can be used at the beginning of a statement:

Function square(x) ( return x * x; )

This is a function declaration. The statement defines the square variable and assigns the given function to it. So far so good. There is only one pitfall in such a definition.

Console.log("The future says:", future()); function future() ( return "We STILL have no flying cars."; )

This code works even though the function is declared below the code that uses it. This is because function declarations are not part of normal top-down program execution. They are "moved" to the top of their scope and can be called by any code in that scope. Sometimes this is convenient because you can write code in the order that makes the most sense without having to worry about having to define all the functions above where they are used.

What happens if we place a function declaration inside a conditional block or loop? You don't have to do that. Historically, different JavaScript running platforms have handled such cases differently, and the current language standard prohibits this. If you want your programs to run sequentially, use function declarations only within other functions or the main program.

Function example() ( function a() () // Normal if (something) ( function b() () // Ay-yay-yay! ) )

Call Stack It's helpful to take a closer look at how execution order works with functions. Here simple program with multiple function calls:

Function greet(who) ( console.log("Hello, " + who); ) greet("Semyon"); console.log("Pokeda");

It is processed something like this: calling greet causes the pass to jump to the beginning of the function. It calls the built-in console.log function, which intercepts control, does its thing, and returns control. Then he gets to the end of greet, and returns to the place where he was called from. The next line calls console.log again.

This can be shown schematically like this:

Top greet console.log greet top console.log top

Because the function must return to the place from which it was called, the computer must remember the context from which the function was called. In one case, console.log should return back to greet. In another, she returns to the end of the program.

The place where the computer remembers the context is called the stack. Each time a function is called, the current context is pushed to the top of the stack. When the function returns, it pops the top context from the stack and uses it to continue running.

Stack storage requires memory space. When the stack gets too big, the computer will stop executing and say something like “stack overflow” or “too much recursion.” The following code demonstrates this - it asks the computer a very complex question, which leads to endless jumps between two functions. More precisely, it would be infinite jumps if the computer had an infinite stack. In reality, the stack overflows.

Function chicken() ( return egg(); ) function egg() ( return chicken(); ) console.log(chicken() + " came first."); // → ??

Optional Arguments The following code is completely legal and runs without problems:

Alert("Hello", "Good evening", "Hello everyone!");

Officially, the function takes one argument. However, when challenged like this, she doesn't complain. She ignores the rest of the arguments and shows "Hello."

JavaScript is very particular about the number of arguments passed to a function. If you transfer too much, the extra ones will be ignored. Too few and those missing will be assigned the value undefined.

The downside to this approach is that it is possible - and even likely - to pass the wrong number of arguments to a function without anyone complaining about it.

The advantage is that you can create functions that take optional arguments. For example, in the next version of the power function, it can be called with either two or one argument - in the latter case, the exponent will be equal to two, and the function works like a square.

Function power(base, exponent) ( if (exponent == undefined) exponent = 2; var result = 1; for (var count = 0; count< exponent; count++) result *= base; return result; } console.log(power(4)); // → 16 console.log(power(4, 3)); // → 64

In the next chapter, we'll see how you can find out in the body of a function the exact number of arguments passed to it. This is useful because... allows you to create a function that takes any number of arguments. For example, console.log uses this property and prints all the arguments passed to it:

Console.log("R", 2, "D", 2); // → R 2 D 2

Closures The ability to use function calls as variables, coupled with the fact that local variables are created anew each time a function is called, leads us to an interesting question. What happens to local variables when a function stops working?

The following example illustrates this issue. It declares the wrapValue function, which creates a local variable. It then returns a function that reads this local variable and returns its value.

Function wrapValue(n) ( var localVariable = n; return function() ( return localVariable; ); ) var wrap1 = wrapValue(1); var wrap2 = wrapValue(2); console.log(wrap1()); // → 1 console.log(wrap2()); // → 2

This is valid and works as it should - access to the variable remains. Moreover, multiple instances of the same variable can exist at the same time, which further confirms the fact that local variables are recreated with each function call.

This ability to work with a reference to an instance of a local variable is called a closure. A function that closes local variables is called a closure. Not only does it free you from worrying about variable lifetimes, but it also allows you to use functions creatively.

With a slight modification, we turn our example into a function that multiplies numbers by any given number.

Function multiplier(factor) ( return function(number) ( return number * factor; ); ) var twice = multiplier(2); console.log(twice(5)); // → 10

A separate variable like localVariable from the wrapValue example is no longer needed. Since the parameter is itself a local variable.

It will take practice to start thinking this way. A good option mental model is to imagine that the function freezes the code in its body and wraps it in packaging. When you see return function(...) (...), think of it as a control panel for a piece of code frozen for later use.

In our example, multiplier returns a frozen piece of code, which we store in the twice variable. The last line calls the function contained in the variable, which causes the stored code to be activated (return number * factor;). It still has access to the factor variable that was defined when calling multiplier, and it also has access to the argument passed during defrost (5) as a numeric parameter.

Recursion A function may well call itself as long as it takes care not to overflow the stack. This function is called recursive. Here is an example of an alternative implementation of exponentiation:

Function power(base, exponent) ( if (exponent == 0) return 1; else return base * power(base, exponent - 1); ) console.log(power(2, 3)); // → 8

This is roughly how mathematicians define exponentiation, and perhaps this describes the concept more elegantly than a cycle. The function calls itself many times with different arguments to achieve multiple multiplication.

However, there is a problem with this implementation: normal environment JavaScript is 10 times slower than the version with a loop. Walking through a loop is cheaper than calling a function.

The speed versus elegance dilemma is quite interesting. There is a certain gap between convenience for humans and convenience for machines. Any program can be sped up by making it larger and more intricate. The programmer is required to find the appropriate balance.

In the case of the first exponentiation, the inelegant loop is quite simple and straightforward. It doesn't make sense to replace it with recursion. Often, however, programs deal with such complex concepts that one wants to reduce efficiency by increasing readability.

The basic rule, which has been repeated more than once, and with which I completely agree, is not to worry about performance until you are absolutely sure that the program is slowing down. If so, find the parts that last the longest and trade elegance for efficiency there.

Of course, we shouldn't completely ignore performance right away. In many cases, as with exponentiation, we do not get much simplicity from elegant solutions. Sometimes experienced programmer immediately sees that the simple approach will never be fast enough.

I point this out because too many novice programmers are obsessed with efficiency even in small things. The result is larger, more complex and often not without errors. Such programs take longer to write, but they often do not work much faster.

But recursion is not always just a less efficient alternative to loops. Some problems are easier to solve with recursion. Most often this is a traversal of several branches of a tree, each of which can branch.

Here's a riddle: you can get an infinite number of numbers by starting with the number 1, and then either adding 5 or multiplying by 3. How do we write a function that, given a number, tries to find the sequence of additions and multiplications that lead to a given number? For example, the number 13 can be obtained by first multiplying 1 by 3 and then adding 5 twice. And the number 15 cannot be obtained this way at all.

Recursive solution:

Function findSolution(target) ( function find(start, history) ( if (start == target) return history; else if (start > target) return null; else return find(start + 5, "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)"); ) return find(1, "1"); ) console.log(findSolution(24)); // → (((1 * 3) + 5) * 3)

This example does not necessarily find the shortest solution - it is satisfied by any. I don't expect you to immediately understand how the program works. But let's understand this great exercise in recursive thinking.

The inner function find does recursion. It takes two arguments - the current number and a string that contains a record of how we arrived at this number. And it returns either a string showing our sequence of steps, or null.

To do this, the function performs one of three actions. If the given number is equal to the goal, then the current story is precisely the way to achieve it, so it returns. If the given number is greater than the goal, there is no point in continuing to multiply and add, because it will only increase. And if we haven't reached the goal yet, the function tries both possible ways, starting with a given number. She summons herself twice, once with each method. If the first call does not return null, it is returned. In another case, the second one is returned.

To better understand how the function achieves its desired effect, let's look at the calls it makes to find a solution to the number 13.

Find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5 ) + 5)") too big find(33, "(((1 + 5) + 5) * 3)") too big find(18, "((1 + 5) * 3)") too big find( 3, "(1 * 3)") find(8, "((1 * 3) + 5)") find(13, "(((1 * 3) + 5) + 5)") found!

The indentation shows the depth of the call stack. The first time, the find function calls itself twice to check solutions starting with (1 + 5) and (1 * 3). The first call looks for a solution starting with (1 + 5) and uses recursion to check all solutions that produce a number less than or equal to the required number. Doesn't find it and returns null. Then the operator || and moves on to a function call that examines the (1 * 3) option. We're in luck here, because in the third recursive call we get 13. This call returns a string, and each of the || along the way it passes this line higher, resulting in returning a solution.

Growing Functions There are two more or less natural ways to introduce functions into a program.

The first is that you write similar code several times. This should be avoided - more code means more room for errors and more reading material for those trying to understand the program. So we take repeating functionality and match it good name and put it in a function.

The second way is that you discover a need for some new functionality that is worthy of being placed in a separate function. You start with the name of the function, and then write its body. You can even start by writing the code that uses the function before the function itself has been defined.

How difficult it is for you to name a function shows how well you understand its functionality. Let's take an example. We need to write a program that prints two numbers, the number of cows and chickens on the farm, followed by the words “cows” and “chickens.” You need to add zeros to the numbers in front so that each one occupies exactly three positions.

007 Cows 011 Chickens

Obviously, we need a function with two arguments. Let's start coding.
// print FarmInventory function printFarmInventory(cows, chickens) ( var cowString = String(cows); while (cowString.length< 3) cowString = "0" + cowString; console.log(cowString + " Коров"); var chickenString = String(chickens); while (chickenString.length < 3) chickenString = "0" + chickenString; console.log(chickenString + " Куриц"); } printFarmInventory(7, 11);

If we add .length to a string, we get its length. It turns out that while loops add leading zeros to numbers until you get a 3-character string.

Ready! But just as we were about to send the code to the farmer (along with a hefty check, of course), he calls and tells us that he has pigs on his farm, and could we add a display of the number of pigs to the program?

Of course it is possible. But when we start copying and pasting the code from these four lines, we realize that we need to stop and think. There must be a better way. We are trying to improve the program:

// output WITH Adding Zeros AND Labels function printZeroPaddedWithLabel(number, label) ( var numberString = String(number); while (numberString.length< 3) numberString = "0" + numberString; console.log(numberString + " " + label); } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Коров"); printZeroPaddedWithLabel(chickens, "Куриц"); printZeroPaddedWithLabel(pigs, "Свиней"); } printFarmInventory(7, 11, 3);

Works! But the name printZeroPaddedWithLabel is a bit strange. It combines three things—output, adding zeros, and a label—into one function. Instead of inserting an entire repeating fragment into a function, let's highlight one concept:

// add Zero function zeroPad(number, width) ( var string = String(number); while (string.length< width) string = "0" + string; return string; } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { console.log(zeroPad(cows, 3) + " Коров"); console.log(zeroPad(chickens, 3) + " Куриц"); console.log(zeroPad(pigs, 3) + " Свиней"); } printFarmInventory(7, 16, 3);

A function with a nice, clear name zeroPad makes the code easier to understand. And it can be used in many situations, not only in our case. For example, to display formatted tables with numbers.

How smart and versatile should the features be? We can write either a simple function that pads a number with zeros up to three positions, or a sophisticated function general purpose for formatting numbers, supporting fractions, negative numbers, dot alignment, padding with different characters, etc.

A good rule of thumb is to add only functionality that you know will be useful. Sometimes it's tempting to create general-purpose frameworks for every little need. Resist him. You'll never finish the job, you'll just end up writing a bunch of code that no one will use.

Functions and Side Effects Functions can be roughly divided into those that are called for their side effects and those that are called to obtain some value. Of course, it is also possible to combine these properties in one function.

The first helper function in the farm example, printZeroPaddedWithLabel, is called because it has a side effect: it prints a string. The second, zeroPad, because of the return value. And it’s not a coincidence that the second function comes in handy more often than the first. Functions that return values ​​are easier to combine with each other than functions that produce side effects.

A pure function is a special kind of value-returning function that not only has no side effects, but also does not depend on the side effects of the rest of the code - for example, it does not work with global variables that could be accidentally changed somewhere else. A pure function, when called with the same arguments, returns the same result (and does nothing else) - which is quite nice. She's easy to work with. A call to such a function can be mentally replaced by the result of its work, without changing the meaning of the code. When you want to test such a function, you can simply call it, and be sure that if it works in a given context, it will work in any context. Less pure functions may return different results depending on many factors, and have side effects that are difficult to test and account for.

However, you should not be embarrassed to write functions that are not entirely pure, or to begin a sacred code cleansing of such functions. Side effects are often beneficial. There is no way to write a clean version of the console.log function, and this function is quite useful. Some operations are easier to express using side effects.

Summary This chapter showed you how to write your own functions. When the function keyword is used as an expression, returns a pointer to the function call. When used as an instruction, you can declare a variable by assigning a function call to it.

The key to understanding functions is local scope. Parameters and variables declared inside a function are local to it, are recreated each time it is called, and are not visible from the outside. Functions declared inside another function have access to its scope.

It is very useful to separate the different tasks performed by a program into functions. You don't have to repeat yourself; functions make code more readable by dividing it into meaningful parts, just as chapters and sections of a book help organize regular text.

ExercisesMinimum In the previous chapter, we mentioned the Math.min function, which returns the smallest of its arguments. Now we can write such a function ourselves. Write a min function that takes two arguments and returns the minimum of them.

Console.log(min(0, 10)); // → 0 console.log(min(0, -10)); // → -10

Recursion We have seen that the % (modulo) operator can be used to determine whether a number (%2) is even. Here's another way to define it:

Zero is even.
The unit is odd.
Any number N has the same parity as N-2.

Write a recursive function isEven according to these rules. It must accept a number and return a boolean value.

Test it at 50 and 75. Try giving it -1. Why is she acting this way? Is it possible to somehow fix it?

Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this?

Console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → ??

Counting the beans.

The character number N of a string can be obtained by adding .charAt(N) (“string”.charAt(5)) to it - in a similar way to getting the length of a string using .length. The return value will be a string consisting of one character (for example, “k”). The first character of a string has position 0, which means that the last character will have position string.length - 1. In other words, a string of two characters has length 2, and its character positions will be 0 and 1.

Write a function countBs that takes a string as an argument and returns the number of “B” characters contained in the string.

Then write a function called countChar, which works something like countBs, but takes a second parameter - the character we'll be looking for in the string (instead of just counting the number of "B" characters). To do this, rework the countBs function.

Functions are one of the most important building blocks of code in JavaScript.

Functions consist of a set of commands and usually perform one specific task(for example, summing numbers, calculating roots, etc.).

Code placed in a function will only be executed after an explicit call to this function.

Function Declaration

1. Syntax:

//Declaration of the function functionFunctionname(ln1, ln2)( Function code) //Calling the functionFunctionname(ln1,lr2);

2. Syntax:

//Declaration of the function var function name=function(ln1, ln2)(Function code) //Calling the function function name(ln1,lr2);

functionname specifies the name of the function. Each function on the page must have a unique name. The function name must be specified in Latin letters and must not begin with numbers.

ln1 and ln2 are variables or values ​​that can be passed into the function. An unlimited number of variables can be passed to each function.

Please note: even if no variables are passed to the function, do not forget to insert parentheses "()" after the function name.

Please note that function names in JavaScript are case sensitive.

Example JavaScript function

The messageWrite() function in the example below will only be executed after the button is clicked.

Note that this example uses the onclick event. JavaScript Events will be discussed in detail later in this textbook.

// The function writes text to the page function messageWrite() ( document.write("This text was written to the page using JavaScript!"); )

Passing Variables to Functions

You can pass an unlimited number of variables to functions.

Please note: all manipulations with variables inside functions are actually performed not on the variables themselves, but on their copy, so the contents of the variables themselves do not change as a result of executing functions.

/* Let's define a function that adds 10 to the passed variable and displays the result on the page */ function plus(a)( a=a+10; document.write("Function output: " + a+"
"); ) var a=25; document.write("The value of the variable before the function call: "+a+"
"); // Call the function by passing it the variable a plus(a); document.write("Value of the variable after calling the function: "+a+"
");

Quick view

To access a global variable from a function rather than a copy of it, use window.variable_name.

Function plus(a)( window.a=a+10; ) var a=25; document.write("The value of the variable before the function call: "+a+"
"); plus(a); document.write("Value of the variable after calling the function: "+a+"
");

Quick view

return command

With the return command you can return values ​​from functions.

//The sum function returns the sum of the variables passed to it function sum(v1,v2)( return v1+v2; ) document.write("5+6=" + sum(5,6) + "
"); document.write("10+4=" + sum(10,4) + "
");

Quick view

Built-in functions

In addition to user-defined functions, JavaScript also has built-in functions.

For example, the built-in isFinite function allows you to check whether the passed value is a valid number.

Document.write(isFinite(40)+"
"); document.write(isFinite(-590)+"
"); document.write(isFinite(90.33)+"
"); document.write(isFinite(NaN)+"
"); document.write(isFinite("This is a string")+"
");

Quick view

Note: full list built-in JavaScript functions You can find it in our.

Local and global variables

Variables created inside functions are called local variables. You can access such variables only within the functions in which they were defined.

After the function code completes execution, such variables are destroyed. This means that variables with the same name can be defined in different functions.

Variables that are created outside of function code are called global variables; such variables can be accessed from anywhere in the code.

If you declare a variable without var inside a function, it also becomes global.

Global variables are destroyed only after the page is closed.

//Declare global variables var1 and var2 var var1="var1 exists"; var var2; function func1() ( //Assign var2 a value inside the function func1 var var2="var2 exists"; ) //From another function, output the contents of the variable var1 and var2 to the page function func2() ( //Output the contents of the variable var1 document.write( var1 + "
"); //Output the contents of the variable var2 document.write(var2); )

Quick view

Note that when printed to the screen, var2 will have an empty value because func1 operates on the local "version" of var2.

Using Anonymous Functions

Functions that do not contain a name when declared are called anonymous.

Anonymous functions are basically declared not to be subsequently called from code like regular functions, but to be passed to other functions as a parameter.

Function arrMap(arr,func)( var res=new Array; for (var i=0;i