Копирование по значению или по ссылке
Между простыми значениями (undefined
, null
, логическими значениями, числами и строками) и объектами (включая массивы и функции) в языке JavaScript имеются фундаментальные отличия.
Простые значения являются неизменяемыми: простое значение невозможно изменить (или «трансформировать»). Это очевидно для чисел и логических значений – нет никакого смысла изменять значение числа. Однако для строк это менее очевидно. Поскольку строки являются массивами символов, вполне естественно было бы ожидать наличие возможности изменять символы в той или иной позиции в строке. В действительности JavaScript не позволяет сделать это, и все строковые методы, которые, на первый взгляд, возвращают измененную строку, на самом деле возвращают новое строковое значение.
Например:
var s = "hello"; // Изначально имеется некоторый текст из строчных символов
s.toUpperCase(); // Вернет "HELLO", но значение s при этом не изменится
s // => "hello": оригинальная строка не изменилась
Кроме того, величины простых типов сравниваются по значению: две величины считаются одинаковыми, если они имеют одно и то же значение. Для чисел, логических значений, null
и undefined
это выглядит очевидным: нет никакого другого способа сравнить их. Однако для строк это утверждение не выглядит таким очевидным. При сравнении двух строковых значений JavaScript считает их одинаковыми тогда и только тогда, когда они имеют одинаковую длину и содержат одинаковые символы в соответствующих позициях.
Объекты отличаются от простых типов.
Во-первых, они являются изменяемыми – их значения можно изменять:
var o = { x:1 }; // Начальное значение объекта
o.x = 2; // Изменить, изменив значение свойства
o.y = 3; // Изменить, добавив новое свойство
var a = [1,2,3] // Массивы также являются изменяемыми объектами
a[0] = 0; // Изменить значение элемента массив
a[3] = 4; // Добавить новый элемент
Объекты не сравниваются по значению: два объекта не считаются равными, даже если они будут иметь одинаковые наборы свойств с одинаковыми значениями. И два массива не считаются равными, даже если они имеют один и тот же набор элементов, следующих в том же порядке:
var o = {x:1}, p = {x:1}; // Два объекта с одинаковыми свойствами
o === p // => false: разные объекты не являются равными
var a = [], b = []; // Два различных пустых массива
a === b // => false: различные массивы не являются равными
Чтобы подчеркнуть отличие от простых типов JavaScript, объекты иногда называют ссылочными типами. Если следовать этой терминологии, значениями объектов являются ссылки, и можно сказать, что объекты сравниваются по ссылке: значения двух объектов считаются равными тогда и только тогда, когда они ссылаются на один и тот же объект в памяти.
var a = []; // Переменная a ссылается на пустой массив.
var b = a; // Теперь b ссылается на тот же массив.
b[0] = 1; // Изменение массива с помощью ссылки в переменной b.
a[0] // => 1: изменение можно наблюдать в переменной a.
a === b // => true: a и b ссылаются на один и тот же объект, поэтому они равны.
Как следует из примера выше, операция присваивания объекта (или массива) переменной фактически присваивает ссылку: она не создает новую копию объекта. Если в программе потребуется создать новую копию объекта или массива, необходимо будет явно скопировать свойства объекта или элементы массива. Следующий пример демонстрирует такое копирование с помощью цикла for:
var a = ['a','b','c']; // Копируемый массив
var b = []; // Массив, куда выполняется копирование
for(var i = 0; i < a.length; i++) { // Для каждого элемента в массиве a[]
b[i] = a[i]; // Скопировать элемент a[] в b[]
}
Точно так же, если потребуется сравнить два отдельных объекта или массива, необходимо будет сравнить значения их свойств или элементов. Ниже приводится определение функции, сравнивающей два массива:
function equalArrays(a,b) {
if (a.length != b.length) return false; // Массивы разной длины не равны
for(var i = 0; i < a.length; i++) // Цикл по всем элементам
if (a[i] !== b[i]) return false; // Если хоть один элемент отличается, массивы не равны
return true; // Иначе они равны
}