Одним из секретов эффективного JavaScript-разработчика является глубокое понимание семантики языка. В этой статье я объясню основные элементарные части языка, используя максимально простые и понятные диаграммы.
Перменная в JavaScript — просто имя, указывающее на значение, хранящееся где-то в памяти. Эти значения могут быть как примитивами (строками, целыми числами, булевыми), так и объектами или функциями.
В следующем примере мы создадим четыре локальных переменных в области видимости (scope) высшего уровня и укажем их на некоторые примитивные значения:
Output
=> true
Обратите внимание, что две переменные указывают на одно и то же значение в памяти. Это происходит потому что примитивы неизменяемы и виртуальная машина может исользовать один экземпляр объекта для всех ссылок-переменных, которые указывают на это значение.
В примере выше мы проверили, указывают ли две ссылки на одно и то же значение, используя оператор === и получили подтверждение true.
Прямоугольник слева в диаграмме — внешняя замкнутая область видимости (closure scope) высшего уровня. Переменные в ней — локальные переменные высшего уровня, важно не путать их со свойствами (properties) объекта global/window.
Объекты — просто наборы ссылок на другие объекты и прототипы. Единственное отличие заключается в добавлении цепочки прототипов (prototype chain), чтобы получить доступ к свойствам, находящимся не в локальном объекте, а в родительском.
Output
=> true
Здесь у нас есть один объект с четырьмя свойствами, на который ссылается переменная tim. Также мы создали новый объект, наследующий первый и ссылаемся на него переменной jack. После этого переписываем два свойства в локальном объекте.
Теперь, если мы начинаем искать свойство jack.likesJavaScript, сначала находим объект, на который указывает jack. Далее ищем свойство likesJavaScript. Так как его там нет, смотрим в родительский объект и находим его там и получаем значение true, на которое это свойство ссылается.
Если вы когда-нибудь задумывались, почему инструменты типа jslint всегда советуют не забывать ставить выражение var перед объявлением переменной, то вот что случается в противном случае:
Обратите внимание, что likesJavaScript теперь свойство глобального объекта, вместо того, чтобы быть свободной переменной во внешней замкнутой области видимости. Это имеет значение только тогда, когда вы миксуете несколько скриптов. Но в реальных программах это именно то, что вы собираетесь делать, не так ли?
Запомните: всегда ставьте выражения var, чтобы удержать переменную в текущем и дочерних замкнутых областях видимости.
Если вам нужно положить что-то в глобальный объект, сделайте это специально, используя window.woo в браузере или global.woo в node.js
JavaScript — это не просто набор связанных структур данных. Он содержит исполняемый, вызываемый код, известный как функции. Функции создают связанные области видимости и замыкания.
Функцию можно изобразить специальным объектом, содержащим не только свойства, но и исполняемый код. Каждая функция имеет специальное свойство [scope] (область видимости), которое представляет среду, в которой находилась функция в момент объявления. Если функция возвращена из другой функции, то эта самая ссылка на среду, откуда ее вернули «закрыта» новой функцией в «замыкание».
В этом примере мы создадим простой factory-метод, генерирующий замыкание и возвращающий функцию.
Output
Cloe the Closure Albert the Awesome
Когда мы вызываем description1() виртуальная машина ищет функцию по этой ссылке и исполняет ее. Эта функция ищет локальную переменную, названную name, и находит ее в замкнутой области видимости. Этот factory-метод хорош тем, что каждая сгенерированная функция имеет свою область видимости для локальных переменных.
Если хотите узнать больше о замыканиях, смотрите в моей статье why use closure (прим.пер. если будет интересно, переведу и эту статью).
Иногда по причинам произдводительности или предпочтений в стиле программирования используются общие (shared) функции, которые позволяют использовать одну и ту же функцию в разных областях видимости, при помощи ключевого слова this.
Создадим пару объектов, которые делят общую функцию. В этой функции будут использоваться указатели на this, чтобы показать разницу.
Output
Lane the Lambda Fred the Functor undefined Zed the Zetabyte
В этой диаграмме мы видим, что хотя Fred.description было присвоено Lane.description, это всего лишь ссылка на функцию. Таким образом, все три ссылки указывают на одну общую анонимную фунцию. Вот почему я стараюсь не назвать функции в конструкторах прототипов «методами», это может ввести в заблуждение о некоторой привязке фунцкиии к конструктору и его «классу».
Если хотите узнать больше о this, смотрите в моей статье what is this (прим.пер. если будет интересно, переведу и эту статью).
Надеюсь, это поможет изучающим JavaScript глубже понять семантику языка. В прошлом я был и разрабочтиком/дизайнером клиентской части, и архитектором серверной, поэтому я выбрал особый визуальный путь объяснения.
Оригинал статьи от Tim Caswell: howtonode.org/object-graphs
От переводчика: это моя первая статья и перевод на Хабре, самому весь цикл статей показался очень полезным. У автора есть еще две части и много других материалов на сайте howtonode.org. Если есть неточности или поправки, исправлю. Не совсем уверен в правильности перевода closure (замыкание) и scope (пространство), может быть у кого-то есть лучшие версии? Если будет интересно, переведу остальные две части.
UPD: Исправил опечатки, привел в порядок примеры кода и по совету ilya42 заменил «пространства» на более правильные «области видимости».
Повсюду ссылки
Перменная в JavaScript — просто имя, указывающее на значение, хранящееся где-то в памяти. Эти значения могут быть как примитивами (строками, целыми числами, булевыми), так и объектами или функциями.
Локальные переменные
В следующем примере мы создадим четыре локальных переменных в области видимости (scope) высшего уровня и укажем их на некоторые примитивные значения:
// создадим несколько локальных переменных в области видимости верхнего уровня
var name = "Tim Caswell";
var age = 28;
var isProgrammer = true;
var likesJavaScript = true;
// Проверим, указывают ли две последние переменные на одно и то же примитивное значение
isProgrammer === likesJavaScript;
Output
=> true
Обратите внимание, что две переменные указывают на одно и то же значение в памяти. Это происходит потому что примитивы неизменяемы и виртуальная машина может исользовать один экземпляр объекта для всех ссылок-переменных, которые указывают на это значение.
В примере выше мы проверили, указывают ли две ссылки на одно и то же значение, используя оператор === и получили подтверждение true.
Прямоугольник слева в диаграмме — внешняя замкнутая область видимости (closure scope) высшего уровня. Переменные в ней — локальные переменные высшего уровня, важно не путать их со свойствами (properties) объекта global/window.
Объекты и цепочки прототипов
Объекты — просто наборы ссылок на другие объекты и прототипы. Единственное отличие заключается в добавлении цепочки прототипов (prototype chain), чтобы получить доступ к свойствам, находящимся не в локальном объекте, а в родительском.
// Создадим родительский объект
var tim = {
name: "Tim Caswell",
age: 28,
isProgrammer: true,
likesJavaScript: true
}
// Создадим дочерний объект
var jack = Object.create(tim);
// Локально переназначим некоторые свойства
jack.name = "Jack Caswell";
jack.age = 4;
// Теперь поищем какое-нибудь свойство в цепочке прототипов
jack.likesJavaScript;
Output
=> true
Здесь у нас есть один объект с четырьмя свойствами, на который ссылается переменная tim. Также мы создали новый объект, наследующий первый и ссылаемся на него переменной jack. После этого переписываем два свойства в локальном объекте.
Теперь, если мы начинаем искать свойство jack.likesJavaScript, сначала находим объект, на который указывает jack. Далее ищем свойство likesJavaScript. Так как его там нет, смотрим в родительский объект и находим его там и получаем значение true, на которое это свойство ссылается.
Глобальный объект
Если вы когда-нибудь задумывались, почему инструменты типа jslint всегда советуют не забывать ставить выражение var перед объявлением переменной, то вот что случается в противном случае:
var name = "Tim Caswell";
var age = 28;
var isProgrammer = true;
// Забываем поставить var
likesJavaScript = true;
Обратите внимание, что likesJavaScript теперь свойство глобального объекта, вместо того, чтобы быть свободной переменной во внешней замкнутой области видимости. Это имеет значение только тогда, когда вы миксуете несколько скриптов. Но в реальных программах это именно то, что вы собираетесь делать, не так ли?
Запомните: всегда ставьте выражения var, чтобы удержать переменную в текущем и дочерних замкнутых областях видимости.
Если вам нужно положить что-то в глобальный объект, сделайте это специально, используя window.woo в браузере или global.woo в node.js
Функции и области видимости
JavaScript — это не просто набор связанных структур данных. Он содержит исполняемый, вызываемый код, известный как функции. Функции создают связанные области видимости и замыкания.
Визуализация замыканий
Функцию можно изобразить специальным объектом, содержащим не только свойства, но и исполняемый код. Каждая функция имеет специальное свойство [scope] (область видимости), которое представляет среду, в которой находилась функция в момент объявления. Если функция возвращена из другой функции, то эта самая ссылка на среду, откуда ее вернули «закрыта» новой функцией в «замыкание».
В этом примере мы создадим простой factory-метод, генерирующий замыкание и возвращающий функцию.
function makeClosure(name) {
return function () {
return name;
};
}
var description1 = makeClosure("Cloe the Closure");
var description2 = makeClosure("Albert the Awesome");
console.log(description1());
console.log(description2());
Output
Cloe the Closure Albert the Awesome
Когда мы вызываем description1() виртуальная машина ищет функцию по этой ссылке и исполняет ее. Эта функция ищет локальную переменную, названную name, и находит ее в замкнутой области видимости. Этот factory-метод хорош тем, что каждая сгенерированная функция имеет свою область видимости для локальных переменных.
Если хотите узнать больше о замыканиях, смотрите в моей статье why use closure (прим.пер. если будет интересно, переведу и эту статью).
Общие функции и this
Иногда по причинам произдводительности или предпочтений в стиле программирования используются общие (shared) функции, которые позволяют использовать одну и ту же функцию в разных областях видимости, при помощи ключевого слова this.
Создадим пару объектов, которые делят общую функцию. В этой функции будут использоваться указатели на this, чтобы показать разницу.
var Lane = {
name: "Lane the Lambda",
description: function () {
return this.name;
}
};
var description = Lane.description;
var Fred = {
description: Lane.description,
name: "Fred the Functor"
};
// Вызваем функцию из разных областей видимости
console.log(Lane.description());
console.log(Fred.description());
console.log(description());
console.log(description.call({
name: "Zed the Zetabyte"
}));
Output
Lane the Lambda Fred the Functor undefined Zed the Zetabyte
В этой диаграмме мы видим, что хотя Fred.description было присвоено Lane.description, это всего лишь ссылка на функцию. Таким образом, все три ссылки указывают на одну общую анонимную фунцию. Вот почему я стараюсь не назвать функции в конструкторах прототипов «методами», это может ввести в заблуждение о некоторой привязке фунцкиии к конструктору и его «классу».
Если хотите узнать больше о this, смотрите в моей статье what is this (прим.пер. если будет интересно, переведу и эту статью).
Заключение
Надеюсь, это поможет изучающим JavaScript глубже понять семантику языка. В прошлом я был и разрабочтиком/дизайнером клиентской части, и архитектором серверной, поэтому я выбрал особый визуальный путь объяснения.
Оригинал статьи от Tim Caswell: howtonode.org/object-graphs
От переводчика: это моя первая статья и перевод на Хабре, самому весь цикл статей показался очень полезным. У автора есть еще две части и много других материалов на сайте howtonode.org. Если есть неточности или поправки, исправлю. Не совсем уверен в правильности перевода closure (замыкание) и scope (пространство), может быть у кого-то есть лучшие версии? Если будет интересно, переведу остальные две части.
UPD: Исправил опечатки, привел в порядок примеры кода и по совету ilya42 заменил «пространства» на более правильные «области видимости».