JavaScript: понимание замыканий и подъема

1

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

sayHello(); // It works!

function sayHello() {
  console.log('Hello');
}

Я также понимаю, что замыкания позволяют функциям сохранять ссылки на переменные, объявленные в той же области:

function outerFxn() {
  let num = 0;

  function innerFxn() {
    console.log(num);
    num++;
  }

  return innerFxn;
}

const logNum = outerFxn();
logNum(); // 0
logNum(); // 1
logNum(); // 2

Все идет нормально. Но здесь какая-то странность, что я надеюсь, что кто-то сможет точно объяснить, что происходит...

Сценарий 1: понятное закрытие

function zero(cb) {
  return setTimeout(cb, 0);
}

function test1() {
  let txt = 'this is a test message';

  function log() {
    console.log(txt);
  }

  zero(log);
}

В приведенном выше примере функция log сохраняет ссылку на область, в которой она была создана, удерживая переменную txt. Затем, когда он выполняется позже в setTimeout, он успешно регистрирует значение переменной txt. Отлично. Тогда вот это...

Сценарий 2: что происходит?

function zero(cb) {
  return setTimeout(cb, 0);
}

function test1() {
  function log() {
    console.log(txt);
  }

  let txt = 'this is a test message';

  zero(log);
}

Я переместил объявление функции log в верхнюю часть области (было бы все равно вставлено, верно?), Тогда я объявляю переменную txt ниже нее. Это все еще работает, и я не уверен, почему. Как log сохраняет ссылку на переменную txt если let и const не подняты? Рассматриваются ли области охвата как целое? Я мог бы немного понять, что движок JavaScript делает шаг за шагом здесь. Спасибо, земля!

  • 0
    Вы можете просто использовать этот альтернативный фрагмент: function f () {console.log ("a1:" + a); а = 123; console.log ("a2:" + a)} оба журнала печати 123
Теги:
hoisting
closures

4 ответа

2
Лучший ответ

"Анализируются ли закрытые области как целое?" - да. Закрытие сохраняет область действия, как и в тот момент, когда вы (лексически) покидаете ее. В вашем примере txt существует, когда вы достигаете закрытия } в test1, поэтому он находится в области видимости, и log не имеет проблем с доступом к нему.

Примечание "lexically" выше: привязки выполняются до времени выполнения, когда важна только ваша вещь. Таким образом, даже это сработает, хотя это не должно быть "с динамической" точки зрения:

function test1() {
    function log() {
        console.log(txt);
    }

    zero(log);
    let txt = 'this is a test message';
}
  • 0
    У меня была догадка, что сфера анализируется в целом. «... в тот момент, когда ты его покидаешь лексически» - вот чего я не понял. Спасибо!
4

Это часть области действия после того, как вы оставите функцию test1. Не имеет значения, если он используется с var, let или const в этой точке. Поскольку весь объект был оценен, переменная существует в области.

Если бы вы пытались использовать log до того, как let объявление let, вы получили ошибку.

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

  • 1
    Ваше редактирование выглядит неправильно для меня. Это var , которые подняты (существуют, но не инициализированы), let физически не существуют до объявления.
  • 0
    @georg, они существуют до объявления. В противном случае они могут быть заменены на более высокие области, но это не так. Это ссылки, которые еще не инициализированы, но они существуют.
Показать ещё 2 комментария
0

это указание времени/исполнения. Подумайте об этом

function test1(){
    var context = { }; 

    function log(){
        if(context.hasOwnProperty("txt")){
            console.log(context.txt); 
        }else{
            throw new Error("there is no value 'txt' declared in this context");
        }
    }

    context.txt = 'this is a test message';
    log();
}

То же самое в вашем коде с нестойкой переменной txt. В то время log выполнен, let txt будет объявлен в правильном контексте функции. Он доступен, даже если он не поднят. Функция log не хранит ссылку на саму переменную, но в целом окружающий контекст выполнения, и этот контекст хранит переменные.

0

В Сценарии 2 вы делаете:

  1. let txt = 'this is a test message' что означает, что txt будет частью области test1().
  2. В то же время вы объявляете log() который будет иметь доступ к области своего родительского test1().

Итак, что происходит во время выполнения? test1() будет оцениваться, и как таковой log() будет иметь доступ к области test1(). Это означает, что txt будет доступен для использования log() для немедленного использования.

Совет: отлаживайте его, ставьте несколько точек останова и смотрите, что происходит.

Изменить: вы также можете считать, что в log() txt не определен, и поэтому его значение должно быть неопределенным... правильно? Тот факт, что console.log(txt) работает, выводит this is a test message из-за приведенного выше пояснения по области видимости. Всегда хорошая практика объявлять ваши переменные в верхней части области функций, а ваши функции внизу области действия, поскольку они будут оцениваться в первую очередь. Рассмотрите человеческий фактор в этой ситуации, лучшая практика также может означать: для вас/любого, кто понимает, что делает код, просто читая его.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню