Pull to refresh

Comments 71

Проверял русский спелчекер… Спасибо, исправил.
>Область человек видимости в JavaScript переменных и объявлений функций
Промт такой промт.
Переводчик или пошутил или ошибся. Топик, в целом, переведен неплохо.
Я не знаю, есть ли официальное название для такого поведения, но мне нравится использовать термин «поднятие»(«hoisting»).

Это «замыкание» (closure).
Также добавлю, что в новом JavaScript есть ключевое слово «let», которое используется так же, как и var, но реализует такой же принцип области видимости, как в Си — на уровне блоков.
Там именно hoisting, т.к. Function Declaration определяются на момент входа в контекст функции.
И почему «hoisting», если в JavaScript это называется «closure»?
При чем тут closure? Замыкание--это как бы автоматическая передача в анонимную функцию всех внешних переменных. А в статье имеется в виду поднятие к началу функции объявления переменных из этой же функции.
Хм, я не так смотрел на определение, думал он имеет ввиду именно передачу в функцию внешних переменных.
Ты помоему немного в терминах запутался.
Замыкание это по сути механизм определения функций, ничего про переменные он не описывает.
Не определения, а… черт, замыкание несколькоих локальных переменных таким образом, чтоб они были доступны в функции, которая может быть «отдана» за пределы видимости замыкаемых переменных.

Простейший пример замыкания:
function a() {
   var x = 5;
   return function() { return x; }
}

тут происходит замыкание переменной «х» внутрь возвращаемой функции.

Это никак не относится к hoisting-гу, который определяет видимость деклараций переменных и названий функций.

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

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

В то же время, если бы в С# было бы «поднятие», то оно было бы реализованно строго на уровне синтаксического анализатора, не создавая никаких новых сущностей (как в «замыкании»).

Мне кажется тут действительно серьезное отличие. И ИМХО лучше бы не было «поднятия» — оно только конфузит чтение кода.
Stoyan Stefanov. JavaScript Шаблоны 2011 — используется термин «подъем».

Вобще более прижившийся перевод термина hoisting — всплытие, но не суть.
Простите, а зачем () в пятом примере в строке после "//какой-то код"?
Чтобы выполнить анонимную функцию, которая используется для создания области видимости.
Спасибо. Впервые просто с таким сталкиваюсь.
Спасибо, интересно и познавательно. Только зачем так было сделано? Одна из раскоряк, из-за которых можно не любить js.
Php, lua и ряд других языков используют туже модель.
Хотя только в php(имхо) правильно додумались еще добавить возможность указывать нужные импорты из parent scope(как бы это на русском то сказать?)
parent scope (как бы это на русском то сказать?)
«Из уровня выше». На деле же в PHP из глобальной зоны видимости.
я имел в виду именно локальную зону видимости.
Бичь js — хавание(всех без разбору) видимых переменных замыканиями с последующими утечками памяти в php обходиться ну очень просто.
я имел в виду именно локальную зону видимости.
В каком месте? Я уже запутался.
Бичь js — хавание (всех без разбору) видимых переменных замыканиями с последующими утечками памяти в php обходиться ну очень просто.
Я не понимаю почему это бич JS. Объясните?
я имел в виду именно локальную зону видимости.
Я понял о чём вы говорили — о синтаксисе анонимных функций в PHP 5.3, я думал совсем о другом.
ну вы преувеличиваете, указание локальной видимости еще было в Perl: my, our, local.
В нем и не такие перлы есть
Кстати,

global $var в PHP — синтаксический сахар от $var =& $GLOBALS['vars'].
Эх коммандер, но вот кого не ожидал глобала так от Вас.
Руки — руки отрывать за глобалы как в php так и в js.
У меня ощущение, что я разговариваю с двумя людьми под одним логином. Вы написали:
Хотя только в php (имхо) правильно додумались еще добавить возможность указывать нужные импорты из parent scope (как бы это на русском то сказать?)
Вы же про global $var пишете, нет? Я и уточнил кое-что про global $var.
я говорил про замыкания.
function a(){
$somevar= 1;
$someotherVal = 'a very big string';
$closure = function (b) use($somevar){
return b($somevar)
}
return $closure;
}

a(function($v){echo($v))}


тут — someotherVal — будет скушано GC, в JS — она получит count++ так как видима из замыкания и не сожрется GC.
Особо тяжко в JS это проявлятся в IE при общении с DOM.
Особо тяжко в JS это проявлятся в IE при общении с DOM.
Это проблемы IE, а не JS.

