Функции. Выразительный JavaScript: Функции

Functions are one of the fundamental building blocks in JavaScript. A function is a JavaScript procedure-a set of statements that performs a task or calculates a value. To use a function, you must define it somewhere in the scope from which you wish to call it.

A method is a function that is a property of an object. Read more about objects and methods in Working with objects .

Calling functions

Defining a function does not execute it. Defining the function simply names the function and specifies what to do when the function is called. Calling the function actually performs the specified actions with the indicated parameters. For example, if you define the function square , you could call it as follows:

Square(5);

The preceding statement calls the function with an argument of 5. The function executes its statements and returns the value 25.

Functions must be in scope when they are called, but the function declaration can be hoisted (appear below the call in the code), as in this example:

Console.log(square(5)); /* ... */ function square(n) { return n * n; }

The scope of a function is the function in which it is declared, or the entire program if it is declared at the top level.

Note: This works only when defining the function using the above syntax (i.e. function funcName(){}). The code below will not work. That means, function hoisting only works with function declaration and not with function expression.

Console.log(square); // square is hoisted with an initial value undefined. console.log(square(5)); // TypeError: square is not a function var square = function(n) { return n * n; }

The arguments of a function are not limited to strings and numbers. You can pass whole objects to a function. The show_props() function (defined in ) is an example of a function that takes an object as an argument.

A function can call itself. For example, here is a function that computes factorials recursively:

Function factorial(n) { if ((n === 0) || (n === 1)) return 1; else return (n * factorial(n - 1)); }

You could then compute the factorials of one through five as follows:

Var a, b, c, d, e; a = factorial(1); // a gets the value 1 b = factorial(2); // b gets the value 2 c = factorial(3); // c gets the value 6 d = factorial(4); // d gets the value 24 e = factorial(5); // e gets the value 120

There are other ways to call functions. There are often cases where a function needs to be called dynamically, or the number of arguments to a function vary, or in which the context of the function call needs to be set to a specific object determined at runtime. It turns out that functions are, themselves, objects, and these objects in turn have methods (see the Function object). One of these, the apply() method, can be used to achieve this goal.

Function scope

Variables defined inside a function cannot be accessed from anywhere outside the function, because the variable is defined only in the scope of the function. However, a function can access all variables and functions defined inside the scope in which it is defined. In other words, a function defined in the global scope can access all variables defined in the global scope. A function defined inside another function can also access all variables defined in its parent function and any other variable to which the parent function has access.

// The following variables are defined in the global scope var num1 = 20, num2 = 3, name = "Chamahk"; // This function is defined in the global scope function multiply() { return num1 * num2; } multiply(); // Returns 60 // A nested function example function getScore() { var num1 = 2, num2 = 3; function add() { return name + " scored " + (num1 + num2); } return add(); } getScore(); // Returns "Chamahk scored 5"

Scope and the function stack Recursion

