0,0
рейтинг
29 марта 2012 в 12:38

Разработка → JavaScript в диаграммах (Часть 1) из песочницы

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

Повсюду ссылки


Перменная в 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 заменил «пространства» на более правильные «области видимости».


Артем Вербовиков @verbovkov
карма
22,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (18)

  • –2
    Гиковато, но полезно. А замыкание оно и в Африке — замыкание. Версий лучше не нужно.
  • +1
    Нужный перевод, хороший материал, без воды, несмотря на то, что статья 2010 года.
    По-моему, зря выкинули примеры кода.

    Спасибо за публикацию.
  • 0
    Вы отлично перевели это статью! прочитал как будто главу в хорошей IT книге!
    И да, статейку про замыкания тоже круто было бы почитать в Вашем переводе :)
  • +6
    Более подробно о контекстах и замыканиях можно почитать у Дмитрия Сошникова: dmitrysoshnikov.com/ecmascript/ru-javascript-the-core/
    • +2
      Лучший материал на русском, который я видел. Особенно цикл статей «Тонкости ECMA-262-3»
  • 0
    Поранилась статья и перевод. Жду продолжения. Спасибо!
  • +1
    Спасибо. Перевод понравился. Ещё вот ссылка на мой топик, где описано, как всё это работает с точки зрения низкоуровневой реализации.
    • 0
      Понравилась ваша статья, спасибо.
  • 0
    Спасибо за перевод, это, наверное, самые наглядные объяснения области видимости и как работает this
  • +2
    1. весьма странно говорить о scope vs global vs window, не обозначая что такое этот самый scope.
    В примере с «локальными переменными» — откуда и чем определяется scope? границаим файла? браузером? жаваскрипт-движком? или предполагается что вокруг него где-то стоят { }?

    2. Object.create — это не семантика языка, а семантика фичи распоследней версии ecmascript.
    Семантика языка былабы через foo.__proto__ = protofoo

    3. Свойство __proto__ на диаграммах не обозначено.

    4. Ссылка на прототип обозначена также как и вложение scope.

    5. теперь у вас есть и сущность [Scope] и свойство замыкания [scope] и отличная диаграмма Cloe the Closure Albert the Awesome на которой непонятно откуда и куда идут стрелочки.

    Вы уверены, что ваша статья что-то проясняет для тех, кто не знаком с жаваскриптом?
    Может быть, лучше отправить новичков на javascript.ru?
    • 0
      По пунктам 2,3 — прототип в стандарте обозначается как [Prototype] (видел вариант [[Prototype]]). __proto__ — это такая же семантика фичи, как и Object.create, да еще и нестандартная. Этого свойства на диаграмме нет, поскольку оно обозначено так же, как и вложение scope.

      Также могу добавить пунктов.

      6. Сущности [Object] и [Scope] указаны как принципиально разные, хотя это не так ([Scope] в некоторых движках может стать объектом при вызове функции без контекста, [Object] можно встроить в scope chain при помощи оператора with)
      С учетом оператора with становится еще важнее пункт 4, поскольку в результате его использования текущий scope будет иметь и внешний scope, и прототип.
  • –1
    var name = "Tim Caswell";
    var age = 28;
    var isProgrammer = true;
    // Забываем поставить var
    likesJavaScript = true;
    

    Пример близок к истине, но ошибочный. true — это сокращённая запись выражения new Boolean(true)? т.е. берёт новый объект. Если хотя бы trueVar — то другое дело.
    • 0
      Вы не путаете с другими языками? Boolean это «костыльная» обертка, для приведения других типов к булевому. К тому же позволяющая всякие извращения:

      var a = true;
      var b = new Boolean(true);
      
      if (a == b) {
          console.log("a == b");
      } // пока все ок
      
      
      if (a === b) {
          console.log("a === b");      
      } else {
          console.log("Хм...");
      }
      
      if (new Boolean(true) == b) {
          console.log("new Boolean(true) == b");
      } else { 
          console.log("О как..."); 
      }
      
      if ((new Boolean(true)).valueOf() == (new Boolean(true)).valueOf()) {
          console.log("Ну хоть как-то");
      }
      
      ​if(new Boolean(new Boolean(false)) == true) {
          console.log("FML!");
      }​​​​​​​​
      
      


      Очевидно, что полезность объекта Boolean в реальной жизни стремится к нулю. Другими словами это мусорный объект в текущих реализациях.
      • 0
        еще могу дополнить:
        if(new Boolean(false)) console.log('WTF?');
        так что да, обьект Boolean бесполезный и приводит к путаницам. А функция Boolean вполне полезная для приведения чего-либо к булевому значению:
        if(Boolean('') && Boolean(0) && Boolean(-1)) console.log('no');
      • 0
        При чём тут конструктор Boolean? С его помощью подчеркнул, что true и true в программе — разные объекты, а не так, как утверждает автор (хотя true===true, т.к. константы). Как раз то, что Вы назвали «Хм...» и «О как». В последнем, кстати, пропустили "=" (надо "==="), чтобы убедиться, что не тру. А константа true и объект new Boolean(true) — это, конечно, разные вещи, потому что разные ссылки на объекты (тут выразительных средств джаваскрипта не хватает, чтобы пояснить на нём суть ошибки автора.
        • 0
          Точнее, можно объяснить так. Константа = это один и тот же объект. Но присваивается по значению. Сразу после присваивания эти значения — уже разные объекты (точнее, примитивы).
  • 0
    В примере выше мы проверили, указывают ли две ссылки на одно и то же значение, используя оператор === и получили подтверждение true.

    OMG. Какая ересь!
    • 0
      Вот и я о том же чуть выше.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.