Я не в курсе какой GC в JS и есть ли какой-то, положенный по стандарту, но всем проблемам с освобождением ссылок (в т.ч. циклических) не один год и они все более-менее имеют решение. PHP получил хороший GC только недавно и от дизайна языка это никак не зависит.
Да вот как-то не совсем это ИЕ проблемы.
В ИЕ просто GC на дом ноды( как на НЕ обьекты JS ) не распространяется.
В любых других браузерах теже грабли — пока последний адресатор стека не помреть — все переменные стека не умрут.
Решения этих проблем есть, и это часто очень простые решения, но 90% современных любителей jquery либо их не знают, либо им не следуют
Тем лучше! Давайте пользоваться тем, что у нас с вами есть конкурентное преимущество!
Ага, кажется я догадался. Вы о синтаксисе PHP 5.3 для анонимных функций.
UFO just landed and posted this here
в Lua с областью видимости все нормально: local x будет виден в пределах блока, в котором объявлен.
Первое что неделями и месяцами мне сносило моск когда я лет 10 назад начинал прикручивать Lua будучи ярыми Сишником — что там нужно было специально просить чтобы переменная не вываливалась наружу.
Кончилось тем что поправил исходники чтобы lua стал синтаксически больше похож на Си. В то же время тем же самым занимались еще трое знакомых, почти что тенденция была.
Сейчас все молча жрут кактус :)
> что там нужно было специально просить чтобы переменная не вываливалась наружу.

вы про то, что любая переменная не объявленная локально считается глобальной? есть такое. это решение мне кажется достаточно логичным.

кстати можно провести параллель с C — если функцию вызываете без декларации, то только на этапе линковки обнаружится проблема ;-)
Вероятно, чтобы не смешивать локальные переменные и уровень выше.
Когда создавался JavaScript, так было принято. ЕМНИП, тоже лексическое объявление переменных в C появилось только в C99 — до этого область видимости была тоже на уровне функции.

На самом деле, статья написана в довольно «шаманской» манере (как, впрочем, и многие прикладные мануалы по JavaScript). Для того, что бы не ошибаться, главное не путать объявления с определениями и присваиваниями, формальные параметры с фактическими, имена переменных и ключевые слова, переменные и память, в которой они размещаются, ссылки и значения и т.д. и т.п. В нормальном курсе обучения программисты всё это проходят и никаких проблем у них не возникает. А вот если они учатся методом тыка, то… :)
Да ладно область видимости, мне больше не нравится что оно само, можно сказать, переделывает код, из-за чего можно говорить «вот этот код на самом деле будет выполняться вот в таком, другом, порядке». Очень не интуитивно. Понятно, что можно разобраться, почитать там и там, но принцип наименьшего удивления нарушен :)
уже в ALGOL 60 был нормальный block lexical scoping, С++ (в те времена C with Classes) в середине 80x так же подхватил эту идею.
Может, у меня слишком узкий круг знакомых, но на них нет ни одного программиста на Алголе…

А в C++ в то время, ЕМНИП, block scoping у многих компиляторов был ущербный — в частности, переменные, объявляемые в условиях циклов, определялись во внешних блоках, а не во внутренних, как сейчас.
Причем здесь ваши знакомые, это ведь не вы JavaScript изобретали. Я думаю в JavaScript забили на block scoping по какой-то другой причине, а не потому что не знаю про такую фичу.

Формально то что объявляется в C++ в for лежит за пределами блока {}, поэтому объявления и не входили в блок. Там и сейчас не все как во внутреннем блоки, т.к. инициализация делается один раз, а не каждый раз как для остальных переменных внутреннего блока цикла.
Скорее всего, просто было проще сделать интерпретатор. На этапе парсинга поднимаем объявления к соответствующему узлу AST, после чего при входе в контекст (евала этого узла) сразу резервируем контейнер под соответствующие ссылки, который существует до выхода из контекста. Точно также в С++ место под переменную на стеке на самом деле определяется на этапе компиляции и в некоторых компиляторах при определённом хакинге можно присваивать и модифицировать содержимое переменной до её объявления.

Там и сейчас не все как во внутреннем блоки

Всё точно также. Просто, опять же, понятие lexical block scoping выходит за рамки block, определённого в C++ (думаю, только в лиспо-подобных языках он может совпадать, т.к. там «ключевые слова» операторов входят в состав некоей формы, которая своими скобками и ограничивает явно область видимость). В частности, тело цикла совсем необязательно обрамлять {}, что бы переменные, объявленные в условии не утекли наружу, равно как и наоборот, одиночный блок {} не вводит нового стекового контекста и место под переменные, объявлённые в этом блоке выделяется и определяется ещё до начала выполнения описанных внутри тела функции инструкций и определений.
> Скорее всего, просто было проще сделать интерпретатор.
Мне кажется это логичной причиной, но не понятно что за ней скрывалось. Может что-то для замыканий нужно было, там же своя система работы с контекстами, возможно уровень функций здорово все упрощает.
P.S. а знакомые при том, что JavaScript создавался как практичный язык. Нафига там LBS, если им на тот момент пользовалось очень малое кол-во людей? Напомню, большинство практически используемых языков тогда требовало объявления переменных либо перед телом фукнции, либо первыми её предложениями. Собственно, и проблемы f(){i=99; if (..) { var i = 42; } } не возникало, т.к. все писали f(){ var i=99, j = 15, k, l, m=23; if (..) { i = 42; } }
Да, первый пример сначала вгоняет в ступор, и потом только до меня дошло, что привычка использовать лямбда-функции немного расслабляет.
Stoyan Stefanov перечисляет такие преимущества использования Single var Pattern:

