Области и замыкания, различное поведение для аналогичного варианта использования в соответствии с отладчиком [дубликат]

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

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

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

var milk = order_milk();
put_in_coffee(milk);

Поскольку JS не знает, что ему нужно дождаться окончания order_milk, прежде чем он выполнит put_in_coffee. Другими словами, он не знает, что order_milk является асинхронным - это то, что не приведет к молоку до некоторого будущего времени. JS и другие декларативные языки, выполняйте один оператор за другим, не ожидая.

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

order_milk(put_in_coffee);

order_milk запускает, заказывает молоко, тогда, когда и только когда он прибывает, он вызывает put_in_coffee.

Проблема с этот подход обратного вызова состоит в том, что он загрязняет нормальную семантику функции, сообщающей свой результат с помощью return; вместо этого функции должны сообщать свои результаты, вызывая обратный вызов, заданный как параметр. Кроме того, этот подход может быстро стать громоздким при работе с более длинными последовательностями событий. Например, предположим, что я хочу дождаться, когда молоко будет помещено в кофе, а затем и только затем выполните третий шаг, а именно - выпить кофе. В конце концов мне нужно написать что-то вроде этого:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

, где я перехожу к put_in_coffee как к молоку, чтобы положить в него, так и к действию (drink_coffee), чтобы выполнить как только молоко был введен. Такой код становится трудно писать, читать и отлаживать.

В этом случае мы могли бы переписать код в вопросе как:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Enter обещает

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

В случае нашего молока и кофе мы создаем order_milk, чтобы вернуть обещание о прибытии молока, затем укажите put_in_coffee как действие then следующим образом:

order_milk() . then(put_in_coffee)

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

order_milk() . then(put_in_coffee) . then(drink_coffee)

Давайте применим обещания к вашей конкретной проблеме. Мы завершим нашу логику запроса внутри функции, которая возвращает обещание:

function get_data() {
  return $.ajax('/foo.json');
}

На самом деле, все, что мы сделали, добавлено к return к вызову $.ajax. Это работает, потому что jQuery $.ajax уже возвращает вид обетоподобной вещи. (На практике, не вдаваясь в подробности, мы предпочли бы обернуть этот вызов, чтобы вернуть реальное обещание, или использовать некоторую альтернативу $.ajax, которая делает это.) Теперь, если мы хотим загрузить файл и дождаться его завершите, а затем сделайте что-нибудь, мы можем просто сказать

get_data() . then(do_something)

, например,

get_data() . 
  then(function(data) { console.log(data); });

. При использовании обещаний мы заканчиваем передачу множества функций в then, поэтому часто полезно использовать более компактные функции стрелок в стиле ES6:

get_data() . 
  then(data => console.log(data));

Ключевое слово async

Но все еще есть что-то неопределенное в том, что нужно писать код одним способом, если синхронно и совершенно по-другому, если асинхронно. Для синхронного мы пишем

a();
b();

, но если a является асинхронным, с обещаниями мы должны написать

a() . then(b);

Выше, мы сказали: «JS не имеет никакого способа узнать что ему нужно дождаться завершения первого вызова, прежде чем он выполнит второй ». Было бы неплохо, если бы можно было сказать JS? Оказывается, существует ключевое слово await, используемое внутри специального типа функции, называемого функцией «async». Эта функция является частью предстоящей версии ES, но уже доступна в транспилерах, таких как Babel, с учетом правильных настроек. Это позволяет нам просто написать

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

. В вашем случае вы могли бы написать что-то вроде

async function foo() {
  data = await get_data();
  console.log(data);
}
114
задан Gabe Kopley 9 February 2015 в 23:19
поделиться

5 ответов

Я нашел отчет о выпуске v8 , который именно о том, что вы просите.

Теперь, чтобы суммировать то, что сказано в этом выпуске ... v8 может хранить переменные, которые являются локальными для функции в стеке или в объекте «context», который живет в куче. Он будет выделять локальные переменные в стеке, пока функция не содержит никакой внутренней функции, которая относится к ним. Это оптимизация. Если любая внутренняя функция ссылается на локальную переменную, эта переменная будет помещена в объект контекста (т. Е. В куче, а не в стеке). Случай eval является особенным: если он вообще вызван внутренней функцией, все локальные переменные помещаются в объект контекста.

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

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

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

Вот пример «если какая-либо внутренняя функция относится к переменной, поместите ее в объект контекста». Если вы запустите это, вы сможете получить доступ к x в инструкции debugger, хотя x используется только в функции foo, , которая никогда не называется !

function baz() {
  var x = "x value";
  var z = "z value";

  function foo () {
    console.log(x);
  }

  function bar() {
    debugger;
  };

  bar();
}
baz();
111
ответ дан Louis 25 August 2018 в 20:26
поделиться

Я также заметил это в nodejs. Я полагаю (и я допускаю, что это только предположение), что при компиляции кода, если x не появляется внутри bar, он не делает x доступным в области bar. Это, вероятно, делает его несколько более эффективным; проблема в том, что кто-то забыл (или не заботился) о том, что даже если в bar нет x, вы можете решить запустить отладчик и, следовательно, еще нужно получить доступ к x изнутри bar.

6
ответ дан David Knipe 25 August 2018 в 20:26
поделиться

Ничего себе, действительно интересно!

Как уже упоминалось, это, по-видимому, связано с scope, но более конкретно, связано с debugger scope. Когда внедренный скрипт оценивается в инструментах разработчика, он, как представляется, определяет ScopeChain, что приводит к некоторой причудливости (поскольку оно связано с областью инспектора / отладчика). Вариант того, что вы разместили, это:

(EDIT - на самом деле, вы упомянули об этом в своем исходном вопросе, yikes, мой плохой! )

function foo() {
  var x = "bat";
  var y = "man";

  function bar() {
    console.log(x); // logs "bat"

    debugger; // Attempting to access "y" throws the following
              // Uncaught ReferenceError: y is not defined
              // However, x is available in the scopeChain. Weird!
  }
  bar();
}
foo();

Для амбициозного и / или любопытного масштаба (heh) из источника, чтобы узнать, что происходит:

https://github.com/WebKit/webkit/tree/master/Source / JavaScriptCore / inspector https://github.com/WebKit/webkit/tree/master/Source/JavaScriptCore/debugger

2
ответ дан Jack 25 August 2018 в 20:26
поделиться

Я подозреваю, что это связано с перестановкой переменных и функций. JavaScript приносит все объявления переменных и функций в начало функции, в которой они определены. Дополнительная информация здесь: http://jamesallardice.com/explaining-function-and-variable-hoisting-in-javascript/

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

function baz() {
  var x = "foo";

  function bar() {
    console.log(x); 
    debugger;
  };
  bar();
}

Как это делает:

function baz() {
  var x = "foo";

  function bar() {
    debugger;
    console.log(x);     
  };
  bar();
}

Надеюсь, что и / или ссылка выше помогает. Это мой любимый вид вопросов SO, BTW:)

0
ответ дан markle976 25 August 2018 в 20:26
поделиться

Как @Louis сказал, что это вызвано оптимизациями v8. Вы можете перемещать стек вызовов в кадр, где эта переменная видна:

Или заменить debugger на

eval('debugger');

eval отключит текущий кусок

14
ответ дан OwnageIsMagic 25 August 2018 в 20:26
поделиться
Другие вопросы по тегам:

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