Подводный мира JavaScript
Изучая Javascript по книге автора Марейн Хавербек Выразительный Javascript я наткнулся на три забавные ассоциации. Идея мне понравилась, и я решил визуализировать другие термины, вызывающие трудности восприятия.
INDEX
- Привязки* - их можно легко отращивать
- Объекты* - осьминоги с любым количеством щупалец
- Массивы* - тоже осьминоги, но длинные и плоские
- Свойства - их имена вытатуированы на щупальцах
- Функции - подводные лодки
- Подробнее: функции - элементы подводного мира
- Методы - умения и трюки осьминогов
- Значения — сокровища подводного мира
Привязки — щупальца осьминога
Привязки (let, const) лучше представлять себе не как коробки, а как щупальца: привязки не содержат значения, а захватывают их. Две привязки могут ссылаться на одно и то же значение. Программа имеет доступ только к тем значениям, на которые есть ссылки. Если нужно что-то запомнить, отращиваем «щупальце», чтобы удерживать это, или захватываем его одним из уже существующих щупалец.
Когда мы создаем привязку, не давая ей значения, щупальцу нечего схватить и оно повисает в воздухе. Если вы попробуете узнать значение пустой привязки, то получите значение undefined.
Привязки свойств аналогичны. Они захватывают значения, но другие привязки и свойства могут удерживать те же значения.
Объекты — осьминоги с произвольным количеством щупалец
Объект можно представить себе как осьминога с произвольным количеством щупалец, на каждом из которых вытатуировано имя. Оператор delete отсекает щупальце такого осьминога. Это унарный оператор. Будучи примененным к свойству объекта, он удаляет из объекта данное именованное свойство. Такое встречается нечасто, но это возможно.
Массивы - тоже осьминоги, но другие
Массивы это особые объекты для хранения последовательностей. Если объект — это осьминог, то массив — это дисциплинированный осьминог с идеально упорядоченными щупальцами!
Итак, массивы - это длинные плоские осьминоги, у которых все щупальца выстроились в аккуратный ряд и каждое щупальце помечено цифрой. Такая цифра есть index (т.е. местоположение элемента в массиве).
Свойства - именованные связи внутри объекта
Татуировки на щупальцах осьминога — это имена свойств объектов.
Почему это свойства?
- У каждого "щупальца" осьминога есть татуировка, которая, по сути, обозначает ключ свойства объекта.
- Эти свойства связывают ключ (татуировку) с соответствующим значением (чем-то, что осьминог может "держать").
- В отличие от привязок, которые в аналогии представлены как сами щупальца, свойства — это именованные связи внутри объекта.
Отличие татуировок от привязок
1: Привязки (щупальца) удерживают значения, к которым они обращаются. Это просто связь между именем и значением в программе. Например:
let a = 10; // Привязка "a" захватывает значение 10
let b = a; // Привязка "b" захватывает то же значение
let octopus = {
arm1: "pearls",
arm2: "gold",
};
console.log(octopus.arm1); // "pearls" — осьминог удерживает жемчуг на щупальце arm1
Здесь:
- arm1 и arm2 — "татуировки", т.е. имена свойств.
- "pearls" и "gold" — значения, которые удерживают щупальца.
Расширим подводную аналогию:
1: Если татуировки (имена свойств) стереть с щупальца с помощью оператора delete, осьминог теряет это щупальце:
delete octopus.arm1; // Щупальце с татуировкой "arm1" исчезает
2: Массивы — это осьминоги, у которых все щупальца расположены в строгом порядке, а вместо имён — пронумерованные метки (индексы):
let arrayOctopus = ["seaweed", "coral", "fish"];
console.log(arrayOctopus[1]); // "coral" — доступ по номеру щупальца
3: Если осьминог (объект) растёт, он может отрастить новые щупальца и добавить новые татуировки:
let arrayOctopus = ["seaweed", "coral", "fish"];
console.log(arrayOctopus[1]); // "coral" — доступ по номеру щупальца
octopus.arm3 = "diamonds"; // новое "щупальце" с именем arm3
Функции — подводные лодки
Функции можно представить как подводные лодки, которые выполняют различные миссии под водой. Когда мы "запускаем" функцию, это похоже на отправку лодки в плавание: мы задаём курс (параметры) и ожидаем результат (место назначения или выполненное действие).
Как это работает:
- Имя функции — это название лодки (например, explorerSubmarine).
- Параметры функции — это инструкции для лодки, например, "погрузиться на глубину 50 метров".
- Результат функции — это данные или действие, которые лодка приносит обратно, например, сообщение об обнаружении, найденного затонувшего корабля, карту маршрута к нему и т.п.
Пример функции:
function exploreUnderwater(depth) {
if (depth < 50) {
return "риф";
} else if (depth < 100) {
return "пещера";
} else {
return "вулкан";
}
}
console.log(exploreUnderwater(60)); // "пещера"
В данном примере функция exploreUnderwater — это подлодка, которая погружается на указанную глубину и возвращает информацию о том, что она обнаружила.
Дополнительный материал о функциях
Функции как типы-ссылки
[Functions as reference types]
Функции в JavaScript — это мощные инструменты, которые можно представить как подводные лодки с уникальными возможностями. Они могут действовать самостоятельно, передаваться другим элементам [entities] и даже возвращать новые подлодки для выполнения дальнейших задач.
function dive() {
console.log("Diving into the deep sea!");
}
let subController = dive;
subController(); // Output: "Diving into the deep sea!"
В этом примере функция dive присваивается переменной subController, создавая ссылку на оригинальную функцию.
Задачи #task:
- Создайте функцию logMessage, которая выводит в консоль сообщение "Sub is ready for a mission!". Присвойте её переменной missionLog. Вызовите функцию через эту переменную.
- Создайте функцию square, которая возвращает квадрат числа. Присвойте её новой переменной squareCalculator. Вызовите функцию через эту переменную для числа 7 и выведите результат в консоль.
Показать решения
-
function logMessage() {
console.log("Sub is ready for a mission!");
}
let missionLog = logMessage;
missionLog(); // Output: "Sub is ready for a mission!" -
function square(num) {
return num * num;
}
const squareCalculator = square;
console.log(squareCalculator(7)); // Output: 49
Использование функций в качестве аргументов
В JavaScript функции можно передавать как аргументы другим функциям. Это называется коллбэками [callbacks].
function executeMission(callback) {
const area = "coral reef";
callback(area);
}
function reportArea(location) {
console.log(`Exploring: ${location}`);
}
executeMission(reportArea); // Output: "Exploring: coral reef"
Функция executeMission принимает другую функцию reportArea в качестве аргумента и вызывает её с указанным параметром.
Наутика о коллбэках
Подлодка executeMission отправляется исследовать океан, но у неё нет своего оборудования для анализа найденных мест. Поэтому она принимает на борт функцию-коллбэк (callback) для исследования и обработки данных. Callback это абстрактное обозначение специалистов-исследователей такого рода.
- Пока подлодка добиралась до исследуемой области, называемой area, (а это коралловый риф), исследователь сообщила своё имя - Функция reportArea
- Как только добрались до location, исследователь начала изучение location, отрапортовав о начале сбора данных - exploring: coral reef.
Этот пример показывает, как функции-коллбэки помогают разделить ответственность: подлодка занимается погружением, а исследователь обрабатывает данные.
Задачи #task:
- Создайте функцию doubleAndCall, которая принимает другую функцию и число. Она должна вызывать переданную функцию с удвоением числа. Создайте функцию logResult, которая выводит результат в консоль, и используйте её как коллбэк.
Показать решения
-
function doubleAndCall(fn, num) {
fn(num * 2);
}
function logResult(result) {
console.log(`Result: ${result}`);
}
doubleAndCall(logResult, 5); // Output: "Result: 10"
Пример применения: вызов функций для всех элементов массива
Метод forEach()
Подлодки могут работать с группой объектов, например, сканировать каждый риф из большого массива рифов. Для этого используется метод forEach, передающий инструкции для сканирования каждого рифа. Метод forEach позволяет выполнять функцию для каждого элемента массива.
const reefChain = ["reef A", "reef B", "reef C"];
reefChain.forEach(function (reef) {
console.log(`Scanning: ${reef}`);
});
// Output:
// Scanning: reef A
// Scanning: reef B
// Scanning: reef C
Задачи #task:
- Создайте массив numbers, содержащий числа 1, 2, 3, 4 и 5. Используйте метод forEach, чтобы подсчитать сумму всех чисел в массиве. Для хранения суммы создайте переменную sum и обновляйте её в теле функции-коллбэка. Аргумент функции-коллбэка назовите num.
Показать решения
-
const numbers = [1, 2, 3, 4];
let sum = 0;
// Создаём переменную sum для хранения суммы numbers.forEach(function (num) {
// Используем метод forEach для подсчёта суммы sum += num;
// Добавляем текущий элемент массива к sum });
console.log(num); // Output: 1, 4, 9, 16
Использование функций в качестве возвращаемых значений
Функции могут не только принимать задачи, но и возвращать другие функции. Это как если бы одна подлодка передавала управление другой, чтобы она завершила миссию.
function createSub(name) {
return function (mission) {
console.log(`Sub ${name} is performing: ${mission}`);
};
}
const researchSub = createSub("Explorer");
researchSub("Exploring the depths"); // Output: "Sub Explorer is performing: Exploring the depths"
В данном примере функция createSub создаёт и возвращает новую функцию, которая затем выполняет указанную задачу.
Объединение вызовов функций (цепочка вызовов)
Цепочка вызовов позволяет выполнять несколько последовательных действий, возвращая объект для последующего вызова. Это как если бы подлодка сначала заправилась, потом погрузилась, а затем начала исследование.
const submarine = {
refuel() {
console.log("Refueling completed!");
return this;
},
dive(depth) {
console.log(`Diving to ${depth} meters.`);
return this;
},
explore() {
console.log("Exploration completed.");
return this;
}
};
submarine.refuel().dive(100).explore();
// Output:
// Refueling completed!
// Diving to 100 meters.
// Exploration completed.
В этом примере объект submarine позволяет вызывать методы последовательно, возвращая себя после каждого вызова.
Задачи #task:
- Создайте объект sub, который содержит методы status, start и end. Метод status должен выводить сообщение "Status check complete.", метод start — "Mission started.", а метод end — "Mission completed.". Все методы должны возвращать объект sub, чтобы их можно было вызывать в цепочке.
- Создайте объект namedSub с методом dive, который принимает глубину (depth) и выводит сообщение "Diving to <глубина> meters.". Добавьте объекту метод getName, который выводит имя подлодки. Все методы должны возвращать объект namedSub, чтобы их можно было вызывать в цепочке.
Показать решения
-
const sub = {
status() {
console.log("Status check complete.");
return this;
},
start() {
console.log("Mission started.");
return this;
},
end() {
console.log("Mission completed.");
return this;
}
};
sub.status().start().end(); // Output: // Status check complete. // Mission started. // Mission completed. -
const namedSub = {
name: "Nautilus",
dive(depth) {
console.log(`Diving to ${depth} meters.`);
return this;
},
getName() {
console.log(`Sub name: ${this.name}`);
return this;
}
};
namedSub.dive(200).getName(); // Output: // Diving to 200 meters. // Sub name: Nautilus
Стандартные методы каждой функции
Функции в JavaScript обладают встроенными методами, такими как bind, call и apply. Эти методы позволяют изменять контекст вызова функции или передавать ей параметры.
Метод bind: создаёт новую функцию с привязанным контекстом.
const captain = { name: "Captain Nemo" };
function command() {
console.log(`${this.name} is giving orders.`);
}
const boundCommand = command.bind(captain);
boundCommand(); // Output: "Captain Nemo is giving orders."
Метод call: вызывает функцию с указанным контекстом и передаёт аргументы по отдельности.
function dive(depth) {
console.log(`${this.name} is diving to ${depth} meters.`);
}
dive.call(captain, 200); // Output: "Captain Nemo is diving to 200 meters."
Метод apply: вызывает функцию с указанным контекстом, но аргументы передаются массивом.
dive.apply(captain, [300]); // Output: "Captain Nemo is diving to 300 meters."
Задачи #task:
- Создайте объект ship с методом sail, который выводит сообщение "<Название корабля> is sailing.". Используйте метод bind, чтобы привязать метод sail к другому объекту harbor.
- Создайте функцию executeMission, которая принимает два параметра: название миссии и её продолжительность. Напишите функцию, которая вызывает executeMission, передавая параметры через массив с использованием метода apply.
Показать решения
-
const ship = { name: "Black Pearl" };
function sail() {
console.log(`${this.name} is sailing.`);
}
const boundSail = sail.bind(ship);
boundSail(); // Output: "Black Pearl is sailing." -
function explore(depth, duration) {
console.log(`Exploring at ${depth} meters for ${duration} hours.`);
}
const params = [400, 5];
explore.apply(null, params);
// Output: "Exploring at 400 meters for 5 hours."
Привязка объектов с помощью метода bind()
Метод bind позволяет привязать определённый контекст this к функции. Это похоже на настройку подлодки, которая будет работать под руководством конкретного капитана.
const captain = { name: "Captain Nemo" };
function command() {
console.log(`${this.name} is giving orders.`);
}
const boundCommand = command.bind(captain);
boundCommand(); // Output: "Captain Nemo is giving orders."
В этом примере функция command привязывается к объекту captain, благодаря чему this внутри функции указывает на объект капитана.
Задачи #task:
- Создайте объект vehicle с методом move, который выводит сообщение "<Название транспорта> is moving.". Используйте метод bind, чтобы привязать метод move к другому объекту bike.
- Создайте функцию multiply, которая принимает два аргумента: множитель и число. Используйте метод bind, чтобы создать новую функцию, которая всегда умножает число на 2.
Показать решения
-
const ship = { name: "Black Pearl" };
function sail() {
console.log(`${this.name} is sailing.`);
}
const boundSail = sail.bind(ship);
boundSail(); // Output: "Black Pearl is sailing." -
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(5)); // Output: 10
Вызов функций с помощью метода call()
Метод call позволяет вызывать функцию с указанным контекстом this и передавать аргументы по отдельности. Это как если бы капитан подлодки отдавал приказ с указанием деталей.
const captain = { name: "Captain Nemo" };
function dive(depth) {
console.log(`${this.name} is diving to ${depth} meters.`);
}
dive.call(captain, 200); // Output: "Captain Nemo is diving to 200 meters."
Здесь метод call устанавливает контекст this в captain и передаёт аргумент depth.
Задачи #task:
- Создайте функцию dive, которая принимает глубину (depth) и выводит сообщение "<Имя капитана> is diving to <глубина> meters.". Используйте метод call, чтобы вызвать эту функцию с объектом captain и заданной глубиной.
- Создайте объект ship с методом sail, который выводит сообщение "<Название корабля> is sailing.". Используйте метод call, чтобы вызвать метод sail с контекстом другого объекта boat.
Показать решения
-
function dive(depth) {
console.log(`${this.name} is diving to ${depth} meters.`);
}
const captain = { name: "Captain Ahab" };
dive.call(captain, 300); // Output: "Captain Ahab is diving to 300 meters." -
const ship1 = { name: "Endeavour" };
const ship2 = { name: "Discovery" };
function sail() {
console.log(`${this.name} is sailing.`);
}
sail.call(ship1); // Output: "Endeavour is sailing."
sail.call(ship2); // Output: "Discovery is sailing."
Вызов функций с помощью метода apply()
Метод apply похож на call, но аргументы передаются в виде массива. Это удобно, если параметры уже собраны в массив.
const captain = { name: "Captain Nemo" };
function explore(depth, duration) {
console.log(`${this.name} is exploring at ${depth} meters for ${duration} hours.`);
}
explore.apply(captain, [400, 5]); // Output: "Captain Nemo is exploring at 400 meters for 5 hours."
Здесь метод apply передаёт аргументы depth и duration из массива в функцию.
Задачи #task:
- Создайте функцию executeMission, которая принимает два параметра: название миссии и её продолжительность. Используйте метод apply, чтобы вызвать эту функцию, передав параметры через массив.
- Создайте функцию sumArrays, которая принимает несколько массивов чисел. Используйте метод apply, чтобы объединить все массивы в один и вычислить их сумму.
Показать решения
-
function describeMission(name, duration) {
console.log(`${name} mission will last for ${duration} hours.`);
}
const missionDetails = ["Deep Dive", 8];
describeMission.apply(null, missionDetails);
// Output: "Deep Dive mission will last for 8 hours." -
function sumArray() {
return Array.prototype.reduce.apply(arguments, [(acc, curr) => acc + curr, 0]);
}
console.log(sumArray.apply(null, [1, 2, 3, 4])); // Output: 10
Методы — трюки осьминога
Методы — это особые действия, которые осьминог может выполнять с помощью своих щупалец. Если свойства осьминога — это "чем он владеет", то методы — это "что он может сделать". Например, осьминог может сжимать жемчужину, махать щупальцем или прятать сокровища.
Как это работает:
- Метод — это функция, привязанная к объекту.
- Вызывая метод, осьминог выполняет действие, используя свои ресурсы (свойства).
- Методы могут изменять свойства осьминога или взаимодействовать с другими объектами подводного мира.
Пример метода:
let octopus = {
arm1: "жемчуг",
arm2: "золото",
hideTreasure: function() {
console.log("Сокровище спрятано!");
}
};
octopus.hideTreasure(); // "Сокровище спрятано!"
Здесь метод hideTreasure — это трюк, который осьминог может выполнить. Когда вы вызываете его, осьминог выполняет запрограммированное действие (в данном случае вывод сообщения в консоль).
Методы, которые взаимодействуют со свойствами:
let octopus = {
treasure: "жемчуг",
changeTreasure: function(newTreasure) {
this.treasure = newTreasure;
console.log("Новое сокровище: " + this.treasure);
}
};
octopus.changeTreasure("золото"); // "Новое сокровище: золото"
В этом примере метод changeTreasure изменяет свойство treasure осьминога. Таким образом, осьминог может обновлять то, что он держит в своих щупальцах.
Значения — жемчужины и сокровища
Значения в JavaScript можно представить как жемчужины или сокровища подводного мира. Это то, что можно хранить, передавать или использовать для выполнения задач. Каждое значение обладает определенной "стоимостью" и может быть использовано осьминогом или черепахой-гидом.
Типы значений:
- Примитивы — простые и неизменные сокровища, такие как числа (42) или строки ("жемчуг").
- Объекты — сундуки, в которых хранятся другие сокровища, упорядоченные с помощью свойств.
Примеры значений:
// Примитивные значения
let pearl = "жемчуг";
let gold = 100;
// Объектное значение
let treasureChest = {
item1: "жемчуг",
item2: "золото"
};
Здесь pearl и gold — это примитивные сокровища, которые можно просто использовать или передать. А treasureChest — это сундук, где сокровища организованы в виде свойств.
Передача значений:
Когда вы передаете значение функции или привязываете его к переменной, это похоже на передачу жемчужины от одного осьминога другому.
function showTreasure(treasure) {
console.log("Сокровище: " + treasure);
}
showTreasure("жемчуг"); // "Сокровище: жемчуг"
Здесь значение "жемчуг" передается функции showTreasure, и она сообщает нам, какое сокровище ей передали.
Сравнение значений:
Иногда нужно узнать, равны ли два значения. Например, осьминог может спросить: "Держу ли я ту же жемчужину, что и черепаха?"
let pearl1 = "жемчуг";
let pearl2 = "жемчуг";
console.log(pearl1 === pearl2); // true — это одно и то же значение
Значения можно сравнивать, объединять или передавать, как если бы это были настоящие сокровища подводного мира.
18-25.11.24 ©️ Yarve (Jaroslav Plotnikov) All articles.