Когда появляется ошибка определения переменных
Перейти к содержимому

Когда появляется ошибка определения переменных

  • автор:

Переменная не определена в 1С 8.3

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

Использование переменных в 1С

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

Тип переменной заранее не задается, а определяется после присвоения какого-либо значения. В памяти они не хранятся, а создаются в момент исполнения кода и стираются с завершением операций. Различают 3 вида переменных:

  • глобальные,
  • локальные,
  • экспортные.

Глобальные переменные

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

image002

Глобальные переменные можно использовать в модулях:

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

В модулях формы и команды для каждой процедуры/функции определяется место исполнения: на сервере (&НаСервере, &НаСервереБезКонтекста) или на клиенте (&НаКлиенте), при этом режим работы программы (серверный или файловый) не имеет значения. Объявленные глобальные переменные в этих модулях во время выполнения кода:

image003

  • &НаКлиенте — передаются от одной процедуры к другой с сохранением значения. При переводе исполнения кода на сервер переменные продолжают существовать на стороне клиента со значениями, которые имели до передачи на сервер;
  • &НаСервере — уничтожаются по окончании выполнения процедур/функций на сервере, обратная передача переменной на клиент не производится. При повторном обращении к серверу создается вновь со значением по умолчанию [Неопределено].

Локальные переменные

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

image004

Экспортные переменные

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

image005

Ошибка «Переменная не определена» в 1С 8.3

Когда программа 1С не может найти переменную, пользователю выдается сообщение «Переменная не определена ([Имя_переменной])».

image006

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

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

Если ни один из вышеуказанных способов не дал результата, то для решения проблемы с ошибкой «Переменная не определена» потребуется разобрать в программном коде и при необходимости внести правки.

Кроме текста ошибки в окне указано место ее возникновения:

  • Документ — вид объекта метаданных;
  • РеализацияТоваровУслуг — имя объекта;
  • Форма. ФормаДокументаТовары — модуль возникновения сбоя;
  • Форма(299,42) — номер строки в коде модуля и порядковый номер символа в строке.

Для перехода в конфигуратор нажмите Подробно — Конфигуратор . Если кнопки Подробно нет в окне:

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

В конфигураторе откроется модуль, в котором не удалось определить переменную, а курсор автоматически переместится на ее имя.

Способы устранения ошибки

Если переменная не определена в процедуре/функции, исполняемой на стороне клиента (&НаКлиенте):

  1. Проверьте код процедуры/функции до места, где идет обращение к переменной: возможно, произошла опечатка в имени.
    image007
  2. Просмотрите раздел объявления переменных в начале модуля: если есть сходные по наименованию переменные, исправьте описку в процедуре/функции.
    image008
  3. До момента использования в процедуре/функции локальной переменной не было присвоено никакого значения, и в памяти 1С ее еще не существует.
    image009
    С глобальными переменными такие случаи исключены, т. к. они создаются сразу после объявления в начале модуля со значением [Неопределено] и могут подставляться в выражения без предварительной записи в них значения.
    Для устранения ошибки потребуется вставить в код строку с присвоением переменной значения. По негласному соглашению программисты 1С при написании исполняемого кода дают имена объектам, реквизитам, процедурам, переменным и т. д., созвучные с записываемой в них информацией или выполняемыми действиями. Обратите внимание:
  4. когда появляется окно с ошибкой «Переменная не определена»: открытие формы, нажатие на кнопку, выбор из списка и т. д.? В примере окно с сообщением об ошибке выдается при попытке изменить дату документа;
  5. на наименование процедуры/функции, которая содержит строку кода с ошибкой —ДатаПриИзменении, содержит алгоритм действий при смене даты;
  6. на имя ненайденной переменной — ТекДатаДокумента, говорит о значении с типом Дата.
    Проанализировав эту информацию, определите, какое значение должно быть в переменной. В представленном коде, возможно, программист имел в виду дату документа до изменения.
    После определения данных, которые должна содержать переменная, в процедуру/функцию перед местом сбоя дописывается строка кода с присвоением переменной требуемого значения.
    image010
    В рассмотренном примере это реализовано через функцию, делающую на сервере запрос к базе на получение данных текущего документа, а поскольку он еще не сохранен, будет получена начальная версия объекта. Далее идет обращение к реквизиту документа Дата, который содержит дату до изменения.

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

image011

  • Попытка прочитать данные базы на стороне клиента. При работе с программой на компьютер пользователя с сервера загружается только необходимая в данный момент информация, все остальные сведения хранятся на сервере. Получить к ним доступ можно через выполнение процедуры/функции на сервере, в противном случае 1С выдаст ошибку «Переменная не определена», даже если код команды указан корректно.
  • Когда ошибку «Переменная не определена» 1С 8.3 выдает при выполнении серверной процедуры/функции (&НаСервере, &НаСервереБезКонтекста):

    1. Не присвоено значение локальной переменной, действия аналогичны использованию на клиенте с поправкой на место исполнения.
      Глобальные переменные на сервере создаются автоматически при выполнении процедур/функций с пометкой &НаСервере или &НаСервереБезКонтекста, так же, как на клиенте им присваивается начальное значение [Неопределено]. По завершении всех операций на сервере переменная уничтожается. При последующих вызовах сервера глобальная переменная примет начальное значение [Неопределено]. Последовательность процедур/функций в модулях не влияет на порядок их выполнения, переход с клиента на сервер и обратно определяется программным кодом.
    2. Переменная объявлена на стороне клиента, а используется на сервере, где ее не существует. Если переменная должна содержать значение, полученное в клиентской процедуре/функции, то можно передать ее на сервер через параметр.
      image012
    3. Глобальная переменная не объявлена — добавьте в начале модуля переменную для сервера.
      image013
    4. При обращении к базе данных неверно указаны имена объектов, параметров, из-за чего программа ошибочно приняла их за переменные — исправьте опечатку.
      image014image015

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

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

    См. также:
    • Метод объекта не обнаружен
    • Поле объекта не обнаружено
    • Нарушение прав доступа 1С 8.3: внешняя обработка
    • Неверный формат хранилища данных 1С
    • Неверный формат хранилища данных 1С 8.3 – как исправить?
    • Ошибка 1С 8.3 “Недостаточно памяти”

    Когда появляется ошибка определения переменных

    Warning: mysqli_connect(): (HY000/1040): Too many connections in /home/c/co37471/eljob/public_html/core/main/Msql.php on line 49

    Warning: mysqli_connect(): (HY000/1040): Too many connections in /home/c/co37471/eljob/public_html/core/main/Msql.php on line 53

    Warning: mysqli_connect(): (HY000/1040): Too many connections in /home/c/co37471/eljob/public_html/core/main/Msql.php on line 53

    Warning: mysqli_connect(): (HY000/1040): Too many connections in /home/c/co37471/eljob/public_html/core/main/Msql.php on line 53
    Some mysqli shit happened: 1040 Too many connections

    Переменная не определена в 1С 8.3

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

    • чистка кеша;
    • тестирование и исправление информационной базы;
    • просмотр зарегистрированных ошибок на сайте 1С,

    придется разбираться с местом возникновения ошибки в 1С самостоятельно.

    БухЭксперт8 подготовил 3 примера получения и исправления ошибки:

    • в доработанном программном коде;
    • при выводе печатной формы;
    • при исполнении кода на Клиенте.

    Ошибка в доработанном программном коде

    В форму типового отчета Движение товаров программист добавил поле Склад . Список выбора значений в это поле ограничен. При открытии отчета появляется ошибка:

    В сообщении об ошибке указано место возникновения ошибки — строка 919 в модуле формы. Для исправления ошибки необходимо выполнить следующие действия:

    1. Нажмите кнопку Подробно в форме сообщения об ошибке.
      В окне Обнаружены ошибки программа укажет:
    2. номер неправильной строки кода;
    3. исполняемую команду, которая привела к ошибке.

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

  • Для перехода в место ошибки нажмите кнопку Конфигуратор .
    В Конфирураторе курсор автоматически установится на строке программного кода с ошибкой. Необходимо проверить используемые командой данные. В данном примере ошибка сигнализирует, что переменной ОсновнойСклад не задано значение: при добавлении в список складов программа не понимает, какой склад требуется добавить.
  • Исправьте ошибку.
    Укажите нужное значение переменной в программном коде, например:
    • ОсновнойСклад = Справочники.Склады.НайтиПоНаименованию(«Основной склад»);
    • ОсновнойСклад = Справочники.Склады.ОсновнойСклад.

      Синтаксис Справочники.Склады.ОсновнойСклад используется только для предопределенного склада с именем ОсновнойСклад :

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

    Ошибка вывода печатной формы

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

    В макет документа добавлена область Подвал :

    В программном коде дописан вывод данных области Подвал :

    После доработки программы при попытке вывода накладной на печать 1С выдает ошибку:

    Информация в окне сообщения об ошибке указывает, что в Модуле менеджера документа ПоступлениеТоваровУслуг не определено значение переменной Ответственный . Для исправления ошибки:

    • Самоучитель по 1С Бухгалтерии 8.3;
    • Самоучитель по 1С ЗУП 8.3.

    1. Перейдите в Конфигуратор . В Модуле менеджера документа ПоступлениеТоваровУслуг перейдите на строку 4804.
    2. Укажите для вывода в печатную форму поля Ответственный — текущего пользователя, используя специальную команду 1С:
      • Пользователи.ТекущийПользователь().
    3. Сохраните изменения и обновите информационную базу.
    4. Перейдите в пользовательский режим для проверки выполненных изменений.

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

    Ошибка исполнения кода на Клиенте

    С переходом на управляемые формы в Бухгалтерия 3.0 работы программистам прибавилось.

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

    Все это привело к тому, что в 1С команды делятся на исполняемые &НаСервере и &НаКлиенте . И при написании программного кода нужно это учитывать, иначе программа не сможет выполнить заданную операцию. При работе с переменными при написании программного кода важно понимать:

    • &НаКлиенте — переменные существуют только на время клиентского вызова;
    • &НаСервере— переменные существуют только на время серверного вызова.

    Разделение команд в программном коде не зависит от того, используете ли вы файловую или клиент-серверную 1С. Разграничение должно четко исполняться, иначе будут возникать ошибки, одну из которых БухЭксперт8 разберет на примере.

    При доработке программного кода программист использовал операцию получения данных склада &НаКлиенте в процедуре ПриОткрытии .

    При сохранении изменений 1С сообщила об ошибке Переменная не определена (Справочники) при определении значения склада:

    • «?»Справочники.Склады.НайтиПоКоду(«»00-000002»).

    Ошибка связана с тем, что данная операция исполняется только на сервере, т. е. &НаСервере . И несмотря на то, что сама команда исполнения корректна, ошибка будет формироваться до тех пор, пока ее не перенесут в область команд исполнения &НаСервере , например так:

    Теперь сохранение изменений пройдет без ошибки.

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

    См. также:
    • Большое количество забивается решеткой
    • Печатная форма недоступна 1С 8.3 при вызове внешней печатной формы
    • Значение поля номер не уникально 1С 8.3: как исправить
    • У пользователя недостаточно прав на исполнение операции

    Помогла статья?

    Получите еще секретный бонус и полный доступ к справочной системе БухЭксперт8 на 14 дней бесплатно

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

    1. Большое количество забивается решеткой в 1СОшибка Большое количество забивается решеткой в 1С 8.3 показывает, что.
    2. Исправление технических ошибок при работе с 1С:Бухгалтерия: для начинающих и опытных пользователей.
    3. Значение не является значением объектного типа 1СКонфигурация 1С состоит из объектов: Константы, Документы, Регистры и ряда.
    4. Как исправить технические ошибки 1С самостоятельноБухЭксперт8 разработала специальную памятку «Help-Desk для бухгалтера». Она поможет исправить.

    Оцените публикацию
    (Пока оценок нет)

    Публикацию можно обсудить в комментариях ниже.
    Обратите внимание!
    В комментариях наши эксперты не отвечают на вопросы по программам 1С и законодательству.
    Задать вопрос нашим специалистам можно в Личном кабинете

    Область видимости переменных, замыкание

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

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

    Но что произойдёт, когда внешние переменные изменятся? Функция получит последнее значение или то, которое существовало на момент создания функции?

    И что произойдёт, когда функция переместится в другое место в коде и будет вызвана оттуда – получит ли она доступ к внешним переменным своего нового местоположения?

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

    Мы будем говорить о переменных let/const здесь

    В JavaScript существует три способа объявить переменную: let , const (современные), и var (пережиток прошлого).

    • В этой статье мы будем использовать переменные let в примерах.
    • Переменные, объявленные с помощью const , ведут себя так же, так что эта статья и о них.
    • Старые переменные var имеют несколько характерных отличий, они будут рассмотрены в главе Устаревшее ключевое слово «var».

    Блоки кода

    Если переменная объявлена внутри блока кода <. >, то она видна только внутри этого блока.

     < // выполняем некоторые действия с локальной переменной, которые не должны быть видны снаружи let message = "Hello"; // переменная видна только в этом блоке alert(message); // Hello >alert(message); // ReferenceError: message is not defined

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

    Без блоков была бы ошибка

    Обратите внимание, что без отдельных блоков возникнет ошибка, если мы используем let с существующим именем переменной:

    // показать сообщение let message = "Hello"; alert(message); // показать другое сообщение let message = "Goodbye"; // SyntaxError: Identifier 'message' has already been declared alert(message);

    Для if , for , while и т.д. переменные, объявленные в блоке кода <. >, также видны только внутри:

    if (true) < let phrase = "Hello"; alert(phrase); // Hello >alert(phrase); // Ошибка, нет такой переменной!

    В этом случае после завершения работы if нижний alert не увидит phrase , что и приведет к ошибке.

    И это замечательно, поскольку это позволяет нам создавать блочно-локальные переменные, относящиеся только к ветви if .

    То же самое можно сказать и про циклы for и while :

    for (let i = 0; i < 3; i++) < // переменная i видна только внутри for alert(i); // 0, потом 1, потом 2 >alert(i); // Ошибка, нет такой переменной!

    Визуально let i = 0; находится вне блока кода <. >, однако здесь в случае с for есть особенность: переменная, объявленная внутри (. ) , считается частью блока.

    Вложенные функции

    Функция называется «вложенной», когда она создаётся внутри другой функции.

    Это очень легко сделать в JavaScript.

    Мы можем использовать это для упорядочивания нашего кода, например, как здесь:

    function sayHiBye(firstName, lastName) < // функция-помощник, которую мы используем ниже function getFullName() < return firstName + " " + lastName; >alert( "Hello, " + getFullName() ); alert( "Bye, " + getFullName() ); >

    Здесь вложенная функция getFullName() создана для удобства. Она может получить доступ к внешним переменным и, значит, вывести полное имя. В JavaScript вложенные функции используются очень часто.

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

    Ниже, makeCounter создает функцию «счётчик», которая при каждом вызове возвращает следующее число:

    function makeCounter() < let count = 0; return function() < return count++; // есть доступ к внешней переменной "count" >; > let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2

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

    Как это работает? Если мы создадим несколько таких счётчиков, будут ли они независимыми друг от друга? Что происходит с переменными?

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

    Лексическое окружение

    Здесь водятся драконы!

    Глубокое техническое описание – впереди.

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

    Для большей наглядности объяснение разбито на несколько шагов.

    Шаг 1. Переменные

    В JavaScript у каждой выполняемой функции, блока кода <. >и скрипта есть связанный с ними внутренний (скрытый) объект, называемый лексическим окружением LexicalEnvironment .

    Объект лексического окружения состоит из двух частей:

    1. Environment Record – объект, в котором как свойства хранятся все локальные переменные (а также некоторая другая информация, такая как значение this ).
    2. Ссылка на внешнее лексическое окружение – то есть то, которое соответствует коду снаружи (снаружи от текущих фигурных скобок).

    «Переменная» – это просто свойство специального внутреннего объекта: Environment Record . «Получить или изменить переменную», означает, «получить или изменить свойство этого объекта».

    Например, в этом простом коде только одно лексическое окружение:

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

    На картинке выше прямоугольник означает Environment Record (хранилище переменных), а стрелка означает ссылку на внешнее окружение. У глобального лексического окружения нет внешнего окружения, так что она указывает на null .

    По мере выполнения кода лексическое окружение меняется.

    Вот более длинный код:

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

    1. При запуске скрипта лексическое окружение предварительно заполняется всеми объявленными переменными.
      • Изначально они находятся в состоянии «Uninitialized». Это особое внутреннее состояние, которое означает, что движок знает о переменной, но на нее нельзя ссылаться, пока она не будет объявлена с помощью let . Это почти то же самое, как если бы переменная не существовала.
    2. Появляется определение переменной let phrase . У неё ещё нет присвоенного значения, поэтому присваивается undefined . С этого момента мы можем использовать переменную.
    3. Переменной phrase присваивается значение.
    4. Переменная phrase меняет значение.

    Пока что всё выглядит просто, правда?

    • Переменная – это свойство специального внутреннего объекта, связанного с текущим выполняющимся блоком/функцией/скриптом.
    • Работа с переменными – это на самом деле работа со свойствами этого объекта.

    Лексическое окружение – объект спецификации

    «Лексическое окружение» – это объект спецификации: он существует только «теоретически» в спецификации языка для описания того, как все работает. Мы не можем получить этот объект в нашем коде и манипулировать им напрямую.

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

    Шаг 2. Function Declaration

    Функция – это тоже значение, как и переменная.

    Разница заключается в том, что Function Declaration мгновенно инициализируется полностью.

    Когда создается лексическое окружение, Function Declaration сразу же становится функцией, готовой к использованию (в отличие от let , который до момента объявления не может быть использован).

    Именно поэтому мы можем вызвать функцию, объявленную как Function Declaration, до самого её объявления.

    Вот, к примеру, начальное состояние глобального лексического окружения при добавлении функции:

    Конечно, такое поведение касается только Function Declaration, а не Function Expression, в которых мы присваиваем функцию переменной, например, let say = function(name) <. >.

    Шаг 3. Внутреннее и внешнее лексическое окружение

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

    Например, для say(«John») это выглядит так (выполнение находится на строке, отмеченной стрелкой):

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

    • Внутреннее лексическое окружение соответствует текущему выполнению say . В нём находится одна переменная name , аргумент функции. Мы вызываем say(«John») , так что значение переменной name равно «John» .
    • Внешнее лексическое окружение – это глобальное лексическое окружение. В нём находятся переменная phrase и сама функция.

    У внутреннего лексического окружения есть ссылка на внешнее outer .

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

    Если переменная не была найдена, это будет ошибкой в строгом режиме ( use strict ). Без строгого режима, для обратной совместимости, присваивание несуществующей переменной создаёт новую глобальную переменную с таким же именем.

    Давайте посмотрим, как происходит поиск в нашем примере:

    • Для переменной name , alert внутри say сразу же находит ее во внутреннем лексическом окружении.
    • Когда alert хочет получить доступ к phrase , он не находит её локально, поэтому вынужден обратиться к внешнему лексическому окружению и находит phrase там.

    Шаг 4. Возврат функции

    Давайте вернёмся к примеру с makeCounter :

    function makeCounter() < let count = 0; return function() < return count++; >; > let counter = makeCounter();

    В начале каждого вызова makeCounter() создается новый объект лексического окружения, в котором хранятся переменные для конкретного запуска makeCounter .

    Таким образом, мы имеем два вложенных лексических окружения, как в примере выше:

    Отличие заключается в том, что во время выполнения makeCounter() создается крошечная вложенная функция, состоящая всего из одной строки: return count++ . Мы ее еще не запускаем, а только создаем.

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

    Таким образом, counter.[[Environment]] имеет ссылку на лексического окружения. Так функция запоминает, где она была создана, независимо от того, где она вызывается. Ссылка на [[Environment]] устанавливается один раз и навсегда при создании функции.

    Впоследствии, при вызове counter() , для этого вызова создается новое лексическое окружение, а его внешняя ссылка на лексическое окружение берется из counter.[[Environment]] :

    Теперь, когда код внутри counter() ищет переменную count , он сначала ищет ее в собственном лексическом окружении (пустом, так как там нет локальных переменных), а затем в лексическом окружении внешнего вызова makeCounter() , где находит count и изменяет ее.

    Переменная обновляется в том лексическом окружении, в котором она существует.

    Вот состояние после выполнения:

    Если мы вызовем counter() несколько раз, то в одном и том же месте переменная count будет увеличена до 2 , 3 и т.д.

    В программировании есть общий термин: «замыкание», – который должен знать каждый разработчик.

    Замыкание – это функция, которая запоминает свои внешние переменные и может получить к ним доступ. В некоторых языках это невозможно, или функция должна быть написана специальным образом, чтобы получилось замыкание. Но, как было описано выше, в JavaScript, все функции изначально являются замыканиями (есть только одно исключение, про которое будет рассказано в Синтаксис «new Function»).

    То есть они автоматически запоминают, где были созданы, с помощью скрытого свойства [[Environment]] , и все они могут получить доступ к внешним переменным.

    Когда на собеседовании фронтенд-разработчику задают вопрос: «что такое замыкание?», – правильным ответом будет определение замыкания и объяснения того факта, что все функции в JavaScript являются замыканиями, и, может быть, несколько слов о технических деталях: свойстве [[Environment]] и о том, как работает лексическое окружение.

    Сборка мусора

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

    Однако если существует вложенная функция, которая все еще доступна после завершения функции, то она имеет свойство [[Environment]] , ссылающееся на лексическое окружение.

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

    function f() < let value = 123; return function() < alert(value); >> let g = f(); // g.[[Environment]] хранит ссылку на лексическое окружение // из соответствующего вызова f()

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

    function f() < let value = Math.random(); return function() < alert(value); >; > // 3 функции в массиве, каждая из которых ссылается на лексическое окружение // из соответствующего вызова f() let arr = [f(), f(), f()];

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

    В приведенном ниже коде после удаления вложенной функции ее окружающее лексическое окружение (а значит, и value ) очищается из памяти:

    function f() < let value = 123; return function() < alert(value); >> let g = f(); // пока существует функция g, value остается в памяти g = null; // . и теперь память очищена.

    Оптимизация на практике

    Как мы видели, в теории, пока функция жива, все внешние переменные тоже сохраняются.

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

    Одним из важных побочных эффектов в V8 (Chrome, Edge, Opera) является то, что такая переменная становится недоступной при отладке.

    Попробуйте запустить следующий пример в Chrome с открытой Developer Tools.

    Когда код будет поставлен на паузу, напишите в консоли alert(value) .

    function f() < let value = Math.random(); function g() < debugger; // в консоли: напишите alert(value); Такой переменной нет! >return g; > let g = f(); g();

    Как вы можете видеть – такой переменной не существует! В теории, она должна быть доступна, но попала под оптимизацию движка.

    Это может приводить к забавным (если удаётся решить быстро) проблемам при отладке. Одна из них – мы можем увидеть не ту внешнюю переменную при совпадающих названиях:

    let value = "Сюрприз!"; function f() < let value = "ближайшее значение"; function g() < debugger; // в консоли: напишите alert(value); Сюрприз! >return g; > let g = f(); g();

    Эту особенность V8 полезно знать. Если вы занимаетесь отладкой в Chrome/Edge/Opera, рано или поздно вы с ней столкнётесь.

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

    Задачи

    Учитывает ли функция последние изменения?

    важность: 5

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

    let name = "John"; function sayHi() < alert("Hi, " + name); >name = "Pete"; sayHi(); // что будет показано: "John" или "Pete"?

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

    Итак, вопрос: учитывает ли она последние изменения?

    Ответ: Pete.

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

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

    Какие переменные доступны?

    важность: 5

    Приведенная ниже функция makeWorker создает другую функцию и возвращает ее. Эта новая функция может быть вызвана из другого места.

    Будет ли она иметь доступ к внешним переменным из места своего создания, или из места вызова, или из обоих мест?

    function makeWorker() < let name = "Pete"; return function() < alert(name); >; > let name = "John"; // создаём функцию let work = makeWorker(); // вызываем её work(); // что будет показано?

    Какое значение будет показано? «Pete» или «John»?

    Ответ: Pete.

    Функция work() в приведенном ниже коде получает name из места его происхождения через ссылку на внешнее лексическое окружение:

    Таким образом, в результате мы получаем «Pete» .

    Но если бы в makeWorker() не было let name , то поиск шел бы снаружи и брал глобальную переменную, что мы видим из приведенной выше цепочки. В этом случае результатом было бы «John» .

    Независимы ли счётчики?

    важность: 5

    Здесь мы делаем два счётчика: counter и counter2 , используя одну и ту же функцию makeCounter .

    Они независимы? Что покажет второй счётчик? 0,1 или 2,3 или что-то ещё?

    function makeCounter() < let count = 0; return function() < return count++; >; > let counter = makeCounter(); let counter2 = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter2() ); // ? alert( counter2() ); // ?

    Ответ: 0,1.

    Функции counter и counter2 созданы разными вызовами makeCounter .

    Так что у них независимые внешние лексические окружения, у каждого из которых свой собственный count .

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *