Показаны сообщения с ярлыком JavaScript. Показать все сообщения
Показаны сообщения с ярлыком JavaScript. Показать все сообщения

четверг, 23 октября 2014 г.

Underscore.js: Утилиты (Underscore.js 1.7.0, Lo-Dash 2.4.1)

Ряд функций общего назначения, часть из которых используется, преимущественно, внутри самой библиотеки:
----------------------------------------------------------------
_.noConflict()
Функция возвращает ссылку на объект _, содержащий все свойства и методы библиотеки. Основное применение - возврат к старой версии библиотеки в случае переопределения включением новой версии (или наоборот).
----------------------------------------------------------------
_.runInContext([context=root]) // Lo-Dash only
Создаёт новый (без добавленных примесей) объект Lo-Dash в контексте context (по умолчанию root) и возвращает его.
----------------------------------------------------------------
_.identity(value)
Возвращает первый аргумент (value): _.identity(x) === x;
Для функций, работающих с коллекциями, является перечислением по умолчанию.
----------------------------------------------------------------
_.constant(value)
Возвращает функцию, возвращающую первый аргумент: 
_.constant(1, 2, 3)() === 1;
----------------------------------------------------------------
_.noop()
Возвращает undefined.
----------------------------------------------------------------
_.times(n, callback, [context])
n раз вызывает функцию callback в контексте context с аргументом равным n-1 (на данной итерации).
Возвращает массив, состоящий из возвращаемых функцией callback значений.
----------------------------------------------------------------
_.random([min=0], max)                      // underscore
_.random([min=0], [max=1], [floating=false])   // lodash
Возвращает целое случайное число (для lodash - с дробной частью, если floating = true, или min/max - дробные) в интервале [min, max]. Если функция имеет только один параметр, то min полагается равным 0. Для lodash вызов функции только c параметром true возвращает дробное в интервале [0..1].
----------------------------------------------------------------
_.mixin(source)                        // underscore 
_.mixin([object], source, [options])   // lodash
Позволяет расширить объект underscore/lodash пользовательскими функциями (для lodash также произвольный объект-функцию, задаваемую необязательным параметром object). Функция для расширения задается в объекте вида: { name: function }, где name - имя функции расширения, а function - ссылка на функцию или непосредственно ее код. В lodash можно указать также объект, содержащий опции. На данный момент используется только одна опция chain типа Boolean (по умолчанию true), указывающая является ли функция для расширения "цепной".
----------------------------------------------------------------
_.iteratee(value, [context], [argCount])    // underscore only
Внутренняя функция underscore. 
----------------------------------------------------------------
_.uniqueId([prefix])
Возвращает уникальный целочисленный идентификатор, начинающийся с опциональной строки prefix. Нумерация происходит с 1 является сквозной для разных префиксов.
----------------------------------------------------------------
_.escape(string)
_.unescape(string)
Подготавливает строку string к вставке в HTML, преобразовывая символы ', ", `, &, <, > в их HTML-эквиваленты (escape) или выполняет обратное преобразование (unescape).
Возвращает модифицированную строку.
----------------------------------------------------------------
_.result(object, property)  // property - string
Если property объекта object - строка с названием функции, возвращает результат выполнения этой функции, иначе возвращает значение свойства property.
----------------------------------------------------------------
_.now()
Возвращает текущий временной штамп (количество мс прошедших с начала эпохи), используя наиболее быстрый способ.

воскресенье, 19 октября 2014 г.

Underscore.js: микрошаблоны JavaScript

Трудности перевода.

В русском языке слово "шаблон" устоялось в качестве перевода двух английских терминов - pattern и template. 

Первое понятие относится к области программной архитектуры и означает некоторые идеи или концепции, которые можно выделить во множестве программных проектов вне зависимости от языка программирования. Наиболее корректный перевод слова pattern - повторяющийся мотив (или рисунок), подобно рисунку плитки или обоев. Принятый в IT-среде перевод - шаблоны проектирования (или даже "калька" - "паттерны" проектирования).

Второе понятие (о котором и пойдёт речь в данной заметке) связано с вёрсткой веб-страниц. Шаблоном (или веб-шаблоном) называют текстовую строку (или даже файл), содержащую как обычную последовательность html-тегов, выводимую без изменений, так и специальные символы и ссылки, которые некоторая процедура заменяет на конкретные значения. Более подходящим названием для этого программного элемента был бы термин "бланк". Подобно своему бумажному прототипу веб-шаблон состоит из неизменных, заранее отпечатанных в типографии элементов, часто утвержденных некоторым стандартом и пустых клеток, ячеек или полос, которые заполняются от руки или на печатной машинке. Такие клетки и ячейки часто содержат подсказки мелким шрифтом - Ф.И.О. (фамилия-имя-отчество) или М.П. (место печати).

Немного истории.

Первые "шаблоны", которые "бланки" появились в языке КОБОЛ, ставшем стандартом в бизнес-среде. Зарплатные ведомости или различные отчеты представляют собой те самые бланки, которые содержат стандартные элементы и конкретные данные в виде фамилий и сумм. Древние языки программирования не имели такой роскоши как конкатенация строк, поэтому вывод данных, содержащих постоянные и переменные данные был довольно громоздким. Типичная функция вывода на экран могла выводить только один параметр - строку или переменную, а иногда для вывода разных типов данных требовались разные функции. Нужно было следить за всеми пробелами и переводами строк, выравниваниями, вывод разрывался в самом неподходящем месте.

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

Name: John  Surname: Smith  Country: USA  Salary: $30000

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

prints("Name: ");     prints(name); 
prints(" Surname: "); prints(surname); 
prints(" Country: "); prints(country); 
prints(" Salary: $"); printn(salary); 
println();

можно использовать такую конструкцию:

printf("Name: %s Surname: %s Country: %s Salary: $%d\n", 
name, surname, country, salary);

Первый параметр функции printf и является таким бланком-шаблоном. Значения переменных, которые передаются в функцию как остальные параметры, заменяют специальные символы %s (строковая переменная) и %d (числовая переменная).

Для работы с шаблонами в Underscore и lodash используется функция _.template и переменная _.templateSettings.

_.template(templateString, [settings])            // underscore
_.template(templateString, [data], [settings])    // lodash

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

<% %>  - вычисление (evaluate), RE: /<%([\s\S]+?)%>/g
<%= %> - замена (interpolate), RE: /<%=([\s\S]+?)%>/g
<%- %> - эскапирование (escape), RE: /<%-([\s\S]+?)%>/g


Содержимое тега "вычисление" - обычный JavaScript-код. Шаблонизатор заменяет код на результат его вычисления.
Содержимое тега "замена" - ключи некоторого объекта (который передается в качестве параметра сгенерированной функции). В итоговой строке ключи объекта заменяются на значения.
Тег "эскапирование" ведёт себя аналогично тегу "замена" с HTML-эскапирование результатов.

Результат выполнения _.template() - функция, которая может получать объект с данными, заполнять шаблон этими данными и возвращать текстовую строку (это может быть строка таблицы или элемент списка HTML-документа).

var tmpl = "Hello, <%= name %>!";
var data = { name: "John" };

var ftmpl = _.template(tmpl);
console.log(ftmpl(data));      // > Hello, John!

В случае lodash объект для заполнения шаблона можно передать в качестве параметра не сгенерированной функции а сразу функции template.
console.log(_.template(tmpl, data)); // Только lodash
Или же учитывая тот факт, что template возвращает функции, можно вызвать ее немедленно в обеих билиотеках:

console.log(_.template(tmpl)(data)); // lodash и underscore

Параметры шаблонов хранятся в объекте _.templateSettings, но могут перекрываться параметрами из опционального объекта settings.

Переменная _.templateSettings является объектов с ключами interpolate, evaluate, escape, содержащими соответствующие регулярные выражения для указания форматов тегов, используемых в шаблоне. Для того, чтобы использовать теги в стиле Mustache можно использовать следующие регулярные выражения:

