время жизни асинхронного обратного вызова javascript - PullRequest
0 голосов
/ 13 января 2019

Я новичок в javascript, но имею некоторый опыт работы с c ++, поэтому меня смущает следующее: я создаю какое-то соединение WebSocket из браузера следующим образом:

function ready() {
    var connection = new WebSocket('ws://localhost:3000');

    connection.onmessage = function(msg) {        
        console.log(msg.data);
    };
}

document.addEventListener("DOMContentLoaded", ready);

переменная connection является локальной, поэтому ссылка на объект WebSocket выходит из области видимости при выходе из ready(). Я ожидаю, что соединение должно прерваться вместе с обратным вызовом, но каким-то образом я все еще могу получить обратные вызовы connection.onmessage. Насколько я понимаю, это не связано с определением javascript, а с тем, как WebSocket соединения организованы внутри. Это так? Не могли бы вы объяснить это поведение, пожалуйста. Я полагаю, что то же самое относится к каждому асинхронному API в javascript, другой пример:

function get(url) {
    var req = new XMLHttpRequest();
    req.open("GET", url);

    req.onload = function() {
        console.log(req.response);
    }

    req.send();
}

get("file.json");

Так что кажется, что обратные вызовы не привязаны к объектам, на которые они надеты. Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 14 января 2019

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

Итак, в вашей функции вы устанавливаете обработчик событий для объекта connection. Пока где-то есть активный код, который все еще имеет ссылку на этот объект соединения, Javascript не будет собирать мусор для этого объекта.

В данном конкретном случае объект connection представляет собой действующее TCP-соединение с другим хостом, и за этим TCP-соединением есть и код Javascript, и собственный код, который по-прежнему имеет ссылку на объект соединения и может вызывать события на нем. Движок Javascript знает, что этот код все еще имеет ссылку на этот объект, поэтому он не будет собирать мусор. И, разумеется, каждый раз, когда новый пакет поступает в соединение webSocket, он вызывает ваш обработчик onMessage.

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

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

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

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

переменная соединения является локальной, поэтому ссылка на объект WebSocket выходит из области видимости, когда ready () выходит

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

Я ожидаю, что соединение должно прерваться вместе с обратным вызовом, но каким-то образом я все еще могу получить обратные вызовы connection.onmessage

см. Выше. Локальные переменные в Javascript могут жить долго после того, как их функция хоста вернется.

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

Это не относится конкретно к webSocket. Как описано выше, это происходит из-за сборки мусора в Javascript. Код, который управляет (все еще живым) соединением webSocket, имеет прямую ссылку на объект connection и таким образом предотвращает его сборку мусора. Чтобы завершить эту действующую ссылку, вы должны закрыть соединение webSocket, а затем код, управляющий соединением webSocket, очистит его ссылки на объект соединения, и тогда он будет пригоден для сборки мусора.

0 голосов
/ 13 января 2019

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

function ready() {
    var connection = new WebSocket('ws://localhost:3000');

    connection.onmessage = function(msg) {        
        console.log(msg.data);
        // this closes the websocket connection
        connection.close();
    };
}

document.addEventListener("DOMContentLoaded", ready);

Обновление

В целом вы беспокоитесь о том, как замыкания работают в JavaScript. Я не могу ссылаться на него напрямую, но если вы перейдете здесь и прокрутите вверх 3 кодовых блока, есть пример, касающийся обратных вызовов и замыканий. Это больше касается итераций, но содержит некоторые важные данные. Целую статью определенно стоит прочитать, чтобы лучше разобраться с некоторыми нюансами в JavaScript.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...