Объекты и функции

В прошлом уроке мы увидели, что переменная хранит ссылку на объект. Теперь посмотрим, что происходит, когда такую переменную передают функции.

Начнём с обычной задачи. Функции нужны данные о пользователе:

print-user-params.jsjs
function printUser(name, age, city) {  console.log(`${name}, ${age}, ${city}`);}printUser('Ира', 20, 'Москва');
Консоль
Ира, 20, Москва

Здесь три связанных значения передаются отдельно. Нужно помнить их порядок, а если список данных растёт, растёт и список параметров. Если данные уже собраны в объект, функции можно передать его целиком:

print-user-object.jsjs
const user = {  name: 'Ира',  age: 20,  city: 'Москва',};function printUser(user) {  console.log(`${user.name}, ${user.age}, ${user.city}`);}printUser(user);
Консоль
Ира, 20, Москва

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

Что попадает в параметр

Когда выполняется printUser(user), JavaScript берёт значение переменной user и записывает его в параметр функции. Для объекта этим значением является ссылка. Поэтому внутри функции появляется ещё одна переменная, ведущая к тому же объекту. Чтобы это было видно, дадим параметру другое имя:

person.jsjs
const user = {  name: 'Ира',  age: 20,};function printUser(person) {  console.log(person.name);}printUser(user);
Консоль
Ира
user person { name: 'Ира', age: 20 }
Параметр person – ещё одно имя для того же объекта.

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

Функция меняет объект

level-up.jsjs
const player = {  name: 'Ира',  level: 1,};function levelUp(character) {  character.level += 1;}levelUp(player);console.log(player.level);
Консоль
2

Параметр character – отдельная переменная, но он ведёт к тому же объекту, что и внешний player. Поэтому строка character.level += 1 меняет не параметр, а свойство общего объекта. После завершения функции объект остаётся изменённым:

до вызова
// player    → { name: 'Ира', level: 1 }// character → тот же объект
после character.level += 1
// player    → { name: 'Ира', level: 2 }// character → тот же объект
Изменение внутри функции видно снаружи
const player = {
name: 'Ира',
level: 1,
};
Создали объект player. Сейчас его level равен 1.
function levelUp(character) {
character.level += 1;
}
levelUp(player);
console.log(player.level);

Изменить объект и заменить параметр – разные вещи

Есть два очень похожих с виду действия: person.level = 1 и person = { name: 'Ира', level: 1 }. Но делают они совершенно разное. Первый случай – мы идём по ссылке к существующему объекту и меняем его свойство:

reset-level.jsjs
const player = {  name: 'Ира',  level: 5,};function resetLevel(person) {  person.level = 1;}resetLevel(player);console.log(player.level);
Консоль
1

Второй случай – параметру присваивают новый объект:

reset.jsjs
const player = {  name: 'Ира',  level: 5,};function reset(person) {  person = {    name: 'Ира',    level: 1,  };}reset(player);console.log(player.level);
Консоль
5

Присваивание person = ... не меняет старый объект. Оно лишь заставляет локальную переменную person указывать на новый. Внешняя переменная player вообще не участвовала в этом присваивании, поэтому продолжает вести к старому объекту:

до присваивания
// player → объект A { level: 5 }// person → объект A
после person = { … }
// player → объект A { level: 5 }// person → объект B { level: 1 }

Функция может возвращать объект

Объект – такое же значение JavaScript, как число или строка. Поэтому функция может не только получать объект, но и возвращать его:

create-player.jsjs
function createPlayer(name) {  return {    name,    level: 1,  };}const player = createPlayer('Ира');console.log(player);
Консоль
{ name: 'Ира', level: 1 }

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

createPlayer('Ира') { name: 'Ира', level: 1 }
Значение вызова – объект, который вернула функция.

Поэтому результат можно сохранить в переменной точно так же, как число или строку.

Каждый вызов может создавать новый объект

Функция может собирать объект из своих аргументов – по одному образцу для всех вызовов:

create-user.jsjs
function createUser(name, age) {  return {    name,    age,    isAdmin: false,  };}const ira = createUser('Ира', 20);const max = createUser('Макс', 31);console.log(ira.name);console.log(max.name);console.log(ira === max);
Консоль
Ира
Макс
false

Каждый вызов выполняет литерал { ... } заново, поэтому ira и max – два разных объекта: ira === max даёт false.

Функцию, основная задача которой – создавать и возвращать объекты, часто называют фабричной функцией (factory function).

Объект в функции: три ситуации

объект получить и прочитать printUser() получить и изменить levelUp() создать и вернуть createUser()
Функция читает объект, меняет его или создаёт новый.

Мы встретили три обычных способа работы функций с объектами:

  • функция получает объект и читает его свойства;
  • функция получает объект и изменяет его;
  • функция сама создаёт объект и возвращает его.

Самое важное различие – во втором случае: если функция меняет свойства переданного объекта, эти изменения видны и снаружи.

Но иногда нужно другое: взять существующий объект, изменить часть данных и оставить оригинал нетронутым. Для этого нужен отдельный объект с теми же данными – копия.

// теперь вы знаете

  1. Объект передаётся функции одним аргументом; параметр – новая локальная переменная с копией ссылки на тот же объект
  2. character.level += 1 внутри функции меняет общий объект – изменение видно снаружи и после возврата
  3. person = { … } перенаправляет только параметр: внешняя переменная ведёт к старому объекту
  4. Функция может вернуть объект: вызов createPlayer('Ира') становится этим объектом, и его можно сохранить в переменной
  5. Фабричная функция собирает и возвращает объект; каждый вызов создаёт новый

Проверьте себя

Что появится в консоли?

const cart = { items: 2 };
function addItem(basket) {
basket.items += 1;
}
addItem(cart);
console.log(cart.items);

Верно ли, что этот код напечатает false?

const user = { name: 'Ира' };
function isSame(person) {
return person === user;
}
console.log(isSame(user));

Что появится в консоли?

const cart = { items: 2 };
function clear(basket) {
basket = { items: 0 };
}
clear(cart);
console.log(cart.items);

Что напечатает этот код?

const player = { name: 'Ира', level: 1 };
function levelUp(character) {
character.level += 1;
return character;
}
const result = levelUp(player);
result.level += 1;
console.log(player.level);

Что появится в консоли?

function createCart(items) {
return { items };
}
console.log(createCart(3).items);

Функция возвращает объект литералом return { count: 0 };. Верно ли, что два её вызова вернут один и тот же объект?

Какую строку поставить вместо // ?, чтобы код напечатал Макс?

const user = { name: 'Ира' };
function rename(person) {
// ?
}
rename(user);
console.log(user.name);