Введение в JavaScript, часть III: Объекты и массивы, подробный обзор
Введение в JavaScript, часть III: Объекты и массивы, подробный обзор
Автоматически переведённая страница
К сожалению, на полный ручной перевод у нас не хватает ресурсов.
Если вы увидели ошибку — отправьте пул-риквест с исправлениями (ссылка для редактирования в конце страницы).
Объекты
Всё в JavaScript — это объекты! За исключением true
, false
, простых чисел и строк, null
и undefined
. Да. Что это значит для вас? Это означает, что большинство вещей JS имеют свойства — те вещи, о которых мы говорили в первой части этого введения в JavaScript.
Так как вы можете создать и хранить новый объект? Синтаксис прост: вы создаете список свойств внутри {
этих }
, разделяя предметы запятыми (,
), и разделяя имена свойств и значения точкой с запятой (:
). Вы, возможно, видели такие конструкции, используя некоторые котомоды, например, tween:
var myObject = {
name: 'Молот Bug-Killing',
description: 'Купите этот молот, и вы сможете сокрушить этих насекомых с легкостью!',
damage: 100500,
price: NaN
};
Мы можем позже читать свойства объектов с помощью точки доступителя — того, который вы, наверное, видели везде, например, myObject.name
.
Объекты передаются между переменными и свойствами целыми, поэтому если вы продолжите предыдущий фрагмент кода и попытаетесь сохранить тот же объект, скажем, в копии, и затем попытаетесь изменить исходный объект, вы заметите, что изменения применяются к новому ссылке. Потому что это просто один объект, общий для разных переменных и свойств! Рассмотрим этот пример:
this.weapon = myObject;
// Позже...
console.log(this.weapon.price); // Это NaN! Это не хорошо, давайте исправим это!
myObject.price = 777; // Обратите внимание, что здесь мы не ссылаемся на `this.weapon`.
console.log(this.weapon.price); // Теперь это 777. Ура!
Вложенные объекты
Вы можете хранить объекты внутри других объектов. Вы можете использовать ссылки на другие объекты в свойствах объекта или ввести их:
this.weapon = {
name: 'The hammer of bug killing',
description: 'Buy this hammer and you will crush these bugs with ease!',
damage: 100500,
price: 777
};
this.helmet = {
name: 'The helmet of system thinking',
description: 'Allows you to get a whole picture',
wit: 5,
price: 100
};
this.gear = {
hands: this.weapon,
head: this.helmet,
body: { // You can inline new objects inside new objects!
name: 'The chestplate of ignorance',
description: 'Protects your mind from the outer world',
wit: -100,
mood: 5,
price: 3
}
};
console.log(this.gear.body.name); // Will return 'The chestplate of ignorance'.
Удаление свойств объектов полностью
Вы можете написать this.enemy = undefined
, и в большинстве случаев все будет хорошо, но если вы работаете с localStorage или другими хранимыми данными, или если вы перебираете свойства объекта, вам скорее всего понадобится удалить свойство без следов — иначе оно все еще есть, хотя и без определенного значения.
Вы можете использовать ключевое слово delete
, чтобы удалить любое свойство из объекта:
if (!templates.isValid(this.enemy)) {
delete this.enemy;
}
Если строки и числа являются константами, почему мы можем использовать методы с ними?
Потому что JavaScript умный! На самом деле, существуют объекты, основанные на простых строках и числах, и вы можете создать такие с помощью new String('Divine sausage')
, new Number(42)
, и даже new Boolean(true)
. Но эти методы не рекомендуются, потому что 99,99% времени вам эта функциональность не нужна. И это очень странная функциональность, которая выходит за рамки этой страницы введения.
Что вам действительно нужно, так это методы, которые есть у Number
и String
; чтобы форматировать эти значения и манипулировать строками. И JavaScript предоставляет их, когда вы пишете ' oh no '.trim()
или (99.9).toFixed(2)
.
Массивы
Массивы можно представить как объекты с числовыми и упорядоченными свойствами, с большим количеством вспомогательных функций для них.
Объявление нового массива заметно отличается от объявления нового объекта:
var groceryList = ['картофель', 'морковь', 'тимьян'];
this.waveEnemyAmount = [10, 10, 15, 15, 20, 25];
console.log(groceryList[0]); // Вывод: картофель
console.log(groceryList[1]); // Вывод: морковь
console.log(this.waveEnemyAmount); // Вывод: весь массив
Обратите внимание, как мы обращаемся к элементам массива: мы используем число в квадратных скобках, начиная с [0]
, чтобы получить значение.
Вы также можете хранить сложные объекты в массивах:
this.shopItems = [{
name: 'The hammer of bug killing',
description: 'Buy this hammer and you will crush these bugs with ease!',
damage: 100500,
price: 777
}, {
name: 'The helmet of system thinking',
description: 'Allows you to get a whole picture',
wit: 5,
price: 100
}, {
name: 'The chestplate of ignorance',
description: 'Protects your mind from the outer world',
wit: -100,
mood: 5,
price: 3
}];
console.log(this.shopItems[0].name); // Will output 'The hammer of bug killing'
console.log(this.shopItems[2].price); // Will output `3`, the price of the chestplate of ignorance
Здесь мы обращаемся к всему объекту с помощью [0]
, [1]
, [2]
и т. д., а затем читаем свойство этого объекта, добавляя .name
и .price
. Будьте внимательны к этой синтаксической конструкции!
Получение длины массива
Массивы имеют несколько функций для упрощения обработки любых игровых данных, которые вам могут понадобиться.
Во-первых, есть свойство length
, которое указывает количество элементов в массиве.
Как его можно использовать? Допустим, вы хотите ограничить количество мусора в инвентаре игрока:
this.inventory = ['sword', 'sword', 'sword', 'sword', 'sword', 'sword', 'sword', 'sword', 'sword', 'apple'];
this.maxInventorySize = 10;
// позже…
if (this.inventory.length < this.maxInventorySize) {
this.inventory.push('gold ingot'); // Добавить новый элемент
}
К сожалению, они никогда не получают этот золотой слиток.
Добавление новых элементов в массивы
Существует три метода для добавления нового элемента в массив:
var pizza = ['tomato sauce'];
pizza.push('pepperoni'); // Этот метод добавляет новый элемент в конец массива
pizza.unshift('dough'); // Этот метод добавляет элемент в начало массива
pizza.splice(2, 0, 'cheese'); // Этот метод добавляет новый элемент после pizza[2]. Пока игнорируем аргумент 0.
console.log(pizza); // Выводит массив с dough, tomato sauce, cheese и pepperoni. Мм... вкусняшка! 🍕
Удаление элементов из массива
Давайте съедим нашу пиццу!
var pizza = ['дрожжи', 'соус', 'сыр', 'перец'];
pizza.pop('дрожжи'); // Удалить последний элемент из массива
pizza.splice(1, 2); // Удалить два элемента, начиная с pizza[1]
pizza.splice(0, 1, 'корочка'); // Удалить один элемент, начиная с pizza[0], и заменить его на 'корочку'
pizza.shift(); // Удалить первый элемент массива
console.log(pizza); // Выводит пустой массив!
Дополнительная информация о методе array.splice
Вы можете увидеть, что pizza.splice
использовался в трех разных случаях: для добавления, удаления и замены значений. Как работает этот метод?
Этот метод используется для замены одного набора элементов на другой. Его полная форма — .splice(startFromIndex, deleteCount, addOne, addTwo, addThree, …)
. Но вы можете удалить несколько элементов и не добавлять ничего, чтобы удалить элементы из массива, или сделать наоборот — не удалять ничего и добавлять новые элементы:
- Когда вы пишете
.splice(3, 0, 'колбасу')
, вы добавляете новые элементы после третьего элемента массива. - Когда вы пишете
.splice(3, 1)
, вы удаляете один элемент. - Когда вы пишите
.splice(3, 1, 'колбасу')
, вы заменяете один элемент на другой. - Вы можете написать
.splice(3, 2)
для удаления нескольких элементов одновременно.
Функции для поиска, фильтрации, сортировки и сокращения массивов
Фильтрация с помощью array.filter
Функция array.filter
— это удобный инструмент, который создает новую массив из существующего. Вы передаете ему в аргумент функцию фильтрации, называемую «предикатом», написанную вами самими.
Давайте выберем всех нейтральных и дружелюбных существ в нашем bestiary:
var bestiary = [
{ name: 'Свинья', aggressiveness: 'нейтральный' },
{ name: 'Кошка', agressiveness: 'дружелюбная' },
{ name: 'Волк', aggressiveness: 'вражеская' },
{ name: 'Медведь', aggressiveness: 'вражеская' },
{ name: 'Волшебная пони', aggressiveness: 'нейтральный' }
];
var neutralAnimals = bestiary.filter( beast => {
if (beast.aggressiveness === 'вражеская') {
return false;
}
return true; // Этот блок будет выполнен только в том случае, если предыдущий блок не выполняется,
// потому что `return` останавливает выполнение функции.
});
console.log(neutralAnimals);
В данном примере все существа, возвращающие false
, будут исключены из массива neutralAnimals
. А те, которые возвращают true
, включатся в этот массив.
Давайте рассмотрим еще один пример: получение списка оружия, которое герой может купить сейчас:
this.money = 1230;
var shop = [
{ name: 'Искусство Измерения Констант', price: 130, type: 'book' },
{ name: 'Молот для борьбы с насекомыми', price: 100500, type: 'weapon' },
{ name: 'Ржавый топор отравления', price: 853, type: 'weapon' },
{ name: 'Свиток молний', price: 167, type: 'book' }
];
// здесь мы пропускаем `return` и скобки — это более короткая синтаксическая конструкция, а результат автоматически возвращается!
var purchaseable = shop.filter(item => item.price <= this.money);
var purchaseableWeapon = purchaseable.filter(item => item.type === 'weapon');
console.log(purchaseableWeapon);
Метод sort
массива
Метод array.sort
может работать без изменений с текстовыми элементами:
var groceryList = [
'морковь',
'картошка',
'салат',
'сосиски'
];
groceryList.sort();
console.log(groceryList); // картошка и морковь будут поменяны местами
Вы можете заметить, что в отличие от других подобных методов, сортировка не создает новый массив, а модифицирует существующий.
Вы также можете передать предикат, который будет возвращать, как два объекта сравниваются друг с другом. Давайте попробуем использовать список товаров из магазина выше и отсортировать его по цене в порядке возрастания:
var shop = [{
name: 'Искусство Царства Постоянных',
price: 130,
type: 'книга'
}, {
name: 'Молот для убийства насекомых',
price: 100500,
type: 'оружие'
}, {
name: 'Топор для уничтожения насекомых',
price: 853,
type: 'оружие'
}, {
name: 'Свиток молний',
price: 167,
type: 'книга'
}];
shop.sort((a, b) => {
return a.price - b.price;
});
Здесь мы берем два элемента и возвращаем разницу между первым (a
) и последним (b
). Если полученный вами результат отрицательный, первый элемент переместится ближе к индексу 0
, а второй элемент поднимется на более высокий индекс. Если результат вашего расчета положительный, происходит противоположное. Если возвращается 0
, элементы не меняются местами.
Совет
Вы можете вызвать метод sort
только один раз — JavaScript будет продолжать сортировать массив до тех пор, пока он не станет стабильным.
Методы поиска и проверки элементов массива
Существует множество методов для поиска элементов в массиве!
Проверка наличия элемента в массиве с помощью array.includes
Функция array.includes(value)
позволяет быстро проверить, присутствует ли определенный элемент в текущем массиве. Она возвращает логическое значение (true
или false
).
var buffs = ['vigor', 'rested', 'rage'];
// Добавить новый элемент в массив только если он еще не присутствует
if (!buffs.includes('blessed')) {
buffs.push('blessed');
}
Проверьте, удовлетворяют ли некоторые элементы в массиве определенному условию с помощью array.some
Это функция высшего порядка для проверки того, удовлетворяет ли по крайней мере один из элементов вашему условию. Она принимает предикат, который проходит через каждый элемент, и возвращает true
, только если по крайней мере одно вызовы этой функции вернули true
.
this.gear = [
{
name: 'Кувалда для истребления насекомых',
description: 'Купите эту кувалду, и вы сможете легко раздавить этих насекомых!',
damage: 100500,
enchantment: 'благословенный'
},
{
name: 'Шлем системного мышления',
description: 'Позволяет получить общее представление',
wit: 5,
enchantment: 'нет'
},
{
name: 'Щиты игнорирования',
description: 'Защищает ваш разум от внешнего мира',
wit: -100,
mood: 5,
enchantment: 'проклятый'
}
];
// Добавить деффлаб только если одно из предметов проклятые
if (this.gear.some(item => item.enchantment === 'проклятый')) {
this.debuffs.push('проклятый');
}
Получаем элемент, удовлетворяющий условию с помощью array.find
и array.findIndex
Они аналогичны по своей концепции методу array.some
, но возвращают первый элемент, который соответствует вашим требованиям. array.find
возвращает само элемент (или undefined
), если ничего не найдено, а array.findIndex
возвращает позицию подходящего элемента в массиве (или -1
, если такого элемента не существует).
this.gear = [{
name: 'The hammer of bug killing',
type: 'weapon'
}, {
name: 'The helmet of system thinking',
type: 'head'
}, {
name: 'The chestplate of ignorance',
type: 'torso'
}];
// Установка нанесенного урона на значение урона текущего элемента снаряжения
var weapon = this.gear.find(item => item.type === 'weapon');
this.damage = weapon.damage;
// Удаление шлемы
var helmetIndex = this.gear.findIndex(item => item.type === 'head');
if (helmetIndex !== -1) { // Убедитесь, что шлем был найден
this.gear.splice(helmetIndex, 1);
}
Найти индекс известного элемента в массиве с помощью array.indexOf
Аналогично array.findIndex
, array.indexOf
возвращает индекс указанного элемента в массиве. Это полезно, если вы храните числа или строки в массиве или у вас есть ссылка на искомый объект.
var groceryList = [
'картофель',
'морковь',
'огурец',
'банан',
'черешня'
];
var carrotIndex = array.indexOf('морковь');
if (carrotIndex !== -1) { // Убедитесь, что мы действительно нашли элемент
groceryList.splice(carrotIndex, 1);
}
Метод 'reduce' массива
Метод 'array.reduce' обходит каждый элемент массива, выполняя свою функцию (предикат), и передавая результат этой функции следующему вызову. Он обычно используется для быстрого сбора различных статистических данных по массиву, и написав предикат с другой логикой, можно рассчитывать различные статистические значения.
Предположим, вы разрабатываете игру в жанре "башня защиты", и у вас есть финальный босс в последнем раунде. Вы хотите рассчитать время до появления босса, суммируя задержки всех раундов.
Учитывая следующую структуру данных, мы можем использовать метод 'reduce' массива для получения суммы всех задержек:
var waves = [
{
delay: 30,
monsters: [
{
type: "Monster_Flyer",
health: 10,
amount: 10
}
]
},
{
delay: 10,
monsters: [
{
type: "Monster_Flyer",
health: 15,
amount: 12
}
]
},
{
delay: 12,
monsters: [
{
type: "Monster_Flyer",
health: 15,
amount: 20
},
{
type: "Monster_Tank",
health: 15,
amount: 20
}
]
},
{
delay: 20,
monsters: [
{
type: "Monster_Boss",
health: 1000,
amount: 1
}
]
}
];
var timeTillBoss = waves.reduce((currentSum, wave) => {
return currentSum + wave.delay;
}, 0); // Здесь 0 является начальным значением.
Теперь давайте используем тот же массив, чтобы получить количество всех врагов в раундах по их типу. Это будет более сложный пример, поскольку нам нужно обрабатывать массивы монстров внутри основного массива:
var monstersInTheLevel = waves.reduce((currentStats, wave) => {
for (const monsterGroup of wave.monsters) {
// Инициализируем группу монстров, если она еще не была добавлена в статистику
if (!currentStats[monsterGroup.type]) {
currentStats[monsterGroup.type] = 0;
}
currentStats[monsterGroup.type] += monsterGroup.amount;
}
}, {}); // Начальное значение - пустой объект
Было ли это понятно? Возможно. Можем ли мы сделать то же самое с обычными циклами? Конечно. Однако если у вас есть разные массивы, которые необходимо обработать, написание циклов для каждого массива может быть утомительным. С помощью 'reduce', 'forEach', 'filter' и 'find' вы можете хранить всю логику предиката в переменной или свойстве, а затем использовать его несколько раз при необходимости, что делает ваш код более чистым.
Тёмные знания о массивах в JavaScript 🕸🕷
Двухмерные массивы
В JavaScript массивы по умолчанию являются одновременными: это просто список. Но если вы создадите список списков, он вдруг станет похожим на двухмерные массивы в других языках программирования!
var myMap = [
[1, 1, 1, 1, 0],
[0, 0, 1, 0, 1],
[2, 0, 1, 1, 1],
[1, 0, 1, 0, 0],
[1, 1, 1, 0, 0],
];
В этом примере вы можете получить значение myMap[2][0]
(которое вернет 2
— элемент во втором ряду и в первой колонке).
Специальный, массивоподобный доступ к свойствам объектов
Вы помните, что почти всё в JavaScript — это объект? А массивы как насчёт них? Почему вы думаете, что у массивов есть специальная синтаксическая конструкция для доступа к их элементам?
Потому что это на самом деле не ограничено только массивами! Каждый объект может читать, удалять и изменять свои свойства с помощью массивоподобного доступора:
var myObject = {
name: 'Убийца багов',
description: 'Купите этот молоток, и вы сможете легко сокрушить этих насекомых!',
damage: 100500,
price: NaN
};
myObject['price'] = 1000;
Любое значение, которое может быть преобразовано в строки, может использоваться в качестве ключа:
// Это действительно странный пример, но что, если так и представляете настраиваемую систему способностей в ARPG?
var abilities = {
'0': 'Лунный удар',
'1': 'Разрезающий прыжок',
'5': 'Лечить'
};
// Переместите навык:
var ability = abilities[1];
delete abilities[1];
abilities[2] = ability;
И самое лучшее в том, что ключи не должны быть статичными! Вы можете вычислять значения, объединяя строки или совершая другие магические вещи:
this.stats = {
resistanceFire: 5,
resistanceIce: 0
};
this.armor = {
name: 'Плащ Холода',
resistanceType: 'Ice',
resistanceBoost: 15
};
// `'resistance' + this.armor.resistanceType` будет 'resistanceIce',
// поэтому свойство this.stats.resistanceIce будет изменено.
this.stats['resistance' + this.armor.resistanceType] += this.armor.resistanceBoost;
Особый случай: строки являются… массивами?!
Строки имеют свойство .length
, которое возвращает длину строки, конечно. Они также могут возвращать символы в конкретных позициях, если вы попытаетесь использовать массивный аксessor:
var string = 'Привет, ct.js!';
console.log(string.length); // вернёт 14
console.log(string[1]); // вернёт 'р'
Но у строк нет всех этих умных методов, таких как .forEach
, .map
или .filter
. На самом деле у них есть свои собственная методы, чтобы упростить преобразование строк, такие как .trim()
, .search
, .replace
и другие. Они также имеют свой собственный .slice
. Не нужно использовать строки как настоящие массивы, но если вам действительно нужен этот режим работы, вы можете использовать Array.from(yourString)
, чтобы создать новый массив, элементы которого будут символами строки.
Заключение
Объекты и массивы невероятно мощные — они в основном свободные структурные формы, которые могут вместить все, что вы в них кладете. Знание того, как эффективно управлять ими, позволит вам писать код быстрее и легче. Но не волнуйтесь, если вы сейчас не можете вспомнить всё — совершенство приходит с практикой, а практика требует времени. Не так ли? Я, например, использую каждый из этих методов в лучшем случае раз в месяц или около того, хехех.
Удачи в кодинге!
CoMiGo