Сайт о телевидении

Сайт о телевидении

» » Функциональное программирование. Функции в JavaScript

Функциональное программирование. Функции в 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 "
  • Функции

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

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

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

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

    Определение функций

    Определение функции начинается с ключевого слова function , за которым указываются следующие компоненты:

    Идентификатор, определяющий имя функции

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

    Пара круглых скобок вокруг списка из нуля или более идентификаторов, разделенных запятыми

    Эти идентификаторы будут определять имена параметров функции и в теле функции могут использоваться как локальные переменные.

    Пара фигурных скобок с нулем или более инструкций JavaScript внутри

    Эти инструкции составляют тело функции: они выполняются при каждом вызове функции.

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

    // Выводит имена и значения всех свойств объекта obj function printprops(obj) { for(var p in obj) console.log(p + ": " + obj[p] + "\n"); } // Вычисляет расстояние между точками (x1,y1) и (x2,y2) function distance(x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; return Math.sqrt(dx*dx + dy*dy); } // Рекурсивная функция (вызывающая сама себя), вычисляющая факториал function factorial(x) { if (x

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

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

    Обратите внимание, что большинство (но не все) функций в примере содержат инструкцию return. Инструкция return завершает выполнение функции и выполняет возврат значения своего выражения (если указано) вызывающей программе. Если выражение в инструкции return отсутствует, она возвращает значение undefined. Если инструкция return отсутствует в функции, интерпретатор просто выполнит все инструкции в теле функции и вернет вызывающей программе значение undefined.

    Большинство функций в примере вычисляют некоторое значение, и в них инструкция return используется для возврата этого значения вызывающей программе. Функция printprops() несколько отличается в этом смысле: ее работа заключается в том, чтобы вывести имена свойств объекта. Ей не нужно возвращать какое-либо значение, поэтому в функции отсутствует инструкция return. Функция printprops() всегда будет возвращать значение undefined. (Функции, не имеющие возвращаемого значения, иногда называются процедурами.)

    Вызов функций

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

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

    Printprops({x:4, age: 24}); var d = distance(1,1,5,6); var f = factorial(5) / factorial(12); f = square(5);

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

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

    Метод - это не что иное, как функция, которая хранится в виде свойства объекта. Если имеется функция func и объект obj, то можно определить метод объекта obj с именем method, как показано ниже:

    // Определим простой объект и функцию var obj = {}; function func(a, b) { return a+b;} // Добавим в объект obj метод obj.method = func; // Теперь можно вызвать этот метод var result = obj.method(4, 5);

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

    Result = obj.method(4, 5); result = obj["method"](4, 5);

    Аргументы и возвращаемое значение при вызове метода обрабатываются точно так же, как при вызове обычной функции. Однако вызов метода имеет одно важное отличие: контекст вызова. Выражение обращения к свойству состоит из двух частей: объекта (в данном случае obj) и имени свойства (method). В подобных выражениях вызова методов объект obj становится контекстом вызова, и тело функции получает возможность ссылаться на этот объект с помощью ключевого слова this. Например:

    Var obj = { x: 0, y: 0, // Метод add: function(a, b) { this.x = a; this.y = b; }, // Еще один метод sum: function() { return this.x + this.y } }; // Вызов методов obj.add(15, 4); console.log(obj.sum()); // 19

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

    Обратите внимание: this - это именно ключевое слово, а не имя переменной или свойства. Синтаксис JavaScript не допускает возможность присваивания значений элементу this.

    Аргументы и параметры функций

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

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

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

    // Добавить в массив arr перечислимые имена // свойств объекта obj и вернуть его. Если аргумент // arr не не был передан, создать и вернуть новый массив function getPropertyNames(obj, /* необязательный */ arr) { if (arr === undefined) arr = ; // Если массив не определен, создать новый for(var property in obj) arr.push(property); return arr; } // Эта функция может вызываться с 1 или 2 аргументами: var a = getPropertyNames({x:1, y:1}); // Получить свойства объекта в новом массиве getPropertyNames({z:5},a); // добавить свойства нового объекта в этот массив console.log(a); // ["x", "y", "z"]

    Обратите внимание, что при объявлении функций необязательные аргументы должны завершать список аргументов, чтобы их можно было опустить. Программист, который будет писать обращение к вашей функции, не сможет передать второй аргумент и при этом опустить первый: он будет вынужден явно передать в первом аргументе значение undefined. Обратите также внимание на комментарий /* необязательный */ в определении функции, который подчеркивает тот факт, что параметр является необязательным.

    Списки аргументов переменной длины

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

    Предположим, что была определена функция func, которая требует один аргумент x. Если вызвать эту функцию с двумя аргументами, то первый будет доступен внутри функции по имени параметра x или как arguments. Второй аргумент будет доступен только как arguments. Кроме того, подобно настоящим массивам, arguments имеет свойство length, определяющее количество содержащихся элементов. То есть в теле функции func, вызываемой с двумя аргументами, arguments.length имеет значение 2.

    Объект Arguments может использоваться с самыми разными целями. Следующий пример показывает, как с его помощью проверить, была ли функция вызвана с правильным числом аргументов, - ведь JavaScript этого за вас не сделает:

    Function func(x, y, z) { // Сначала проверяется, правильное ли количество аргументов передано if (arguments.length != 3) { throw new Error("Функция func вызвана с " + arguments.length + " аргументами, а требуется 3."); } // А теперь сам код функции... }

    Обратите внимание, что зачастую нет необходимости проверять количество аргументов, как в данном примере. Поведение по умолчанию интерпретатора JavaScript отлично подходит для большинства случаев: отсутствующие аргументы замещаются значением undefined, а лишние аргументы просто игнорируются.

    Объект Arguments иллюстрирует важную возможность JavaScript-функций: они могут быть написаны таким образом, чтобы работать с любым количеством аргументов. Следующая функция принимает любое число аргументов и возвращает значение самого большого из них (аналогично ведет себя встроенная функция Math.max()):

    Function maxNumber() { var m = Number.NEGATIVE_INFINITY; // Цикл по всем аргументам, поиск и // сохранение наибольшего из них for(var i = 0; i m) m = arguments[i]; // Вернуть наибольшее значение return m; } var largest = maxNumber(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); // 10000

    Функции, подобные этой и способные принимать произвольное число аргументов, называются функциями с переменным числом аргументов (variadic functions, variable arity functions или varargs functions) . Этот термин возник вместе с появлением языка программирования C.

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

    Не следует забывать, что arguments фактически не является массивом - это объект Arguments. В каждом объекте Arguments имеются пронумерованные элементы массива и свойство length, но с технической точки зрения это не массив. Лучше рассматривать его как объект, имеющий некоторые пронумерованные свойства.

    Помимо элементов своего массива объект Arguments определяет свойства callee и caller . При попытке изменить значения этих свойств в строгом режиме ECMAScript 5 гарантированно возбуждается исключение TypeError. Однако в нестрогом режиме стандарт ECMAScript утверждает, что свойство callee ссылается на выполняемую в данный момент функцию. Свойство caller не является стандартным, но оно присутствует во многих реализациях и ссылается на функцию, вызвавшую текущую.

    Свойство caller можно использовать для доступа к стеку вызовов, а свойство callee особенно удобно использовать для рекурсивного вызова неименованных функций:

    Var factorial = function(x) { if (x

    Свойства и методы функций

    Мы видели, что в JavaScript-программах функции могут использоваться как значения. Оператор typeof возвращает для функций строку «function», однако в действительности функции в языке JavaScript - это особого рода объекты. А раз функции являются объектами, то они имеют свойства и методы, как любые другие объекты. Существует даже конструктор Function(), который создает новые объекты функций. В следующих подразделах описываются свойства и методы функций.

    Свойство length

    В теле функции свойство arguments.length определяет количество аргументов, переданных функции. Однако свойство length самой функции имеет иной смысл. Это свойство, доступное только для чтения, возвращает количество аргументов, которое функция ожидает получить - число объявленных параметров.

    В следующем фрагменте определяется функция с именем check(), получающая массив аргументов arguments от другой функции. Она сравнивает свойство arguments.length (число фактически переданных аргументов) со свойством arguments.callee.length (число ожидаемых аргументов), чтобы определить, передано ли функции столько аргументов, сколько она ожидает. Если значения не совпадают, генерируется исключение. За функцией check() следует тестовая функция func(), демонстрирующая порядок использования функции check():

    // Эта функция использует arguments.callee, поэтому она // не будет работать в строгом режиме function check(args) { var actual = args.length; // Фактическое число аргументов var expected = args.callee.length; // Ожидаемое число аргументов if (actual !== expected) // Если не совпадают, генерируется исключение throw new Error("ожидается: " + expected + "; получено " + actual); } function func(x, y, z) { // Проверить число ожидаемых и фактически переданных аргументов check(arguments); // Теперь выполнить оставшуюся часть функции return x + y + z; }

    Свойство prototype

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

    Прототипы и свойство prototype обсуждались в предыдущей статье.

    Методы call() и apply()

    Методы call() и apply() позволяют выполнять косвенный вызов функции, как если бы она была методом некоторого другого объекта. Первым аргументом обоим методам, call() и apply(), передается объект, относительно которого вызывается функция; этот аргумент определяет контекст вызова и становится значением ключевого слова this в теле функции. Чтобы вызвать функцию func() (без аргументов) как метод объекта obj, можно использовать любым из методов, call() или apply():

    Func.call(obj); func.apply(obj);

    Любой из этих способов вызова эквивалентен следующему фрагменту (где предполагается, что объект obj не имеет свойства с именем m):

    Obj.m = func; // Временно сделать func методом obj obj.m(); // Вызывать его без аргументов. delete obj.m; // Удалить временный метод.

    В строгом режиме ECMAScript 5 первый аргумент методов call() и apply() становится значением this, даже если это простое значение, null или undefined. В ECMAScript 3 и в нестрогом режиме значения null и undefined замещаются глобальным объектом, а простое значение - соответствующим объектом-оберткой.

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

    В следующем примере демонстрируется практическое применение метода call():

    // Ниже определены две функции, отображающие свойства и // значения свойств произвольного объекта. Способ // отображения передаются в виде аргумента func function print1(func, obj) { for (n in obj) func(n +": " + obj[n]); } function print2(func, objDevice, obj) { for (n in obj) func.call(objDevice, n +": " + obj[n]); } var obj = {x:5, y:10}; print2(document.write, document, obj); // Работает корректно print2(console.log, console, obj); print1(document.write, obj); // Возникнет исключение Illegal invocation, т.к. print1(console.log, obj); // невозможно вызвать эти методы без объекта контекста

    Метод bind()

    Метод bind() впервые появился в ECMAScript 5, но его легко имитировать в ECMAScript 3. Как следует из его имени, основное назначение метода bind() состоит в том, чтобы связать (bind) функцию с объектом. Если вызвать метод bind() функции func и передать ему объект obj, он вернет новую функцию. Вызов новой функции (как обычной функции) выполнит вызов оригинальной функции func как метода объекта obj. Любые аргументы, переданные новой функции, будут переданы оригинальной функции. Например:

    // Функция, которую требуется привязать function func(y) { return this.x + y; } var obj = {x:1}; // Объект, к которому выполняется привязка var g = func.bind(obj); // Вызов g(x) вызовет obj.func(x)

    Такой способ связывания легко реализовать в ECMAScript 3, как показано ниже:

    // Возвращает функцию, которая вызывает func как метод объекта obj // и передает ей все свои аргументы function bind(func, obj) { if (func.bind) return func.bind(obj); // Использовать метод bind, если имеется else return function() { // Иначе связать, как показано ниже return func.apply(obj, arguments); }; }

    Метод bind() в ECMAScript 5 не просто связывает функцию с объектом. Он также выполняет частичное применение: помимо значения this связаны будут все аргументы, переданные методу bind() после первого его аргумента. Частичное применение - распространенный прием в функциональном программировании и иногда называется каррингом (currying) .

    Идеи динамичного формирования контента на web-ресуре стали нормой. Статические страницы и шаблонное сайтостроение окончательно завершили свою миссию.

    Однако современный web-ресурс не обязательно должен быть представлен набором страниц, формируемых сервером и обновляемых браузером (JS+AJAX).

    Web-ресурс в момент прихода посетителя может представлять собой пару заголовков для протокола, немного текста в «head», несколько строк кода в «body» и все. Остальное «додумается » в процессе работы посетителя - это идеальный сайт или стремящийся быть таковым.

    Место описания и сущность функций

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

    Понятие алгоритма вне функции здесь отсутствует в принципе. Разумеется, разработчик может в любом месте страницы вставить скрипт, поместить в него код и он будет исполнен. Но какой смысл в коде, который исполняется только раз: при загрузке (перегрузке) страницы? Разве что можно установить начальные значения каких-нибудь малозначимых переменных.

    Скрипт - это место описания нужных переменных и функций, нежели добрый кусок кода, написанный ради самого себя. Именно набор функций является существенным и значимым, возможно - их взаимная непосредственная связь, но чаще бывает все иначе. Место описания функции и место ее применения вовсе не одно и тоже.

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

    Функциональная динамика

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

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

    Изначально здесь нет никакой последовательности и нет никакой параллельности. Здесь есть адекватная реакция web-ресурса на события. Насколько быстро JavaScript отработает ту или иную функцию, зависит от многих технических (компьютер, линии связи) и семантических (логика алгоритма, предметная область, смысл задачи) факторов.

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

    Это новое мышление в разработке: распределенная обработка информации в недрах отдельно взятого браузера!

    Синтаксис переменных и функций

    JavaScript-переменные размещаются как в теге «script», так и в теле функции. Функции определяются так же. Особого смысла писать внутри функции еще одну функцию нет, но это может быть необходимо по различным и вполне обоснованным причинам.

    Описание функции в общем случае начинается с ключевого слова «function», за которым следует ее имя, список аргументов в круглых скобках через запятую и тело функции в фигурных скобках.

    В данном примере описаны две функции, обеспечивающие AJAX-обмен между страницей и сервером. Переменная scXHR описана выше, потому доступна как в InitXML, так и внутри WaitReplySC.

    Имя функции и парамет «функция»

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

    Здесь важно также отметить, что WaitReplySC - это функция. Но в строке scXHR.onreadystatechange = WaitReplySC она передается как параметр. Это общее правило передачи функций в другие функции в качестве параметров. Указал скобки и передал в них ее параметр (параметры) - функция исполнится немедленно. Передал только имя, ну и что с того. Вызов функции сделает тот, кто получил ее имя.

    Функциональность, реализуемая через AJAX, позволяет выполнить вызов функции JavaScript через данные, полученные от сервера. Фактически, посылая запрос на сервер, та или иная функция может вовсе и не «знать», к какой функции она обращается и с какой информацией.

    Выход из функции и ее результат

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

    Если требуется, чтобы функция возвращала результат, можно воспользоваться оператором возврата JavaScript: return. В теле функции может быть достаточное количество операторов возврата. Совершенно не обязательно, что все они будут возвращать результат одного и того же типа.

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

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

    Аргументы функций

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

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

    Изменение аргумента функции действительно внутри функции, но не вне ее. Для того чтобы что-то изменить вне функции, нужно воспользоваться оператором JavaScript return, через который передать необходимое значение наружу.

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

    У arguments есть свойство callee, которое предназначено для вызова функции, что выполняется в данный момент времени. Если вызвать саму себя, то вариант JavaScript функция в функции позволит реализовать рекурсию.

    Использование функций

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

    Одна функция может обслуживать несколько элементов страницы и несколько событий. Посредством параметра «this» можно передать функции информацию, откуда она была вызвана.

    Классическое использование JS-функций - обработчики событий на элементах. В данном примере в форме входа/выхода посетителя будет вызвана функция scfWecomeGo() или scfWelcomeCancel(), а при выборе режима работы scfMenuItemClick(this).

    В последнем случае передается параметр «this», который позволяет чудесным образом узнать, из какого именно дива произошел вызов. Вообще, JavaScript настолько качественно имплантирован в DOM и он так удобно позволяет перемещаться по его элементам, собирать нужную информацию, что динамика страницы может быть просто непредсказуемой.

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

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

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

    О распределенном мышлении

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

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

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

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

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

    Аргументы и результаты функций

    JavaScript позволяет привести код к «полнофункциональному» состоянию. Нормально, когда аргументом функции является функция. Допускается вариант, когда функция возвращает функцию. JavaScript относится к этому совершенно спокойно.

    Это хороший механизм, но достаточно сложный в отношении реализации. Технически все допустимо, семантически обеспечить логику передачи «функционала» под силу только квалифицированному разработчику.

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

    Забота разработчика понятна и проста. Есть задача, нужно решение, а не ошибка вроде «JavaScript error the operation is insecure», чистый экран или остановка всего движка браузера.

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

    Исполнение сформированного кода

    Реализовать исполнение кода, сформированного в процессе работы другого кода, можно посредством «eval». Это не считается отличным решением, но часто можно не усложнять код излишними функциями, а ограничиться банальным формированием строки JavaScript кода и попросту исполнить ее.

    В данном примере формируется строчка вставки в действующий див некоторой информации. Номер дива и содержание информации различны для различных позиций, потому такое решение в данной ситуации гарантированно не обеспечит ситуацию «javascript error the operation is insecure», но надежно даст нужный эффект.

    Нюанс парадигмы JavaScript «функция в функции»

    Если есть возможность обойтись без излишеств, лучше им воспользоваться. Все перечисленные варианты хороши. Безусловно, во многих случаях это единственное решение.

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

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

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

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

    Функции - ключевая концепция в JavaScript. Важнейшей особенностью языка является первоклассная поддержка функций ​ (functions as first-class citizen) . Любая функция это объект, и следовательно ею можно манипулировать как объектом, в частности:

    • передавать как аргумент и возвращать в качестве результата при вызове других функций (функций высшего порядка);
    • создавать анонимно и присваивать в качестве значений переменных или свойств объектов.

    Это определяет высокую выразительную мощность JavaScript и позволяет относить его к числу языков, реализующих функциональную парадигму программирования (что само по себе есть очень круто по многим соображениям).

    Функция в JavaScript специальный тип объектов, позволяющий формализовать средствами языка определённую логику поведения и обработки данных.

    Для понимания работы функций необходимо (и достаточно?) иметь представление о следующих моментах:

    Объявление функций Функции вида "function declaration statement"

    Объявление функции (function definition , или function declaration , или function statement ) состоит из ключевого слова function и следующих частей:

    • Имя функции.
    • Список параметров (принимаемых функцией) заключенных в круглые скобки () и разделенных запятыми.
    • Инструкции, которые будут выполненны после вызова функции, заключают в фигурные скобки { } .

    Например, следующий код объявляет простую функцию с именим square:

    Function square(number) { return number * number; }

    Функция square принимает один параметр, названный number. Состоит из одной инструкции, которая означает вернуть параметр этой функции (это number) умноженный на самого себя. Инструкция return указывает на значение, которые будет возвращено функцией.

    Return number * number;

    Примитивные параметры (например, число) передаются функции значением; значение передаётся в функцию, но если функция меняет значение параметра, это изменение не отразится глобально или после вызова функции.

    Если Вы передадите объект как параметр (не примитив, например, или определяемые пользователем объкты), и функция изменит свойство переданного в неё объекта, это изменение будет видно и вне функции, как показано в следующим примере:

    Function myFunc(theObject) { theObject.make = "Toyota"; } var mycar = {make: "Honda", model: "Accord", year: 1998}; var x, y; x = mycar.make; // x получает значение "Honda" myFunc(mycar); y = mycar.make; // y получает значение "Toyota" // (свойство было изменено функцией)

    Функции вида "function definition expression"

    Функция вида "function declaration statement" по синтаксису является инструкцией (statement ), ещё функция может быть вида "function definition expression". Такая функция может быть анонимной (она не имеет имени). Например, функция square может быть вызвана так:

    Var square = function(number) { return number * number; }; var x = square(4); // x получает значение 16

    Однако, имя может быть и присвоено для вызова самой себя внутри самой функции и для отладчика (debugger ) для идентифицирования функции в стек-треках (stack traces ; "trace" - "след" / "отпечаток").

    Var factorial = function fac(n) { return n < 2 ? 1: n * fac(n - 1); }; console.log(factorial(3));

    Функции вида "function definition expression" удобны, когда функция передается аргументом другой функции. Следующий пример показывает функцию map , которая должна получить функцию первым аргументом и массив вторым.

    Function map(f, a) { var result = , // Create a new Array i; for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; }

    В следующим коде наша функция принимает функцию, которая является function definition expression, и выполняет его для каждого элемента принятого массива вторым аргументом.

    Function map(f, a) { var result = ; // Create a new Array var i; // Declare variable for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; } var f = function(x) { return x * x * x; } var numbers = ; var cube = map(f,numbers); console.log(cube);

    Функция возвращает: .

    В JavaScript функция может быть объявлена с условием. Например, следующая функция будет присвоена переменной myFunc только, если num равно 0:

    Var myFunc; if (num === 0) { myFunc = function(theObject) { theObject.make = "Toyota"; } }

    В дополнение к объявлениям функций, описанных здесь, Вы также можете использовать конструктор Function для создания функций из строки во время выполнения (runtime ), подобно .

    Метод - это функция, которая является свойством объекта. Узнать больше про объекты и методы можно по ссылке: Работа с объектами .

    Вызовы функций

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

    Square(5);

    Эта инструкция вызывает функцию с аргументом 5. Функция вызывает свои инструкции и возвращает значение 25.

    Функции могут быть в области видимости, когда они уже определены, но функции вида "function declaration statment" могут быть подняты (поднятие - hoisting ), также как в этом примере:

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

    Область видимости функции - функция, в котором она определена, или целая программа, если она объявлена по уровню выше.

    Примечание: Это работает только тогда, когда объявлении функции использует вышеупомянутый синтаксис (т.е. function funcName(){}). Код ниже не будет работать. Имеется в виду то, что поднятие функции работает только с function declaration и не работает с function expression.

    Console.log(square); // square поднят со значением undefined. console.log(square(5)); // TypeError: square is not a function var square = function(n) { return n * n; }

    Аргументы функции не ограничиваются строками и числами. Вы можете передавать целые объекты в функцию. Функция show_props() (объявленная в Работа с объектами) является примером функции, принимающей объекты аргументом.

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

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

    Затем вы можете вычислить факториалы от одного до пяти следующим образом:

    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

    Есть другие способы вызвать функцию. Существуют частые случаи, когда функции необходимо вызывать динамически, или поменять номера аргументов функции, или необходимо вызвать функцию с привязкой к определенному контексту. Оказывается, что функции сами по себе являются объектами, и эти объекты в свою очередь имеют методы (посмотрите объект ). Один из них это метод , использование которого может достигнуть этой цели.

    Область видимости функций

    (function scope)

    Переменные объявленные в функции не могут быть доступными где-нибудь вне этой функции, поэтому переменные (которые нужны именно для функции) объявляют только в scope функции. При этом функция имеет доступ ко всем переменным и функциям, объявленным внутри её scope. Другими словами функция объявленная в глобальном scope имеет доступ ко всем переменным в глобальном scope. Функция объявленная внутри другой функции ещё имеет доступ и ко всем переменным её родителькой функции и другим переменным, к которым эта родительская функция имеет доступ.

    // Следующие переменные объявленны в глобальном scope var num1 = 20, num2 = 3, name = "Chamahk"; // Эта функция объявленна в глобальном scope function multiply() { return num1 * num2; } multiply(); // вернет 60 // Пример вложенной функции function getScore() { var num1 = 2, num2 = 3; function add() { return name + " scored " + (num1 + num2); } return add(); } getScore(); // вернет "Chamahk scored 5"

    Scope и стек функции

    (function stack)

    Рекурсия

    Функция может вызывать саму себя. Три способа такого вызова:

  • по имени функции
  • по переменной, которая ссылается на функцию
  • Для примера рассмотрим следующие функцию:

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

    Внутри функции (function body ) все следующие вызовы эквивалентны:

  • bar()
  • arguments.callee()
  • foo()
  • Функция, которая вызывает саму себя, называется рекурсивной функцией (recursive function ). Получается, что рекурсия аналогична циклу (loop ). Оба вызывают некоторый код несколько раз, и оба требуют условия (чтобы избежать бесконечного цикла, вернее бесконечной рекурсии). Например, следующий цикл:

    Var x = 0; while (x < 10) { // "x < 10" - это условие для цикла // do stuff x++; }

    можно было изменить на рекурсивную функцию и вызовом этой функции:

    Function loop(x) { if (x >= 10) // "x >= 10" - это условие для конца выполения (тоже самое, что "!(x < 10)") return; // делать что-то loop(x + 1); // рекурсионный вызов } loop(0);

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

    Function walkTree(node) { if (node == null) // return; // что-то делаем с элементами for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }

    В сравнении с функцией loop , каждый рекурсивный вызов сам вызывает много рекурсивных вызовов.

    Также возможно превращение некоторых рекурсивных алгоритмов в нерекурсивные, но часто их логика очень сложна, и для этого потребуется использование стека (stack ). По факту рекурсия использует stach: function stack.

    Поведение stack"а можно увидеть в следующем примере:

    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) и замыкания (closures)

    Вы можете вложить одну функцию в другую. Вложенная функция (nested function ; inner ) приватная (private ) и она помещена в другую функцию (outer ). Так образуется замыкание (closure ). Closure - это выражение (обычно функция), которое может иметь свободные переменные вместе со средой, которая связывает эти переменые (что "закрывает" ("close" ) выражение).

    Поскольку вложенная функция это closure, это означает, что вложенная функция может "унаследовать" (inherit ) аргументы и переменные функции, в которую та вложена. Другими словами, вложенная функция содержит scope внешней ("outer" ) функции.

    Подведем итог:

    • Вложенная функция имеет доступ ко всем инструкциям внешней функции.
    • Вложенная функция формирует closure: она может использовать аргументы и переменные внешней функции, в то время как внешняя функция не может использовать аргументы и переменные вложенной функции.

    Следующий пример показывает вложенную функцию:

    Function addSquares(a, b) { function square(x) { return x * x; } return square(a) + square(b); } a = addSquares(2, 3); // возвращает 13 b = addSquares(3, 4); // возвращает 25 c = addSquares(4, 5); // возвращает 41

    Поскольку вложенная функция формирует closure, Вы можете вызвать внешную функцию и указать аргументы для обоих функций (для outer и innner).

    Function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); // Подумайте над этим: дайте мне функцию, // который передай 3 result = fn_inside(5); // возвращает 8 result1 = outside(3)(5); // возвращает 8

    Сохранение переменных

    Обратите внимание, значение x сохранилось, когда возвращалось inside . Closure должно сохранять аргументы и переменные во всем scope. Поскольку каждый вызов предоставляет потенциально разные аргументы, создается новый closure для каждого вызова во вне. Память может быть очищена только тогда, когда inside уже возвратился и больше не доступен.

    Это не отличается от хранения ссылок в других объектах, но часто менее очевидно, потому что не устанавливаются ссылки напрямую и нельзя посмотреть там.

    Несколько уровней вложенности функций (Multiply-nested functions)

    Функции можно вкадывать несколько раз, т.е. функция (A) хранит в себе функцию (B), которая хранит в себе функцию (C). Обе фукнкции B и C формируют closures, так B имеет доступ к переменным и аргументам A, и C имеет такой же доступ к B. В добавок, поскольку C имеет такой доступ к B, который имеет такой же доступ к A, C ещё имеет такой же доспут к A. Таким образом cloures может хранить в себе несколько scope; они рекурсивно хранят scope функций, содержащих его. Это называется chaining (chain - цепь ; Почему названо "chaining" будет объяснено позже)

    Рассмотрим следующий пример:

    Function A(x) { function B(y) { function C(z) { console.log(x + y + z); } C(3); } B(2); } A(1); // в консоле выведится 6 (1 + 2 + 3)

    В этом примере C имеет доступ к y функции B и к x функции A . Так получается, потому что:

  • Функция B формирует closure, включающее A , т.е. B имеет доступ к аргументам и переменным функции A .
  • Функция C формирует closure, включающее B .
  • Раз closure функции B включает A , то closure С тоже включает A, C имеет доступ к аргументам и переменным обоих функций B и A . Другими словами, С cвязывает цепью (chain ) scopes функций B и A в таком порядке.
  • В обратном порядке, однако, это не верно. A не имеет доступ к переменным и аргументам C , потому что A не имеет такой доступ к B . Таким образом, C остается приватным только для B .

    Конфликты имен (Name conflicts)

    Когда два аргумента или переменных в scope у closure имеют одинаковые имена, происходит конфликт имени (name conflict ). Более вложенный (more inner ) scope имеет приоритет, так самый вложенный scope имеет наивысший приоритет, и наоборот. Это цепочка областей видимости (scope chain ). Самым первым звеном является самый глубокий scope, и наоборот. Рассмотрим следующие:

    Function outside() { var x = 5; function inside(x) { return x * 2; } return inside; } outside()(10); // возвращает 20 вместо 10

    Конфликт имени произошел в инструкции return x * 2 между параметром x функции inside и переменной x функции outside . Scope chain здесь будет таким: { inside ==> outside ==> глобальный объект (global object )}. Следовательно x функции inside имеет больший приоритет по сравнению с outside , и нам вернулось 20 (= 10 * 2), а не 10 (= 5 * 2).

    Замыкания

    (Closures)

    Closures это один из главных особенностей JavaScript. JavaScript разрешает вложенность функций и предоставляет вложенной функции полный доступ ко всем переменным и функциям, объявленным внутри внешней функции (и другим переменным и функцим, к которым имеет доступ эта внешняя функция).

    Однако, внешняя функция не имеет доступа к переменным и функциям, объявленным во внутренней функции. Это обеспечивает своего рода инкапсуляцию для переменных внутри вложенной функции.

    Также, поскольку вложенная функция имеет доступ к scope внешней функции, переменные и функции, объявленные во внешней функции, будет продолжать существовать и после её выполнения для вложенной функции, если на них и на неё сохранился доступ (имеется ввиду, что переменные, объявленные во внешней функции, сохраняются, только если внутренняя функция обращается к ним).

    Closure создается, когда вложенная функция как-то стала доступной в неком scope вне внешней функции.

    Var pet = function(name) { // Внешняя функция объявила переменную "name" var getName = function() { return name; // Вложенная функция имеет доступ к "name" внешней функции } return getName; // Возвращаем вложенную функцию, тем самым сохраняя доступ // к ней для другого scope } myPet = pet("Vivie"); myPet(); // Возвращается "Vivie", // т.к. даже после выполнения внешней функции // name сохранился для вложенной функции

    Более сложный пример представлен ниже. Объект с методами для манипуляции вложенной функции внешней функцией можно вернуть (return ).

    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

    В коде выше переменная 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

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

    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? } } }

    Использование объекта arguments

    Объект arguments функции является псевдо-массивом. Внутри функции Вы можете ссылаться к аргументам следующим образом:

    Arguments[i]

    где i - это порядковый номер аргумента, отсчитывающийся с 0. К первому аргументу, переданному функции, обращаются так arguments . А получить количество всех аргументов - arguments.length .

    С помощью объекта arguments Вы можете вызвать функцию, передавая в неё больше аргументов, чем формально объявили принять. Это очень полезно, если Вы не знаете точно, сколько аргументов должна принять Ваша функция. Вы можете использовать arguments.length для определения количества аргументов, переданных функции, а затем получить доступ к каждому аргументу, используя объект arguments .

    Для примера рассмотрим функцию, которая конкатенирует несколько строк. Единственным формальным аргументом для функции будет строка, которая указывает символы, которые разделяют элементы для конкатенации. Функция определяется следующим образом:

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

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

    // возвращает "red, orange, blue, " myConcat(", ", "red", "orange", "blue"); // возвращает "elephant; giraffe; lion; cheetah; " myConcat("; ", "elephant", "giraffe", "lion", "cheetah"); // возвращает "sage. basil. oregano. pepper. parsley. " myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

    Т.к. arguments является псевдо-массивом, к нему применимы некоторые методы массивов, например, for .. in

    Function func() { for (value in arguments){ console.log(value); } } func(1, 2, 3); // 1 // 2 // 3

    Примечание: arguments является псевдо-массивом, но не массивом. Это псевдо-массив, в котором есть пронумерованные индексы и свойство length . Однако он не обладает всеми методами массивов.

    Оставшиеся параметры (Rest parameters)

    На введение стрелочных функций повлияли два фактора: более короткие функции и лексика this .

    Более короткие функции

    В некоторый функциональных паттернах приветствуется использование более коротких функций. Сравните:

    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

    Лексика this

    До стрелочных функций каждая новая функция определяла свое значение this (новый объект в случае конструктора, undefined в strict mode, контекстный объект, если функция вызвана как метод объекта, и т.д.). Это оказалось раздражающим с точки зрения объектно-орентированного стиля программирования.

    Function Person() { // Конструктор Person() определяет `this` как самого себя. this.age = 0; setInterval(function growUp() { // Без strict mode функция growUp() определяет `this` // как global object, который отличается от `this` // определенного конструктором Person(). this.age++; }, 1000); } var p = new Person();

    В ECMAScript 3/5 эта проблема была исправлена путем присвоения значения this переменной, которую можно было бы замкнуть.

    Function Person() { var self = this; // Некоторые выбирают `that` вместо `self`. // Выберите что-то одно и будьте последовательны. self.age = 0; setInterval(function growUp() { // The callback refers to the `self` variable of which // the value is the expected object. self.age++; }, 1000); }

    Смотрите также Function в Справочнике JavaScript для получения дополнительной информации по функции как объекту.