Где хранятся анонимные функции обратного вызова для setTimeout?

У меня есть простая программа, которая выглядит так:

console.info("Start of the program execution!");

setTimeout(() => {
    console.info("I ran after a second!");
}, 1000);

console.info("The end of the file!");

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

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

function doSth()
{
    console.info("I ran after a second!");
}

console.info("Start of the program execution!");

setTimeout(doSth, 1000);

console.info("The end of the file!");

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

Для некоторых наблюдений я сначала поместил отладчик в пятую строку программы; console.info("The end of the file!");. Затем я смотрю на Scope Инструментов разработчика. Анонимной функции нигде не видно.

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

Таким образом, эти два наблюдения приводят меня в замешательство. Если анонимная функция не присутствовала в глобальной памяти ПОСЛЕ вызова setTimeout(), то как ее можно поместить в стек вызовов, когда setTimeout() завершит свою работу? Функция «идет» с setTimeout() в домен webAPIs, а затем возвращается оттуда?

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

Barmar 28.06.2024 21:29

Функция не обязательно должна находиться в какой-либо области видимости. Это просто объект, на который ссылается объект таймаута.

Barmar 28.06.2024 21:30

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

Barmar 28.06.2024 21:30

@Barmar Имеет смысл. У меня вопрос: где хранится функция (или функциональный объект, как вы ее называете)? Я не вижу его нигде в глобальной памяти, и вдруг он появляется в стеке вызовов!

mukesharyal 28.06.2024 21:31

Цикл событий содержит список объектов таймера в некоторой внутренней переменной. Объекты таймера содержат ссылки на свои функции обратного вызова. Этот список не отображается ни в одном общедоступном интерфейсе.

Barmar 28.06.2024 21:32

@Бармар Опять! Согласованный! «Объекты таймера содержат ссылки на свои функции обратного вызова». Конечно, они должны это сделать. Но где эта функция в глобальной памяти? Объект таймера содержит ссылки. Ссылки куда? Я не вижу этого в памяти.

mukesharyal 28.06.2024 21:35

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

Keith 28.06.2024 21:35

логически, код остается текстом, помещенным «в режим ожидания» во время задержки, и этот код появляется только тогда, когда о нем позаботится интерпретатор JS.

Mister Jojo 28.06.2024 21:38

@Keith Это имеет смысл. Не могли бы вы предоставить дополнительную информацию о том, как setTimeout() работает под капотом? Знает ли setTimeout(), где в куче находится функция? Все ли webAPIs «знают» об объектах, которые мы им предоставляем, и их фактическом положении в куче?

mukesharyal 28.06.2024 21:40

«Анонимной функции нигде не видно». - куда ты смотрел? Если бы вы сделали снимок кучи, вы бы его нашли.

Bergi 28.06.2024 21:45

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

Bergi 28.06.2024 21:47
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
11
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете себе представить, что setTimeout() и setInterval() реализованы примерно так:

class Timer {
  static allTimers = {};
  static timerId = 0;

  constructor(timeout, func, interval) {
    this.timerId = ++timerId;
    this.timeout = timeout;
    this.func = func;
    this.interval = interval;
    this.expire_time = Date.now() + timeout;
    this.allTimers[timerId] = this;
    addTimerToEventLoop(this);
  }
  
  clear(id) {
    let timer = this.allTimers[id];
    if (timer) {
      removeTimerFromEventLoop(timer);
    }
}

function setTimeout(timeout, func) {
  let newTimer = new Timer(timeout, func, false);
  return newTimer.timerId;
}

function setInterval(timeout, func) {
  let newTimer = new Timer(timeout, func, true);
  return newTimer.timerId;
}

Функции addTimerToEventLoop() и removeTimerFromEventLoop() представляют собой внутренний интерфейс с циклом событий, который заставляет их запускаться по расписанию.

Функции хранятся в свойстве func каждого объекта Timer. Если бы класс Timer действительно был виден пользовательскому коду, а не был внутренним для движка JavaScript, вы могли бы найти его в Timer.allTimers.

Ответ принят как подходящий

Стандарт HTML определяет шаги инициализации setTimeout. Он включает в себя определение задачи на шаге 5:

  1. Пусть задача — это задача, выполняющая следующие подэтапы:

        [1. ... 2. ...]

    1. Если обработчик является функцией, вызовите обработчик

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

По истечении периода ожидания выполняется шаг, называемый CompleteStep, описанный в шаге 11 той же процедуры:

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

Фактическое выполнение этого шага алгоритма описано в разделе выполнить шаги после таймаута.

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

Порядок оценки

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

  1. Функция обратного вызова (объект):

    () => {
        console.info("I ran after a second!");
    }
    

    Именно на этом этапе выделяется функциональный объект. Затем:

  2. 1000

    Это простое примитивное значение.

  3. Выражение setTimeout:

    setTimeout(() => {
        console.info("I ran after a second!");
    }, 1000);
    

    В этот момент вызывается setTimeout, и он получает ссылку на объект функции, созданный на шаге 1, и значение примитива 1000.

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

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

Итак, если функция уже существует в глобальной памяти, то объект задачи имеет ссылку на эту функцию, а если это анонимная функция, СНАЧАЛА она сохраняется в куче с помощью setTimeout(), а ЗАТЕМ ее ссылка сохраняется в объект задачи. Это то, что ты пытаешься сказать? Кто выполняет работу по хранению этой функции в куче? Это JavaScript или setTimeout()?

mukesharyal 29.06.2024 06:45

Функция уже существует в памяти до вызова setTimeout. Это общий принцип языков программирования: если есть f(x, y), то оценивается x, затем оценивается y и затем вызывается f. Результатом вычисления выражения функции является объект функции, который размещается в куче, как и любой другой объект JavaScript. setTimeout вызывается и получает ссылку на эту память. Эту ссылку он затем копирует в свое свойство объекта задачи, чтобы она не собиралась мусором при возврате setTimeout.

trincot 29.06.2024 08:06

Ого! Итак, когда я запустил setTimeout(), анонимная функция находилась в локальной области setTimeout(), которая затем добавила эту функцию из своей локальной области в объект задачи, и, таким образом, даже когда setTimeout() выскочила из стека, таким образом, имея свою локальную область видимости. область действия также была уничтожена, функция все еще находилась в объекте задачи и, следовательно, на нее ссылались оттуда, когда обратный вызов вызывался после установленного времени. Это именно то? Это ДЕЙСТВИТЕЛЬНО имеет абсолютный смысл. Большое спасибо. Я приму ответ сейчас, потому что на все мои вопросы были даны ответы и мои запросы выполнены. :-)

mukesharyal 29.06.2024 08:30

Да, это так, как вы пишете.

trincot 29.06.2024 08:31

Другие вопросы по теме

Похожие вопросы

Добавить числа в объект в массиве JavasScript из индекса ниже значения и возвращаемого значения
Печать и предварительный просмотр веб-страницы не работают должным образом в браузере Microsoft Edge
Возврат значений на страницу Razor — запутанный Javascript или .cs?
Сборка npm run не удалась из-за несовместимости машинописного текста или lodash
TypeScript «Нет перегрузки, соответствующей этому вызову»
Способ определения точных свойств стиля, которые в данный момент применяются веб-анимацией (без смешивания со всеми применяемыми в данный момент стилями)
Как загрузить изображение на холст в Gradio с помощью специального HTML и JavaScript?
Dash.js извлекает файлы .m4s при настройке video.currentTime и приводит к ошибкам. Ошибки при настройке video.currentTime в Dash.js
Загрузка файлового потока из браузера, получение запроса
Класс JavaScript со статическими методами и объект со свойствами функции