Pull to refresh

Comments 49

помимо контекста важна еще и такая деталь, как
var y = 10;
потому что заменив её на
y = 10;
мы получим желаемый результат
Я бы даже добавил это в статью как пример модификации переменной глобального контекста. Важный момент, imho.
Результат то, кстати, может как раз и не желаемый в большинстве случаев, но такое поведение надо понимать.
На самом деле, в топике объясняется почему это происходит, но пример хороший, упомянуть действительно стоит. Сейчас оформим.
UFO landed and left these words here
Я пока не эксперт, поясните подробнее. Под всплытием имеется ввиду объявление переменных в конце тела функции? Если так, то действительно стоит рассмотреть этот вопрос, но это уже особенности явного и неявного объявления переменных — это тема другого микро-топика.
> Под всплытием имеется ввиду объявление переменных в конце тела функции?
Можно сказать и так.
Это тема напрямую относится к понятию scope.
Я думаю этот вопрос все же лучше рассмотреть отдельно, потому что если рассматривать их в одном топике, топик будет слишком сложным.
Еще такой пример, чтобы показать, что в замыкание попадает именно переменная, а не ее значение:

function counter(i) {
    inc = function() { alert(++i) };
    dec = function() { alert(--i) };
}

counter(0);
inc(), inc(), dec();
Толково написано.
Вот только когда речь идёт о замыканиях, сразу вспоминаются утечки памяти — и на этом аспекте хорошо бы остановиться подробнее. Дабы начиная использовать этот мощный инструмент JS, люди не делали таких трудноотлавливаемых ошибок.
Утечки памяти это в первую очередь особенность реализации интерпретатора. То есть попросту говоря они зависят от того где используется ваш код. Я пока не готов подробно описать утечки, просто времени не хватает. :)
Ещё стоит упомянуть о не совсем очевидном моменте jsbin.com/ozeyoz/1/
var my_var = 'глобальная';

function my_function() {
  var my_var = 'локальная';
  var some_function = new Function('console.log(my_var)');
  some_function();
}

my_function(); // 'глобальная'
Тело функции переданное строкой в конструктор Function привязывается к глобальному контексту?
Ну да, можно и так сказать.

Насколько я понимаю, через конструктор Function() функция создается в глобальном контексте, и локальный, соответственно не использует.
Неделя основ Javascript'a на хабре!

Наверняка будет полезно.

По моим наблюдениям, большинство веб-разработчиков начинают изучение javascript'a неосновательно. Так, будто это не полноценный язык, а надстройка над html для создания красивостей.
UFO landed and left these words here
Вы извините, но судя по тому что за основной источник информации вы взяли википедию, а не стандарт, читать статью даже не хочется.

Почему вы ни слова не сказали о том, что ECMAScript 5 определяет такие понятие как лексическое окружение и записи лексического окружения?

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

Используете правильную терминологию, в данном случае речь идет о свободных переменных.

Этот пример затрагивает очень важный вопрос — вопрос неявного объявления переменных в JavaScript.


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

Есть только одно НО: если переменная не будет найдена ни в одном объекте в цепочке областей видимости, то интерпретатор JavaScript объявит используемую переменную автоматически и присвоит значение ей

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

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

Замыкание — это совокупность тела любой функции и ее лексического окружения
Я что-ли должен продолжать или вы все-таки сами?

Вместо выводов: Надеюсь описанный выше пример наглядно демонстрирует что такое лексическая область видимости и чем она отличается от динамической.


Издеваетесь?
Почему ни слова о функции eval, конструкторе Function, инструкции with?
Покажите место в стандарте где хоть слово есть о динамической области видимости.

Если у подняли такую сложную тему, то добавьте соответствующий материал:

Lexical scope, Lexical environments, Environment Record, Reference Specification Type, Closure, Hoisting

Не подумайте что я придираюсь, но читать стандарт перед написанием таких тем и знать о том что тема уже раскрыта (1, 2, 3, 4, 5, 6, 7) в других местах просто необходимо.

Пара примеров на вскидку:

alert(foo) // ?

foo = function() {
	return 1;
};


var foo = function() {
	var i = 0;

	foo = function() {
		return i++;
	}

	return foo();
};

foo(); // ?
foo(); // ?
foo(); // ?


alert(foo) // ?

foo = function() {
	return 1;
};


Если интересно, то у меня есть небольшая библиотека на тему замыканий и частичного применения функций

