Объявление функции в разделе declaration. Структура программы. Комментарии в JavaScript

Как объект языка Си, функцию необходимо объявить. Объявление функции пользователя, т.е. ее декларация, выполняется в двух формах – в форме описания и в форме определения.

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

В стандарте языка используется следующий способ декларации функций:

тип_результата ID_функции(тип переменной1, …, тип переменной N);

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

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

Пример описания функции fun со списком параметров:

float fun(int, float, int, int);

Полное определение функции имеет следующий вид:

тип_результата ID_функции(список параметров)

код функции

Тип результата определяет тип выражения, значение которого возвращается в точку ее вызова при помощи оператора return <выражение> .

Если тип функции не указан, то по умолчанию предполагается тип int .

Список параметров состоит из перечня типов и идентификаторов параметров, разделенных запятыми.

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

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

В данном случае оператор return можно не ставить.

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

Пример функции, определяющей наименьшее значение из двух целочи­слен­ных переменных:

int min (int x, int y)

return (x

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

Если у функции отсутствует список параметров, то при декларации такой функции желательно в круглых скобках также указать ключевое слово void. Например, void main(void) .

The function keyword can be used to define a function inside an expression.

The source for this interactive example is stored in a GitHub repository. If you"d like to contribute to the interactive examples project, please clone https://github.com/mdn/interactive-examples and send us a pull request.

Syntax

var myFunction = function [name ]([param1 [, param2[ , ..., paramN ]]]) { statements };

Parameters

name The function name. Can be omitted, in which case the function is anonymous . The name is only local to the function body. paramN The name of an argument to be passed to the function. statements The statements which comprise the body of the function.

Description

A function expression is very similar to and has almost the same syntax as a function statement (see function statement for details). The main difference between a function expression and a function statement is the function name, which can be omitted in function expressions to create anonymous functions. A function expression can be used as a IIFE (Immediately Invoked Function Expression) which runs as soon as it is defined. See also the chapter about functions for more information.

Function expression hoisting

Function expressions in JavaScript are not hoisted, unlike function declarations . You can"t use function expressions before you define them:

Console.log(notHoisted) // undefined //even though the variable name is hoisted, the definition isn"t. so it"s undefined. notHoisted(); // TypeError: notHoisted is not a function var notHoisted = function() { console.log("bar"); };

Named function expression

If you want to refer to the current function inside the function body, you need to create a named function expression. This name is then local only to the function body (scope) . This also avoids using the non-standard arguments.callee property.

Var math = { "factit": function factorial(n) { console.log(n) if (n <= 1) { return 1; } return n * factorial(n - 1); } }; math.factit(3) //3;2;1;

The variable the function expression is assigned to will have a name property. The name doesn"t change if it"s assigned to a different variable. If function name is omitted, it will be the variable name (implicit name). If function name is present, it will be the function name (explicit name). This also applies to arrow functions (arrows don"t have a name so you can only give the variable an implicit name).

Var foo = function() {} foo.name // "foo" var foo2 = foo foo2.name // "foo" var bar = function baz() {} bar.name // "baz" console.log(foo === foo2); // true console.log(typeof baz); // undefined console.log(bar === baz); // false (errors because baz == undefined)

Examples

The following example defines an unnamed function and assigns it to x . The function returns the square of its argument:

Var x = function(y) { return y * y; }; button.addEventListener("click", function(event) { console.log("button is clicked!") })

Specifications

Specification Status Comment
ECMAScript Latest Draft (ECMA-262)
Draft
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of "Function definitions" in that specification.
Standard
ECMAScript 5.1 (ECMA-262)
Standard
ECMAScript 3rd Edition (ECMA-262)
The definition of "Function definition" in that specification.
Standard Initial definition. Implemented in JavaScript 1.5.

Browser compatibility

The compatibility table on this page is generated from structured data. If you"d like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

Update compatibility data on GitHub

Desktop Mobile Server
Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet Node.js
function Chrome Full support Yes Edge Full support 12 Firefox Full support 1 IE Full support Yes Opera Full support Yes Safari Full support Yes WebView Android Full support Yes Chrome Android Full support Yes Firefox Android Full support 4 Opera Android Full support Yes Safari iOS Full support Yes Samsung Internet Android Full support Yes nodejs Full support Yes
Trailing comma in parameters Chrome Full support 58 Edge No support No Firefox Full support 52 IE No support No Opera Full support 45 Safari No support No WebView Android Full support 58 Chrome Android Full support 58 Firefox Android Full support 52 Opera Android Full support 43 Safari iOS No support No Samsung Internet Android Full support 7.0 nodejs Full support 8.0.0

Legend

Full support Full support No support No support

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

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

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

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

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

Var sum = function(num1,num2) { return console.log(num1+num2); };

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

//например, вызовем функцию, которая содержится в переменной sum и передадим ей 2 аргумента. sum(7,4);

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

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

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

Var factorial = function fact(num) { if (num

Этот код определяет функцию и сохраняет ссылку на неё в переменную factorial . Для вызова функции внутри этой функции используем имя fact . Обратите внимание, что обратиться к функции вне её тела по имени fact нельзя, для этого необходимо использовать переменную factorial .

Но и в этом случае можно обойтись без имени, т.к. JavaScript позволяет вызвать функцию внутри своего тела с помощью свойства callee объекта arguments .

Var factorial = function(num) { if (num

JavaScript - Самовызывающаяся функция

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

Например, вызовем функцию sum немедленно со значениями параметров 7 и 4.

Var sum = function(num1,num2) { return console.log(num1+num2); }(7,4);

Процесс немедленного вызова функции иногда называют инициализацией или инстанциацией функции.

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

(function(num1,num2) { return console.log(num1+num2); }(7,4));

Отличия Function Declaration и Function Expression

Основные различия между функциями Function Declaration и Function Expression представим в следующей таблице:

Function Declaration Function Expression
Браузер (интерпретатор JavaScript) считает функцию - Function Declaration, если она расположена в основном потоке кода (не является частью какого-либо выражения). Браузер находит функцию как Function Expression, если она размещена в составе некоторого выражения.
При объявлении функции этим способом, переменная, по которой происходит обращение к ней, создаётся автоматически. function square(a) { return a*a; } // обращение к функции console.log(square(5)); Для обращения к функции необходимо создать переменную и сохранить в неё ссылку на эту функцию. var square = function(a) { return a*a; } // обращение к функции console.log(square(5));
Инициализируется до выполнения кода (в соотвествующей области видимости). Данное действие ещё называют поднятием или hoisting (хойстингом). Такие функции можно вызывать до объявления. // вызываем функцию до её объявления console.log(square(7)); function square(a) { return a*a; } Функции Function Expression нельзя использовать до объявления. Поднимается только сама переменная. // ошибка при вызове функции console.log(square(7)); var square = function(a) { return a*a; }

В этом случае происходит следующее:

Var square; console.log(square(7)); // но на этом этапе переменная square имеет значение undefined square = function(a) { return a*a; }

При использовании use strict функция, объявленная как Function Declaration, будет видна только внутри блока, в котором она объявлена. "use strict"; if (true) { function sum(a,b,c) { return a+b+c; } console.log(sum(10,20,10)); } // ошибка доступа к функции sum console.log(sum(4,5,4)); В отличие от Function Declaration, доступ к функции можно получить вне блока, в котором она создана: "use strict"; if (true) { var sum = function (a,b,c) { return a+b+c; } console.log(sum(10,20,10)); } // имеем доступ к функции sum console.log(sum(4,5,4));
Функцию, объявленную как Function Declaration вызвать немедленно нельзя.
Для того чтобы это осуществить необходимо функцию сделать частью выражения, например, обернуть её в круглые скобки. После этого функция будет считаться частью выражения (Function Expression) и её можно вызвать немедленно.
Например: var sum = (function sum(a,b,c) { return a+b+c; })(5,6,7); console.log(typeof sum); // number console.log(sum); // number
Функцию, объявленную как Function Expression можно вызвать немедленно. var sum = function (a,b,c) { return a+b+c; }(5,6,7); console.log(typeof sum); // number console.log(sum); // number При этом переменная sum (в данном случае) содержит уже не ссылку на функцию, а её результат (в этом примере число).

Вывод: Способ объявления функции с помощью выражения определения позволяет использовать функцию как переменную. Данная возможность языка 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)); // Uncaught 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:

  1. the function"s name
  2. 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:

  1. bar()
  2. arguments.callee()
  3. 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:

  1. B forms a closure including A , i.e. B can access A "s arguments and variables.
  2. C forms a closure including B .
  3. 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); }

В предыдущих разделах мы рассмотрели те элементарные "кирпичики" (операторы и выражения), из которых строится программа. В ранних языках программирования, например в первых версиях Basic, на этом все и заканчивалось. Программа - это последовательность операторов, и все. Пока программы невелики, такой подход вполне работоспособен. Если в программе 10 операторов, то никакой дополнительной структуры не нужно. Но если в про грамме 10 000 операторов или 10 000 000 операторов (а такие программы есть, и они работают), то без введения дополнительной структуры не обойтись.

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

Данная структуризация программ полностью, удовлетворяет принципам структурного и модульного программирования. В настоящем разделе мы подробно обсудим работу на уровне модуля, о работе с приложениями и проектами будет рассказано в разделе 20.6 "Редактор Visual Basic for Application".

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

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

Существует три уровня видимости и пять способов объявления:

Процедура (область видимости - только та процедура, в которой переменная объявлена).

  • Оператор Dim объявляет переменную внутри тела процедуры и в любом месте процедуры, но до операторов, использующих ее. Время жизни данной переменной - это рамки процедуры, т. е. при входе в данную процедуру под переменную выделяется память и происходит ее инициализа ция, затем, в ходе выполнения процедуры, значение переменной может меняться, после выхода из процедуры выделенная память освобождается, и, соответственно, теряется значение переменной.
  • Оператор static антагонистичен оператору Dim, он объявляет статическую переменную. Разница в том, что при выходе из процедуры у статической переменной память не отбирается, а становится (в силу области видимости) временно недоступной, а соответственно, сохраняется ее значение, которым при повторном обращении к процедуре можно воспользоваться.

Модуль (область видимости - все процедуры модуля, в котором переменная объявлена):

  • Оператор Private объявляет переменную в разделе описаний Declarations (вне процедур модуля).
  • Оператор Dim абсолютно аналогичен (в данном случае) оператору Private.

Приложение (область видимости - все процедуры всех модулей активного приложения):

  • Оператор Public объявляет переменную в разделе описаний Declarations.

Процедуры, как и все определяемые пользователем элементы VBA, требуют объявления. Объявление процедуры имеет следующий синтаксис:

Sub имяПроцедуры ([ списокАргументов ]}

[ блокОператоров1 ]

[ блокОператоров2 ] End Sub

Ключевое слово Private задает следующую область видимости для процедуры - только модуль, в котором она описана. То есть ее могут вызывать только процедуры того же модуля. Ключевое слово Public, наоборот, объявляет процедуру доступной для всех модулей проекта. По умолчанию процедура общедоступна, т. е. имеет статус Public. Что касается использования ключевого слова Friend, то о нем мы расскажем чуть позже, когда речь пойдет о классах VBA.

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

ИмяПроцедуры - это идентификатор процедуры, удовлетворяющий всем правилам создания имен.

После имени идут обязательные скобки, но необязательный список-Аргументов. Давайте подробнее рассмотрим объявление одного аргумента; если их больше, то они просто разделяются запятой.

Имя-Аргумента [()] [= значениеПоУмолчанию]

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

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

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

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

Тем самым вызываемая процедура, изменяя значение формального параметра, изменяет значение переменной.

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

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

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

После описания процедуры идет блокОператоров1 (обычно называемый телом процедуры), в котором могут быть использованы значения аргументов" процедуры. Если в ходе выполнения операторов процедуры встречается оператор Exit Sub (выход из процедуры), то выполнение процедуры прекращается и управление передается оператору, следующему за оператором вызова процедуры.

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

Function имяФункции [(списокАргументов)]

[блокОператоров1]

[имяФункции = Выражение]

[блокОператоров2]

[имяФункции = Выражение] End Function

Во-первых, ключевое слово sub заменено на Function. Во-вторых, после объявления аргументов следует указать тип возвращаемого функцией значения. В-третьих, в теле функции есть присваивание имени функции какого-либо значения, имеющего объявленный тип. Конечно, подобное присваивание необязательно, но тогда ваша функция будет похожа на процедуру, а результатом функции окажется значение по умолчанию, определенное для соответствующего типа. И наконец, вместо ключевого слова Exit Sub для выхода из функции используется ключевое слово Exit Function.

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

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

Программа 20.14. Объявление процедур и функций

Public Sub InitBookShops(arr() As Integer)

Dim i, str For i = LBound (arr) To UBound (arr)

str = "Ввести заказ для магазина №" & i arr(i) = InputBox(str) Next i End Sub

Public Function SaleAbility(arr() As Integer, _ "

Optional numOfBooks As Integer = 5000) As Boolean

For Each elem In arr

sumOfBooks = sumOfBooks + elem Next If sumOfBooks < numOfBooks Then

SaleAbility = True Else

SaleAbility = False End If End Function

Замечание

В теле функции SaleAbility имеется сознательно допущенная нерациональность. Вместо последнего условного оператора if. . .Then. . .Else можно и нужно написать эквивалентный, более эффективный оператор присваивания SaleAbility = sumOfBooks < numOfBooks. Этим замечанием мы специально акцентируем внимание читателя на подобных мелких, но важных "хитростях" хорошего стиля программирования.

Помимо вышеописанного объявления процедур и функций, в VBA существует особый вид процедур. Это процедуры реакции на событие, вызываемое системой или пользователем (см. главу 22 "Разработка приложения"). Например, для документов Word определены события open и close, для рабочих книг Еxcel - Beforesave и Beforedose, для объектов пользовательских классов - initialize и Terminate, нажатие кнопки диалогового окна тоже является событием и т. д. Пользователь способен сам создать процедуру реакции на подобные события, например попросить выводить сообщение "До свидания, спасибо за работу!" при закрытии документа.

Синтаксис подобной процедуры такой же, как и у обыкновенной, только в ее имени сначала указан объект, с которым будет связано событие, потом - символ подчеркивания (_), а затем - собственно имя события.

Программа 20.15. Процедура реакции на событие

Private Sub Document_Close()

MsgBox ("До свидания, спасибо за работу!")

Замечание

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


В современных системах программирования имеются богатые и все время развивающиеся библиотеки готовых компонент, которые называются элементами управления (controls) и тесно интегрированы со встроенными механизмами событийного управления. Использование готовых элементов управления удобно, продуктивно и должно быть рекомендовано в большинстве случаев. Более подробная информация по этому вопросу дана в главе 22 "Разработка приложения".

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

Первый, самый простой, вызов:

ИмяПроцедуры СписокФактическихПараметров

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

Также можно вызывать процедуру, используя ключевое слово Call: Call имяПроцедуры (СписокФактическихПараметров)

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

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

ИмяПеременной = ИмяФункции (СписокФактическихПараметров)

Давайте на примере вышеобъявленных процедур и функций покажем их вызов.

Программа 20.16. Вызов процедур и функций

Dim bookshops(1 To 25) As Integer Dim result As Boolean

result = SaleAbility(bookshops, 3000)

MsgBox(result) End Sub

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

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

Программа 20.17. Использование именованных аргументов

Dim bookshops(1 To 25) As Integer

Dim result As Boolean

result = SaleAbility(arr:= bookshops, numOfBooks:= 3000)

MsgBox(result) End Sub

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

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

Программа 20.18. Использование параметра ParamArray

Sub FullSum(ParamArray arr() As Variant)

Dim sum As Integer

For i = LBound(arr) To UBound(arr) sum = sum + arr(i)

MsgBox (sum) End Sub

FullSum 100, 2000, 350, 450 End Sub

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

Программа 20.19. Рекурсивный вызов функции

Function fctrl(n As Integer) As Variant

If (n <= 1) Then fctrl = 1

fctrl = n * fctrlfn - 1) End If End Function

MsgBox fctrl(20) End Sub

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

В заключение мы рассмотрим пример, показывающий различие между передачей параметров по ссылке и по значению, в котором приведены две процедуры: RefVal и MainCalc. Вспомогательная процедура RefVal использует три формальных аргумента, описанные по-разному. Далее в теле этой процедуры каждый из них увеличивается на единицу, а затем их значения выводятся на экран. Основная процедура MainCalc устанавливает значения переменных а, ь и с, а затем передает их в качестве параметров процедуре RefVal. При этом первый параметр передается по ссылке (по умолчанию), второй - по значению, а третий - снова по ссылке. После возврата из процедуры RefVal основная процедура также выводит на экран значения трех переменных, передававшихся в качестве параметров. Всего на экран выводится шесть значений. Сначала это числа 11, 21 и 31 (все полученные значения увеличились на 1 и выводятся процедурой RefVal). Затем это числа И, 20 и 31 (эти значения выводятся процедурой MainCalc, причем переменные, переданные по ссылке, увеличились, а переменная, переданная по значению, - нет).

Программа 20.20. Разница между ByRef и ByVal

Sub RefVal(x, ByVal у, ByRef z)

MsgBox (z) End Sub

Sub MainCalc 0 a = 10 b = 20 с = 30 Call RefVal(a, b, c)

MsgBox(a) MsgBox(b) MsgBox(c) End Sub

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

error: