— Вышел Neovim 0.10.
— Обзор нововведений в браузерах. Поддержка новых анимаций, нативные поповеры. Ну и ждём кастомные селекты. The latest in Web UI (Google I/O ‘24)
— Статья описывает концепцию рецептов в дизайн системе
— Отличное видео для новичков: React JS c Нуля – Курс для начинающих БЕЗ ВОДЫ
— Если вы помните, что когда-то был такой Wunderlist (который потом Microsoft купил), то знайте: разработчики вандерлиста выпустили новое приложение — Superlist. Выглядит красиво, пользоваться приятно, как и раньше.
Все записи с тэгом learning
JavaScript. Взаимодействие с сервером
Примечание: Это статья — конспект моей лекции. В ней описываются ключевые понятия.
Обновление: В примерах ниже обновились url, в результате запросы по http не работают. Поэтому я внес небольшие корректировки и при получении url картинок заменяю http на https. Работающий код можно посмотреть тут https://codepen.io/denisfl/pen/xxeQzpp.
Чтобы взаимодействовать с сервером (запрашивать или отправлять данные), нам нужны методы. Каждый запрос, который мы отправляем на сервер, включает в себя endpoint и тип отправляемого запроса. Endpoint — это, своего рода, шлюз между клиентом и сервером. В зависимости от валидности запроса сервер отправляет ответ. Если запрос успешен, сервер возвращает данные, например, в формате JSON. В случае ошибки, сервер возвращает сообщение об ошибки. Ответы сервера обычно сопровождаются кодами состояния (status codes), которые помогают понять, что сервер пытается сказать при получении запроса.
Например: 200–299 — успешный запрос, 400–499 — ошибка клиента, 500–599 — ошибка сервера. Вот тут можно посмотреть полный список кодов: HTTP response status codes.
Методы для отправки HTTP-запросов
GET
. С помощью него клиент запрашивает у сервера содержимое ресурса.
HEAD
. Это метод для получения заголовков ресурса. Обычно применяется для получения метаданных и проверки менялся ли ресурс с момента последнего посещения и существует ли он.
POST
. Через этот метод клиент может передать данные в теле сообщения. Это могут быть какие-то данные с заполненной формы. POST
является неидемпотентным методом, то есть при его отправке результат может отличаться. Кроме того, ответы на него не будут кэшироваться.
OPTIONS
. С помощью него можно запросить список методов, которые он или его ресурс поддерживает. Также OPTIONS
можно использовать для того, чтобы «пропинговать» сервер — протестировать его работоспособность.
PUT
. Метод создаёт новый ресурс или заменяет существующий данными, которые указаны в теле запроса.
PATCH
. Работает таким же образом, как и PUT
, только по отношению к части ресурса.
DELETE
. Клиент сообщает о том, что хотел бы удалить некий ресурс.
TRACE
. С помощью него можно проверить, изменяют ли промежуточные узлы в сети запрос клиента.
CONNECT
. Запускает туннель между клиентом и сервером.
Как запросить данные
XMLHttpRequest. Это довольно старый метод, тем не менее он все еще используется.
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
Axios
. Это клиент созданный на Промисах, был создан для удобства работы с HTTP-запросами.
axios.get('https://api.example.com/data')
.then(response => console.log(response.data))
.catch(error => console.error('Ошибка:', error));
Fetch API. Это функция JavaScript, которую можно использовать для отправки запроса на любой URL-адрес веб-API и получения ответа.
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Ошибка:', error));
Fetch и Axios очень похожи по функциональности. Axios создавался в тот момент, когда Fetch API не поддерживалось в разных браузерах и нужно было обеспечить совместимость с Internet Explorer. Сейчас можно использовать и то и другое, в зависимости от того, с чем комфортнее работать.
Запросили, получили, показали
Обычно если мы что-то запросили с сервера через GET, мы хотим это показать. В самом простом случае, мы вставляем полученные данные в DOM. Тут мы не говорим о современных решениях для работы с Virtual DOM.
Возьмем данные с http://pets-v2.dev-apis.com/pets и отрендерим их на страницы.
Для запроса используем fetch api:
async function fetchData() {
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
// Допустим, у вас есть элемент в DOM для вставки данных
const container = document.getElementById('pets-container');
// Очищаем контейнер перед вставкой новых данных
container.innerHTML = '';
// Вставляем данные в DOM
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.textContent = `Имя: ${pet.name}, Вид: ${pet.animal}`;
container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
}
}
// Вызываем функцию для загрузки данных
fetchData();
И применим этот код к html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pets Data</title>
<style>
.pet {
margin-bottom: 20px;
border: 1px solid #ccc;
padding: 10px;
}
.pet img {
max-width: 100%;
height: auto;
}
</style>
</head>
<body>
<div id="loading-message">Загрузка данных...</div>
<div id="pets-container"></div>
<script>
async function fetchData() {
const loadingMessage = document.getElementById('loading-message');
const container = document.getElementById('pets-container');
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
loadingMessage.style.display = 'none'; // Скрываем сообщение о загрузке
// Очищаем контейнер перед вставкой новых данных
container.innerHTML = '';
// Вставляем данные каждого питомца в DOM
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.classList.add('pet');
// Вставляем изображения питомца
pet.images.forEach(imageUrl => {
const img = document.createElement('img');
img.src = imageUrl;
petElement.appendChild(img);
});
// Вставляем информацию о питомце
const info = document.createElement('div');
info.innerHTML = `
<p><strong>Имя:</strong> ${pet.name}</p>
<p><strong>Вид:</strong> ${pet.animal}</p>
<p><strong>Город:</strong> ${pet.city}</p>
<p><strong>Штат:</strong> ${pet.state}</p>
<p><strong>Описание:</strong> ${pet.description}</p>
<p><strong>Порода:</strong> ${pet.breed}</p>
`;
petElement.appendChild(info);
container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
loadingMessage.textContent = 'Ошибка загрузки данных';
}
}
// Вызываем функцию для загрузки данных
fetchData();
</script>
</body>
</html>
В JavaScript функции являются объектами, и они могут иметь доступ к своему лексическому окружению, включая переменные и другие функции, определенные в том же контексте. В нашем случае, функция fetchData – это асинхронная функция, которая создает свое собственное лексическое окружение, когда она вызывается. Это лексическое окружение включает в себя все переменные и функции, определенные внутри нее, а также доступ к внешнему контексту, где она была объявлена.
Внутри функции fetchData мы обращаемся к внешним переменным, таким как loadingMessage и container. Эти переменные были определены в том же контексте, где и функция fetchData, и, следовательно, они доступны ей как внешние переменные.
Функция fetchData использует доступ к DOM-элементам loadingMessage и container, чтобы изменять их содержимое и стили. Это возможно благодаря замыканию, которое сохраняет ссылку на лексическое окружение, в котором была определена функция fetchData.
Внутри цикла forEach создается замыкание для каждой итерации. Это происходит потому, что каждый раз, когда вызывается метод appendChild() или устанавливается свойство innerHTML, создается новая функция, которая сохраняет ссылку на лексическое окружение функции fetchData.
Таким образом, замыкания в JavaScript позволяют функциям сохранять доступ к внешним переменным и контексту, в котором они были определены, даже когда эти функции вызываются в других контекстах или в асинхронном коде.
В примере нижу функция fetchData и все переменные, которые она использует, организованы внутри объекта fetchDataObject. Все внутренние переменные функции, такие как loadingMessage и container, теперь доступны через ключевое слово this. Таким образом, объект fetchDataObject является замыканием, которое сохраняет доступ к своему лексическому окружению, включая переменные и методы.
const fetchDataObject = {
loadingMessage: document.getElementById('loading-message'),
container: document.getElementById('pets-container'),
fetchData: async function() {
const _self = this; // Сохраняем ссылку на текущий объект в переменной _self
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
_self.loadingMessage.style.display = 'none'; // Используем _self для доступа к свойству loadingMessage
_self.container.innerHTML = ''; // Используем _self для доступа к свойству container
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.classList.add('pet');
pet.images.forEach(imageUrl => {
const img = document.createElement('img');
img.src = imageUrl;
petElement.appendChild(img);
});
const info = document.createElement('div');
info.innerHTML = `
<p><strong>Имя:</strong> ${pet.name}</p>
<p><strong>Вид:</strong> ${pet.animal}</p>
<p><strong>Город:</strong> ${pet.city}</p>
<p><strong>Штат:</strong> ${pet.state}</p>
<p><strong>Описание:</strong> ${pet.description}</p>
<p><strong>Порода:</strong> ${pet.breed}</p>
`;
petElement.appendChild(info);
_self.container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
_self.loadingMessage.textContent = 'Ошибка загрузки данных'; // Используем _self для доступа к свойству loadingMessage
}
}
};
fetchDataObject.fetchData();
// Вызываем метод fetchData объекта fetchDataObject для загрузки данных
fetchDataObject.fetchData();
В примере с объектом fetchDataObject, мы используем this внутри объекта для доступа к его свойствам (this.loadingMessage, this.container) и методам. Здесь this указывает на сам объект fetchDataObject, поэтому мы можем обращаться к его свойствам и методам через this.
В то же время, в асинхронной функции fetchData, когда мы создаем переменные loadingMessage и container, мы получаем доступ к этим переменным, используя this, потому что в момент выполнения асинхронной функции контекст может измениться и this может потерять своё значение. Таким образом, мы сохраняем доступ к нужным нам переменным, используя их как свойства объекта this.
А теперь перепишем объект fetchDataObject, используя стрелочные функции.
const fetchDataObject = {
loadingMessage: document.getElementById('loading-message'),
container: document.getElementById('pets-container'),
fetchData: async () => {
const { loadingMessage, container } = fetchDataObject;
try {
const response = await fetch('http://pets-v2.dev-apis.com/pets');
if (!response.ok) {
throw new Error('Ошибка при загрузке данных');
}
const data = await response.json();
loadingMessage.style.display = 'none';
container.innerHTML = '';
data.forEach(pet => {
const petElement = document.createElement('div');
petElement.classList.add('pet');
pet.images.forEach(imageUrl => {
const img = document.createElement('img');
img.src = imageUrl;
petElement.appendChild(img);
});
const info = document.createElement('div');
info.innerHTML = `
<p><strong>Имя:</strong> ${pet.name}</p>
<p><strong>Вид:</strong> ${pet.animal}</p>
<p><strong>Город:</strong> ${pet.city}</p>
<p><strong>Штат:</strong> ${pet.state}</p>
<p><strong>Описание:</strong> ${pet.description}</p>
<p><strong>Порода:</strong> ${pet.breed}</p>
`;
petElement.appendChild(info);
container.appendChild(petElement);
});
} catch (error) {
console.error('Произошла ошибка:', error);
loadingMessage.textContent = 'Ошибка загрузки данных';
}
}
};
fetchDataObject.fetchData();
Теперь функция fetchData
не создает собственный контекст this
, а вместо этого использует контекст объекта fetchDataObject
. Мы также использовали деструктуризацию объекта внутри функции для получения доступа к свойствам loadingMessage
и container
.
JavaScript. Функции, типы данных и методы работы с ними
Примечание: Это статья — конспект моей лекции. В ней описываются ключевые понятия.
Типы данных
Примитивные типы данных в JavaScript:
— number
: числовое значение, например, 5 или 3.14.
— string
: строковое значение, например, "Привет" или 'Мир'.
— boolean
: логическое значение, true
или false
.
— null
: специальное значение, представляющее отсутствие значения.
— undefined
: значение, которое не было присвоено.
— symbol
(в ECMAScript 6): уникальное и неизменное значение.
Объекты
Объекты в JavaScript представляют собой коллекции пар ключ-значение, где ключи – это строки (или символы), а значения могут быть любого типа данных, включая другие объекты, массивы и функции.
const person = {
name: "John",
age: 30,
isAdmin: false,
greet: function() {
console.log(`Привет, меня зовут ${this.name}!`);
}
};
console.log(person.name); // Вывод: John
console.log(person.age); // Вывод: 30
person.greet(); // Вывод: Привет, меня зовут John!
Объекты могут быть созданы с использованием фигурных скобок {}
и заполнены парами ключ-значение.
Доступ к свойствам объекта можно получить с помощью оператора . или квадратных скобок []
.
console.log(person.name); // Вывод: John
console.log(person["age"]); // Вывод: 30
Свойства объекта могут быть добавлены или изменены после его создания.
person.country = "USA"; // Добавление нового свойства
person.age = 31; // Изменение значения существующего свойства
Свойства объекта могут быть удалены с помощью оператора delete
.
delete person.isAdmin; // Удаление свойства
Свойства объекта могут быть перебраны с использованием циклов или методов перебора.
// Цикл for..in
for (let key in person) {
console.log(key + ": " + person[key]);
}
// Метод Object.keys()
const keys = Object.keys(person);
console.log(keys); // Вывод: ["name", "age", "country"]
Существование свойств в объекте можно проверить с помощью оператора in
или метода hasOwnProperty
.
console.log("name" in person); // Вывод: true
console.log(person.hasOwnProperty("age")); // Вывод: true
Копирование объектов может быть выполнено с использованием различных методов, таких как Object.assign()
, оператор распространения (...
) или конструктор Object
.
// Object.assign()
const copiedPerson1 = Object.assign({}, person);
// Оператор распространения (spread operator)
const copiedPerson2 = { …person };
// Конструктор Object
const copiedPerson3 = new Object(person);
Массивы
Это упорядоченные списки элементов, каждый из которых может быть любым типом данных, включая другие массивы и объекты.
Массивы могут быть созданы с использованием литерала массива []
или с помощью конструктора new Array()
.
const numbers = [1, 2, 3, 4, 5]; // Массив чисел
const names = ['John', 'Alice', 'Bob']; // Массив строк
const mixedArray = [1, 'two', true]; // Массив смешанных типов данных
const emptyArray = []; // Пустой массив
// Создание массива с помощью конструктора
const fruits = new Array('apple', 'banana', 'orange');
Элементы массива можно получить, используя индекс элемента. Индексы массивов начинаются с 0.
console.log(numbers[0]); // Вывод: 1console.log(names[1]); // Вывод: Alice
Элементы могут быть добавлены в конец массива с помощью метода push()
и удалены с помощью метода pop()
. Также элементы могут быть добавлены и удалены из начала массива с помощью методов unshift()
и shift()
.
const colors = ['red', 'green', 'blue'];
colors.push('yellow'); // Добавление элемента в конец массива
console.log(colors); // Вывод: ['red', 'green', 'blue', 'yellow']
colors.pop(); // Удаление последнего элемента массива
console.log(colors); // Вывод: ['red', 'green', 'blue']
Массивы могут быть перебраны с использованием различных методов, таких как цикл for
, метод forEach()
, цикл for...of
и др.
// Использование цикла forfor (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
// Использование метода forEach()
numbers.forEach((number) => {
console.log(number);
});
// Использование цикла for…of
for (let number of numbers) {
console.log(number);
}
JavaScript предоставляет множество встроенных методов для работы с массивами, таких как concat()
, slice()
, indexOf()
, filter()
, map()
и многие другие. Эти методы предоставляют функциональность для добавления, удаления, изменения и фильтрации элементов массива.
const numbers1 = [1, 2, 3];
const numbers2 = [4, 5, 6];
const combinedNumbers = numbers1.concat(numbers2); // Объединение массивов
console.log(combinedNumbers); // Вывод: [1, 2, 3, 4, 5, 6]
const slicedNumbers = combinedNumbers.slice(2, 4); // Вырезка подмассива
console.log(slicedNumbers); // Вывод: [3, 4]
const index = combinedNumbers.indexOf(5); // Поиск индекса элемента
console.log(index); // Вывод: 4
Прототипы
Это механизм, который позволяет объектам наследовать свойства и методы других объектов. Каждый объект в JavaScript имеет ссылку на свой прототип, которая используется для поиска свойств и методов, которые не определены непосредственно в самом объекте.
Как работают прототипы:
Каждый объект в JavaScript имеет свой прототип. Прототип объекта – это ссылка на другой объект, известный как “родительский” объект. Когда свойство или метод не найдены в самом объекте, JavaScript автоматически обращается к его прототипу для поиска.
Если свойство или метод не найдены в прототипе объекта, JavaScript продолжает поиск в прототипе этого прототипа, и так далее, пока не будет найдено нужное свойство или метод, либо цепочка прототипов не достигнет конечного объекта null
. Эта цепочка прототипов называется “цепочкой прототипов” (prototype chain
).
Пример использования прототипов:
// Создание объекта с помощью литерала объекта
const animal = {
type: 'Animal',
sound: function() {
console.log('Издает звук');
}
};
// Создание нового объекта с использованием animal в качестве прототипа
const dog = Object.create(animal);
dog.type = 'Dog'; // Переопределение свойства type для объекта dog
// Вызов метода из прототипа animal
dog.sound(); // Вывод: Издает звук
// Проверка, является ли animal прототипом для dog
console.log(Object.getPrototypeOf(dog) === animal); // Вывод: true
Функции
Позволяют переиспользовать код, упрощают поддержку и читаемость.
Могу быть объявлены несколькими способами.
Function declaration
// Используем ключевое слово function
function greet(name) {
return Hi ${name}
}
Function expression
// Используем стрелочную функци
const greet = (name) => {
return Hi ${name}
}
Функции могут принимать параметры и возвращать значения. Возвращаемое значение — это результат выполнения функции. Этот результат может быть передан обратно в вызывающий код.
const summ = (x, y) => {
return x + y
}
const log = () => {
console.log(summ(1, 2))
}
log() // Результат вывода: 3
Callback functions
Это функции, которые передаются в качестве аргументов в другие функции и вызываются после завершения определенной операции или события.
const button = document.querySelector('#myButton')
function handleClick() {
console.log('Кнопка нажата')
}
button.addEventListener('click', handleClick)
В чем преимущества? Callback-функции позволяют выполнить определенный код после завершения асинхронных операций. Например, загрузка данных или выполнение запросов.
В чем проблема? Вложенные callback-функции могут привести к нечитаемому и трудно поддерживаемому коду, известному как “callback hell”. Этого можно избежать, используя методы, такие как Promises или async/await, которые предлагают более линейный и читаемый подход к обработке асинхронного кода.
Пример “callback hell”:
function fetchData(url, callback) {
setTimeout(() => {
const data1 = 'Данные 1';
callback(data1, (data1, callback) => {
setTimeout(() => {
const data2 = 'Данные 2';
callback(data2, (data2, callback) => {
setTimeout(() => {
const data3 = 'Данные 3';
callback(data3);
}, 1000);
});
}, 1000);
});
}, 1000);
}
fetchData('https://example.com/api', (data1, callback) => {
console.log(data1);
callback(data1, (data2, callback) => {
console.log(data2);
callback(data2, (data3) => {
console.log(data3);
});
});
});
Пример использования промисов для асинхронных запросов:
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = 'Данные';
resolve(data);
}, 1000);
});
}
fetchData('https://example.com/api')
.then((data1) => {
console.log(data1);
return fetchData('https://example.com/api');
})
.then((data2) => {
console.log(data2);
return fetchData('https://example.com/api');
})
.then((data3) => {
console.log(data3);
})
.catch((error) => {
console.error('Произошла ошибка:', error);
});
Этот код использует промисы для выполнения асинхронных операций и избегает “callback hell”, предоставляя более линейный и читаемый подход к обработке асинхронных запросов. Каждый .then() блок представляет собой следующую операцию после успешного выполнения предыдущей. Если происходит ошибка, она обрабатывается в блоке .catch().
Прототипы и функции
В JavaScript функции также являются объектами, и у них также есть прототипы. Функции имеют свойство prototype, которое используется при создании объектов с помощью оператора new. Когда объект создается с использованием конструктора функции, прототип этой функции становится прототипом создаваемого объекта.
// Определение конструктора функции
function Person(name) {
this.name = name;
}
// Добавление метода в прототип функции Person
Person.prototype.greet = function() {
console.log(`Привет, меня зовут ${this.name}!`);
};
// Создание объекта с использованием конструктора Person
const person1 = new Person('John');
const person2 = new Person('Alice');
// Вызов метода из прототипа функции Person
person1.greet(); // Вывод: Привет, меня зовут John!
person2.greet(); // Вывод: Привет, меня зовут Alice!