Функция внутри процедуры 1С: Знач, ссылки, Возврат и контекст вызова
Содержание
Чтобы вызвать функцию из процедуры 1С, напишите её имя со скобками и присвойте результат переменной: Сумма = РассчитатьСумму(Цена, Количество);. Если функция лежит в том же модуле, больше ничего не нужно. Если в другом модуле — у неё должно стоять слово Экспорт, а вызов пишется через имя модуля: ОбщийМодульРасчетов.РассчитатьСумму(Цена, Количество).
Всё остальное — детали, на которых обычно и спотыкаются: параметры в 1С по умолчанию передаются по ссылке, значения по умолчанию задаются только константами, а вызов с клиента на сервер копирует данные туда и обратно. Ниже примеры для платформы 8.3 на встроенном языке, одинаковые для любой конфигурации — БП, ЗУП, УТ или своей.
Процедура и функция: чем они различаются при вызове
Разница одна, но принципиальная: функция возвращает значение, процедура — нет.
| Признак | Процедура | Функция |
|---|---|---|
| Ключевые слова | Процедура … КонецПроцедуры | Функция … КонецФункции |
| Возврат значения | нет, Возврат; только прерывает | Возврат Значение; |
| Можно ли в выражении | нет | да |
| Можно ли вызвать отдельной строкой | да | да, результат отбрасывается |
Последняя строка таблицы важна. Функцию можно вызвать «как процедуру», без присваивания: ЗаписатьВЖурнал(Текст);. Платформа выполнит её и просто выбросит результат. Обратное не работает
: процедуру нельзя поставить справа от знака = — модуль не скомпилируется.
Если функция дошла до КонецФункции, не встретив Возврат, она вернёт Неопределено. Это частая причина «пустых» результатов: в одной ветке Если возврат есть, в другой — забыли.
Порядок объявления в модуле не важен. В 1С 8.3 процедура в начале модуля может вызвать функцию, описанную в самом конце, — предварительное объявление, как в 7.7, не требуется.
Синтаксис вызова функции из процедуры и приём результата
Минимальный пример: процедура обработчика кнопки вызывает функцию расчёта в том же модуле формы.
&НаКлиенте
Процедура Рассчитать(Команда)
Итог = СуммаСоСкидкой(Объект.Сумма, 5);
Сообщить(СтрШаблон("Итого со скидкой: %1", Формат(Итог, "ЧДЦ=2; ЧГ=0")));
КонецПроцедуры
&НаКлиенте
Функция СуммаСоСкидкой(Сумма, ПроцентСкидки)
Возврат Окр(Сумма * (100 - ПроцентСкидки) / 100, 2);
КонецФункции
При Объект.Сумма = 12 000 функция вернёт 11 400: 12 000 × 95 / 100.
Результат функции можно использовать сразу, без промежуточной переменной:
- 📌 в условии:
Если ЕстьОстаток(Товар) Тогда; - 📌 в выражении:
Всего = НДС(Сумма) + Сумма;; - 📌 как параметр другой функции:
Сообщить(ПредставлениеСуммы(СуммаСоСкидкой(Сумма, 5)));; - 📌 через точку, если функция возвращает объект или структуру:
ПолучитьНастройки().Склад.
Как вернуть из функции несколько значений
Возврат отдаёт ровно одно значение. Когда нужно два-три результата, есть два пути.
Первый — вернуть структуру:
Функция ОстатокИСебестоимость(Товар)
Результат = Новый Структура("Количество, Сумма", 0, 0);
// ... заполнение по данным запроса
Возврат Результат;
КонецФункции
Процедура Проверить()
Данные = ОстатокИСебестоимость(Товар);
Если Данные.Количество = 0 Тогда
Сообщить("Нет остатка");
КонецЕсли;
КонецПроцедуры
Второй — заполнить параметры, переданные по ссылке, а функцией вернуть только признак успеха. Этот приём разберём в следующем разделе.
Структура читается проще и не создаёт побочных эффектов. Параметры-«выходы» уместны, когда функция уже что-то возвращает, а дополнительные данные нужны не всегда.
Передача параметров в функцию: по ссылке и через Знач
По умолчанию параметр процедуры или функции 1С передаётся по ссылке. Функция работает не с копией, а с той же переменной, что указана при вызове. Присвоили параметру новое значение внутри — изменилась и переменная в вызывающей процедуре.
Процедура Тест()
Счетчик = 10;
Увеличить(Счетчик);
Сообщить(Счетчик); // 11
КонецПроцедуры
Функция Увеличить(Число)
Число = Число + 1;
Возврат Число;
КонецФункции
Чтобы защитить исходную переменную, перед именем параметра ставят ключевое слово Знач:
Функция Увеличить(Знач Число)
Число = Число + 1;
Возврат Число;
КонецФункции
Теперь после вызова Счетчик останется равным 10, а функция вернёт 11.
Тонкость с коллекциями и объектами
Знач копирует значение переменной. Для числа, строки, даты это и есть сами данные. Для массива, структуры, таблицы значений или объекта — это копия ссылки на тот же экземпляр.
| Что делает функция с параметром | Без Знач | С Знач |
|---|---|---|
Массив.Добавить(1) | массив вызывающего меняется | массив вызывающего тоже меняется |
Массив = Новый Массив | у вызывающего теперь новый массив | у вызывающего старый массив |
Число = Число + 1 | у вызывающего изменилось | у вызывающего прежнее |
Знач не защищает содержимое коллекции от изменений. Если функция не должна трогать исходную таблицу, сделайте копию явно: для таблицы значений — Таблица.Скопировать(), для структуры или массива — переберите элементы в новую коллекцию.
Когда передача по ссылке полезна
- ✅ функция возвращает признак успеха, а данные отдаёт через параметр:
Если НайтиКонтрагента(ИНН, Контрагент) Тогда; - ✅ нужно накопить текст ошибок в одной строке через несколько вызовов;
- ⚠️ во всех остальных случаях это источник трудноуловимых ошибок: переменная «сама» меняется после вызова.
Хорошая привычка: ставить Знач у всех параметров-примитивов, которые функция не должна менять. Читатель кода сразу видит, какие параметры — входные.
Необязательные параметры и значения по умолчанию
Параметр становится необязательным, если в объявлении ему присвоено значение:
Функция ПредставлениеСуммы(Сумма, Валюта = "руб.", ДробнаяЧасть = Истина)
ФорматнаяСтрока = ?(ДробнаяЧасть, "ЧДЦ=2", "ЧДЦ=0");
Возврат СтрШаблон("%1 %2", Формат(Сумма, ФорматнаяСтрока), Валюта);
КонецФункции
Варианты вызова:
ПредставлениеСуммы(1500)— оба необязательных параметра берутся по умолчанию.ПредставлениеСуммы(1500, "USD")— меняется только валюта.ПредставлениеСуммы(1500, , Ложь)— валюта по умолчанию, дробная часть отключена. Пропущенный параметр обозначают пустым местом между запятыми.
Правила, которые проверяет компилятор и которые стоит помнить:
- 📌 значением по умолчанию может быть только литерал: число, строка, дата,
Истина,Ложь,Неопределено,Null. Выражение вродеТекущаяДата()илиНовый Массивтам не допускается; - 📌 если нужен «пустой массив по умолчанию», объявите параметр
= Неопределенои создайте массив внутри функции, когда параметр не передан; - 📌 необязательные параметры удобнее ставить в конец списка — иначе вызов превращается в ряд запятых.
Функция Отфильтровать(Список, Исключения = Неопределено)
Если Исключения = Неопределено Тогда
Исключения = Новый Массив;
КонецЕсли;
// ...
КонецФункции
Обязательные параметры пропускать нельзя: при нехватке аргументов модуль не пройдёт проверку с ошибкой Недостаточно фактических параметров, при лишних — Слишком много фактических параметров.
Вызов функции из другого модуля, с клиента и на сервере
Пока функция и процедура живут в одном модуле и в одном контексте, вызов тривиален. Сложности начинаются, когда их разделяет граница модуля или граница «клиент — сервер».
Из другого модуля
У функции в объявлении должно быть слово Экспорт:
Функция СуммаСоСкидкой(Сумма, ПроцентСкидки) Экспорт
Как писать вызов, зависит от того, где функция находится:
| Где лежит функция | Как вызвать |
|---|---|
| Общий модуль | ИмяОбщегоМодуля.ИмяФункции() |
| Модуль менеджера документа | Документы.ИмяДокумента.ИмяФункции() |
| Модуль менеджера справочника | Справочники.ИмяСправочника.ИмяФункции() |
| Модуль объекта | ДокументОбъект.ИмяФункции(), где ДокументОбъект получен, например, через Ссылка.ПолучитьОбъект() |
| Тот же модуль | ИмяФункции() без префикса, Экспорт не нужен |
Общий модуль: какие флажки поставить
В свойствах общего модуля есть флажки, от которых зависит, откуда видна функция:
- 🖥️ Клиент (управляемое приложение) — функцию можно вызвать с клиента, выполнится на клиенте;
- 🗄️ Сервер — функция доступна из серверного кода;
- 🔁 Вызов сервера — функцию серверного модуля можно вызвать с клиента, выполнится она на сервере;
- 🔀 Клиент + Сервер без «Вызов сервера» — код компилируется в обоих местах и выполняется там, откуда вызван.
Если клиентская процедура вызывает функцию серверного модуля без флажка «Вызов сервера», модуль не скомпилируется: на клиенте такой общий модуль просто не виден, и проверка выдаст ошибку вида Переменная не определена (ИмяМодуля).
В модуле формы: директивы компиляции
В модуле управляемой формы перед каждой процедурой стоит директива:
| Директива | Где выполняется | Что доступно |
|---|---|---|
&НаКлиенте | на клиенте | реквизиты формы, интерактивные действия |
&НаСервере | на сервере | реквизиты формы и база данных; форма передаётся на сервер целиком |
&НаСервереБезКонтекста | на сервере | только база и параметры; реквизиты формы недоступны |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвана | только параметры |
Клиентская процедура может вызвать серверную функцию напрямую:
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
Объект.Договор = ОсновнойДоговор(Объект.Контрагент);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОсновнойДоговор(Знач Контрагент)
Возврат Контрагент.ДоговорПоУмолчанию;
КонецФункции
Обратное не работает: из &НаСервере вызвать &НаКлиенте нельзя. Если серверу нужно что-то сделать на клиенте, верните признак в результате функции и обработайте его в клиентской процедуре.
Что происходит с параметрами при вызове сервера
При вызове с клиента на сервер параметры сериализуются и передаются по сети. Параметры без Знач после завершения функции передаются обратно на клиент — так платформа сохраняет семантику передачи по ссылке. Это двойной трафик.
Поэтому у серверных функций, которые вызываются с клиента, ставьте Знач у всех входных параметров. Особенно у таблиц и массивов: каждый лишний килобайт уходит туда и обратно.
Через границу проходят только сериализуемые значения: примитивы, ссылки, массивы, структуры, соответствия, данные формы. Объект документа (ДокументОбъект) на клиент не передаётся — его там просто нет.
Асинхронные функции
Начиная с платформы 8.3.18 есть ключевые слова Асинх и Ждать. Асинхронная функция возвращает Обещание, а результат получают через Ждать:
&НаКлиенте
Асинх Процедура Удалить(Команда)
Ответ = Ждать ВопросАсинх("Удалить строку?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
// удаление
КонецЕсли;
КонецПроцедуры
Без Ждать в переменную попадёт объект обещания, а не ответ пользователя. Процедура, внутри которой есть Ждать, сама должна быть объявлена с Асинх.
Частые ошибки при вызове функции и как их исправить
Экспорт или функция формы с другой директивой компиляции, недоступная из места вызова.Неопределено или ссылка вместо объекта.Возврат. Поставьте точку останова на КонецФункции — если отладчик туда дошёл, значит, возврат пропущен.Знач к параметру.= стоит процедура. Переделайте её в функцию с Возврат или вызовите отдельной строкой.Отдельно про рекурсию: функция может вызывать сама себя, это нормальный приём для обхода иерархии справочника. Но без условия выхода она уйдёт в бесконечный вызов и упадёт с ошибкой переполнения стека. Всегда проверяйте, что на каждом шаге вход уменьшается — например, вы спускаетесь к дочерним элементам, у которых их нет бесконечно.
Как писать вызовы функций быстро и читаемо
Несколько привычек, которые заметно упрощают сопровождение кода.
Одна функция — одна задача. Если в имени напрашивается «И» (ПроверитьИЗаписать), это две функции. Короткие функции проще вызывать из разных процедур и проще тестировать.
Имя — глагол или существительное по смыслу. Функции, которые возвращают значение, называйте по результату: ОсновнойДоговор(), ОстатокТовара(). Процедуры — по действию: ЗаполнитьТабличнуюЧасть().
Не вызывайте сервер в цикле. Классическая ошибка производительности:
// плохо: N серверных вызовов
Для Каждого Строка Из Объект.Товары Цикл
Строка.Цена = ЦенаНаСервере(Строка.Номенклатура);
КонецЦикла;
При 200 строках это 200 обращений к серверу. Лучше собрать номенклатуру в массив, передать его одним вызовом и получить соответствие «товар → цена»:
// хорошо: один серверный вызов
СписокТоваров = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
СписокТоваров.Добавить(Строка.Номенклатура);
КонецЦикла;
Цены = ЦеныНаСервере(СписокТоваров);
Для Каждого Строка Из Объект.Товары Цикл
Строка.Цена = Цены.Получить(Строка.Номенклатура);
КонецЦикла;
Здесь ЦеныНаСервере — функция &НаСервереБезКонтекста с параметром Знач Товары, которая одним запросом выбирает цены и возвращает Соответствие.
Предпочитайте &НаСервереБезКонтекста. Директива &НаСервере тащит на сервер всю форму с реквизитами. Если функции нужны только два-три значения, передайте их параметрами.
Не злоупотребляйте Выполнить() и Вычислить(). Вызов функции по имени из строки работает, но компилятор не проверит имя, а в безопасном режиме и в веб-клиенте такой код ограничен. В типовых конфигурациях на БСП для этого есть общая функция ОбщегоНазначения.ВыполнитьМетодКонфигурации, которая проверяет имя перед вызовом.
Комментируйте параметры экспортных функций. Перед объявлением принято писать блок: что делает функция, какие параметры, какого типа и что возвращает. Конфигуратор и EDT показывают этот комментарий в подсказке при наборе вызова.
Частые вопросы
Можно ли вызвать функцию без присваивания результата?
Можно ли объявить процедуру внутри другой процедуры?
Сколько параметров может быть у функции?
Чем отличается Возврат в процедуре и в функции?
Возврат; без значения просто завершает её досрочно. В функции после Возврат пишется значение, которое получит вызывающий код.Почему функция с клиента работает медленно, хотя код в ней простой?
&НаСервере: на сервер уходит вся форма. Переведите функцию на &НаСервереБезКонтекста и поставьте Знач у параметров.