Функции первого класса. Функциональное программирование

2 года назад·4 мин. на чтение

В этой статье на простых и доступных примерах рассмотрим одну из концепций функционального программирования - Функции первого класса.

Это серия статей о функциональном программировании:
  1. Парадигмы программирования
  2. Композиция
  3. Функторы
  4. Каррирование
  5. Чистые функции
  6. Функции первого класса (рассматривается в этой статье)

Что такое функция первого класса?

Считается, что язык программирования поддерживает функции первого класса, если он не имеет ограничений на то, как функции могут создаваться или использоваться. Говорят, что язык программирования имеет функции первого класса, когда функции в этом языке рассматриваются как любая другая переменная. В общем, языки программирования накладывают ограничения на способы манипулирования вычислительными элементами. Говорят, что элементы с наименьшими ограничениями имеют статус первого класса. Вот некоторые из "прав и привилегий" первоклассных элементов:
  • Может быть назначен обычным переменным
  • Передаются в качестве аргументов функциям
  • Возвращается как результат функций
  • Входит в любые структуры данных
Рассмотрим примеры каждого пункта.

Особенности функций первого класса

1. Функция первого класса может быть назначена обычным переменным

const string = "Foo"
const num = 2
const bool = false
const greet  = (name) => `Hello ${name}`
// ...другие примитивные типы данных

greet('John') // Hello John

2. Функция первого класса передается в качестве аргумента в другие функции

const nums = [1, 2, 3, 4, 5]

const addOne = (n) => n + 1

const addedOne = nums.map(addOne) // [2, 3, 4, 5, 6]
Есть функция addOne, которая обрабатывается как переменная и передается в функцию .map. При этом функция addOne действительно является функцией первого класса.

3. Функция первого класса возвращается как результат функции

const makeCounter = () => {
  let count = 0
  return () => ++count
}

const counter = makeCounter()

counter() // 1
counter() // 2
counter() // 3
counter() // 4
Функция makeCounter вернула функцию, которую мы присвоили переменной счетчика. Где переменная counter теперь содержит обычную функцию.

4. Функция первого класса входит в любые другие структуры данных

const wakeUp = name => `${name}, wake up early!`
const takeShower = name => `${name}, take shower!`
const workout = name => `${name}, workout!`
const shutUp = name => `${name}, shut up!`

const morningRoutines = [
    wakeUp,
    takeShower,
    workout,
    shutUp
]

morningRoutines.forEach(routine => routine('John'))
// John, wake up early!
// John, take shower!
// John, workout!
// John, shut up!
Мы можем хранить функции в массивах и, как вы уже догадались, мы также можем хранить их в объектах и так же перебирать их.

Почему функции первого класса важны

Функциональное программирование находится под сильным влиянием математики. Функциональное программирование хотело бы, чтобы математика была включена в каждую строку кода. Хотя математика состоит только из функций и переменных, она все равно очень мощная и выразительная. Это то, что пытается сделать и функциональное программирование - решать каждую отдельную проблему с использованием функций и только функций. Когда вы в языке программирования можете обращаться с функцией так же просто, как с переменной, этот язык будет гораздо более гибким и откроет много возможностей для улучшений. Функциональное программирование сделает ваш код более предсказуемым, тестируемым, повторно используемым, настраиваемым, кэшируемым, поддерживаемым, компонуемым и читабельным.
Итак, вы спросите себя: «Хорошо, я понимаю взаимосвязь между функциональным программированием и математикой, но как первоклассные функции сделают возможными все эти преимущества?» Очень хороший вопрос. Так как функциональное программирование полностью зависит от наличия привилегий функций, функции первого класса — это краеугольный камень для всех концепций функционального программирования. Наличие в языке программирования функций первого класса позволяет иметь удивительные шаблоны, которые рассмотрим далее.

Паттерны на основе функций первого класса

1. Функции высшего порядка (Higher-order functions)

Функции считаются функциями высшего порядка, когда они принимают функции в качестве аргументов (например, большинство методов Array, .map, .filter, .reduce, .every) и/или возвращают функцию в качестве результата (точно так же, как makeCounter).

2. Замыкания

Замыкание — это функция, возвращаемая «родительской» функцией, и имеющая доступ к внутреннему состоянию родительской функции. Как и в предыдущем примере с makeCounter. Чтобы уточнить, приведем еще один пример.
/*1*/ const add = (x) => (y) => x + y
/*2*/ 
/*3*/ const add5  = add(5)  // add5  = (y) => 5 + y
/*4*/ const add10 = add(10) // add10 = (y) => 10 + y
/*5*/
/*6*/ add5(1) // 6
/*7*/ add10(1) // 11
Рассмотрим каждую строчку. Строка 1: add — это функция, которая принимает первый параметр x и возвращает анонимную функцию, которая принимает второй параметр y и возвращает x + y. Строка 3: выполнение add(5) вернет функцию со значением 5 внутри нее. Компилятор/оптимизатор поймет это именно так:
const add5 = (y) => 5 + y 
Строка 4: точно такая же, как и строка 3. Выполнение add(10) вернет функцию со значением 10 внутри нее. Компилятор/Оптимизатор поймет это именно так:
const add10 = (y) => 10 + y
Строка 6 и строка 7: это обычные вызовы функций для ранее «динамически» созданных функций add5 и add10. После понимания того, что делает каждая строка, разберемся в терминологии для add, add5 и add10:
  • add — функция высшего порядка. Почему? Потому что она возвращает функцию.
  • Но add5 и add10 являются замыканиями. Почему? Потому что они имеют значения 5 и 10 соответственно, заключенные (связанные) в лексической области видимости их родителя и все еще доступные им. (Вот почему, когда мы вызываем add5(1), он будет использовать уже переданное 5 для add).

3. Каррирование

Это механизм применения концепции ленивых вычислений. Его мы подробно рассматривали в отдельной части.

Итоги

Функции первого класса — это не шаблон, это особенность языка программирования. Эта возможность позволяет легко обращаться с функциями как с переменными без ограничений. Наличие этой возможности делает язык более мощным и готовым к функциональному программированию. В таком языке мы можем создавать очень мощные утилиты, такие как функции высшего порядка, замыкания, каррирование и многое другое.

Как ускорить сайт с помощью ленивой загрузки изображений

2 года назад·1 мин. на чтение

Ленивая загрузка изображений — один из самых простых способов ускорить загрузку сайта, поскольку для самой простой реализации ленивой загрузки требуется всего одна строка кода.

Есть несколько продвинутых методов, которые вы можете использовать, чтобы ваша отложенная загрузка выглядела так же, как на изображении ниже, с размытием и плавным переходом от размытого к полному изображению. В этой статье рассмотрим все, что вам нужно знать о ленивой загрузке, а также о том, как создать этот продвинутый эффект ленивой загрузки.

Что такое ленивая загрузка?

Ленивая загрузка (отложенная загрузка) — это метод, используемый для отсрочки загрузки контента до тех пор, пока он не понадобится. В случае изображений это означает, что изображение не будет загружено до тех пор, пока пользователь не прокрутит до точки, где изображение будет видно на экране. Это отличный способ ускорить работу вашего сайта, поскольку вы загружаете только те изображения, которые пользователь действительно увидит. Это особенно полезно для сайтов с большим количеством изображений, поскольку вы можете сэкономить много пропускной способности, загружая только те изображения, которые пользователь действительно увидит. Если у вас высокая скорость интернета или вы просматриваете сайты только с небольшими, хорошо оптимизированными изображениями, вы можете не увидеть преимущества отложенной загрузки изображений, поскольку вы можете загрузить все изображения почти мгновенно. Но для всех остальных ленивая загрузка изображений меняет играет важную роль. Это касается не только людей со сверхмедленным интернет-соединением. Изображения являются одним из, если не самым большим по размеру контентом, который загрузит ваш пользователь, поэтому, даже если у него быстрое подключение к Интернету, ленивая загрузка изображений все равно может иметь огромное значение для времени загрузки вашего сайта.

Базовая реализация ленивой загрузки

Как я уже упоминал в начале этой статьи, ленивая загрузка изображений так же проста, как добавление одного атрибута к тегу изображения. Атрибуту loading можно присвоить значение lazy, чтобы включить отложенную загрузку изображения. Браузер автоматически определит, когда загружать изображение, в зависимости от того, насколько близко изображение находится на экране.
<img src="image.jpg" loading="lazy" />
Самым большим недостатком этой базовой отложенной загрузки является то, что пользователь увидит пустое место, где должно быть изображение, пока изображение не будет загружено. Это не идеальный пользовательский опыт, поэтому оставшаяся часть этой статьи покажет вам, как воспользоваться отложенной загрузкой, чтобы показать размытое изображение-заполнитель до тех пор, пока не будет загружено полное изображение.

Продвинутая отложенная загрузка

Размытые изображения-заполнители отображаются до тех пор, пока не будет загружено полное изображение, и являются первым шагом к созданию этого расширенного эффекта отложенной загрузки. Чтобы создать размытое изображение-заполнитель, вам просто нужно создать версию изображения со сверхнизким разрешением. Есть много способов сделать это, например, использовать такой сервис, как BlurHash, вручную изменить размер изображения в таком инструменте, как Figma, или автоматически с помощью такого инструмента, как ffmpeg. Мы будем использовать ffmpeg для создания изображений-заполнителей для этой статьи, поскольку это наиболее гибкий вариант, который можно легко автоматизировать. Все, что нужно сделать, это запустить приведенный ниже код в командной строке в каталоге, содержащем изображение, для которого требуется сгенерировать изображение-заполнитель.
ffmpeg -i imageName.jpg -vf scale=20:-1 imageName-small.jpg
При этом будет сгенерировано изображение шириной 20 пикселей, а высота будет автоматически рассчитана для сохранения пропорций исходного изображения. Вы можете изменить ширину на любую другую, но по наблюдениям, 20 пикселей хорошо подходят для большинства изображений и достаточно малы, чтобы загружаться почти мгновенно даже при медленном интернет-соединении. Изображения-заполнители будут примерно по 1КБ каждое.
Следующим шагом является создание div и установка фонового изображения этого div на наше супер маленькое изображение. Это будет изображение-заполнитель, которое будет отображаться до тех пор, пока не будет загружено полное изображение. Наш код будет выглядеть примерно так.
<div class="blurred-img"></div>
.blurred-img {
  background-image: url(imageName-small.jpg);
  background-repeat: no-repeat;
  background-size: cover;
}
Этот div с blurred-img имеет размер в зависимости от размера содержимого в нем. Однако мы можем легко исправить это, добавив img в наш div и убедившись, что он скрыт по умолчанию, чтобы мы никогда не видели его в наполовину загруженном состоянии.
<div class="blurred-img">
  <img src="imageName.jpg" loading="lazy" />
</div>
.blurred-img img {
  opacity: 0;
}
Это даст нам эффект, который мы ищем. Эффект размытия, который мы получаем автоматически, связан с тем, что сверхмаленькое изображение автоматически увеличивается браузером. Если вы хотите добавить больше размытия, вы всегда можете использовать свойство CSS filter, чтобы добавить фильтр к blurred-img.
.blurred-img {
  filter: blur(10px);
}
Вы даже можете сделать еще один шаг вперед, добавив пульсирующую анимацию к изображению-заполнителю. Это сделает еще более очевидным, что изображение загружается.
.blurred-img::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: pulse 2.5s infinite;
  background-color: white;
}

@keyframes pulse {
  0% {
    opacity: 0;
  }
  50% {
    opacity: 0.1;
  }
  100% {
    opacity: 0;
  }
}
Теперь единственное, что осталось сделать, это показать основное изображение после его загрузки. Это немного сложнее, чем остальная часть кода, который мы написали до сих пор, поскольку требует от нас использования JavaScript, но все же довольно просто. Нам просто нужно добавить прослушиватель событий к изображению, который будет срабатывать после загрузки изображения.
<div class="blurred-img">
  <img src="imageName.jpg" loading="lazy" />
</div>
const blurredImageDiv = document.querySelector(".blurred-image")
const img = blurredImageDiv.querySelector("img")
function loaded() {
  blurredImageDiv.classList.add("loaded")
}

if (img.complete) {
  loaded()
} else {
  img.addEventListener("load", loaded)
}
.blurred-img {
  background-repeat: no-repeat;
  background-size: cover;
}
.blurred-img::before {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  animation: pulse 2.5s infinite;
  background-color: var(--text-color);
}

@keyframes pulse {
  0% {
    opacity: 0;
  }
  50% {
    opacity: 0.1;
  }
  100% {
    opacity: 0;
  }
}

.blurred-img.loaded::before {
  animation: none;
  content: none;
}

.blurred-img img {
  opacity: 0;
  transition: opacity 250ms ease-in-out;
}

.blurred-img.loaded img {
  opacity: 1;
}
Здесь много кода, поэтому разберем его шаг за шагом. В коде JavaScript мы выбираем blurred-img а затем выбираем img в этом div. Затем мы проверяем свойство complete у img, чтобы увидеть, загрузился ли он еще. Если это так, это означает, что изображение уже загружено, поэтому мы можем просто вызвать функцию loaded. Однако, если это условие ложно, нам нужно добавить прослушиватель событий в img, который будет срабатывать после загрузки изображения, а затем вызывать loaded. loaded просто добавляет класс loaded в blurred-img. В CSS у нас есть несколько изменений в коде. Сначала мы удалили animation/content из элемента blurred-img::before. Это остановит пульсирующую анимацию после загрузки изображения. Мы также добавили transition к элементу img, чтобы он плавно исчезал при добавлении loaded класса в div blurred-img.img Наконец, мы изменяем непрозрачность img на 1, чтобы она была видна при загрузке.

Итоги

Ленивая загрузка изображений — довольно простая техника, которую можно использовать для улучшения пользовательского опыта вашего сайта. Простейшая версия отложенной загрузки занимает всего одну строку кода, но ее можно расширить до некоторых довольно аккуратных методов загрузки с небольшим количеством дополнительного кода.