И еще один пример вдогонку:

void function() {
	'use strict';
	
	alert(this);                     // ?
	alert(eval('this'));             // ?
        alert((null, eval)('this'));      // ?
	alert(Function('return this')()) // ?
}();
Извините, я исправлюсь :)

Но по поводу ваших замечаний, я соглашусь не со всеми:

«Стандарт определяет только один тип переменных.,
Отсутствие спецификатора var при объявлении — свидетельствует об инициализации свойства глобального объекта.»
Во-первых, я не говорил про тип переменных, вы что то спутали, я говорил про способы объявления переменной. А как вы отличаете «Отсутствие спецификатора var при объявлении» от обращения к переменной объявленной в объемлющей функции?

«Почему ни слова о функции eval, конструкторе Function, инструкции with?» Потому что не было у меня такой цели. Почитайте пожалуйста заголовок топика. Более того, я бы с удовольствием прочитал вашу заметку об этом, написать свою пока не могу, не хватает времени.

«Не подумайте что я придираюсь, но читать стандарт перед написанием таких тем и знать о том что тема уже раскрыта (1, 2, 3, 4, 5, 6, 7) в других местах просто необходимо.»

Автор на которого вы ссылаетесь, мне незнаком. Изучу на досуге.
А как вы отличаете «Отсутствие спецификатора var при объявлении» от обращения к переменной объявленной в объемлющей функции?:

foo = 1;
var bar = 1;

void function () {
    alert(['foo' in this, 'bar' in this]); // [true, false];
}();​
Спасибо, пример хороший, но то что foo = 1; это объявление переменной — это не очевидно.
Поправка, конечно же речь об инициализации свойства глобального объекта. :)
Не батенька, в обоих случаях true.
Вообще это не я писал, но в целом поддерживаю, у меня тоже true в обоих случаях
Ну вообще, ответ ему я и прислал, а вам пришло уведомление потому что вы автор топика.
Дык понятно, что не в консоле. А вы попробуйте в примере убрать галочку — onload — jsfiddle.net/tEvnj/1/
Вы действительно не понимаете разницы между свойство и переменной?
foo = 1;
var bar = 1;

delete foo; // true
delete bar; // false

alert(bar); // 1
alert(foo); // RefferenceError 
Пол часа искал:
«Если присвоить значение переменной, не объявленной с помощью инструкции var, JavaScript неявно объявит эту переменную за вас. Однако переменные, объявленные таким образом, всегда создаются как глобальные, даже если они работают только в теле функции. Чтобы не создавать глобальную переменную (или не использовать существующую), когда достаточно локальной переменной для отдельной функции, всегда помещайте инструкцию var в тело функции. Лучше всего объявлять с ключевым словом var все переменные – и глобальные, и локальные.»

Дэвид Флэнаган. JavaScript. Подробное руководство. 5-е издание. стр. 69
Со всем уважением к Дэвиду Флэнагану, но в этой ситуации он лукавит, т.к. никакого неявного объявления переменной не происходит, и это легко проверить, т.к. в отличии от свойств (в данном случае речь идет о свойствах глобального объекта) переменные нельзя удалить.

В этом правиле есть только одно исключение и оно четко прописано в стандарте:

'use strict';
global = 1;
alert(global) // ReferenceError


Также советую посмотреть раздел 8.7.2 PutValue (V, W)

Насчет JavaScript — это реализация стандарта ECMAScript, при этом каждая реализация оставляет за собой право расширять предоставляемый функционал. Самый очевидный пример DOM API.
Ну а все же, ничего не посоветуете из литературы кроме стандарта?
Собственно мой коммент ниже для вас тоже подайдёт.
Сегодня снова нашел упоминание про объявление переменных без ключевого слова var, но уже у другого автора:

Nicholas C. Zakas — Professional JavaScript for Web Developers (Programmer to Programmer) — 2011:

Although it’s possible to define global variables by omitting the var operator, this approach is not recommended. Global variables defined locally are hard to maintain and cause confusion, because it’s not immediately apparent if the omission of var was intentional. Strict mode throws a ReferenceError when an undeclared variable is assigned a value.

