Как использовать Proxy в JavaScript?

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

Раскрываем возможности прокси (Proxy) JavaScript, погружаемся в кастомизацию операций с объектами

Введение

В мире JavaScript разработчики постоянно ищут способы повысить мощность и гибкость своего кода. Прокси в JavaScript, представленный в ECMAScript 6 (ES6), стал замечательной фичей, позволяющей разработчикам перехватывать и настраивать основные операции над объектами. Выступая в качестве посредника, прокси открывают множество возможностей, позволяя разработчикам с легкостью изменять поведение объектов, проверять входные данные и защищать их. В этой статье мы углубимся во внутреннюю работу JavaScript Proxy. Мы рассмотрим его фундаментальное использование, концепцию ловушек, определяющих поведение прокси, и различные типы доступных ловушек. Кроме того, мы узнаем, как прокси можно использовать для валидации значений, проверки безопасности и защиты данных, а также для устранения ограничений, совместимости браузеров и соображений производительности. Итак, давайте отправимся в это путешествие открытий и разгадаем секреты JavaScript Proxy!

Базовое использование прокси JavaScript

При работе с JavaScript Proxy первым шагом является создание прокси-объекта с использованием нового синтаксиса new Proxy() Этот синтаксис позволяет определить целевой объект и объект обработчика, содержащий ловушки. Эти ловушки представляют собой специальные методы, которые перехватывают операции на прокси и позволяют нам настраивать их поведение. Например, get перехватывает доступ к свойству прокси-объекта. Определив эту ловушку, мы можем настроить реакцию прокси при доступе к свойству. Точно так же set перехватывает присвоение свойства, позволяя нам проверить или изменить присвоенное значение. Таким образом, ловушки в контексте прокси JavaScript — это специальные методы, которые действуют как посредники, перехватывая и позволяя настраивать операции объекта, такие как доступ к свойствам и назначение прокси-объекта. Они предоставляют разработчикам детальный контроль над поведением прокси, позволяя настраивать и манипулировать по мере необходимости.
Давайте посмотрим на пример:
const target = {
  name: 'John',
  age: 20
};

const handler = {
  get(target, property) {
    console.log(`Getting property: ${property}`);
    return target[property];
  },
  set(target, property, value) {
    console.log(`Setting property: ${property} = ${value}`);
    target[property] = value;
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // Output: Getting property: name, John
proxy.age = 30; // Output: Setting property: age = 20
console.log(proxy.age); // Output: Getting property: age, 30
В приведенном выше примере мы создаем прокси-объект, который является оболочкой target объекта. get логирует доступное свойство и возвращает соответствующее значение из target объекта. Аналогичным образом, set логирует заданное свойство и присваивает значение target объекту. Используя эти ловушки, мы можем беспрепятственно перехватывать и изменять операции с объектами в соответствии с нашими требованиями.

Общие сведения о прокси-ловушках

Прокси JavaScript предоставляет несколько ловушек, каждая из которых соответствует отдельной операции с прокси-объектом. Некоторые из часто используемых ловушек включают get, set, has, apply, construct и другие. Эти ловушки позволяют нам перехватывать и настраивать такие операции, как доступ к свойствам, назначение, вызов функций, создание экземпляров объектов и многое другое. Давайте рассмотрим некоторые из этих ловушек на примерах:

get

const target = {
  name: 'John',
  age: 20
};

const handler = {
  get(target, property) {
    console.log(`Getting property: ${property}`);
    return target[property];
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // Output: Getting property: name, John
console.log(proxy.age); // Output: Getting property: age, 20
В этом примере get перехватывает доступ к свойству на прокси и логирует доступ к свойству перед возвратом его значения.

set

const target = {
  name: 'John',
  age: 20
};

const handler = {
  set(target, property, value) {
    console.log(`Setting property: ${property} = ${value}`);
    target[property] = value;
  }
};

const proxy = new Proxy(target, handler);

proxy.name = 'Jerry'; // Output: Setting property: name = Jerry
console.log(proxy.name); // Output: Jerry
Здесь set перехватывает назначение свойства на прокси и логирует назначенное свойство и значение, прежде чем изменять их в целевом объекте. Используя эти и другие ловушки, мы можем эффективно настраивать и контролировать поведение прокси-объектов в соответствии с нашими конкретными потребностями.

Перехват операций с объектами с помощью прокси

JavaScript Proxy позволяет нам перехватывать и настраивать различные операции с объектами. Давайте рассмотрим несколько примеров:

Удаление свойства

const target = {
  name: 'John',
  age: 20
};

const handler = {
  deleteProperty(target, property) {
    console.log(`Deleting property: ${property}`);
    delete target[property];
  }
};

const proxy = new Proxy(target, handler);

delete proxy.age; // Output: Deleting property: age
console.log(proxy); // Output: { name: 'John' }
В этом примере ловушка deleteProperty перехватывает удаление свойства прокси-объекта. Мы можем настроить поведение, чтобы логировать удаленное свойство, а затем удалить его из целевого объекта.

Перехват вызова функции

const target = {
  sum: (a, b) => a + b
};

const handler = {
  apply(target, thisArg, argumentsList) {
    console.log(`Calling function: ${target.name}`);
    return target.apply(thisArg, argumentsList);
  }
};

const proxy = new Proxy(target.sum, handler);

console.log(proxy(2, 3)); // Output: Calling function: sum, 5
В этом случае ловушка apply перехватывает вызов функции в прокси-объекте. Мы можем настроить поведение для логирования имени функции, прежде чем вызывать ее с предоставленными аргументами.

Перехват проверки наличия свойства

Ловушка has срабатывает, когда оператор in используется для проверки наличия свойства в прокси-объекте. Определив эту ловушку, можно настроить реакцию прокси-объекта на проверки существования свойства. Это полезно, если требуется динамически управлять наличием свойств или реализовать механизмы управления доступом.
const target = {
  name: 'John',
  age: 20
};

const handler = {
  has(target, property) {
    console.log(`Checking property existence for "${property}"`);
    return property in target;
  }
};

const proxy = new Proxy(target, handler);

console.log('name' in proxy); // Output: Checking property existence for "name", true
console.log('email' in proxy); // Output: Checking property existence for "email", false
В приведенном примере кода мы создаем объект handler с ловушкой has. Внутри ловушки has мы логируем сообщение, указывающее, что выполняется проверка существования свойства. Затем мы используем оператор in, чтобы проверить, существует ли свойство в прокси-объекте. Ловушка перехватывает эту операцию и возвращает true или false в зависимости от того, существует ли свойство в базовом целевом объекте.

Перехват создания объекта

Ловушка construct в JavaScript Proxy перехватывает создание объекта, когда прокси-объект используется в качестве функции-конструктора с new. Он позволяет настраивать процесс создания экземпляров, проверять аргументы конструктора и применять определенные шаблоны создания объектов.
class Person {
  constructor(name) {
    this.name = name;
  }
}

const handler = {
  construct(target, argumentsList) {
    console.log(`Creating new instance of "${target.name}" with arguments: ${argumentsList}`);
    return new target(...argumentsList);
  }
};

const ProxyPerson = new Proxy(Person, handler);

const john = new ProxyPerson('John'); // Output: Creating new instance of "Person" with arguments: John
В примере кода мы определяем объект handler с помощью construct. Когда прокси-объект вызывается с new, construct перехватывает создание объекта. Мы логируем сообщение, указывающее на создание нового экземпляра с конкретными аргументами. Ловушку construct можно использовать для реализации пользовательской логики инициализации, проверки входных данных или обеспечения требуемого поведения во время создания объекта.

Прокси для валидации и безопасности

Одним из мощных вариантов использования JavaScript Proxy является проверка данных и безопасность. Прокси могут использоваться для обеспечения соблюдения определенных правил и ограничений на доступ к данным и их изменение. Например, мы можем гарантировать, что доступ к определенным свойствам возможен только при определенных условиях, или ограничить несанкционированные изменения. Рассмотрим следующий пример:
const target = {
  password: 'secretpassword'
};

const handler = {
  get(target, property) {
    if (property === 'password') {
      console.log('Access denied!');
      return undefined;
    }
    return target[property];
  },
  set(target, property, value) {
    if (property === 'password') {
      console.log('Unauthorized modification!');
      return false;
    }
    target[property] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.username); // Output: undefined
console.log(proxy.password); // Output: Access denied!

proxy.password = 'newpassword'; // Output: Unauthorized modification!
В этом примере get ограничивает доступ к свойству password, запрещая его извлечение. Аналогичным образом, ловушка set предотвращает несанкционированное изменение свойства password. Используя JavaScript Proxy, мы можем добавить дополнительный уровень проверки и безопасности к нашим данным, гарантируя, что выполняются только разрешенные операции.

Ограничения и совместимость с браузерами

Хотя прокси JavaScript является мощной функцией, у него есть некоторые ограничения, о которых следует помнить. Во-первых, не все браузеры поддерживают прокси, поэтому важно проверить совместимость браузеров, прежде чем использовать их в продакшене. Однако с ростом внедрения ECMAScript 6 поддержка прокси стала более распространенной. Еще одно ограничение заключается в том, что прокси не могут перехватывать определенные операции, которые считаются фундаментальными или внутренними для JavaScript. К ним относятся такие операции, как Object.preventExtensions(), Object.isExtensible() и Object.setPrototypeOf(). Поэтому прокси могут подходить не для всех сценариев и вариантов использования. Кроме того, стоит отметить, что использование прокси может привести к снижению производительности, особенно при работе с крупномасштабными приложениями. Перехват и настройка операций с объектами сопряжены с затратами. Хотя прокси могут быть оптимизированы в определенных случаях, важно учитывать потенциальное влияние на производительность и оценивать, перевешивают ли преимущества компромиссы с производительностью.

Реальные варианты использования прокси в JavaScript

JavaScript Proxy находит практическое применение в различных реальных сценариях. Вот несколько примеров:
  • Логирование: прокси можно использовать для логирования операций с объектами и отслеживания изменений, предоставляя ценную отладочную информацию.
  • Кэширование: прокси могут реализовывать механизмы кэширования для повышения производительности за счет хранения и извлечения вычисленных или дорогостоящих значений.
  • Мемоизация: прокси можно использовать для реализации мемоизации, которая помогает оптимизировать вызовы функций, кэшируя их результаты на основе предоставленных аргументов.
  • Контроль доступа: прокси могут применять правила контроля доступа, гарантируя, что доступ к определенным свойствам или операциям могут получить только авторизованные сущности.
Это всего лишь несколько случаев, когда прокси JavaScript можно использовать для улучшения функциональности кода и предоставления элегантных решений сложных проблем.

Итоги

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

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

год назад·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, чтобы она была видна при загрузке.

Итоги

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