en.javascript.info/1-js/4-data-structures/6-object-reference/article.md
2015-04-18 01:40:37 +03:00

157 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Объекты: передача по ссылке
Фундаментальным отличием объектов от примитивов, является их хранение и копирование "по ссылке".
[cut]
## Копирование по значению
Обычные значения: строки, числа, булевы значения, `null/undefined` при присваивании переменных копируются целиком или, как говорят, *"по значению"*.
```js
var message = "Привет";
var phrase = message;
```
В результате такого копирования получились две полностью независимые переменные, в каждой из которых хранится значение `"Привет"`.
<img src="variable-copy-value.png">
## Копирование по ссылке
С объектами -- всё не так.
**В переменной, которой присвоен объект, хранится не сам объект, а "адрес его места в памяти", иными словами -- "ссылка" на него.**
Вот как выглядит переменная, которой присвоен объект:
```js
var user = {
name: "Вася"
};
```
<img src="variable-contains-reference.png">
Внимание: объект -- вне переменной. В переменной -- лишь "адрес" (ссылка) для него.
**При копировании переменной с объектом -- копируется эта ссылка, а объект по-прежнему остается в единственном экземпляре.**
Например:
```js
//+ no-beautify
var user = { name: "Вася" }; // в переменной - ссылка
var admin = user; // скопировали ссылку
```
Получили две переменные, в которых находятся ссылки на один и тот же объект:
<img src="variable-copy-reference.png">
**Так как объект всего один, то изменения через любую переменную видны в других переменных:**
```js
//+ run
var user = { name: 'Вася' };
var admin = user;
*!*admin.name*/!* = 'Петя'; // поменяли данные через admin
alert(*!*user.name*/!*); // 'Петя', изменения видны в user
```
[smart header="Переменная с объектом как \"ключ\" к сейфу с данными"]
Ещё одна аналогия: переменная, в которую присвоен объект, на самом деле хранит не сами данные, а ключ к сейфу, где они хранятся.
При копировании её, получается что мы сделали копию ключа, но сейф по-прежнему один.
[/smart]
## Клонирование объектов
Иногда, на практике -- очень редко, нужно скопировать объект целиком, создать именно полную независимую копию, "клон" объекта.
Что ж, можно сделать и это. Для этого нужно пройти по объекту, достать данные и скопировать на уровне примитивов.
Примерно так:
```js
//+ run
var user = {
name: "Вася",
age: 30
};
*!*
var clone = {}; // новый пустой объект
// скопируем в него все свойства user
for (var key in user) {
clone[key] = user[key];
}
*/!*
// теперь clone - полностью независимая копия
clone.name = "Петя"; // поменяли данные в clone
alert( user.name ); // по-прежнем "Вася"
```
В этом коде каждое свойство объекта `user` копируется в `clone`. Если предположить, что они примитивны, то каждое скопируется по значению и мы как раз получим полный клон.
Если же свойства объектов, в свою очередь, могут хранить ссылки на другие объекты, то нужно обойти такие подобъекты и тоже склонировать их. Это называют "глубоким" клонированием.
## Вывод в консоли
Откройте консоль браузера (обычно [key F12]) и запустите следующий код:
```js
//+ run
var time = {
year: 2345,
month: 11,
day: 10,
hour: 11,
minute: 12,
second: 13,
microsecond: 123456
}
console.log(time); // (*)
time.microsecond++; // (**)
console.log(time);
time.microsecond++;
console.log(time);
time.microsecond++;
```
Как видно, в нём некий объект выводится строкой `(*)`, затем он меняется в строке `(**)` и снова выводится, и так несколько раз. Пока ничего необычного, типичная ситуация -- скрипт делает какую-то работу с объектом и выводит в консоли то, как она продвигается.
Необычное -- в другом!
При раскрытии каждый объект будет выглядеть примерно так (скриншот из Chrome):
<img src="object-reference-console.png">
**Судя по выводу, свойство `microsecond` всегда было равно `123459`... Или нет?**
Если посмотреть на код выше то, очевидно, нет! Это свойство меняется, а консоль нас просто дурит.
**При "раскрытии" свойств объекта в консоли -- браузер всегда выводит их текущие (на момент раскрытия) значения.**
Так происходит именно потому, что вывод не делает "копию" текущего содержимого, а сохраняет лишь ссылку на объект. Запомните эту особенность консоли, в будущем, при отладке скриптов у вас не раз возникнет подобная ситуация.
## Итого
<ul>
<li>Объект присваивается и копируется "по ссылке". То есть, в переменной хранится не сам объект а, условно говоря, адрес в памяти, где он находится.</li>
<li>Если переменная-объект скопирована или передана в функцию, то копируется именно эта ссылка, а объект остаётся один в памяти.</li>
</ul>
Это -- одно из ключевых отличий объекта от примитива (числа, строки...), который при присвоении как раз копируется "по значению", то есть полностью.