Оптимизация производительности веб-сайтов была распространенной темой в сообществе. В сообществе есть много отличных статей, посвященных методам оптимизации, таким как сильное сжатие, предварительная загрузка и распаковка статических ресурсов, таких как Css и Js.
Однако узкое место в производительности большинства сайтов веб-приложений обычно связано с большим количеством запросов на взаимодействие с данными в дополнение к загрузке статических ресурсов. Если мы хотим, чтобы наш веб-сайт был удобным для пользователей, помимо оптимизации статических ресурсов, необходима оптимизация взаимодействия с данными.
Сегодняшняя статья позволяет нам начать с другой точки зрения и поговорить о том, как улучшить взаимодействие с пользователем вашего сайта веб-приложения на уровне взаимодействия с данными.
Прежде всего, прежде чем приступить к основному содержанию, давайте поговорим с вами о том, что такое «оптимизация производительности веб-сайта на уровне запроса данных».
Проще говоря, традиционные веб-страницы имеют несколько различных этапов инициирования запроса:
Если вам интересно узнать каждогоиндивидуальный Полеиз Вы можете уточнить значениекссылкаTiming breakdown phases explained。
Здесь мы сосредоточимся на двух частях:
Ожидание ответа сервера (TTFB) представляет собой время, необходимое серверу для обработки этого запроса. Как интерфейсному инженеру, оптимизация этой части времени часто должна выполняться совместно с соответствующим сервером.
Загрузка контента — это время загрузки ресурса, которое мы подчеркнули в заголовке. Оно представляет собой продолжительность времени, в течение которого браузер загружает данные ответа службы при этой сетевой передаче.
Нетрудно представить, что, будь то TTFB или загрузка контента, если мы максимально сократим время передачи этого запроса в ссылке запроса, элементы отображения страницы, которые полагаются на эту часть данных, будут представляется пользователю быстрее.
Часто, когда мы открываем веб-сайт, время загрузки традиционных ресурсов приложения/json обычно находится в пределах 100 мс.
«Мой интерфейс реагирует очень быстро, так не нужно ли мне его оптимизировать?» Я думаю, большинство студентов зададутся вопросом, важен ли опыт оптимизации в 100 мс?
Обычно время передачи данных через Интернет обратно пропорционально фактической пропускной способности пользователя. Когда пропускная способность пользователя выше, скорость передачи данных увеличивается, и время загрузки соответственно сокращается. При более низкой пропускной способности скорость передачи данных снизится, что приведет к увеличению времени загрузки страниц.
Это означает, что оптимизация IOI для загрузки ресурсов не так уж важна, если у пользователя хорошая сеть. Но мы никогда не можем просить пользователей пойти на компромисс в отношении пропускной способности нашего веб-сайта.
Чаще всего так называемый метод оптимизации означает, что, когда разные группы конечных пользователей посещают наш веб-сайт, они надеются получить быструю обратную связь от страниц. Если мы оптимизируем производительность веб-сайта только для пользователей с хорошим оборудованием и сетями, это будет успешным. пустая трата времени. Давление, связанное с медленным доступом к веб-сайту, переносится с разработчиков на пользователей.
Следовательно, это не означает, что короткое время загрузки ресурса при бесперебойной сети означает, что мы отказываемся от его оптимизации. Более того, студенты, знакомые с фронтендом, обязательно столкнутся с некоторыми интерфейсами для передачи больших объемов данных и различными границами. плохой мобильной сети. Сцена правильная.
привычный NodeJs одноклассники для Streaming изконцепция должна быть вам знакома,существовать Web Среда выполнения также предоставляет набор Stream API позволять JavaScript Возможность доступа к потокам данных из сети.
При передаче по сети HTTP2 Передача данных через уровень двоичного кадрирования поддерживается по умолчанию при передаче данных по сети. Streaming способ прочитать ответ, в HTTP 1.1 Мы можем установить Transfer-Encoding: chunked
Заголовок ответа указывает, что «лучшее» передается в формате фрагментов данных.
Проще говоря, Streams API позволяет нам программно получать доступ к потокам данных, получаемым по сети, посредством JavaScript, при этом настраивая обработку возвращаемых потоков данных в соответствии с собственными потребностями.
Здесь мы в основном фокусируемся на ReadableStream а также WritableStream。
о Streaming Для получения подробной информации и использования вы можете обратиться к MDN - Web Streaming или Vercel - An Introduction to Streaming on the Web。
Давайте посмотриминдивидуальныйо ReadableStream а также WritableStream Простой пример:
const decoder = new TextDecoder();
const encoder = new TextEncoder();
// Создайте читаемый поток
const readableStream = new ReadableStream({
// Когда будет создан читаемый поток, он будет выполнен немедленно, и читаемый поток будет поставлен в очередь через метод доступа для Whenfront, когда text
start(controller) {
const text = 'hello, Welcome to flow my Github: 19Qingfeng.';
controller.enqueue(encoder.encode(text));
controller.close();
}
});
// Создайте отдельный записываемый поток
const writableStream = new WritableStream({
// Каждый раз есть что-то новое chunk Доступный для записи поток создается, когда он готов к записи. write метод
write(chunk) {
console.log(decoder.decode(chunk));
}
});
readableStream.pipeTo(writableStream); // hello, Welcome to flow my Github: 19Qingfeng.
В приведенном выше коде мы передаем TextEncoder
Воля hello, Welcome to flow my Github: 19Qingfeng.
преобразован в stream отисуществоватьсоздавать Читабельный поток Воляон врезается в поток。
В то же время позже мы создали writableStream
Записываемый объект потока, содержимое потока, доступное для чтения через pipeTo
Перенос метода в записываемый поток.
writableStream Каждый раз будут новые chunk Срабатывает, когда готов к записи write метод, в write В методе,который мы использовали TextEncoder идти Воля stream Раскодируйте, и консоль естественным образом выведет hello, Welcome to flow my Github: 19Qingfeng
。
о WritableStream/ReadableStream изконцепцияа Также Углубленное использование: друзья, которым интересно узнать больше, могут проверить это самостоятельно. MDN Документация.
API Fetch предоставляет интерфейс для получения ресурсов, а Web Fetch больше похож на замену XMLHttpRequest.
Fetch Ядро лежит в HTTP Абстракция интерфейса, включая Request
,Response
,Headers
,Body
,а также используется для инициализации асинхронных запросов из global fetch
。
Проще говоря, принеси Api предоставляет очень мощный набор Api Может кпозволять нам пройти JavaScript оперировать этими абстрактными HTTP модуль.
Обычно мы используем Fetch Api с файлом response.json для получения данных, полученных от интерфейса удаленной службы:
async function getUserJSON() {
let url = 'https://api.github.com/users/19Qingfeng';
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('Request Failed', error);
}
}
fetch Возвращаемое значение метода — это Promise<Response>
объект, мы можем использовать fetch вернулся response в объекте json метод из Response Залезай json Форматированный ответ данных.
На самом деле, принеси Api вернулся Response Есть еще один на body свойство.
Мы можем пройти назад response из body Недвижимость Получить индивидуальный ответ из ReadableStream Пример.
Это означает, что мы можем сразу прочитать содержимое ответа с помощью метода json, не дожидаясь загрузки всех ответов во время передачи по сети.
Вместо этого вы можете пройти response.body к методу ReadableStream для пакетного чтения данных из и для повышения скорости ответа данных интерфейса.
Далее давайте посмотрим, как использовать Fetch Response из body свойство.
Сначала мы используем экспресс для быстрого создания NodeServer для размещения серверного приложения:
// express код степени обслуживания
const express = require('express');
const data = require('./data.json');
const path = require('path');
const app = express();
app.use(express.static(path.resolve(__dirname, '../public')));
app.post('/api/data', (req, res) => {
res.json(data);
});
app.listen(3000, () => {
console.log('start server on 3000;');
});
здесь из
./data.json
Вы можете разместить любой абзац json содержание.
В то же время нам также необходимо public Поместите один под html Файл взят со страницы отображения Также Выполнить клиентскую часть логики:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>
// index.js
setTimeout(() => {
fetchUserDataByStreaming();
}, 100);
async function fetchUserDataByStreaming() {
const response = await fetch('/api/data', {
method: 'post'
});
const body = response.body;
// Получите читаемую программу чтения потоковых объектов.
const reader = body.getReader();
// создавать TextDecoder декодирование
const textReader = new TextDecoder();
function getValueFromReader() {
reader.read().then((res) => {
if (res.done) {
console.log('reader done');
return;
}
if (res.value) {
const text = textReader.decode(res.value);
const element = document.getElementById('root');
element.innerHTML = element.innerHTML + text;
console.log(textReader.decode(res.value));
// Если он не закончился, продолжите вызов getValueFromReader Рекурсивное чтение fetch содержание
getValueFromReader();
}
});
}
getValueFromReader();
}
В приведенной выше логике мы передаем Express Создал NodeServer, пока Создан /api/data
из post интерфейс.
Доступ после запуска приложения localhost:3000
будет загружен позже html файлы, выполняемые одновременно ./index.js
из клиентской логики.
Клиент будет внутри 100ms позвони после fetchUserDataByStreaming
метод.
fetchUserDataByStreaming Есть несколько основных шагов:
/api/data
интерфейс.
/api/data
При ответе (HTTP Status Code для 200 ), мы будем использовать response.body
Получите читаемый поток этого ответа.
getReader()
метод создаст reader
,и Воля Stream блокировка. только текущий reader
Воля Стрим вышел после других reader
можно использовать. в то же время,getReader()
Возвращаемое значение методиз используется точно так же, как и функция-генератор.
{ value: chunk, done: false }
done выражать stream Еще не конец, и value На этот раз там написано изсодержание.
{ value: undefined, done: true }
,природа done Выразитьreadable закрыто.
Здесь мы напрямую используем res.body Волявернулсяданные безмозглый пропуск innerHTML добавлено на страницу.
На фото выше для Воля network Отрегулируйте slow 3g дело из performance frames。
Внимательные студенты, возможно, обнаружили, что дело обстоит иначе. XMLHTTPRequest изинтерфейс ожиданиясодержание Только после завершения всех загрузоккполучатьданныедругой。
Fetch Api из Response.body вернулся readableStream для Мы предоставляем доступныекшаг за шагомполучатьданныеи Не нужно ждать всехизсодержание Загрузка завершена。
объединить Freames Приходите и посмотрите,на страницеизсодержание Он отображается по частям.и Не прямо на одном экране Волясодержаниепровести грубыйиззаменять。
существования Если вы будете осторожны, то можете обнаружить, что на странице разбросано множество искаженных слов:
Фактически, это связано с тем, что по умолчанию TextDecoder анализирует Response.body в соответствии с кодировкой utf-8 (один байт представляет 8 бит).
существовать ReadableStream разделит данные в соответствии с минимальной единицей байтов и вызовет body.getReader() вернулся res.value для Uint8Array Тип: Uint8Array это автор 8 битовое беззнаковое целое число(Это одининдивидуальныйбайт)композицияизмножество。
Страница для написана на чистом английском языке (utf-8 Следующий код — индивидуальный английский дляодининдивидуальныйбайт), который, естественно, можно передать напрямую. TextDecoder При прямом чтении не будет искаженных символов.
Но на китайской сцене обычно существуют UTF-8 Китайцы будут оккупированы следующим 3 индивидуальныйбайт,грубыйизиспользовать TextDecoder идтидекодированиевернулся Uint8Array Может привести к частичному обрезанию индивидуального китайского иероглифа «Воляодинизмногобайт», uft-8. Кодировка не может распознать часть китайских иероглифов байт и природа, а также приводит к искажению символов.
Правила синтаксического анализа различных текстов в кодировке utf-8: проблема, нам нужно понять кодировку utf-8:
Проще говоря, utf-8 — это метод кодирования байтов переменной длины.
байт | Пример |
---|---|
1байт | 0xxxxxxx |
2байт | 110xxxxx 10xxxxxx |
3байт | 1110xxxx 10xxxxxx 10xxxxxx |
4байт | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
5байт | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
6байт | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
Это означает, что каждый раз существование читается stream Нам нужно опираться на Uint8Array из Хвост сам движется вперед, чтобы определить, можно ли из хвостового байта сформировать полноценного индивидуального utf-8 Кодируем, если есть возможность то вызываем TextDecoder руководитьдекодирование,если это невозможнокмы должнысуществовать На этот разиз stream Неполныйизбайт Сноваруководитьдекодирование Предотвратить искажение символов в браузере。
Проще говоря Мы можем согласно uft-8 из правил кодирования, Воля Uint8Array типпреобразован в двоичном формате определяет, соответствует ли хвостовой байт единственному индивидуальному символу из и.
setTimeout(() => {
fetchUserDataByStreaming();
}, 100);
async function fetchUserDataByStreaming() {
const response = await fetch('/api/data', {
method: 'post'
});
const body = response.body;
// Получить читаемый объект потока
const reader = body.getReader();
// создавать TextDecoder декодирование
const textReader = new TextDecoder();
// создаватьодининдивидуальный На этот разответизобщая ситуация buffer Используется для сохранения уже полученных изданных
// Типдля Uint8Array[]
let buffer = [];
function getValueFromReader() {
reader.read().then((res) => {
if (res.done) {
console.log('reader done');
return;
}
if (res.value) {
const chunk = res.value;
// Воля Эти возвращаемые данные добавляются к возвращаемым данным
buffer.push(chunk);
// Отформатированный Uint8Array[] Получите полные изданные
const completeBuffer = mergeArrays(...buffer);
// Определите, нужно ли отбрасывать искаженные символы,получать На этот разможет быть полнымдекодированиеизданные
const finallyBuffer = splitValidBuffer(completeBuffer);
// декодирование
const textBufferString = textReader.decode(finallyBuffer);
const element = document.getElementById('root');
// Заменить всю сумму
element.innerHTML = textBufferString;
// Если он не закончился, продолжите вызов getValueFromReader Рекурсивное чтение fetch содержание
getValueFromReader();
}
});
}
getValueFromReader();
}
function splitValidBuffer(buffer) {
const reg = /^1*/;
let count = 0;
let shouldCount = 0;
for (let i = buffer.length - 1; i >= 0; i--) {
const byte = buffer[i];
// Волябайтпреобразован в двоичном формате
const value = byte.toString(2).padStart(8, '0');
// Определите, является ли этот байт частью для
if (!`${value}`.startsWith('10')) {
// Не для части
shouldCount = `${value}`.match(reg)[0].length;
break;
} else {
// Начало байт
count++;
}
}
if (shouldCount === count) {
return buffer;
} else {
return buffer.slice(0, buffer.length - (count + 1));
}
}
function mergeArrays(...arrays) {
let out = new Uint8Array(
arrays.reduce((total, arr) => total + arr.length, 0)
);
let offset = 0;
for (let arr of arrays) {
out.set(arr, offset);
offset += arr.length;
}
return out;
}
В приведенном выше коде мы существуем Fetch Response После возвращениясоздаватьодининдивидуальныймножествоспасти всехвернулся buffer содержание,Затемсуществоватькаждый раз reader.read()
Вызывается в методе decode Воляот response.body 已получатьизвсесодержаниеруководить decode。
При этом мы существуем каждый раз decode методза хвостбайтруководитьсуждение об эффективности,Удаление приведет к искажению символов и неполным байтам.
Таким образом, можно гарантировать согласованность окончательного возвращаемого текста и решить проблему искаженного кода окончательного текста.
в это время,Пересмотрите каждый кадр страницы.,Искаженный текст на странице исчез:
статьясерединаиз Код, который вы можетексуществоватьСмотрите здесь。
когда Ран,Часто в традиционных приложениях изданные серверной части, возвращаемые во внешний интерфейс, имеют определенный формат, означающий изданные. Итак, к,Легко пройти res.body Получение сегментированного отображения данных не может удовлетворить большинство форматов данных в бизнес-сценариях.
Простой пример индивидуального,Обычно мы заключаем клиентское и серверное соглашение. Возвращенные будут хранить данные верхнего уровня существования внизу, а также содержит ошибку, связанную с тем, хранит ли атрибут верхнего уровня ошибку существования.,Таким образом, формат возвращенных может измениться:
{
"data": "real response Data",
"error": null,
}
Столкнувшись с этой частью формата, мы не можем бездумно получить ответ из readableStream с помощью описанного выше метода.
в то же время,Один индивидуальный сценарий из бизнес-сценария: сервер вернул реальные данные для одного индивидуального типа массива из формата.,Интерфейсная страница должна отображать один индивидуальный элемент на основе каждого индивидуального элемента в данных.,например:
{
"data": [
{
"title": "title1",
"desc": "desc"
},
{
"title": "title2",
"desc": "desc2"
},
{
"title": "title3",
"desc": "desc3"
}
],
"error": null
}
В приведенном выше формате данных data Массив из каждого элемента, который нам может понадобиться отобразить для одного человека на странице, существует. Карта, естественно пройти не думая TextDecoder rea методполучатьданныедаодинсвоего рода ошибкаиз Способ。
Чтобы соответствовать описанному выше бизнес-сценарию, если мы читаем ResponseBody в пакетном режиме и возвращаем его клиенту, на самом деле это неправильный путь.
Более грубый метод заключается в том, что мы используем TextEncoder/TextDecoder для перехвата возвращенного формата данных json в соответствии с определенными правилами.
Автор: Воля stream серединавернулсябайтпреобразован в json Часть перехвата строксодержаниеиз Способиз Это действительно возможнокудовлетворить насизнуждаться,Но на самом деле это не имеет никакой общности.
Мы можем попробовать другой, более общий способ:
Если этот запрос содержит специальный заголовок запроса, сервер вернет соглашение о внешнем и внутреннем интерфейсах из специальной структуры «изданные». Если этот запрос не содержит специального заголовка запроса, природа возвращает общий заголовок application/json. Просто отформатируйте.
например,То же самое касается и вышеизданных. Мы можем к Воля вернуть индивидуальный формат, отличный от JSON.,напримермы можемк Волявышеизданныесуществовать Сервер обрабатывает его вдля:
"error": null
"data": [
{
"title": "title1",
"desc": "desc"
},
{
"title": "title2",
"desc": "desc2"
},
{
"title": "title3",
"desc": "desc3"
}
]
существуют К серверу можно легко получить доступ через
\n
(число) Правила и клиент договариваются о том, как анализировать эту структуру данных.
Хотя этот метод реализации звучит немного причудливо, на самом деле некоторые зрелые фреймворки с открытым исходным кодом в сообществе уже использовали этот метод, чтобы значительно улучшить скорость отклика изданных страниц.
напримерсуществовать Remix серединаиз Defer Эта идея реализуется путем соответствия SPA Режим перехода под из LoaderData данные, полученные от пользователя, значительно снизили скорость ответа страницы.
Однако эта идея реализации требует, чтобы одноклассники на стороне сервера вносили изменения вместе.,Заинтересованные студенты могут попробовать эту идею самостоятельно.,Здесь я не буду это демонстрировать в статье.
Подробно расскажу в следующей статье Remix как использовать fetch Чтобы добиться оптимизации скорости перехода страниц, заинтересованные друзья могут ксосредоточиться Последующие статьи.
Когда, конечно, вы также можете прочитать это сами Remix readStreamSections метод.
частосуществовать XMLHttpRequest в, для Content download Студенты, изучающие фронт-энд, редко имеют возможность эффективно сократить эту часть времени.
и WebFetch Api из Появляться Сотрудничать chunk из Сетевой метод передачи, для нас из Web Применить сетевой запрос на Content download оптимизация обеспечивает более эффективный способ групповой обратной связи с пользователем.
когда Ран За исключением слабой сетидело из Content download Оптимизация пространства, Fetch Api Также существуют другие места, потому что мы приносим больше возможностей.
Если весь фон изданный можно переключить на streaming (аналогично передаче больших файлов), то теоретически его можно использовать на всех сайтах ваших приложений. Web Fetch Оптимизация отклика интерфейса приводит к задержке отображения. Any data request does not need to wait for the interface to fully respond before it can be displayed !。
В статье мы говорим о веб-оптимизации с точки зрения производительности, я надеюсь, что она поможет каждому.
Хотя в статье упоминается, что взгляды Иза относительно радикальны,,Но в целом,Web Stream изWAYS Действительно для Наша страница предлагает возможность быстрого ответа на данныеиз.