05. Переменные

В этой заметке — разговор о переменных, ибо данные без связи с переменными имеют в программе мало смысла.

Описание переменных

Переменные в JavaScript описываются при помощи нетипизированной инструкции var:


var x = 1;   // x равно 1
var y;       // y равно undefined
var s="", d; // s равно пустой строке, d равно undefined

При описании переменной может быть присвоено значение, в противном случае она получает значение undefined.

В языке С запись int x = 1; есть не просто описание переменной (с присвоением значения), но и описание типа: int — целое число. После такого описания переменной x нельзя присваивать значения другого типа. Язык JavaScript — нетипизированный, переменная во время исполнения кода может многократно менять тип своего значения:


var x = 1; // x -- число
x = true;  // теперь x -- логическое значение
x = "";    // теперь x -- строка
x = function (t) { .... }  // теперь x -- функция

Повторное описание не меняет значение переменной, если только не сопровождается повторной инициализацией:


var x = 1; // x равно 1
var x;     // x равно 1 (а не undefined)
var x = 2; // x равно 2

Локальные и глобальные переменные

Переменные бывают локальные и глобальные.

Локальные переменные — это переменные, описанные в теле функции. Они видны (доступны) только внутри своей функции и перекрывают глобальные переменные с теми же именами (если такие есть).

Глобальные переменные — это переменные, описанные вне функций. Они видны (доступны) всюду: как вне всех функций, так и внутри любой из них (если только не перекрываются локальными переменными).

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


var x = 1;   // Глобальная переменная
function f()
{
  var x = 2; // Локальная переменная
  return x;
}
var y = f(); // Глобальная переменная y получает значение 2
x == y;      // Равно false  (глобальная переменная x не изменилась)

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


function f()
{
  x = 2; // Глобальная переменная
}
f();
y = x; // Глобальной переменной y присваивается 2 -- значение 
       // глобальной переменной x

Рекомендация. Обязательно описывайте внутренние переменные функции, иначе они станут глобальными. На самом деле, лучше всегда описывать переменные явно до их использования.

Обращение к неописанной переменной приводит к ошибке времени исполнения.


var x = 1 + z; // Приводит к ошибке, если z не описано (явно или неявно)

Ошибка возникнет и при выполнении такого кода (подумайте, почему):


function f()
{
  x = 2; 
}
y = x;  

Аргументы функции — это локальные переменные

Аргументы функции внутри её тела становятся локальными переменными, которые инициализируются передаваемыми в функцию значениями.


var x=0, y=0, z=0; 
function f(x, y, z)
{
  x *= 2; // x -- локальная переменная функции
  y *= 2; // y -- локальная переменная функции
  z *= 2; // z -- локальная переменная функции
  return x + y + z;
}
var t = f(1, 2, 3); // Равно 12 (1*2+2*2+3*2)
var k = x + y + z;  // Равно 0  (0+0+0)

Глобальные переменные — свойства глобального объекта

Глобальные переменные становятся свойствами глобального объекта window (объект, описывающий окно браузера). Следующие записи эквивалентны:


var x = 1; 
x = 1; 
window.x = 1; 

Работа переменных по ссылке и по значению

Переменные содержат:

Пример 1


var x = 1;
var y = x; // Копирование значения:
y = y + 1; // x содержит 1, а y содержит 2 

Пример 2


var x = {t:1};
var y = x; // Копирование ссылки на объект (а не самого объекта)
y.t = 2;   // y.t как и x.t теперь содержит 2, ибо и x и у ссылаются 
           // на один объект

Пример 3


var x = "1";
var y = x; // Копирование значения
z = y.concat(2); // y содержит "1", а z содержит "12" 

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

В самом деле, пусть инструкция var y = x; копирует в переменную y не строку, а ссылку на нее. Выражение z = y.concat(2) создает новую строку (не меняя исходной y), и ссылка на новую строку записывается в z.

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

Удобно считать, что строки хранятся по значению (как и другие данные элементарного типа). Но, реально, конечно, строки должны храниться по ссылкам ради эффективности работы JavaScript!

Манипулирование данными по значению и по ссылке

Поговорим подробнее о двух возможных способах работы с данными:

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

Над данными можно выполнять три операции:

  1. Копировать
  2. Передавать в функцию
  3. Сравнивать

Если с данными работать по значению, то:

  1. Копируются сами данные
  2. Передаются в функцию сами данные
  3. Сравниваются сами данные

Если с данными работать по ссылке, то:

  1. Копируются ссылки (а не данные)
  2. Передаются в функцию ссылки (а не данные)
  3. Сравниваются ссылки (а не данные)

Примеры работы с данными по значению

Пример 1


var x = 1;
var y = x; // В y копируется единица из x

Пример 2


var x = 1;
function f(t)
{
  t = t+1;
} 
f(x);   // В функцию передаётся копия значения переменной x. 
        // Изменение этой копии не повлияет на оригинал:     
x == 1; // Равно true.

Пример 3


var x = 1;
var y = 1; 
x==y; // Равно true так как сравниваются равные значения 

Примеры работы с данными по ссылке

Пример 1


var x = {p:1}; // Переменная x содержит не сам объект, а ссылку на него
var y = x; // В y копируется ссылка на объект x (а не сам объект)
y.p = 2;   // Меняется свойство объекта (на который ссылаются и x, и y)
x.p == 2;  // Равно true        

Пример 2


var x = {p:1}; // Переменная x содержит не сам объект, а ссылку на него
function f(t)
{           // t -- локальная переменная, она содержит ссылку на объект
  t.p = 2;  // t.p -- доступ к свойству глобального объекта по ссылке
} 
f(x);       // Функция внутри себя меняет свойство глобального объекта x
x.p == 2;   // Равно true

Пример 3


var x = {p:1}; // Переменная x содержит ссылку на объект
var y = {p:1}; // Переменная y содержит другую ссылку на другой объект
x == y;        // Равно false, так как ссылки разные 
               // (хотя сами объекты идентичны)
x.p == y.p;    // Равно true, так как сравниваются числа (по значению)

В следующей таблице кратко изложены принципы работы с данными по значению и по ссылке.

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

В разделе задания собраны контрольные вопросы (с ответами).