_.templateSettings.evaluate = /\{\{([\s\S]+?\}\}/g;
_.templateSettings.interpolate = /\{\{=([\s\S]+?\}\}/g;
_.templateSettings.escape = /\{\{-([\s\S]+?\}\}/g;

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

_.template("Hello, <%= data.name %>", { variable: data })(data);

вместо


_.template("Hello, <%= name %>")(data);

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

четверг, 16 октября 2014 г.

JavaScript: Good Parts. Выжимки из книги.

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

var obj = { level1: { level2: {level3: 42} } };
obj.level1.level2.level3;          // > 42
obj['level1']['level2']['level3']; // > 42

При использовании dot-синтаксиса свойство объекта должно соответствовать правилам именования идентификаторов (не быть зарезервированным словом и не начинаться с цифры, не содержать знаков препинания). В противном случае используется []-синтаксис и название свойства заключается в кавычки (исключение возможно только если название свойства - число, так обеспечивается массивоподобный синтаксис).

var obj2 = { 1: { 2: { 3: 42 } } };
obj2[1][2][3]; // > 42

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

var l1 = "level1", l2 = "level2", l3 = "level3";
obj[l1][l2][l3]; // > 42

Внимание! Любая переменная, которая используется в качестве индекса при []-синтаксисе преобразуется к строковму литералу при помощи метода .toString(). Поэтому, например:

var x = {}, y = {};
var y[x] = 1;
console.log(y); // { '[object: Object'] : 1 } 

Попытка обратиться к несуществующему свойству вернёт undefined. Для установки значений по умолчанию можно использовать ||:

x = obj.level5 || null;
y = obj.property || "(none)";

Попытка обращения к вложенному свойству несуществующего свойства (свойству объекта undefined по сути) вызовет ошибку "TypeError":

x = obj.level4.level5; // > TypeError

Для предотвращения ошибки можно использовать &&. Благодаря ленивым вычислениям конструкция вернет undefined и не дойдет до ошибки:

x = obj.level4 && obj.level4.level5; // > undefined

Значение свойства объекта устанавливается операцией присваивания. Если свойство уже есть значение замещается, если свойства нет - объект расширяется путем добавления нового свойства.

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

var x = { a: 1, b: 2, c: { d: 5 } }
var y = { m: 10, n: 20 }
y.p = x.c;
x.c.d = 100;
y.p.d; // > 100 (!)

Функции служат для повторного использования кода, сокрытия данных, композиции, а также определяют поведение объектов. Объекты, созданные как объектные литералы, создаются на основе Object.prototype. Функции - на основе Function.prototype.

При создании функции (как литерала или при помощи new Function) автоматически происходит следующее присваивание:

this.prototype = { constructor: this };

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

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

Каррирование - замена функции от нескольких аргументов на функцию с меньшим числом аргументов. Вместо аргументов, значения которых заведомо известны, используются константы.

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

function curry(exp) {
  return function(base) {
    return Math.pow(base, exp);
  }
}

var square = curry(2);

var cube = curry(3);
square(10); // > 100
cube(10); // > 1000

Создание объектов в каскадно-функциональном стиле:


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

// Метаметод, позволяющий добавлять новые методы любым функциям.
// Метод добавляется только если его не существовало ранее.
Function.prototype.method = function (name, func) {
  if (!this.prototype[name]) {
    this.prototype[name] = func;
  }
  return this;

};

// От какого объекта создаваемый объект будет наследовать методы и свойства. 
// Должен вызываться до определения собственных свойств и методов.
Function.method('inherits', function (Parent) {
  this.prototype = new Parent( );
  return this;

});

Использование этих функций сведётся к следующей цепочке вызовов:

var Cat = function (name) {
  this.name = name;
  this.saying = 'meow';
}.
inherits(Mammal).
method('purr', function (n) {
  var i, s = '';
  for (i = 0; i < n; i += 1) {
    if (s) {
      s += '-';
    }
  s += 'r';
  }
  return s;
}).
method('get_name', function() {
  return this.says() + ' ' + this.name + ' ' + this.says();
});

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

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

1. Любым способом создаётся новый объект. Это может быть использование конструктора и new, Object.create, объектного литерала {} или функции, возвращающей объект. Именно этот объект будет результатом выполнения функции.

2. При необходимости объявляются приватные переменные и функции (как обычные vars и functions). Доступ к этим переменным и функциям извне невозможен.

3. Новый объект (созданный на шаге 1) расширяется методами и свойствами. Эти методы и свойства имеют эксклюзивный доступ к переменным и функциям (приватным свойствам и методам), созданным на шаге 2.

4. Возвращаем объект.

var constructor = function (init_data, object_to_extend) {
  // init_data - объект, содержащий набор пар ключ-значение для
  // инициализации создаваемого объекта.
  // object_to_extend - опциональный объект, который можно расширить
  // вместо создания нового объекта.
  var that = object_to_extend || {};
  // Эти свойство и метод будут доступны только внутри объекта.
  var priv_prop = init_data.prop || 42;
  var priv_func = function() { return priv_prop * 10; };
  // Эти свойство и метод пользуются приватным свойством и методом,
  // но будут сделаны публичными, став свойствами возвращаемого
  // объекта. "Двухходовое" присваивание позволяет использовать 
  // внутри объекта методы и свойства по их внутренним именам.
  var pub_prop = 100;
  var pub_func = function() { 
       return pub_prop + priv_prop + priv_func(); };
  that.pub_prop = pub_prop;
  that.pub_func = pub_func;
  return that;
};

var obj = constructor({prop: 8});
obj.pub_func(); // > 188

var obj2 = { name: "Dan" };
obj2 = constructor({}, obj2);
obj2.pub_func();   // > 562
console.log(obj2); // > { name: 'Dan', pub_prop: 100, 
                   // pub_func: [Function] }

В наследующем объекте бывает нужно переопределить метод с некоторым именем, при этом в методе объекта-потомка может использоваться результат вызова метода родителя. В этом случае возможны следующие варианты:

var enhanced_constructor = function(init_data) {
  var that = constructor(init_data);
  var superior_func = that.pub_func;             // 1
  var superior_func = that.superior('pub_func'); // 2
  var superior_func = that.pub_func.bind(that);  // 3
}

пятница, 10 октября 2014 г.

JavaScript: objects from scratch.

Из стандарта Ecma-Script:

ES - язык, основанный на объектах (object-based) и любая программа на нем представляет собой скопление взаимодействующих объектов.
Каждый объект представляет собой набор свойств, а каждое свойство, в свою очередь, имеет ряд атрибутов, определяющих как это свойство может быть использовано (например, быть доступным для чтения).
Свойство объекта также должно быть объектом (или функцией как разновидностью объекта или же литералом). 

В JavaScript объекты можно создавать разными способами. Наиболее употребимые из них - вызов функции-конструктора с помощью оператора new (при этом создастся пустой объект с дополнительными свойствами заданными в конструкторе):

var obj = new fnConstructor(value);

или же используя т.н. "синтаксический сахар", создавая пустой объект при помощи присваивания {}.

var obj = {};

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

var obj = Object.create(null);

Вновьсозданному объекту можно назначить свойства при помощи следующего вызова:

Object.defineProperty(object, name, config);

где object - ссылка на объект, name - строковый литерал, содержащий имя назначаемого свойства, а config - объект, содержащий ряд атрибутов свойств объекта (т.н. дескриптор):

value: '42' - предустановленное значение настраиваемого свойства, по умолчанию - undefined. Если свойство должно являться методом (содержать некоторый код), то его определение выглядит как value: function() { ... }

writable: true - возможность записывать значения в свойства (false дает readonly свойства). По умолчанию установлено в false. Исключение TypeError генерируется при записи в readonly свойство объекта только в strict mode.

enumerable: true - свойство объекта будет доступно при переборе с помощью конструкции (for key in obj) или вызова Object.keys(). В любом случае вызов Object.getOwnPropertyNames(obj) покажет даже такие "скрытые" свойства. По умолчанию свойство устанавливается в false.

configurable: true - свойство объекта можно переопределять или удалять. Будучи раз установленным в false свойство переопределению уже не поддаётся (кроме изменения параметра writable на false), а любые попытки изменить значения параметров value, enumerable, configurable, writable (на true), get/set приведут к исключению TypeError даже в обычном режиме (а попытка удалить свойство в strict mode).

get/set: эти свойства требуют указания функций геттера и сеттера, которые будут автоматически вызываться при попытке считать или установить значение (при этом устанавливаемое значение будет передаваться функции сеттеру в качестве параметра). С помощью этих свойств можно реализовать протоколирование считываний-назначений или какую-либо сложную обработку получаемых значений, приведение их к определенному типу, а также изменить свойства на функции при изменении кода с необходимостью сохранения API (например, если вместо хранения возраста в объекте начала храниться дата рождения, возврат возраста можно сделать как геттер-функцию от даты рождения).
Свойства get/set нельзя использовать одновременно со свойствами value и writeable (вызовет TypeError даже в обычном режиме).

Наряду с методом Object.defineProperty() можно использовать Object.defineProperties() для установки целого ряда свойств в одном вызове. Важно помнить, что при вызове defineProperty() свойства writeable, enumerable, configurable по умолчанию установлены в false, тогда как при создании свойства при помощи конструкции:

obj.prop = value;

эти свойства установлены в true.

defineProperty() можно вызывать из конструктора (в качестве ссылки на объект предпочтительно использовать this), а для улучшения читаемости кода можно использовать заготовки типа:

propWEC = { writeable: true, enumerable: true, configurable: true };

Свойства можно назначать при объявлении объекта в форме литерала, однако при этом устанавливается ряд умолчаний:

var a = { 
  name: 'Mikhail', 
  surname: 'Ivanov, 
  get fullName() { return name + surname },
  set fullName(newname) { 
                  var names = newname.trim().split(/\s+/); 
                  this.name = names[0] || '';
                  this.surname = names[1] || ''; }
}

Для каждого свойства объекта происходит упрощенное назначение name: value, при этом модификаторы writable, enumerable, configurable устанавливаются в true. А в качестве геттеров и сеттеров можно определить только анонимные функции, для предопределенных придётся использовать вызов defineProperty().

Для получения набора атрибутов, которые имеет то или иное свойство объекта используется вызов getOwnPropertyDescriptor(obj, 'property');

Помимо этого объект, определенный через {} получает в качестве предка Object.prototype, с которым наследует ряд стандартных свойств объекта JavaScript (типа toString() и т.п.).

Литералы могут быть объявлены только как часть выражения. В противном случае возможна трактовка их как блочных выражений с меткой:

{ a: 10 }   // Метка а
({ a: 10 }) // Объект со свойством a.

Свойства, для которых в дескрипторе атрибут configurable установлен в true, можно удалять

delete a.name;
console.log(a.name); // > undefined

Для каждого объекта набор присущих ему свойств можно получить тремя способами (см. описание атрибута enumerable).


среда, 8 октября 2014 г.

Функция как объект

JavaScript придерживается идеологии: "все - объект". В этом отношении функции мало чем отличаются от других объектов - они так же могут иметь собственные методы и свойства.

function f() {};
f.x = 10;
f.y = function() { console.log (f.x) };

f.y(); // > 10

Функцию можно создать при помощи конструктора Function:

var f = new Function(a, 'console.log(a)');

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

Код выше эквивалентен обычному объявлению функции:

function f(a) { console.log(a) }

В отличие от всех других объектов объект типа function имеет скрытое свойство [[Call]], которое и содержит выполняемый код функции. Выполнение происходит когда имя функции (или функциональное выражение) сопровождается парой скобок (возможно с передаваемыми параметрами):

f(10);
или
function f(a){ console.log(a) }(10);

В результате "превратить" обычный объект в функцию или создать функцию путем объявления объекта через фигурные скобки не удастся - только через непосредственное объявление или через конструктор Function (второй способ используется достаточно редко).

Каждая объявленная в глобальной области видимости функция является методом объекта global (как и каждая объявленная в ней переменная - ее свойством). По сути методы являются исполняемыми свойствами. А в JavaScript код функции - недоступное для модификации исполняемое свойство функции как объекта.




понедельник, 6 октября 2014 г.

Переменные и область видимости

Конспект из You Don't know JS, book 1: Scope and Closure.

Переменная, которая встречается в коде, может интерпретироваться в одном из двух контекстов, называемых RHS и LHS - right-hand-side и left-hand-side, часто встречаются значения lvalue и rvalue. Стороны выбираются относительно оператора присваивания:

a = b;

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

Правая сторона (rvalue) представляет собой контейнер хранящий значения и важность представляют сами значения. В правостороннем контексте компилятор пытается эти значения извлечь.

В вызове console.log(a) на a ссылаются в правостороннем контексте, значение аргумента не меняется в ходе вызова функции (метода).

В присваивании a = 2 на а ссылаются в левостороннем контексте, т.к. изначальное значение а не важно компилятору, оно меняется в процессе присваивания.

При вызове любой функции, например: f(2) используются оба контекста. Имя функции обрабатывается компилятором в правостороннем контексте, по имени f он получает некоторое значение (которое представляет собой некоторый адрес, указывающий на начало блока с некоторым кодом, представляющим собой тело функции). В процессе вызова происходит неявное присваивание аргументу (численный литерал 2) формальному параметру функции. Если формальные параметры не объявлены в заголовке, то значение получит свойство объекта arguments с ключом 0. Если параметры были объявлены, то значение arguments[0] дополнительно скопируется в первый из объявленных в заголовке формальных параметров.

function foo(a) {
  var b = a;
  return a + b;
}

var c = foo( 2 );

RHS-ссылки: вызов foo, b = a, значение a и b при возврате результата выполнения функции.
LHS-ссылки: a = 2 при вызове foo, b = a при определении значения переменной b, c = foo() - присваивание результата вызова функции переменной.

Область видимости - набор правил в соответствии с которым движок может находить переменную по ее имени. При наличии вложенных функций каждая из них получает свою область видимости:

var x = 1, y = 3;

function a() {
  var y = 2;
  function b() {
    function c() {
      x = y;
    }
  }
}

Функции a, b, c имеют свои области видимости (с вложена в b, b вложена в а), кроме того существует глобальная область видимости (соответствующая объектам window или global).

Когда компилятор обнаруживает лексему y он последовательно проводит поиск в области видимости c, затем b, затем a, где и находит переменную. То же происходит с переменной x, но поиск завершается успехом в глобальной области видимости. Как только переменная найдена, поиск прекращается, что позволяет иметь переменные с одинаковыми именами на разных областях видимости. y, объявленная на глобальном уровне, будет "затенена" y, объявленной в области видимости a. Однако глобальные переменные доступны в любом месте программы как свойства глобального объекта.

В том случае, если RHS-поиск не увенчается успехом в глобальной области видимости, движок сгенерирует ошибку ReferenceError. LHS-поиск завершается в глобальной области видимости и если требуемое имя не находится и там, автоматически создается переменная с запрашиваемым именем. Однако в Strict mode поиск завершается ошибкой ReferenceError по аналогии с RHS.

TypeError генерируется в том случае, когда RHS-поиск завершился успешно, однако программа пытается выполнить действие, не предусмотренное для данного значения (например, вызывает как функцию переменную, не являющуюся ссылкой на функцию или пытается вызвать метод у переменной, содержащей значения undefined или null).

JavaScript использует лексическую область видимости, помимо этого существует динамическая область видимости, используемая, например, в языке сценариев Bash и некоторых режимах Perl. Лексическая область времени определяется на момент компиляции (выполнения), функция видит переменные в зависимости от того где она определена. В динамической области видимости функция видит переменные в зависимости от того где она вызвана.

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

function bar() {
  var a = 3;
  foo();
}

var a = 2;

bar();

Результатом выполнения bar() в JavaScript будет 2, потому что вызываемая в ней функция foo() определена в глобальном контексте и переменную a она ищет там же. Переменной а, объявленной внутри функции bar() для нее не существует. В языках с динамической областью видимости результатом выполнения будет 3, потому что функция foo() вызывается  после определения переменной a внутри функции bar() и пользуется именно этой "версией" переменной. В то же время поведение переменной this подчиняется правилам динамической области видимости (она получает разные ссылки в зависимости от места вызова), но об этом в другом разделе.

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

И with, и eval замедляют скорость выполнения скрипта, делая невозможным ряд оптимизаций на этапе компиляции. Лучше не использовать их вообще, тем более, что они запрещены в strict mode (разрешен только непрямой eval).

В JavaScript отсутствует блочная видимость, она будет введена только со стандартом языка ES6. Однако конструкция try/catch позволяет локализовать переменные:

try {
  undefined()
} catch(err) {
  console.log(err);
}

console.log(err); // > ReferenceError

Для объявления переменной, видимой только в пределах блока (это может быть просто { }, if() { }, for() { ], while() { }) используется конструкция

let variable = value;

В отличие от объявления через var, объявление через let происходит строго в том месте, где расположен let. Поэтому:

console.log(a); // > undefined
var a = 5;

потому что объявление var a отделяется от присваивания компилятором и переносится в начало области видимости (hoisting - подъем), тогда как

console.log(a); // > ReferenceError - переменная считается необъявленной.
let a = 5;

Для эмуляции поведения let в ES5 можно использовать такой код:

try{throw value}
catch(variable){
   // use variable;

}

Если работа ведется с крупным объектом, то сборщик мусора гарантированно очистит память, удалив из нее объект, сразу после окончания блока catch. Во всех остальных случаях этот код несколько громоздок. Однако использование вместо него IFFE тоже имеет ряд недостатков: внутри нее меняется значение this, а также усложняется использование return, break и continue.

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


function outer() {
  var a = 2;
  function inner() {
    console.log( a );
  }
  return inner;
}

var clousure = outer();


clousre(); // > 2

Мы объявили функцию inner() внутри функции outer() и получили способ ее вызова вне той области видимости, в которой она была объявлена. На момент вызова closure() функция outer() уже прекратила свою работу и исчезла ее внутренняя переменная a. Однако closure() имеет доступ как к переменным, объявленным внутри функции inner(), так и внутри функции outer().

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

function polymorph(arg) {
  function ones() { console.log('I like ones!'); }
  function twos() { console.log('I like twos!'); }

  if (arg === 1) return ones;
  if (arg === 2) return twos;
  return function() { console.log('I like noones and notwos') };
}

onef = polymorph(0); onef(); // > I like ones!
nof  = polymorph(0); nof();  // > I like noones and notwos

"Проброс" к внутренней функции может быть косвенным:

var fn;

function outer1() {
    var a = 2;
    function inner() {
        console.log( a ); // 2
    }
    fn = inner;
}

function outer2() {
    fn();
}

outer1();

outer2(); // 2

outer2() получает доступ к функции inner() через внешнюю переменную fn, с которой транспортируется ссылка на внутреннюю функцию. При этом вместе с кодом функции inner() fn выносит за пределы outer() и всю область видимости outer() со всеми определенными в ней переменными.

Рассмотрим следующий код:

for (var i=1; i<=5; i++) {
  setTimeout( function timer(){
    console.log(i);
  }, i*1000 );
}

Функция timer() вызывается спустя некоторое время, которое достаточно для того, чтобы цикл успел завершиться. Поэтому на момент выполнения даже первой итерации i уже равно 6 и вся конструкция выведет последовательность 6 6 6 6 6
Кроме того каждая итерация создаёт новый экземпляр функции timer(), однако область видимости у всех них одна и та же и используют они одно и то же значение переменной i.

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

for (var i=1; i<=5; i++) {
 (function(j){
  setTimeout( function timer(){
    console.log(i);
  }, j*1000 );
  )(i);
}

IIFE получает дополнительный параметр, т.к. без  него мы получим только изолированную область видимости внутри замыкания, но она будет пуста. Передавая параметр (присваивание можно выполнять непосредственно временной переменной внутри IIFE) мы тем самым сохраняем текущее значение i на каждой итерации цикла. При этом у каждой итерации будет свое собственное значение j, которое и используется отложенной функцией при ее вызове. IIFE становится ячейкой, консервирующей нужное нам значение.

В стандарте ES6 можно использовать let вместо var и замыкания:

"use strict";
for (var i = 1; i<=5; i++) {
    let j = i;
      setTimeout(function timer() {
          console.log(j);
      }, j * 1000);
}

Циклы в ES6 имеют собственную область видимости, а переменная, объявленная с помощью let, уникальная в пределах этой области видимости.

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

"use strict";
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
          console.log(i);
  }, i * 1000);
}