Почему я могу использовать функцию до ее определения в JavaScript?


168

Этот код всегда работает, даже в разных браузерах:

function fooCheck() {
  alert(internalFoo()); // We are using internalFoo() here...

  return internalFoo(); // And here, even though it has not been defined...

  function internalFoo() { return true; } //...until here!
}

fooCheck();

Я не мог найти единственную ссылку на то, почему это должно работать, все же. Я впервые увидел это в записке Джона Резига, но это было только упомянуто. Там нет никакого объяснения там или где-либо в этом отношении.

Может ли кто-нибудь, пожалуйста, просветить меня?


3
В более новых версиях Firefox это не работает, если код находится в try / catch. Смотрите эту скрипку: jsfiddle.net/qzzc1evt
Джошуа Смит

Ответы:


217

functionДекларация магии и вызывает его идентификатор , чтобы быть связанным , прежде чем что - либо в его код-блок * выполняется.

Это отличается от присваивания с functionвыражением, которое оценивается в обычном порядке сверху вниз.

Если вы изменили пример, чтобы сказать:

var internalFoo = function() { return true; };

это перестало бы работать.

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

Это задокументировано в стандарте ECMAScript , раздел 10.1.3 . К сожалению, ECMA-262 не очень читаемый документ даже по стандартам!

*: содержащая функция, блок, модуль или скрипт.


Я думаю, что это действительно не читается. Я только что прочитал раздел, который вы указали в 10.1.3, и не понял, почему положения там будут вызывать такое поведение. Спасибо за информацию.
Эду Фелипе

2
@bobince Хорошо, я начал сомневаться в себе, когда не смог найти ни одного упоминания о термине «подъем» на этой странице. Надеемся, что в этих комментариях достаточно Google Juice ™, чтобы все исправить :)
Матиас Биненс

2
Это популярный вопрос / ответ. Рассмотрите возможность обновления со ссылкой / выдержкой из аннотированной спецификации ES5. (Что немного более доступно.)

1
В этой статье есть несколько примеров: JavaScript-Scoping-and-
Hoisting

Я обнаружил, что довольно много библиотек используют эту функцию до определения, даже некоторые языки разрешают ее официально, например. Haskell. Если честно, это может быть не плохо, так как в некоторых случаях вы можете написать немного более выразительно.
windmaomao

28

Это называется HOISTING - вызов (вызов) функции до того, как она была определена.

Два разных типа функций, о которых я хочу написать:

Функции выражения и функции объявления

  1. Функции выражения:

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

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

    let lastName = function (family) {
     console.log("My last name is " + family);
    };
    let x = lastName("Lopez");

    Вот как вы можете написать это в ECMAScript 6:

    lastName = (family) => console.log("My last name is " + family);
    
    x = lastName("Lopez");
  2. Функции декларации:

    Функции, объявленные со следующим синтаксисом, не выполняются немедленно. Они «сохраняются для последующего использования» и будут выполнены позже, когда они будут вызваны (вызваны). Этот тип функции работает, если вы называете ее ДО или ПОСЛЕ того, где определено. Если вы вызываете функцию объявления до того, как она была определена, Hoisting работает правильно.

    function Name(name) {
      console.log("My cat's name is " + name);
    }
    Name("Chloe");

    Подъемный пример:

    Name("Chloe");
    function Name(name) {
       console.log("My cat's name is " + name);
    }

let fun = theFunction; fun(); function theFunction() {}также будет работать (Node и браузеры)
fider

14

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

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

Ваш код может ссылаться на именованные объекты (переменные, другие функции и т. Д.), Которые определены далее, но вы не можете выполнить ссылочный код, пока не будут доступны все фрагменты.

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

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


3

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

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


2

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

http://www.dustindiaz.com/javascript-function-declaration-ambiguity/


Ссылка больше не мертва.
mwclarke

1
Ссылка мертва.
Джером Индефензо

Вот снимок с archive.org. Похоже, автор снял весь их сайт за устаревший контент, а не то, что этот пост попадает в эту категорию.
jkmartindale

1

Тело функции «internalFoo» должно идти куда-то во время синтаксического анализа, поэтому, когда код читается (или синтаксический анализ) интерпретатором JS, создается структура данных для функции и присваивается имя.

Только позже, затем код запускается, JavaScript фактически пытается выяснить, существует ли «internalFoo» и что это такое, можно ли его вызывать и т. Д.


-4

По той же причине следующее всегда будет помещать fooв глобальное пространство имен:

if (test condition) {
    var foo;
}

8
На самом деле, это по совсем другим причинам. ifБлок не создает сферу, в то время как function()блок всегда создает один. Реальная причина заключалась в том, что определение глобальных имен javascript происходит на этапе компиляции, поэтому даже если код не запускается, имя определяется. (Извините, что так долго комментировал)
Эду Фелипе
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.