24 мая 2011 в 01:13 Пять способов вызвать функцию
  • JavaScript
  • Перевод

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

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

Давайте напишем простую функцию, которая возвращает массив из трех элементов - текущего значения this и двух аргументов, переданных в функцию.
function makeArray(arg1, arg2){ return [ this, arg1, arg2 ]; }

Самый распространенный способ: глобальный вызов Новички часто объявляют функции так, как показано в примере выше. Вызвать эту функцию не составляет труда:
makeArray("one", "two"); // => [ window, "one", "two" ]
Погодите. Откуда взялся объект window ? Почему это у нас this равен window ?

В JavaScript, неважно, выполняется ли скрипт в браузере или в ином окружении, всегда определен глобальный объект . Любой код в нашем скрипте, не «привязанный» к чему-либо (т.е. находящийся вне объявления объекта) на самом деле находится в контексте глобального объекта. В нашем случае, makeArray - не просто функция, «гуляющая» сама по себе. На самом деле, makeArray - метод глобального объекта (в случае исполнения кода в браузере) window . Доказать это легко:
alert(typeof window.methodThatDoesntExist); // => undefined alert(typeof window.makeArray); // => function
То есть вызов makeArray("one", "two"); равносилен вызову window.makeArray("one", "two"); .

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

Правило вызова функций №1: Если функция вызывается напрямую, без указания объекта (например, myFunction()), значением this будет глобальный объект (window в случае исполнения кода в браузере).

Вызов метода Давайте создадим простой объект и сделаем makeArray его методом. Объект объявим с помощью литеральной нотации, а после вызовем наш метод:
// создаем объект var arrayMaker = { someProperty: "какое-то значение", make: makeArray }; // вызываем метод make() arrayMaker.make("one", "two"); // => [ arrayMaker, "one", "two" ] // альтернативный синтаксис, используем квадратные скобки arrayMaker["make"]("one", "two"); // => [ arrayMaker, "one", "two" ]
Видите разницу? Значение this в этом случае - сам объект. Почему не window , как в предыдущем случае, ведь объявление функции не изменилось? Весь секрет в том, как передаются функции в JavaScript. Function - это стандартный тип JavaScript, являющийся на самом деле объектом, и как и любой другой объект, функции можно передавать и копировать. В данном случае, мы как бы скопировали всю функцию, включая список аргументов и тело, и присвоили получившийся объект свойству make объекта arrayMaker . Это равносильно такому объявлению:
var arrayMaker = { someProperty: "Какое-то значение"; make: function (arg1, arg2) { return [ this, arg1, arg2]; } };
Правило вызова функций №2: В функции, вызванной с использованием синтаксиса вызова метода, например, obj.myFunction() или obj["myFunction"]() , this будет иметь значение obj .

Непонимание этого простого, в общем-то, принципа часто приводит к ошибкам при обработке событий:
function buttonClicked(){ var text = (this === window) ? "window" : this.id; alert(text); } var button1 = document.getElementById("btn1"); var button2 = document.getElementById("btn2"); button1.onclick = buttonClicked; button2.onclick = function(){ buttonClicked(); };
Щелчок по первой кнопке покажет сообщение «btn1» , потому что в данном случае мы вызываем функцию как метод, и this внутри функции получит значение объекта, которому этот метод принадлежит. Щелчок по второй кнопке выдаст «window» , потому что в этом случае мы вызываем buttonClicked напрямую (т.е. не как obj.buttonClicked()). То же самое происходит, когда мы назначаем обработчик события в тэге элемента, как в случае третьей кнопки. Щелчок по третьей кнопке покажет то же самое сообщение, что и для второй.

При использовании библиотек вроде jQuery думать об этом не надо. jQuery позаботится о том, чтобы переписать значение this в обработчике события так, чтобы значением this был элемент, вызвавший событие:
// используем jQuery $("#btn1").click(function() { alert(this.id); // jQuery позаботится о том, чтобы "this" являлась кнопкой });
Каким образом jQuery удается изменить значение this ? Читайте ниже.

Еще два способа: apply() и call() Логично, что чем чаще вы используете функции, тем чаще вам приходится передавать их и вызывать в разных контекстах. Зачастую возникает необходимость переопределить значение this . Если вы помните, функции в JavaScript являются объектами. На практике это означает, что у функций есть предопределенные методы. apply() и call() - два из них. Они позволяют переопределять значение this:
var car = { year: 2008, model: "Dodge Bailout" }; makeArray.apply(car, [ "one", "two" ]); // => [ car, "one", "two" ] makeArray.call(car, "one", "two"); // => [ car, "one", "two" ]
Эти два метода очень похожи. Первый параметр переопределяет this . Различия между ними заключаются в последющих аргументах: Function.apply() принимает массив значений, которые будут переданы функции, а Function.call() принимает аргументы раздельно. На практике, по моему мнению, удобнее применять apply() .

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

