Главное правило JavaScript заключается в следующем: операции над элемен' тарными типами производятся по значению, а над ссылочными типами, как
и следует из их названия, – по ссылке. Числа и логические величины – это эле_ ментарные типы в JavaScript. Элементарные потому, что состоят из небольшого
и фиксированного числа байтов, операции над которыми с легкостью выполня_ ются низкоуровневым интерпретатором JavaScript. Представителями ссылоч_ ных типов являются объекты. Массивы и функции – это специализированные типы объектов и потому также являются ссылочными типами. Эти типы данных могут состоять из произвольного числа свойств или элементов, поэтому опериро_ вать ими не так просто, как значениями элементарных типов, имеющими фик_ сированные размеры. Поскольку размеры массивов и объектов могут быть чрез_ вычайно велики, операции по значению над ними могут привести к неоправдан_ ному копированию и сравнению гигантских объемов памяти.
А что можно сказать о строках? Строки могут иметь произвольную длину, поэто_ му их вполне можно было бы рассматривать как ссылочный тип. Тем не менее в JavaScript строки обычно рассматриваются как элементарный тип просто по_ тому, что они не являются объектами. В действительности строки не вписыва_ ются в двухполярный элементарно_ссылочный мир. Подробнее я остановлюсь на строках чуть позже.
Лучший способ выяснить различия между данными, операции над которыми производятся по ссылке и по значению, состоит в изучении практического при_ мера. Тщательно разберитесь в следующем примере, обращая особое внимание на комментарии. В примере 3.1 выполняется копирование, передача и сравне_ ние чисел. Поскольку числа являются элементарными типами, данный пример является иллюстрацией операций, выполняемых по значению.
Пример 3.1. Копирование, передача и сравнение величин по значению
// Первой рассматривается операция копирования
по значению
var
n
=
1;
//
Переменная n хранит значение 1
var
m
=
n;
//
Копирование по значению: переменная m хранит другое значение 1
// Данная функция используется для иллюстрации операции передачи величины по значению
// Как вы увидите, функция работает не совсем так, как хотелось бы
function add_to_total(total, x)
{
total = total + x; // Эта строка изменяет лишь внутреннюю копию total
}
// Теперь производится обращение к функции, которой передаются по значению числа,
// содержащиеся в переменных n и m. Копия значения из переменной n внутри функции
// доступна под именем total. Функция складывает копии значений переменных m и n,
64 Глава 3. Типы данных и значения
// записывая результат в копию значения переменной n. Однако это не оказывает
// никакого влияния на оригинальное значение переменной n за пределами функции.
// Таким образом, в результате вызова этой функции мы не получаем никаких изменений. add_to_total(n, m);
// Сейчас мы проверим операцию сравнения по значению.
// В следующей строке программы литерал 1 представляет собой совершенно
// независимое числовое значение, "зашитое" в текст программы. Мы сравниваем
// его со значением, хранящимся в переменной n. В данном случае, чтобы
// убедиться в равенстве двух чисел, выполняется их побайтовое сравнение.
if (n == 1) m = 2; // n содержит то же значение, что и литерал 1;
// таким образом в переменную m будет записано значение 2
Теперь рассмотрим пример 3.2. В этом примере операции копирования, переда_ чи и сравнения выполняются над объектами. Поскольку объекты относятся к ссылочным типам, все действия над ними производятся по ссылке. В данном примере использован объект Date, подробнее о котором можно прочитать в треть_ ей части книги.
Пример 3.2. Копирование, передача и сравнение величин по ссылке
// Здесь создается объект, который соответствует дате Рождества в 2007 году
// Переменная xmas хранит ссылку на объект, а не сам объект
var xmas = new Date(2007, 11, 25);
// Затем выполняется копирование ссылки, получается вторая ссылка на оригинальный объект var solstice = xmas; // Обе переменные ссылаются на тот же самый объект
// Здесь выполняется изменение объекта с помощью новой ссылки
solstice.setDate(21);
// Изменения можно наблюдать при использовании первой ссылки xmas.getDate( ); // Возвращает 21, а не первоначальное значение 25
// То же происходит при передаче объектов и массивов в функции.
// Следующая функция складывает значения всех элементов массива.
// Функции передается ссылка на массив, а не копия массива.
// Благодаря этому функция может изменять содержимое массива, переданного
// по ссылке, и эти изменения будут видны после возврата из функции. function add_to_totals(totals, x)
// Наконец, далее выполняется операция сравнения по ссылке.
// При сравнении двух переменных, созданных ранее, обнаруживается,
// что они эквивалентны, поскольку ссылаются на один и тот же объект даже
// при том, что производилось изменение даты по одной из них:
(xmas == solstice) // Возвращает значение true
// Две переменные, созданные позднее, ссылаются на разные объекты,
// каждый из которых содержит одну и ту же дату. var xmas = new Date(2007, 11, 25);
var solstice_plus_4 = new Date(2007, 11, 25);
// Но согласно правилу "сравнения по ссылке" ссылки на разные объекты
3.15. По значению или по ссылке
// не считаются эквивалентными!
(xmas != solstice_plus_4) // Возвращает значение true
Прежде чем закончить обсуждение темы выполнения операций над объектами
и массивами по ссылке, добавим немного ясности. Фраза «передача по ссылке» мо_ жет иметь несколько смыслов. Для некоторых из вас эта фраза означает такой спо_ соб вызова функции, который позволяет изменять эти значения внутри функции
и наблюдать эти изменения за ее пределами. Однако данный термин трактуется в этой книге в несколько ином смысле. Здесь просто подразумевается, что функ_ ции передается ссылка на массив или объект, но не сам объект. Функция же с по_ мощью этой ссылки получает возможность изменять свойства объекта или элемен_ ты массива, и эти изменения сохраняются по выходе из функции. Те из вас, кто знаком с другими трактовками этого термина, могут заявить, что объекты и масси_ вы передаются по значению, правда, этим значением фактически является ссылка на объект, а не сам объект. Пример 3.3 наглядно иллюстрирует эту проблему.
Пример 3.3. Ссылки передаются по значению
// Здесь приводится другая версия функции add_to_totals(). Хотя она не работает,
// потому что вместо изменения самого массива она изменяет ссылку на этот массив. function add_to_totals2(totals, x)
{
newtotals = new Array(3); newtotals[0] = totals[0] + x;