Все таки интересно, это намеренное упрощение, или же нормальных книг по JS нет?
Я думаю намеренное. Видимо, большое количество ошибок, которое авторы в своей практике выловили, было связано с пропуском «var». Поэтому от греха подальше решили предупреждать об этом всегда.
Дело не в том, что они предупреждают об этом. Дело в том что, ввиду дискуссии выше, кажется это предупреждение не совсем точно.
Цитата приведена не для того что бы холивар разводить. А только для подтверждения моих слов. Если эта информация неточна, дайте ссылочку на источник, где можно пробелы в знаниях заполнить. :)
Ну конечно нет, не знаю. Но это не важно. Я всего лишь указал на то, что ваш пример неправилен, вне контекста правы ли вы в этой ситуации или нет. У вас просто там было ошибка.

Далее, по поводу спора.

    var test = 123;
    
    var pd = Object.getOwnPropertyDescriptor(this, 'test');
    
    pd.value = 321;
    
    Object.defineProperty(this, 'test2', pd);
    
    delete test;
    delete test2;
   
    console.log(test);
    console.log(test2);


Вообще, по хорошему, вы правы. Переменная это не тоже самое, что свойство. Но в частном случае глобального контектса — это тоже самое. Ну и ещё, если добавить в pd, writable = false, то получится эмуляция const.
Но в частном случае глобального контектса — это тоже самое


Неужели?

alert(bar) // undefined
alert(foo) // Refference Error

var bar = 1;
foo = 1;


Еще более интересный пример:

alert(bar) // undefined
alert(foo) // Refference Error

if (true) {
    var bar = 1;
    foo = 1;
}


В первом своем комментарии к этой статье, я уже приводил аналогичный пример.
Аглоритм объявления переменных и так понятен, непонятно, что в нём вы такого интересного нашли, разве что для тех, кто только его узнал.

Но давайте разберёмся. Для глобального контекста:

1. Объявление переменных и своств не одно и тоже (Разные алгоритмы соответственно).
2. Обращение к переменным и свосвам может быть взаимо заменяемое. (С учётом правил конечно, первого пункта).

var foo;
this.bar = void 0;

alert(bar);
alert(this.foo);


3. Поведение переменной и свойста, и итоговое сохранение в контексте одинаковое (Как видно из премера с дескрипторами — никакой разницы).
4. Такое объявление:

"use strict";
foo = 1;


Это не объявление свойства глобальному объекту или какому либо ещё в прямом смысле. Это объявление верно только тогда, когда используется оператор with.

var env = {};
with (env) {
foo = 1;
}


Но так как strict mode запрещает использовать with, отключает его. Логично, что и объявление свойства глобальному контексту таким образом отключается (да именно свойства потому, что она работает по алготирму объявления свойства. Объявление переменной работает по другому алгоритму, но в итоге, это тоже свойство объекта).
Насколько я понимаю, если JS-код выполняется в ходе загрузки страницы, то это трактуется как будто он обернут в (function() {… }()), поэтому «var bar = 1» в таком контексте не объявляет свойство глобального объекта, а объявляет локальную переменную этой псевдо-функции. А вот если этот код исполнить в консоли, то «var bar = 1» и просто «bar = 1» трактуется полностью одинаково — как поле глобального объекта.
В консоли выполняется просто eval в глобальном контексте. У eval есть особенность в том, что переменные объявленные внутри него не получают флаг configurable = false и поэтому могут быть удалены. Тот код, котый исполняется между тегами исполняется нормально, внутренней функцие как например в node.js/v8 — runInContext. Это конечно трактовать как первую функцию, например в Chakra в новых ие, это выполнение и есть первая функция. Вся особенность глобального объекта в том, что ключевое слово this этой функции указывает на LexicalEnvironment этой же функции и получается так, что присвоить свойство или объявить переменную этому объекту почти одно и тоже, за исключением, алгоритмов исполнения. В общем виде псевдо код этой функции выглядит так:

function runInContext(context, code) {
  with (context) {
    (function() {
      eval.call(context, code);
    }).call(context);
  }
};

function runInStrictContext(context, code) {
  (function() {
    eval.call(context, code);
  }).call(context);
};


Конечно оно так не будет работать и это не лучший пример, однако относительно может помочь понять.
Ещё есть такая штука, ну так, для интереса:

Object.prototype.globalPropOrVar = 123;

alert(globalPropOrVar);

alert(({}).globalPropOrVar);
А почему так? Объясните, если не трудно. Или глобальные переменные, объявленные c var инициализируются только после того как глобальный скрипт отработал?
Кажется понял — переменные без var являются свойствами глобального объекта, а с var — глобальными переменными :)
Sign up to leave a comment.

Articles