Руководство по современному браузерному наблюдателю OBServer API

JavaScript Vue.js
Руководство по современному браузерному наблюдателю OBServer API

предисловие

Некоторое время назад я изучал реализацию внешней платформы мониторинга/встраивания аномалий.

Размышляя о решении, я подумал о собственном наблюдателе браузера и API жизненного цикла страницы.

Поэтому, просматривая данные, я случайно обнаружил, что современные браузеры поддерживают до четырех разных типов наблюдателей:

  • Intersection Observer, перекрестный наблюдатель.
  • Mutation Observer, наблюдатель изменений.
  • Resize Observer, наблюдатель представления.
  • Performance Observer, наблюдатель за производительностью

IntersectionObserver MutationObserver ResizeObserver PerformanceObserver
использовать Посмотрите, виден ли элемент в окне просмотра Наблюдайте за изменениями в DOM Наблюдайте за изменением размера области просмотра Мероприятие по отслеживанию показателей производительности
метод observe()
disconnect()
takeRecords()
observe()
disconnect()
takeRecords()
unobserve()
observe()
disconnect()
unobserve()
observe()
disconnect()
takeRecords()
заменять Dom Mutation events getBoundingClientRect() возвращает размер элемента и его положение относительно видимого окна.

События прокрутки и изменения размера
Изменить размер события Интерфейс производительности
использовать 1. Бесконечная прокрутка
2. Ленивая загрузка изображения
3. Точка захоронения интереса
4. Управление анимацией/видео исполнением (оптимизация производительности)
1. Более высокая производительность привязки данных и ответа
2. Реализуйте параллаксную прокрутку
3. Предварительная загрузка изображения
4. Реализуйте форматированный текстовый редактор
1. Более умные адаптивные макеты (заменяет @media)
2. Адаптивные компоненты
1. Мониторинг производительности более мелких частиц
2. Проанализируйте влияние производительности на бизнес (влияет ли быстрое/медленное взаимодействие на продажи)
## 1. IntersectionObserver: перекрестный наблюдатель

IntersectionObserverИнтерфейс, который обеспечивает способ асинхронного наблюдения за целевым элементом и его предками или окном просмотра документа верхнего уровня (viewport) метод, который пересекает состояние, элемент-предок и область просмотра (viewport)называется корнем (root)

1. Значение внешности

Хотите рассчитать положение элементов веб-страницы, очень зависящее отDOMЯвный запрос статуса. Но эти запросы являются синхронными, влекут за собой дорогостоящие накладные расходы на вычисления (перерисовка и перекомпоновка), а постоянный опрос приводит к значительной потере производительности.

Поэтому были разработаны следующие решения:

  • Создайте пользовательскую предварительную загрузку и ленивую загрузку DOM и данных.
  • Реализует привязанный к данным высокопроизводительный список прокрутки, который загружает и отображает подмножество набора данных.
  • пройти черезscrollи другие события или в виде плагинов для расчета реальной видимости элемента.

И все они имеют несколько общих черт:

  1. Базовая форма реализации — это «пассивный запрос», который запрашивает каждый элемент относительно некоторых элементов (глобальное окно просмотра).
  2. Информация может передаваться асинхронно (например, из другого потока), и единообразной обработки перехвата ошибок не существует.
  3. webПоддержка платформ отсутствует, и у каждой свой процессинг. Для совместимости требуется, чтобы разработчики потребляли много энергии.

2. IntersectionObserverПреимущества

Intersection Observer APIПредоставив разработчикам новый метод асинхронного запроса элемента относительно других элементов или глобального окна просмотра, чтобы решить вышеуказанные проблемы:

  • Асинхронная обработкаустраняет дорогиеDOMи стилевые запросы, непрерывный опрос и необходимость использования пользовательских плагинов.
  • Благодаря устранению необходимости в этих методах приложения могут работать значительно медленнее.CPU,GPUи затрат на ресурсы.

3. IntersectionObserverосновное использование

использоватьIntersectionObserver APIТребуются три основных шага:

  1. Создать наблюдателя
  2. Определить события обратного вызова
  3. Определите целевой объект для наблюдения

1.Создать наблюдателя

const options = {
    root: document.querySelector('.scrollContainer'),
    rootMargin: '0px',
    threshold: [0.3, 0.5, 0.8, 1] }
    
const observer = new IntersectionObserver(handler, options)

Эти параметры объясняются на местном языке как:

  1. root: указывает корневой элемент
  2. rootMargin: задает корневое поле (наблюдаемый диапазон влияния корневого элемента) с использованием синтаксиса, аналогичного настройке полей CSS.
  3. threshold: порог, может быть массивом.[0.3]Означает, что обработчик вызывается, когда целевой элемент видим на 30% внутри элемента, указанного корневым элементом.

2. Определить события обратного вызова

Функция обратного вызова запускается, когда целевой элемент и корневой элемент пересекают пороговое значение.

function handler (entries, observer) { 
    entries.forEach(entry => { 
    // 每个成员都是一个IntersectionObserverEntry对象。
    // 举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。
    // entry.boundingClientRect 
    // entry.intersectionRatio 
    // entry.intersectionRect 
    // entry.isIntersecting 
    // entry.rootBounds 
    // entry.target 
    // entry.time 
    }); 
}
  • отметка времени
  • rootОграничивает положение корневого элемента
  • Информация о положении целевого элемента boundingClientRect
  • пересечениеRect Информация о местоположении перекрестка
  • IntersectionRatio Видимое соотношение целевого элемента, см. рисунок ниже.
  • цель.

3. Определите целевой объект для наблюдения

Доступ к любому целевому элементу можно получить, вызвав.observer(target)метод наблюдения.

const target = document.querySelector(“.targetBox”); 
observer.observe(target);

Кроме того, есть два метода:

перестать слушать цель

observer.unobserve(target)

Прекратить прослушивание всех целей

observer.disconnect()

4. Пример 1: Ленивая загрузка изображений

HTML:

<img src="placeholder.png" data-src="img-1.jpg">
<img src="placeholder.png" data-src="img-2.jpg">
<img src="placeholder.png" data-src="img-3.jpg">
<!-- more images -->

сценарий:

let observer = new IntersectionObserver(
(entries, observer) => { 
entries.forEach(entry => {
    /* 替换属性 */
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
  });
}, 
{rootMargin: "0px 0px -200px 0px"});

document.querySelectorAll('img').forEach(img => { observer.observe(img) });

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

5. Пример 2: Точки захоронения интересов

Что касается скрытых процентов, более общее решение:

From: "IntersectionObserver супер полезного API"

const boxList = [...document.querySelectorAll('.box')]

var io = new IntersectionObserver((entries) =>{
  entries.forEach(item => {
    // intersectionRatio === 1说明该元素完全暴露出来,符合业务需求
    if (item.intersectionRatio === 1) {
      // 。。。 埋点曝光代码
      io.unobserve(item.target)
    }
  })
}, {
  root: null,
  threshold: 1, // 阀值设为1,当只有比例达到1时才触发回调函数
})

// observe遍历监听所有box节点
boxList.forEach(box => io.observe(box))

Что касается того, как судить, заинтересован ли пользователь, метод записи зависит от мнения:

  • Он находится посередине экрана и держится более 2 секунд, а засчитывается один раз.
  • Наведите указатель мыши на область, чтобы запустить таймер для записи времени.
  • PCКоличество щелчков мышью/время наведения записывается на терминале, а мобильный терминал записываетtouchмероприятие

Я не начну писать здесь (я ленивый).

6. Управление анимацией/видео исполнением

Вот версия контрольного видео

HTML:

<video src="OSRO-animation.mp4" controls=""></video>

js:

let video = document.querySelector('video');
let isPaused = false; /* Flag for auto-paused video */
let observer = new IntersectionObserver((entries, observer) => { 
  entries.forEach(entry => {
    if(entry.intersectionRatio!=1  && !video.paused){
      video.pause(); isPaused = true;
    }
    else if(isPaused) {video.play(); isPaused=false}
  });
}, {threshold: 1});
observer.observe(video);

Эффект:

2. Mutation Observer: сменить наблюдателя

интерфейс обеспечивает пары мониторингаDOMСпособность вносить изменения. Он предназначен для старогоMutationEventsЗамена функции, котораяDOM3 Eventsчасть спецификации.

1. Значение внешности

Ведь даMutationEventsФункционал более чем удовлетворительный:

  1. существуетMDNТак же написано вDOM EventПризнанные недостатки в API, против использования.
  2. Основные недостатки: проблемы с производительностью и кросс-браузерная поддержка.
  3. дляDOMДобавить кmutationСлушатель крайне понижен и дополнительно модифицированDOMПроизводительность документа (в 1,5-7 раз медленнее), кроме того, удаление прослушивателя не устраняет ущерб.

From: "Мониторинг завершения загрузки и изменений DOM - приложение MutationObserver"

MutationEventsПринцип: прослушивание путем связывания событийDOM

На первый взгляд все кажется нормальным, поэтому давайте перечислим связанные отслеживаемые события:

DOMAttributeNameChanged
DOMCharacterDataModified
DOMElementNameChanged
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDocument
DOMSubtreeModified

Не помните, при таком количестве событий браузеры разных ядер и версий боятся, что совместимость будет вне времени.

2. MutationObserverПреимущества

а такжеMutation ObserverПреимущества:

  • MutationEventsСобытия запускаются синхронно, т.DOMИзменение немедленно вызовет соответствующее событие;
  • Mutation Observerзапускается асинхронно,DOMизменения не будут запущены немедленно, а будут ждать, пока все текущиеDOMСрабатывает, когда операция завершена.
  • Вы можете контролировать цель через элементы конфигурацииDOMЖурнал изменений следующего дочернего элемента

Проще говоря:异步万岁!

3. MutationObserverосновное использование

использоватьMutationObserver APIТребуются три основных шага:

  1. Создать наблюдателя
  2. Определите функцию обратного вызова
  3. Определите целевой объект для наблюдения

1. Создайте наблюдателя

let observer = new MutationObserver(callback);

2. Определите функцию обратного вызова

Функция обратного вызова в приведенном выше коде будет вызываться после каждого изменения DOM. Функция обратного вызова принимает два параметра, первый — это массив изменений, а второй — экземпляр наблюдателя, вот пример:

function callback (mutations, observer) {
  mutations.forEach(function(mutation) {
    console.log(mutation);
  });
});

каждый из нихmutationИметь соответствующийMutationRecordобъект, записанныйDOMЖурнал изменений для каждого изменения

MutationRecordОбъект содержит информацию о DOM и имеет следующие свойства:

Атрибуты значимость
type тип наблюдаемого движения (attribute,characterDataилиchildList)
target измененныйDOMузел
addedNodes добавленDOMузел
removedNodes удаленDOMузел
previousSibling предыдущий одноуровневый узел или возврат, если нетnull
nextSibling следующий родственный узел или возврат, если нетnull
attributeName Свойства, которые изменились. если установленоattributeFilter, возвращаются только предварительно указанные свойства
oldValue Значение до изменения. Это свойство толькоattributeа такжеcharacterDataИзменения действительны, если они происходятchildListменять, возвращатьnull

3. Определите целевой объект для наблюдения

MutationObserver.observe(dom, options)

Запустите прослушиватель и получите два параметра.

  • Первый параметр: наблюдаемыйDOMузел.
  • Второй параметр: настроить изменения, которые необходимо соблюдатьoptions.
mutationObserver.observe(content, {
    attributes: true, // Boolean - 观察目标属性的改变
    characterData: true, // Boolean - 观察目标数据的改变(改变前的数据/值)
    childList: true, // Boolean - 观察目标子节点的变化,比如添加或者删除目标子节点,不包括修改子节点以及子节点后代的变化
    subtree: true, // Boolean - 目标以及目标的后代改变都会观察
    attributeOldValue: true, // Boolean - 表示需要记录改变前的目标属性值
    characterDataOldValue: true, // Boolean - 设置了characterDataOldValue可以省略characterData设置
    // attributeFilter: ['src', 'class'] // Array - 观察指定属性
});

приоритет :

  1. attributeFilter/attributeOldValue > attributes
  2. characterDataOldValue > characterData
  3. attributes/characterData/childList(или более конкретный) хотя бы один из них верен;
  4. Определенный элемент существует, соответствующий параметр можно игнорировать или его необходимоtrue

Кроме того, есть два метода:

Перестань смотреть. После звонка обозреватель больше не будет срабатывать, а подписка будет отписана

MutationObserver.disconnect()

Очистить историю изменений. То есть необработанные изменения больше не обрабатываются. Этот метод возвращает массив измененных записей.Обратите внимание, что этот метод вступает в силу немедленно.

MutationObserver.takeRecords()

4. Пример 1:MutationObserverСлушайте изменения текста

Основное использование:

const target = document.getElementById('target-id')

const observer = new MutationObserver(records => {
  // 输入变更记录
})

// 开始观察
observer.observe(target, {
  characterData: true
})

Здесь может быть несколько процедур.

  • Пузырьковое пасхальное яйцо для чата обнаруживает в тексте указанный пакет строки/смайлика и запускает анимацию падения эмодзи, аналогичную чату WeChat.
  • Поиск горячей темы в поле ввода, при вводе "#», активируйте поле поиска, чтобы предварительно просмотреть текст или выделить темы.

имеетVueНебольшой плагин реализован так:

Из: "vue-хэштег-текстовое поле"

5. Пример 2: Сценарий мини-игры Color Block

Эта реализация тоже очень хороша:

Взлом игры с выбором цвета — MutationObserver

Логика игры очень проста: когда цвет цветного блока в середине меняется, в течение отведенного времени выберите вариант того же цвета, что и вариант внизу, чтобы набрать очки. Сложность в том, что чем дальше уровень, тем больше вариантов, и ближе цвет вариантов, например:

На самом деле принцип очень прост, то есть соблюдайте цветовые блоки.backgroundColor(изменение атрибутаattributes), затем вызовите событие clicke.click().

var targetNode = document.querySelector('#kolor-kolor');
var config = { attributes: true };
var callback = function(mutationsList, observer) {
    if (mutationsList[0].type == 'attributes') {
        console.log('attribute change!');
        let ans = document.querySelector('#kolor-kolor').style.backgroundColor;
        document.querySelectorAll('#kolor-options a').forEach( (e) => {
            if (e.style.backgroundColor == ans) {
                e.text = 'Ans!';
                e.click()
            }
        })
    }
};

var observer = new MutationObserver(callback);
observer.observe(targetNode, config);

3. ResizeObserver, наблюдатель представления

ResizeObserver APIэто новыйJavaScript API,а такжеIntersectionObserver APIОчень похожи, они оба позволяют нам прослушивать изменения элемента.

1. Значение внешности

  • Процесс разработки проблема часто встречалась, заключается в том, как контролироватьdivменяется размер.
  • Но, как известно, для контроляdivизменений размера, прикрепите слушателей кwindowсерединаresizeмероприятие.
  • Но это может легко привести к проблемам с производительностью из-за большого количества триггерных событий.
  • Другими словами, используйте

window.resizeОбычно расточительно, потому что сообщает нам, как изменяется размер каждого окна просмотра, а не только когда изменяется размер элемента.

  • а такжеresizeСобытие срабатывает почти 60 раз в секунду, легко вызывая проблемы с производительностью при изменении размера окна.

Например, если вы хотите изменить размер элемента, вам нужноresizeфункция обратного вызоваcallback()вызыватьgetBoundingClientRectилиgetComputerStyle. Но если вы не будете тщательно обрабатывать все операции чтения и записи, вы получите беспорядочный макет. Например, этот небольшой пример:

2. ResizeObserverПреимущества

ResizeObserver APIЕсть два основных преимущества:

  • мелкозернистыйDOMнаблюдение за элементом вместоwindow
  • Никаких дополнительных затрат на производительность, инициирует вызов до или после макета чертежа.

3. ResizeObserverосновное использование

использоватьResizeObserver APIТакже есть три шага:

  1. Создать наблюдателя
  2. Определите функцию обратного вызова
  3. Определите целевой объект для наблюдения

1. Создайте наблюдателя

let observer = new ResizeObserver(callback);

2. Определите функцию обратного вызова

const callback = entries => {
    entries.forEach(entry => {
        
    })
}

Каждыйentryэто объект с двумя свойствамиcontentRectа такжеtarget

contentRectВсе это некоторая информация о местоположении:

Атрибуты эффект
bottom top + heightзначение
height Высота самого элемента, исключаяpadding,borderстоимость
left padding-leftзначение
right left + widthзначение
top padidng-topзначение
width Ширина самого элемента, исключаяpadding,borderстоимость
x размер сtopтакой же
y размер сleftтакой же

3. Определите целевой объект для наблюдения

observer.observe(document.body)

unobserveМетод: отменить наблюдение за одним узлом

observer.unobserve(document.body)

disconnectМетод: отменить все наблюдения узлов

observer.disconnect(document.body)

4. Пример 1: Масштабированный градиентный фон

html:

<div class="box">
    <h3 class="info"></h3>
</div>
<div class="box small">
    <h3 class="info"></h3>
</div>

Добавить стиль точки:

body {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 2vw;
    box-sizing: border-box;
}
.box {
    text-align: center;
    height: 20vh;
    border-radius: 8px;
    box-shadow: 0 0 4px rgba(0,0,0,.25);
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 1vw
}
.box h3 {
    color: #fff;
    margin: 0;
    font-size: 5vmin;
    text-shadow: 0 0 10px rgba(0,0,0,0.4);
}
.box.small {
    max-width: 550px;
    margin: 1rem auto;
}

JavaScriptКод:

const boxes = document.querySelectorAll('.box');
let callbackFired = 0;
const myObserver = new ResizeObserver(entries => {
    for (let entry of entries) {
        callbackFired++
        const infoEl = entry.target.querySelector('.info');
        const width = Math.floor(entry.contentRect.width);
        const height = Math.floor(entry.contentRect.height);
        const angle = Math.floor(width / 360 * 100);
        const gradient = `linear-gradient(${ angle }deg, rgba(0,143,104,1) 50%, rgba(250,224,66,1) 50%)`;
        entry.target.style.background = gradient;
        infoEl.innerText = `
        I'm ${ width }px and ${ height }px tall
        Callback fired: ${callbackFired}
        `;
    }
});
boxes.forEach(box => {
    myObserver.observe(box);
});

Вот что вы видите, когда перетаскиваете окно браузера, чтобы изменить его размер:

5. Пример 2: реактивныйVueкомпоненты

  • Предположим, вы хотите создать компонент postItem на большом экране с эффектом отображения.

  • Нужен этот эффект на мобильном телефоне:

просто@mediaМожно добиться:

@media only screen and (max-width: 576px) {
  .post__item {
    flex-direction: column;
  }
  
  .post__image {
    flex: 0 auto;
    height: auto;
  }
}
  • Но легко увидеть следующий макет, когда вы просматриваете страницу на большем, чем ожидалось, экране (слишком большом):

@mediaСамая большая проблема с запросом:

  • Отклик компонента зависит от размера экрана, а не от его собственного размера.

Вот реализация версии команды:

использовать:

Эффект:

Этоvue-responsive-componentsКонкретный код реализации библиотеки, а также реализацию в виде компонентов можно посмотреть, если интересно.

4. PerformanceObserver: наблюдатель за производительностью

Это браузер иNode.jsAPI, которые существуют в обоихW3CизPerformance TimelineТехнические характеристики

  • В браузере мы можем использовать объект окна, чтобы получитьwindow.performanceа такжеwindow.PerformanceObserver.
  • пока вNode.jsтребуется в программеperf_hooksЧтобы получить объект производительности, выполните следующие действия:
    const { PerformanceObserver, performance } = require('perf_hooks');
    

1. Значение внешности

Первый взглядPerformanceинтерфейс:

  • Вы можете получить информацию о производительности на текущей странице. этоHigh Resolution Time APIчастьPerformance Timeline API,Navigation Timing AP,User Timing APIа такжеResource Timing API.

  • Performance APIЭто знакомый интерфейс, который записывает огромную коллекцию объектов с несколькими показателями производительности.

  1. Чтобы получить запись производительности загрузки страницы, вам нужно вызватьperformance.getEntriesилиperformance.getEntriesByNameчтобы получить.
  2. И эффективность исполнения может быть достигнута толькоperformance.nowвычислять.

Для решения вышеуказанной проблемы вPerformance Timeline Level 2, за исключением того, что расширенныйPerformanceВ дополнение к основному определениюPerformanceObserverинтерфейс.

2. PerformanceObserverПреимущества

PerformanceObserverявляется внутреннимPerformanceРеализован шаблон наблюдателя, который также немного поддерживается современными браузерами.Observerодин.

Откуда: Знаете ли вы уровень 2 временной шкалы производительности? 》

Он решает следующие 3 проблемы:

  • Избегайте незнания того, когда произойдут события производительности, требующие повторных ротацийtimelineПолучить записи.
  • Избегайте дублирования логики для получения разных индикаторов данных о производительности.
  • Избегайте расовых отношений, когда другим ресурсам необходимо управлять буфером производительности браузера.

W3CОфициальный документ веб-сайта призывает разработчиков использовать как можно большеPerformanceObserver, вместо прохожденияPerformanceПолучите параметры и показатели производительности.

3. PerformanceObserverиспользование

использоватьPerformanceObserver APIТребуются три основных шага:

  1. Создать наблюдателя
  2. Определите событие функции обратного вызова
  3. Определите целевой объект для наблюдения

1. Создайте наблюдателя

let observer = new PerformanceObserver(callback); 

2. Определите событие функции обратного вызова

const callback = (list, observer) => {
   const entries = list.getEntries();
   entries.forEach((entry) => {
    console.log(“Name: “ + entry.name + “, Type: “ + entry.entryType + “, Start: “ + entry.startTime + “, Duration: “ + entry.duration + “\n”); });
}

каждый из нихlistявляется полнымPerformanceObserverEntryListОбъект:Содержит три методаgetEntries,getEntriesByType,getEntriesByName:

метод эффект
getEntries() Возвращает список, содержащий некоторые объекты, используемые для переноса различных данных о производительности, без какой-либо фильтрации.
getEntriesByType() Возвращает список, содержащий некоторые объекты, используемые для переноса различных данных о производительности, отфильтрованных по типу.
getEntriesByName() Возвращает список, содержащий некоторые объекты для хранения различных данных о производительности, отфильтрованных по имени.

3. Определите целевой объект для наблюдения

observer.observe({entryTypes: ["entryTypes"]});

observer.observe(...)Метод принимает допустимый тип записи, который можно наблюдать. Эти типы входных данных могут принадлежать различным API производительности, таким какUser tmingилиNavigation Timing API. ЭффективныйentryTypeстоимость:

Атрибуты псевдоним Типы описывать
рамка, навигация PerformanceFrameTiming, PerformanceNavigationTiming URL Адрес файла.
resource PerformanceResourceTiming URL Разрешенный URL запрошенного ресурса.
mark PerformanceMark DOMString Имя, используемое при создании отметки путем вызова performance.mark().
measure PerformanceMeasure DOMString Имя, используемое при создании показателя путем вызова performance.measure().
paint PerformancePaintTiming DOMString Либо «первая краска», либо «первая содержательная краска».
longtask PerformanceLongTaskTiming DOMString Случаи сообщения о длительных задачах

4. Пример 1: Мониторинг статических ресурсов

Из: "Мониторинг ресурсов"

function filterTime(a, b) {
  return (a > 0 && b > 0 && (a - b) >= 0) ? (a - b) : undefined;
}

let resolvePerformanceTiming = (timing) => {
  let o = {
    initiatorType: timing.initiatorType,
    name: timing.name,
    duration: parseInt(timing.duration),
    redirect: filterTime(timing.redirectEnd, timing.redirectStart), // 重定向
    dns: filterTime(timing.domainLookupEnd, timing.domainLookupStart), // DNS解析
    connect: filterTime(timing.connectEnd, timing.connectStart), // TCP建连
    network: filterTime(timing.connectEnd, timing.startTime), // 网络总耗时

    send: filterTime(timing.responseStart, timing.requestStart), // 发送开始到接受第一个返回
    receive: filterTime(timing.responseEnd, timing.responseStart), // 接收总时间
    request: filterTime(timing.responseEnd, timing.requestStart), // 总时间

    ttfb: filterTime(timing.responseStart, timing.requestStart), // 首字节时间
  };

  return o;
};

let resolveEntries = (entries) => entries.map(item => resolvePerformanceTiming(item));

let resources = {
  init: (cb) => {
    let performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance;
    if (!performance || !performance.getEntries) {
      return void 0;
    }

    if (window.PerformanceObserver) {
      let observer = new window.PerformanceObserver((list) => {
        try {
          let entries = list.getEntries();
          cb(resolveEntries(entries));
        } catch (e) {
          console.error(e);
        }
      });
      observer.observe({
        entryTypes: ['resource']
      })
    } else {
        window.addEventListener('load', () => {
        let entries = performance.getEntriesByType('resource');
        cb(resolveEntries(entries));
      });
    }
  },
};

Справочные статьи и резюме

Есть много справочных статей:

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

и иметь соответствующиеPolyfillsреализация версии.

Сводки и документы в интернете разной глубины.Если есть ошибки прошу поправить.