Конструкторы Я не буду подробно останавливаться на объявлении собственных типов в JavaScript, но считаю необходимым напомнить, что в JavaScript нет классов, а любой пользовательский тип нуждается в конструкторе. Кроме того, методы пользовательского типа лучше объявлять через prototype , который является свойством фукции-конструктора. Давайте создадим свой тип:
// объявляем конструктор function ArrayMaker(arg1, arg2) { this.someProperty = "неважно"; this.theArray = [ this, arg1, arg2 ]; } // объявляем методы ArrayMaker.prototype = { someMethod: function () { alert("Вызван someMethod"); }, getArray: function () { return this.theArray; } }; var am = new ArrayMaker("one", "two"); var other = new ArrayMaker("first", "second"); am.getArray(); // => [ am, "one", "two" ]
Важным в этом примере является наличие оператора new перед вызовом функции. Если бы не он, это был бы глобальный вызов, и создаваемые в конструкторе свойства относились бы к глобальному объекту. Нам такого не надо. Кроме того, в конструкторах обычно не возвращают значения явно. Без оператора new конструктор вернул бы undefined , с ним он возвращает this . Хорошим стилем считается наименование конструкторов с заглавной буквы; это позволит вспомнить о необходимости оператора new .

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

Правило вызова функций №4: При вызове функции с оператором new , значением this будет новый объект, созданный средой исполнения JavaScript. Если эта функция не возвращает какой-либо объект явно, будет неявно возвращен this .

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

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. Важнейшей особенностью языка является первоклассная поддержка функций ​ (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 для получения дополнительной информации по функции как объекту.

    • Перевод

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

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

    Давайте напишем простую функцию, которая возвращает массив из трех элементов - текущего значения this и двух аргументов, переданных в функцию.
    function makeArray(arg1, arg2){ return [ this, arg1, arg2 ]; }

    Самый распространенный способ: глобальный вызов Новички часто объявляют функции так, как показано в примере выше. Вызвать эту функцию не составляет труда:
    makeArray("one", "two"); // => [ window, "one", "two" ]
    Погодите. Откуда взялся объект window ? Почему это у нас this равен window ?

    В JavaScript, неважно, выполняется ли скрипт в браузере или в ином окружении, всегда определен глобальный объект . Любой код в нашем скрипте, не «привязанный» к чему-либо (т.е. находящийся вне объявления объекта) на самом деле находится в контексте глобального объекта. В нашем случае, makeArray - не просто функция, «гуляющая» сама по себе. На самом деле, makeArray - метод глобального объекта (в случае исполнения кода в браузере) window . Доказать это легко:
    alert(typeof window.methodThatDoesntExist); // => undefined alert(typeof window.makeArray); // => function
    То есть вызов makeArray("one", "two"); равносилен вызову window.makeArray("one", "two"); .

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

    Правило вызова функций №1: Если функция вызывается напрямую, без указания объекта (например, myFunction()), значением this будет глобальный объект (window в случае исполнения кода в браузере).

    Вызов метода Давайте создадим простой объект и сделаем makeArray его методом. Объект объявим с помощью литеральной нотации, а после вызовем наш метод:
    // создаем объект var arrayMaker = { someProperty: "какое-то значение", make: makeArray }; // вызываем метод make() arrayMaker.make("one", "two"); // => [ arrayMaker, "one", "two" ] // альтернативный синтаксис, используем квадратные скобки arrayMaker["make"]("one", "two"); // => [ arrayMaker, "one", "two" ]
    Видите разницу? Значение this в этом случае - сам объект. Почему не window , как в предыдущем случае, ведь объявление функции не изменилось? Весь секрет в том, как передаются функции в JavaScript. Function - это стандартный тип JavaScript, являющийся на самом деле объектом, и как и любой другой объект, функции можно передавать и копировать. В данном случае, мы как бы скопировали всю функцию, включая список аргументов и тело, и присвоили получившийся объект свойству make объекта arrayMaker . Это равносильно такому объявлению:
    var arrayMaker = { someProperty: "Какое-то значение"; make: function (arg1, arg2) { return [ this, arg1, arg2]; } };
    Правило вызова функций №2: В функции, вызванной с использованием синтаксиса вызова метода, например, obj.myFunction() или obj["myFunction"]() , this будет иметь значение obj .

    Непонимание этого простого, в общем-то, принципа часто приводит к ошибкам при обработке событий:
    function buttonClicked(){ var text = (this === window) ? "window" : this.id; alert(text); } var button1 = document.getElementById("btn1"); var button2 = document.getElementById("btn2"); button1.onclick = buttonClicked; button2.onclick = function(){ buttonClicked(); };
    Щелчок по первой кнопке покажет сообщение «btn1» , потому что в данном случае мы вызываем функцию как метод, и this внутри функции получит значение объекта, которому этот метод принадлежит. Щелчок по второй кнопке выдаст «window» , потому что в этом случае мы вызываем buttonClicked напрямую (т.е. не как obj.buttonClicked()). То же самое происходит, когда мы назначаем обработчик события в тэге элемента, как в случае третьей кнопки. Щелчок по третьей кнопке покажет то же самое сообщение, что и для второй.

    При использовании библиотек вроде jQuery думать об этом не надо. jQuery позаботится о том, чтобы переписать значение this в обработчике события так, чтобы значением this был элемент, вызвавший событие:
    // используем jQuery $("#btn1").click(function() { alert(this.id); // jQuery позаботится о том, чтобы "this" являлась кнопкой });
    Каким образом jQuery удается изменить значение this ? Читайте ниже.

    Еще два способа: apply() и call() Логично, что чем чаще вы используете функции, тем чаще вам приходится передавать их и вызывать в разных контекстах. Зачастую возникает необходимость переопределить значение this . Если вы помните, функции в JavaScript являются объектами. На практике это означает, что у функций есть предопределенные методы. apply() и call() - два из них. Они позволяют переопределять значение this:
    var car = { year: 2008, model: "Dodge Bailout" }; makeArray.apply(car, [ "one", "two" ]); // => [ car, "one", "two" ] makeArray.call(car, "one", "two"); // => [ car, "one", "two" ]
    Эти два метода очень похожи. Первый параметр переопределяет this . Различия между ними заключаются в последющих аргументах: Function.apply() принимает массив значений, которые будут переданы функции, а Function.call() принимает аргументы раздельно. На практике, по моему мнению, удобнее применять apply() .

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

    Конструкторы Я не буду подробно останавливаться на объявлении собственных типов в JavaScript, но считаю необходимым напомнить, что в JavaScript нет классов, а любой пользовательский тип нуждается в конструкторе. Кроме того, методы пользовательского типа лучше объявлять через prototype , который является свойством фукции-конструктора. Давайте создадим свой тип:
    // объявляем конструктор function ArrayMaker(arg1, arg2) { this.someProperty = "неважно"; this.theArray = [ this, arg1, arg2 ]; } // объявляем методы ArrayMaker.prototype = { someMethod: function () { alert("Вызван someMethod"); }, getArray: function () { return this.theArray; } }; var am = new ArrayMaker("one", "two"); var other = new ArrayMaker("first", "second"); am.getArray(); // => [ am, "one", "two" ]
    Важным в этом примере является наличие оператора new перед вызовом функции. Если бы не он, это был бы глобальный вызов, и создаваемые в конструкторе свойства относились бы к глобальному объекту. Нам такого не надо. Кроме того, в конструкторах обычно не возвращают значения явно. Без оператора new конструктор вернул бы undefined , с ним он возвращает this . Хорошим стилем считается наименование конструкторов с заглавной буквы; это позволит вспомнить о необходимости оператора new .

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

    Правило вызова функций №4: При вызове функции с оператором new , значением this будет новый объект, созданный средой исполнения JavaScript. Если эта функция не возвращает какой-либо объект явно, будет неявно возвращен this .

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

    В этой главе:

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

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

    Определение и вызов функций

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

    Function ИмяФункции (аргументы) { блок выражений }

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

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

    В простейшем случае аргументы могут отсутствовать, а блок операций моет быть представлен единственным оператором:

    Function MyFirstFunc () { var MyMessage="Это – моя функция!"; alert(MyMessage); }

    Здесь мы определили функцию, которая будет выдавать окно с сообщением «Это – моя функция!». Следует заметить, что даже если функция не принимает никаких аргументов, она все равно должна иметь пару круглых скобок после своего названия.

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

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

    Function remainder_free(j) { var i=0; while (i