Объекты и функции
В прошлом уроке мы увидели, что переменная хранит ссылку на объект. Теперь посмотрим, что происходит, когда такую переменную передают функции.
Начнём с обычной задачи. Функции нужны данные о пользователе:
function printUser(name, age, city) { console.log(`${name}, ${age}, ${city}`);}printUser('Ира', 20, 'Москва');Здесь три связанных значения передаются отдельно. Нужно помнить их порядок, а если список данных растёт, растёт и список параметров. Если данные уже собраны в объект, функции можно передать его целиком:
const user = { name: 'Ира', age: 20, city: 'Москва',};function printUser(user) { console.log(`${user.name}, ${user.age}, ${user.city}`);}printUser(user);Теперь связанные данные путешествуют вместе – одним объектом. Функции больше не нужно принимать каждое поле отдельным параметром. Объект можно расширять новыми свойствами, не меняя сам способ передачи данных: вызов по-прежнему остаётся printUser(user).
Когда выполняется printUser(user), JavaScript берёт значение переменной user и записывает его в параметр функции. Для объекта этим значением является ссылка. Поэтому внутри функции появляется ещё одна переменная, ведущая к тому же объекту. Чтобы это было видно, дадим параметру другое имя:
const user = { name: 'Ира', age: 20,};function printUser(person) { console.log(person.name);}printUser(user);В JavaScript аргументы передаются по значению. В случае объекта этим значением является ссылка на него. Поэтому person получает копию ссылки, а не копию самого объекта.
const player = { name: 'Ира', level: 1,};function levelUp(character) { character.level += 1;}levelUp(player);console.log(player.level);Параметр character – отдельная переменная, но он ведёт к тому же объекту, что и внешний player. Поэтому строка character. меняет не параметр, а свойство общего объекта. После завершения функции объект остаётся изменённым:
// player → { name: 'Ира', level: 1 }// character → тот же объект// player → { name: 'Ира', level: 2 }// character → тот же объектplayer. Сейчас его level равен 1.Есть два очень похожих с виду действия: person. и person = { name: 'Ира', level: 1 }. Но делают они совершенно разное. Первый случай – мы идём по ссылке к существующему объекту и меняем его свойство:
const player = { name: 'Ира', level: 5,};function resetLevel(person) { person.level = 1;}resetLevel(player);console.log(player.level);Второй случай – параметру присваивают новый объект:
const player = { name: 'Ира', level: 5,};function reset(person) { person = { name: 'Ира', level: 1, };}reset(player);console.log(player.level);Присваивание person = ... не меняет старый объект. Оно лишь заставляет локальную переменную person указывать на новый. Внешняя переменная player вообще не участвовала в этом присваивании, поэтому продолжает вести к старому объекту:
// player → объект A { level: 5 }// person → объект A// player → объект A { level: 5 }// person → объект B { level: 1 }Объект – такое же значение JavaScript, как число или строка. Поэтому функция может не только получать объект, но и возвращать его:
function createPlayer(name) { return { name, level: 1, };}const player = createPlayer('Ира');console.log(player);При выполнении return создаётся объект, а вызов функции становится его значением:
Поэтому результат можно сохранить в переменной точно так же, как число или строку.
Функция может собирать объект из своих аргументов – по одному образцу для всех вызовов:
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);Каждый вызов выполняет литерал { ... } заново, поэтому ira и max – два разных объекта: ira === max даёт false.
Функцию, основная задача которой – создавать и возвращать объекты, часто называют фабричной функцией (factory function).
Мы встретили три обычных способа работы функций с объектами:
- функция получает объект и читает его свойства;
- функция получает объект и изменяет его;
- функция сама создаёт объект и возвращает его.
Самое важное различие – во втором случае: если функция меняет свойства переданного объекта, эти изменения видны и снаружи.
Но иногда нужно другое: взять существующий объект, изменить часть данных и оставить оригинал нетронутым. Для этого нужен отдельный объект с теми же данными – копия.
// теперь вы знаете
- Объект передаётся функции одним аргументом; параметр – новая локальная переменная с копией ссылки на тот же объект
character.внутри функции меняет общий объект – изменение видно снаружи и после возвратаlevel += 1 person = { … }перенаправляет только параметр: внешняя переменная ведёт к старому объекту- Функция может вернуть объект: вызов
createPlayer('Ира')становится этим объектом, и его можно сохранить в переменной - Фабричная функция собирает и возвращает объект; каждый вызов создаёт новый
Что появится в консоли?
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);