• Provides a single place to look for all the local variables needed by the function
• Prevents logical errors when a variable is used before it’s defined (see “Hoisting: A
Problem with Scattered vars” on page 14)
• Helps you remember to declare variables and therefore minimize globals
• Is less code (to type and to transfer over the wire)
Я правильно понимаю, что функция b(), определённая вышеприведённым кодом

function b() {
   a = 10;
   return;
   function a() {}
} 

после всплытия внутренней функции обретает вид

function b(){
   var a = function(){}
   a = 10;
   return;
}

в силу чего переменная a неожиданно для программиста становится локальною — так что как раз поэтому, хотя она и обретает значение 10 перед выходом по return, значение «a=10» не сумеет уйти из функции в одноимённую глобальную переменную?

Неожиданно, конечно; но и поделом всякому любителю обозначать одним именем функцию и переменную в одной и той же области видимости, хотя бы и не в глобальной.
после всплытия будет выглядеть так:
function b(){
function a(){}
a = 10;
return;
}


Притом FD всплывают раньше чем перменные объявленные с помощью var т.е.
function b(){
var a = 1;
function a(){}
alert(a);
}

b() // 1
Вы уверены в том, что этот Ваш комментарий безупречно отражает именно то, что Вы хотели сказать?
Я спрашиваю потому, что приведённый мною выше код «после всплытия внутренней функции» имеет вид

function b(){
   var a = function(){}
   a = 10;
   return;
}

который и без того совершенно соответствует и Вашему тезису «FD всплывают раньше, чем переменные», да притом и Вашему коду «после всплытия будет выглядеть так», отличаясь от Вашего кода только тем, что у меня в явном виде записано «var a = function(){}» (что было мне необходимо, так как я намеревался максимально ясно показать, как именно переменная a стала вдруг локальною в функции b().
UFO just landed and posted this here
Там будет не
var a = function(){}
а
var a = function a(){}
Этот мой комментарий, как вы указали, показывает как работает hoisting. Если вы будете более внимательны вы увидите разницу. Function Expression != Function Declaration и в своем ответе я вам как бы намекаю на эту разницу.
Почему неожиданно, в JS функции сразу инициализированы уже при входе в чейн (если описаны не через var), где бы они не были описаны (в пределах чейна), все логично вроде бы.
Если уж честно признаться, мне не особенно по нраву манера записывать определения переменных единственным var:

var x = 1, 
    bar, 
    baz = "something";

Предпочитаю вместо этого использовать несколько var кряду:

var x = 1;
var bar;
var baz = "something";

Причины такого предпочтения довольно просты:

  • смысл каждой строки («здесь объявляется переменная») явствует при первом же взгляде на неё и не утрачивается в том случае, если начало блока объявлений переменных откручено за верхний край окна с кодом (что бывает важно, если их там не три, как в примере, а пара-тройка-другая десятков);
     
  • пунктуация в конце строк единообразна: не нужно менять запятую на точку с запятой (и наоборот) в том случае, когда некоторая строчка кода внезапно начала (или перестала) быть последнею (то же самое и с началами строк: не нужно возёхаться с добавлением или устранением кодового слова «var», если та или иная строка начала или перестала быть первою);
     
  • если над одним и тем же кодом работают несколько человек, стоящих по разные стороны фронта священной войны за употребление пробелов и символов табуляции, то они не передерутся по вопросу о том, что должно стоять во второй строке (и во всех последующих строках) под «var»: отступ ли там, или табуляция, и если табуляция, то из пробелов или «табов» и какого размера.
Главное преимущество единственного var вполне очевидно — сжатие javascript. Если вы хотите сжать минификатором свой скрипт до как можно меньших размеров — необходимо использовать один и только один var.
Последний пункт имеет изящное решение:
var
    x = 1, 
    bar, 
    baz = "something";

Любители холиваров остались без пищи.
Google Closure Compiler и UglifyJS умеют объединять все идущие подряд объявления переменных в одно, причём Uglify делает это умнее
преждевременная минификация — корень всех зол.
Преждевременная минификация — корень не всех зол, а только некоторых.
>если над одним и тем же кодом работают несколько человек,
то у них должен быть единый стандарт кодирования.
UFO just landed and posted this here
Ни первый ни второй код не вызывают удивления. Достаточно просто знать принципы. Нет переменных или функций или массивов. Есть объекты или хэши.
А теперь опять посмотрите на примеры кода вначале статьи. Все стало простым и логичным.
Что значит нет переменных и как это объясняет то, что в начале статьи?
У Дмитрия Сошникова есть прекрасная серия статей, в которых очень подробно разжевано все внутреннее устройства JavaScript (ECMA-262-3): dmitrysoshnikov.com/tag/ecma-262-3/
Очень рекомендую.
Коллеги, используйте CoffeeScript, там большинство этих проблем даже не возникает.
Sign up to leave a comment.

Articles