A function can refer to and call itself. There are three ways for a function to refer to itself:

  • the function"s name
  • an in-scope variable that refers to the function
  • For example, consider the following function definition:

    Var foo = function bar() { // statements go here };

    Within the function body, the following are all equivalent:

  • bar()
  • arguments.callee()
  • foo()
  • A function that calls itself is called a recursive function . In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case). For example, the following loop:

    Var x = 0; while (x < 10) { // "x < 10" is the loop condition // do stuff x++; }

    can be converted into a recursive function and a call to that function:

    Function loop(x) { if (x >= 10) // "x >= 10" is the exit condition (equivalent to "!(x < 10)") return; // do stuff loop(x + 1); // the recursive call } loop(0);

    However, some algorithms cannot be simple iterative loops. For example, getting all the nodes of a tree structure (e.g. the DOM) is more easily done using recursion:

    Function walkTree(node) { if (node == null) // return; // do something with node for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }

    Compared to the function loop , each recursive call itself makes many recursive calls here.

    It is possible to convert any recursive algorithm to a non-recursive one, but often the logic is much more complex and doing so requires the use of a stack. In fact, recursion itself uses a stack: the function stack.

    The stack-like behavior can be seen in the following example:

    Function foo(i) { if (i < 0) return; console.log("begin: " + i); foo(i - 1); console.log("end: " + i); } foo(3); // Output: // begin: 3 // begin: 2 // begin: 1 // begin: 0 // end: 0 // end: 1 // end: 2 // end: 3

    Nested functions and closures

    You can nest a function within a function. The nested (inner) function is private to its containing (outer) function. It also forms a closure . A closure is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

    Since a nested function is a closure, this means that a nested function can "inherit" the arguments and variables of its containing function. In other words, the inner function contains the scope of the outer function.

    • The inner function can be accessed only from statements in the outer function.
    • The inner function forms a closure: the inner function can use the arguments and variables of the outer function, while the outer function cannot use the arguments and variables of the inner function.

    The following example shows nested functions:

    Function addSquares(a, b) { function square(x) { return x * x; } return square(a) + square(b); } a = addSquares(2, 3); // returns 13 b = addSquares(3, 4); // returns 25 c = addSquares(4, 5); // returns 41

    Since the inner function forms a closure, you can call the outer function and specify arguments for both the outer and inner function:

    Function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give // it result = fn_inside(5); // returns 8 result1 = outside(3)(5); // returns 8

    Preservation of variables

    Notice how x is preserved when inside is returned. A closure must preserve the arguments and variables in all scopes it references. Since each call provides potentially different arguments, a new closure is created for each call to outside. The memory can be freed only when the returned inside is no longer accessible.

    This is not different from storing references in other objects, but is often less obvious because one does not set the references directly and cannot inspect them.

    Multiply-nested functions

    Functions can be multiply-nested, i.e. a function (A) containing a function (B) containing a function (C). Both functions B and C form closures here, so B can access A and C can access B. In addition, since C can access B which can access A, C can also access A. Thus, the closures can contain multiple scopes; they recursively contain the scope of the functions containing it. This is called scope chaining . (Why it is called "chaining" will be explained later.)

    Consider the following example:

    Function A(x) { function B(y) { function C(z) { console.log(x + y + z); } C(3); } B(2); } A(1); // logs 6 (1 + 2 + 3)

    In this example, C accesses B "s y and A "s x . This can be done because:

  • B forms a closure including A , i.e. B can access A "s arguments and variables.
  • C forms a closure including B .
  • Because B "s closure includes A , C "s closure includes A , C can access both B and A "s arguments and variables. In other words, C chains the scopes of B and A in that order.
  • The reverse, however, is not true. A cannot access C , because A cannot access any argument or variable of B , which C is a variable of. Thus, C remains private to only B .

    Name conflicts

    When two arguments or variables in the scopes of a closure have the same name, there is a name conflict . More inner scopes take precedence, so the inner-most scope takes the highest precedence, while the outer-most scope takes the lowest. This is the scope chain. The first on the chain is the inner-most scope, and the last is the outer-most scope. Consider the following:

    Function outside() { var x = 5; function inside(x) { return x * 2; } return inside; } outside()(10); // returns 20 instead of 10

    The name conflict happens at the statement return x and is between inside "s parameter x and outside "s variable x . The scope chain here is { inside , outside , global object}. Therefore inside "s x takes precedences over outside "s x , and 20 (inside "s x) is returned instead of 10 (outside "s x).

    Closures

    Closures are one of the most powerful features of JavaScript. JavaScript allows for the nesting of functions and grants the inner function full access to all the variables and functions defined inside the outer function (and all other variables and functions that the outer function has access to). However, the outer function does not have access to the variables and functions defined inside the inner function. This provides a sort of encapsulation for the variables of the inner function. Also, since the inner function has access to the scope of the outer function, the variables and functions defined in the outer function will live longer than the duration of the outer function execution, if the inner function manages to survive beyond the life of the outer function. A closure is created when the inner function is somehow made available to any scope outside the outer function.

    Var pet = function(name) { // The outer function defines a variable called "name" var getName = function() { return name; // The inner function has access to the "name" variable of the outer //function } return getName; // Return the inner function, thereby exposing it to outer scopes } myPet = pet("Vivie"); myPet(); // Returns "Vivie"

    It can be much more complex than the code above. An object containing methods for manipulating the inner variables of the outer function can be returned.

    Var createPet = function(name) { var sex; return { setName: function(newName) { name = newName; }, getName: function() { return name; }, getSex: function() { return sex; }, setSex: function(newSex) { if(typeof newSex === "string" && (newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")) { sex = newSex; } } } } var pet = createPet("Vivie"); pet.getName(); // Vivie pet.setName("Oliver"); pet.setSex("male"); pet.getSex(); // male pet.getName(); // Oliver

    In the code above, the name variable of the outer function is accessible to the inner functions, and there is no other way to access the inner variables except through the inner functions. The inner variables of the inner functions act as safe stores for the outer arguments and variables. They hold "persistent" and "encapsulated" data for the inner functions to work with. The functions do not even have to be assigned to a variable, or have a name.

    Var getCode = (function() { var apiCode = "0]Eal(eh&2"; // A code we do not want outsiders to be able to modify... return function() { return apiCode; }; })(); getCode(); // Returns the apiCode

    There are, however, a number of pitfalls to watch out for when using closures. If an enclosed function defines a variable with the same name as the name of a variable in the outer scope, there is no way to refer to the variable in the outer scope again.

    Var createPet = function(name) { // The outer function defines a variable called "name". return { setName: function(name) { // The enclosed function also defines a variable called "name". name = name; // How do we access the "name" defined by the outer function? } } }

    Using the arguments object

    The arguments of a function are maintained in an array-like object. Within a function, you can address the arguments passed to it as follows:

    Arguments[i]

    where i is the ordinal number of the argument, starting at zero. So, the first argument passed to a function would be arguments . The total number of arguments is indicated by arguments.length .

    Using the arguments object, you can call a function with more arguments than it is formally declared to accept. This is often useful if you don"t know in advance how many arguments will be passed to the function. You can use arguments.length to determine the number of arguments actually passed to the function, and then access each argument using the arguments object.

    For example, consider a function that concatenates several strings. The only formal argument for the function is a string that specifies the characters that separate the items to concatenate. The function is defined as follows:

    Function myConcat(separator) { var result = ""; // initialize list var i; // iterate through arguments for (i = 1; i < arguments.length; i++) { result += arguments[i] + separator; } return result; }

    You can pass any number of arguments to this function, and it concatenates each argument into a string "list":

    // returns "red, orange, blue, " myConcat(", ", "red", "orange", "blue"); // returns "elephant; giraffe; lion; cheetah; " myConcat("; ", "elephant", "giraffe", "lion", "cheetah"); // returns "sage. basil. oregano. pepper. parsley. " myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

    Note: The arguments variable is "array-like", but not an array. It is array-like in that it has a numbered index and a length property. However, it does not possess all of the array-manipulation methods.

    Two factors influenced the introduction of arrow functions: shorter functions and non-binding of this .

    Shorter functions

    In some functional patterns, shorter functions are welcome. Compare:

    Var a = [ "Hydrogen", "Helium", "Lithium", "Beryllium" ]; var a2 = a.map(function(s) { return s.length; }); console.log(a2); // logs var a3 = a.map(s => s.length); console.log(a3); // logs

    No separate this

    Until arrow functions, every new function defined its own value (a new object in the case of a constructor, undefined in function calls, the base object if the function is called as an "object method", etc.). This proved to be less than ideal with an object-oriented style of programming.

    Function Person() { // The Person() constructor defines `this` as itself. this.age = 0; setInterval(function growUp() { // In nonstrict mode, the growUp() function defines `this` // as the global object, which is different from the `this` // defined by the Person() constructor. this.age++; }, 1000); } var p = new Person();

    In ECMAScript 3/5, this issue was fixed by assigning the value in this to a variable that could be closed over.

    Function Person() { var self = this; // Some choose `that` instead of `self`. // Choose one and be consistent. self.age = 0; setInterval(function growUp() { // The callback refers to the `self` variable of which // the value is the expected object. self.age++; }, 1000); }

    Another essential concept in coding is functions , which allow you to store a piece of code that does a single task inside a defined block, and then call that code whenever you need it using a single short command - rather than having to type out the same code multiple times. In this article we"ll explore fundamental concepts behind functions such as basic syntax, how to invoke and define them, scope, and parameters.

    Prerequisites: Objective:
    Basic computer literacy, a basic understanding of HTML and CSS, JavaScript first steps .
    To understand the fundamental concepts behind JavaScript functions.
    Where do I find functions?

    In JavaScript, you"ll find functions everywhere. In fact, we"ve been using functions all the way through the course so far; we"ve just not been talking about them very much. Now is the time, however, for us to start talking about functions explicitly, and really exploring their syntax.

    Pretty much anytime you make use of a JavaScript structure that features a pair of parentheses - () - and you"re not using a common built-in language structure like a for loop , while or do...while loop , or if...else statement , you are making use of a function.

    Built-in browser functions

    We"ve made use of functions built in to the browser a lot in this course. Every time we manipulated a text string, for example:

    Var myText = "I am a string"; var newString = myText.replace("string", "sausage"); console.log(newString); // the replace() string function takes a string, // replaces one substring with another, and returns // a new string with the replacement made

    Or every time we manipulated an array:

    Var myArray = ["I", "love", "chocolate", "frogs"]; var madeAString = myArray.join(" "); console.log(madeAString); // the join() function takes an array, joins // all the array items together into a single // string, and returns this new string

    Or every time we generated a random number:

    Var myNumber = Math.random(); // the random() function generates a random // number between 0 and 1, and returns that // number

    We were using a function!

    Note : Feel free to enter these lines into your browser"s JavaScript console to re-familiarize yourself with their functionality, if needed.

    The JavaScript language has many built-in functions to allow you to do useful things without having to write all that code yourself. In fact, some of the code you are calling when you invoke (a fancy word for run, or execute) a built in browser function couldn"t be written in JavaScript - many of these functions are calling parts of the background browser code, which is written largely in low-level system languages like C++, not web languages like JavaScript.

    Bear in mind that some built-in browser functions are not part of the core JavaScript language - some are defined as part of browser APIs, which build on top of the default language to provide even more functionality (refer to this early section of our course for more descriptions). We"ll look at using browser APIs in more detail in a later module.

    Functions versus methods

    One thing we need to clear up before we move on - technically speaking, built in browser functions are not functions - they are methods . This sounds a bit scary and confusing, but don"t worry - the words function and method are largely interchangeable, at least for our purposes, at this stage in your learning.

    The distinction is that methods are functions defined inside objects. Built-in browser functions (methods) and variables (which are called properties ) are stored inside structured objects, to make the code more efficient and easier to handle.

    You don"t need to learn about the inner workings of structured JavaScript objects yet - you can wait until our later module that will teach you all about the inner workings of objects, and how to create your own. For now, we just wanted to clear up any possible confusion of method versus function - you are likely to meet both terms as you look at the available related resources across the Web.

    Custom functions

    You"ve also seen a lot of custom functions in the course so far - functions defined in your code, not inside the browser. Anytime you saw a custom name with parentheses straight after it, you were using a custom function. In our random-canvas-circles.html example (see also the full ) from our loops article , we included a custom draw() function that looked like this:

    Function draw() { ctx.clearRect(0,0,WIDTH,HEIGHT); for (var i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); ctx.fill(); } }

    This function draws 100 random circles inside an element. Every time we want to do that, we can just invoke the function with this

    rather than having to write all that code out again every time we want to repeat it. And functions can contain whatever code you like - you can even call other functions from inside functions. The above function for example calls the random() function three times, which is defined by the following code:

    Function random(number) { return Math.floor(Math.random()*number); }

    We needed this function because the browser"s built-in Math.random() function only generates a random decimal number between 0 and 1. We wanted a random whole number between 0 and a specified number.

    Invoking functions

    You are probably clear on this by now, but just in case ... to actually use a function after it has been defined, you"ve got to run - or invoke - it. This is done by including the name of the function in the code somewhere, followed by parentheses.

    Function myFunction() { alert("hello"); } myFunction() // calls the function once

    Anonymous functions

    You may see functions defined and invoked in slightly different ways. So far we have just created a function like so:

    Function myFunction() { alert("hello"); }

    But you can also create a function that doesn"t have a name:

    Function() { alert("hello"); }

    This is called an anonymous function - it has no name! It also won"t do anything on its own. You generally use an anonymous function along with an event handler, for example the following would run the code inside the function whenever the associated button is clicked:

    Var myButton = document.querySelector("button"); myButton.onclick = function() { alert("hello"); }

    The above example would require there to be a element available on the page to select and click. You"ve already seen this structure a few times throughout the course, and you"ll learn more about and see it in use in the next article.

    You can also assign an anonymous function to be the value of a variable, for example:

    Var myGreeting = function() { alert("hello"); }

    This function could now be invoked using:

    MyGreeting();

    This effectively gives the function a name; you can also assign the function to be the value of multiple variables, for example:

    Var anotherGreeting = function() { alert("hello"); }

    This function could now be invoked using either of

    MyGreeting(); anotherGreeting();

    But this would just be confusing, so don"t do it! When creating functions, it is better to just stick to this form:

    Function myGreeting() { alert("hello"); }

    You will mainly use anonymous functions to just run a load of code in response to an event firing - like a button being clicked - using an event handler. Again, this looks something like this:

    MyButton.onclick = function() { alert("hello"); // I can put as much code // inside here as I want }

    Function parameters

    Some functions require parameters to be specified when you are invoking them - these are values that need to be included inside the function parentheses, which it needs to do its job properly.

    Note : Parameters are sometimes called arguments, properties, or even attributes.

    As an example, the browser"s built-in Math.random() function doesn"t require any parameters. When called, it always returns a random number between 0 and 1:

    Var myNumber = Math.random();

    The browser"s built-in string replace() function however needs two parameters - the substring to find in the main string, and the substring to replace that string with:

    Var myText = "I am a string"; var newString = myText.replace("string", "sausage");

    Note : When you need to specify multiple parameters, they are separated by commas.

    It should also be noted that sometimes parameters are optional - you don"t have to specify them. If you don"t, the function will generally adopt some kind of default behavior. As an example, the array join() function"s parameter is optional:

    Var myArray = ["I", "love", "chocolate", "frogs"]; var madeAString = myArray.join(" "); // returns "I love chocolate frogs" var madeAString = myArray.join(); // returns "I,love,chocolate,frogs"

    If no parameter is included to specify a joining/delimiting character, a comma is used by default.

    Function scope and conflicts

    Let"s talk a bit about scope - a very important concept when dealing with functions. When you create a function, the variables and other things defined inside the function are inside their own separate scope , meaning that they are locked away in their own separate compartments, unreachable from inside other functions or from code outside the functions.

    The top level outside all your functions is called the global scope . Values defined in the global scope are accessible from everywhere in the code.

    JavaScript is set up like this for various reasons - but mainly because of security and organization. Sometimes you don"t want variables to be accessible from everywhere in the code - external scripts that you call in from elsewhere could start to mess with your code and cause problems because they happen to be using the same variable names as other parts of the code, causing conflicts. This might be done maliciously, or just by accident.

    For example, say you have an HTML file that is calling in two external JavaScript files, and both of them have a variable and a function defined that use the same name:

    greeting(); // first.js var name = "Chris"; function greeting() { alert("Hello " + name + ": welcome to our company."); } // second.js var name = "Zaptec"; function greeting() { alert("Our company is called " + name + "."); }

    Both functions you want to call are called greeting() , but you can only ever access the second.js file"s greeting() function - it is applied to the HTML later on in the source code, so its variable and function overwrite the ones in first.js .

    Keeping parts of your code locked away in functions avoids such problems, and is considered best practice.

    It is a bit like a zoo. The lions, zebras, tigers, and penguins are kept in their own enclosures, and only have access to the things inside their enclosures - in the same manner as the function scopes. If they were able to get into other enclosures, problems would occur. At best, different animals would feel really uncomfortable inside unfamiliar habitats - a lion or tiger would feel terrible inside the penguins" watery, icy domain. At worst, the lions and tigers might try to eat the penguins!

    The zoo keeper is like the global scope - he or she has the keys to access every enclosure, to restock food, tend to sick animals, etc.

    Active learning: Playing with scope

    Let"s look at a real example to demonstrate scoping.

  • First, make a local copy of our function-scope.html example. This contains two functions called a() and b() , and three variables - x , y , and z - two of which are defined inside the functions, and one in the global scope. It also contains a third function called output() , which takes a single parameter and outputs it in a paragraph on the page.
  • Open the example up in a browser and in your text editor.
  • Open the JavaScript console in your browser developer tools. In the JavaScript console, enter the following command: output(x); You should see the value of variable x output to the screen.
  • Now try entering the following in your console output(y); output(z); Both of these should return an error along the lines of "ReferenceError: y is not defined ". Why is that? Because of function scope - y and z are locked inside the a() and b() functions, so output() can"t access them when called from the global scope.
  • However, what about when it"s called from inside another function? Try editing a() and b() so they look like this: function a() { var y = 2; output(y); } function b() { var z = 3; output(z); } Save the code and reload it in your browser, then try calling the a() and b() functions from the JavaScript console: a(); b(); You should see the y and z values output in the page. This works fine, as the output() function is being called inside the other functions - in the same scope as the variables it is printing are defined in, in each case. output() itself is available from anywhere, as it is defined in the global scope.
  • Now try updating your code like this: function a() { var y = 2; output(x); } function b() { var z = 3; output(x); } Save and reload again, and try this again in your JavaScript console:
  • a(); b(); Both the a() and b() call should output the value of x - 1. These work fine because even though the output() calls are not in the same scope as x is defined in, x is a global variable so is available inside all code, everywhere.
  • Finally, try updating your code like this: function a() { var y = 2; output(z); } function b() { var z = 3; output(y); } Save and reload again, and try this again in your JavaScript console:
  • a(); b(); This time the a() and b() calls will both return that annoying "
  • Последнее обновление: 09.04.2018

    Функции представляют собой набор инструкций, выполняющих определенное действие или вычисляющих определенное значение.

    Синтаксис определения функции:

    Function имя_функции([параметр [, ...]]){ // Инструкции }

    Определение функции начинается с ключевого слова function , после которого следует имя функции. Наименование функции подчиняется тем же правилам, что и наименование переменной: оно может содержать только цифры, буквы, символы подчеркивания и доллара ($) и должно начинаться с буквы, символа подчеркивания или доллара.

    После имени функции в скобках идет перечисление параметров. Даже если параметров у функции нет, то просто идут пустые скобки. Затем в фигурных скобках идет тело функции, содержащее набор инструкций.

    Определим простейшую функцию:

    Function display(){ document.write("функция в JavaScript"); }

    Данная функция называется display() . Она не принимает никаких параметров и все, что она делает, это пишет на веб-страницу строку.

    Однако простого определения функции еще недостаточно, чтобы она заработала. На надо еще ее вызвать:

    function display(){ document.write("функция в JavaScript"); } display();

    Необязательно давать функциям определенное имя. Можно использовать анонимные функции:

    Var display = function(){ // определение функции document.write("функция в JavaScript"); } display();

    Фактически мы определяем переменную display и присваиваем ей ссылку на функцию. А затем по имени переменной функция вызывается.

    Также мы можем динамически присваивать функции для переменной:

    Function goodMorning(){ document.write("Доброе утро"); } function goodEvening(){ document.write("Добрый вечер"); } var message = goodMorning; message(); // Доброе утро message = goodEvening; message(); // Добрый вечер

    Параметры функции

    Рассмотрим передачу параметров:

    Function display(x){ // определение функции var z = x * x; document.write(x + " в квадрате равно " + z); } display(5); // вызов функции

    Функция display принимает один параметр - x. Поэтому при вызове функции мы можем передать для него значение, например, число 5, как в данном случае.

    Если функция принимает несколько параметров, то с помощью spread-оператора... мы можем передать набор значений для этих параметров из массива:

    Function sum(a, b, c){ let d = a + b + c; console.log(d); } sum(1, 2, 3); let nums = ; sum(...nums);

    Во втором случае в функцию передается числа из массива nums. Но чтобы передавался не просто массив, как одно значение, а именно числа из этого массива, применяется spread-оператор (многоточие...).

    Необязательные параметры

    Функция может принимать множество параметров, но при этом часть или все параметры могут быть необязательными. Если для параметров не передается значение, то по умолчанию они имеют значение "undefined".

    Function display(x, y){ if(y === undefined) y = 5; if(x === undefined) x = 8; let z = x * y; console.log(z); } display(); // 40 display(6); // 30 display(6, 4) // 24

    Здесь функция display принимает два параметра. При вызове функции мы можем проверить их значения. При этом, вызывая функцию, необязательно передавать для этих параметров значения. Для проверки наличия значения параметров используется сравнение со значением undefined .

    Есть и другой способ определения значения для параметров по умолчанию:

    Function display(x = 5, y = 10){ let z = x * y; console.log(z); } display(); // 50 display(6); // 60 display(6, 4) // 24

    Если параметрам x и y не передаются значения, то они получаются в качестве значений числа 5 и 10 соответствено. Такой способ более лаконичен и интуитивен, чем сравнение с undefined.

    При этом значение параметра по умолчанию может быть производным, представлять выражение:

    Function display(x = 5, y = 10 + x){ let z = x * y; console.log(z); } display(); // 75 display(6); // 96 display(6, 4) // 24

    В данном случае значение параметра y зависит от значения x.

    При необходимости мы можем получить все переданные параметры через глобально доступный массив arguments :

    Function display(){ var z = 1; for(var i=0; i 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)

    Этот пример не обязательно находит самое короткое решение – он удовлетворяется любым. Не ожидаю, что вы сразу поймёте, как программа работает. Но давайте разбираться в этом отличном упражнении на рекурсивное мышление.

    Внутренняя функция find занимается рекурсией. Она принимает два аргумента – текущее число и строку, которая содержит запись того, как мы пришли к этому номеру. И возвращает либо строчку, показывающую нашу последовательность шагов, либо null.

    Для этого функция выполняет одно из трёх действий. Если заданное число равно цели, то текущая история как раз и является способом её достижения, поэтому она и возвращается. Если заданное число больше цели, продолжать умножения и сложения смысла нет, потому что так оно будет только увеличиваться. А если мы ещё не достигли цели, функция пробует оба возможных пути, начинающихся с заданного числа. Она дважды вызывает себя, один раз с каждым из способов. Если первый вызов возвращает не null, он возвращается. В другом случае возвращается второй.

    Чтобы лучше понять, как функция достигает нужного эффекта, давайте просмотрим её вызовы, которые происходят в поисках решения для числа 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!

    Отступ показывает глубину стека вызовов. В первый раз функция find вызывает сама себя дважды, чтобы проверить решения, начинающиеся с (1 + 5) и (1 * 3). Первый вызов ищет решение, начинающееся с (1 + 5), и при помощи рекурсии проверяет все решения, выдающие число, меньшее или равное требуемому. Не находит, и возвращает null. Тогда-то оператор || и переходит к вызову функции, который исследует вариант (1 * 3). Здесь нас ждёт удача, потому что в третьем рекурсивном вызове мы получаем 13. Этот вызов возвращает строку, и каждый из операторов || по пути передаёт эту строку выше, в результате возвращая решение.

    Выращиваем функции Существует два более-менее естественных способа ввода функций в программу.

    Первый – вы пишете схожий код несколько раз. Этого нужно избегать – больше кода означает больше места для ошибок и больше материала для чтения тех, кто пытается понять программу. Так что мы берём повторяющуюся функциональность, подбираем ей хорошее имя и помещаем её в функцию.

    Второй способ – вы обнаруживаете потребность в некоей новой функциональности, которая достойна помещения в отдельную функцию. Вы начинаете с названия функции, и затем пишете её тело. Можно даже начинать с написания кода, использующего функцию, до того, как сама функция будет определена.

    То, насколько сложно вам подобрать имя для функции, показывает, как хорошо вы представляете себе её функциональность. Возьмём пример. Нам нужно написать программу, выводящую два числа, количество коров и куриц на ферме, за которыми идут слова «коров» и «куриц». К числам нужно спереди добавить нули так, чтобы каждое занимало ровно три позиции.

    007 Коров 011 Куриц

    Очевидно, что нам понадобится функция с двумя аргументами. Начинаем кодить.
    // вывестиИнвентаризациюФермы 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);

    Если мы добавим к строке.length, мы получим её длину. Получается, что циклы while добавляют нули спереди к числам, пока не получат строчку в 3 символа.

    Готово! Но только мы собрались отправить фермеру код (вместе с изрядным чеком, разумеется), он звонит и говорит нам, что у него в хозяйстве появились свиньи, и не могли бы мы добавить в программу вывод количества свиней?

    Можно, конечно. Но когда мы начинаем копировать и вставлять код из этих четырёх строчек, мы понимаем, что надо остановиться и подумать. Должен быть способ лучше. Пытаемся улучшить программу:

    // выводСДобавлениемНулейИМеткой 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);

    Работает! Но название printZeroPaddedWithLabel немного странное. Оно объединяет три вещи – вывод, добавление нулей и метку – в одну функцию. Вместо того, чтобы вставлять в функцию весь повторяющийся фрагмент, давайте выделим одну концепцию:

    // добавитьНулей 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);

    Функция с хорошим, понятным именем zeroPad облегчает понимание кода. И её можно использовать во многих ситуациях, не только в нашем случае. К примеру, для вывода отформатированных таблиц с числами.

    Насколько умными и универсальными должны быть функции? Мы можем написать как простейшую функцию, которая дополняет число нулями до трёх позиций, так и навороченную функцию общего назначения для форматирования номеров, поддерживающую дроби, отрицательные числа, выравнивание по точкам, дополнение разными символами, и т.п.

    Хорошее правило – добавляйте только ту функциональность, которая вам точно пригодится. Иногда появляется искушение создавать фреймворки общего назначения для каждой небольшой потребности. Сопротивляйтесь ему. Вы никогда не закончите работу, а просто напишете кучу кода, который никто не будет использовать.

    Функции и побочные эффекты Функции можно грубо разделить на те, что вызываются из-за своих побочных эффектов, и те, что вызываются для получения некоторого значения. Конечно, возможно и объединение этих свойств в одной функции.

    Первая вспомогательная функция в примере с фермой, printZeroPaddedWithLabel, вызывается из-за побочного эффекта: она выводит строку. Вторая, zeroPad, из-за возвращаемого значения. И это не совпадение, что вторая функция пригождается чаще первой. Функции, возвращающие значения, легче комбинировать друг с другом, чем функции, создающие побочные эффекты.

    Чистая функция – особый вид функции, возвращающей значения, которая не только не имеет побочных эффектов, но и не зависит от побочных эффектов остального кода – к примеру, не работает с глобальными переменными, которые могут быть случайно изменены где-то ещё. Чистая функция, будучи вызванной с одними и теми же аргументами, возвращает один и тот же результат (и больше ничего не делает) – что довольно приятно. С ней просто работать. Вызов такой функции можно мысленно заменять результатом её работы, без изменения смысла кода. Когда вы хотите проверить такую функцию, вы можете просто вызвать её, и быть уверенным, что если она работает в данном контексте, она будет работать в любом. Не такие чистые функции могут возвращать разные результаты в зависимости от многих факторов, и иметь побочные эффекты, которые сложно проверять и учитывать.

    Однако, не надо стесняться писать не совсем чистые функции, или начинать священную чистку кода от таких функций. Побочные эффекты часто полезны. Нет способа написать чистую версию функции console.log, и эта функция весьма полезна. Некоторые операции легче выразить, используя побочные эффекты.

    Итог Эта глава показала вам, как писать собственные функции. Когда ключевое слово function используется в виде выражения, возвращает указатель на вызов функции. Когда оно используется как инструкция, вы можете объявлять переменную, назначая ей вызов функции.

    Ключевой момент в понимании функций – локальные области видимости. Параметры и переменные, объявленные внутри функции, локальны для неё, пересоздаются каждый раз при её вызове, и не видны снаружи. Функции, объявленные внутри другой функции, имеют доступ к её области видимости.

    Очень полезно разделять разные задачи, выполняемые программой, на функции. Вам не придётся повторяться, функции делают код более читаемым, разделяя его на смысловые части, так же, как главы и секции книги помогают в организации обычного текста.

    УпражненияМинимум В предыдущей главе была упомянута функция Math.min, возвращающая самый маленький из аргументов. Теперь мы можем написать такую функцию сами. Напишите функцию min, принимающую два аргумента, и возвращающую минимальный из них.

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

    Рекурсия Мы видели, что оператор % (остаток от деления) может использоваться для определения того, чётное ли число (% 2). А вот ещё один способ определения:

    Ноль чётный.
    Единица нечётная.
    У любого числа N чётность такая же, как у N-2.

    Напишите рекурсивную функцию isEven согласно этим правилам. Она должна принимать число и возвращать булевское значение.

    Потестируйте её на 50 и 75. Попробуйте задать ей -1. Почему она ведёт себя таким образом? Можно ли её как-то исправить?

    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)); // → ??

    Считаем бобы.

    Символ номер N строки можно получить, добавив к ней.charAt(N) (“строчка”.charAt(5)) – схожим образом с получением длины строки при помощи.length. Возвращаемое значение будет строковым, состоящим из одного символа (к примеру, “к”). У первого символа строки позиция 0, что означает, что у последнего символа позиция будет string.length – 1. Другими словами, у строки из двух символов длина 2, а позиции её символов будут 0 и 1.

    Напишите функцию countBs, которая принимает строку в качестве аргумента, и возвращает количество символов “B”, содержащихся в строке.

    Затем напишите функцию countChar, которая работает примерно как countBs, только принимает второй параметр - символ, который мы будем искать в строке (вместо того, чтобы просто считать количество символов “B”). Для этого переделайте функцию countBs.

    Похожие публикации