JavaScript中5个重要的Observer函数小结

 更新时间:2024年01月15日 10:35:22   作者:小马甲丫  
浏览器为开发者提供了功能丰富的Observer,本文主要介绍了JavaScript中5个重要的Observer函数小结,具有一定的参考价值,感兴趣的可以了解一下

一、前言

浏览器为开发者提供了功能丰富的Observer,在这篇文章中,我们将深入研究这些常见的浏览器 Observer,剖析它们的作用、用法以及它们在 Web 开发中的应用场景。

二、MutationObserver

MutationObserver用于监听DOM对象的变更(包括子节点),当节点属性发生变化,或执行增删改操作时执行对应的callback

MutationObserver为我们提供了一种十分方便的监听DOM变化的方式。

2.1、基本使用

// Observer需要一个用于监听的目标DOM
const targetNode = document.getElementById("app");

//用于确定mutation监听变化的范围
const config = { 
  attributes: true, // 监听目标节点的属性变化,例如id,class等属性
  childList: true, // 除目标节点外还要监听目标节点的直接子节点
  subtree: true,  // subtree的范围大于childList,还包括子节点children
  characterData: true   // 监听TextNode需要额外配置,默认TextNode变化不会触发callback
};

// 当观察到变动时执行的回调函数,mutationsList包含本次变更的信息
const callback = function (mutationsList, observer) {
  console.log(mutationsList)
};

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

2.2、API介绍

2.2.1、observe

observe用于开启对某个DOM的监听,一个MutationObserver可以通过多次调用observe监听多个DOM的变化。

当变化发生时MutationObserver会将一个或多个mutation对象传给callback的第一个参数,mutation对象内包含本次变更的相关信息下面看一下mutation的结构

{
  addedNodes: [],  //新增DOM时会包含被新增的DOM
  attributeName: "id",   //本次变更的属性名
  attributeNamespace: null,  //命名空间URI,一般用不到
  nextSibling: null, //当存在添加/删除节点的操作时会存在nextSibling/previousSibling, 引用上一个/下一个兄弟节点
  previousSibling: null,
  oldValue: null,
  removedNodes: [],
  target: Text,
  type: "characterData" //变更类型,如characterData,childList等
}

2.2.2、disconnect

observer.disconnect()

调用observer.disconnectObserver将不再监听target,如果不需要监听请及时调用该方法,以免产生预期之外的行为以及内存泄漏。

2.2.3、takeRecords

takeRecords用于获取在事件队列中但还未传递给callbackmutation对象,通常使用在调用disconnect时又不想丢失之前的mutationRecords(如果mutation连续触发,可能出现mutation还在队列中但未传递给callback的情况)。

2.3、常见场景

对于需要监听DOM变化的场景可考虑使用MutationObserver,利于用于Tag group内元素的动态渲染,下面使用MutationObserver实现一个简单的Todo List

2.3.1、源码

<!DOCTYPE html>
<html>
<head>
  <title>MutationObserver To-Do List Demo</title>
  <style>
    #todo-list {
      list-style-type: none;
    }
  </style>
</head>
<body>
  <h1>待办事项列表</h1>

  <ul id="todo-list">
    <li>完成作业</li>
    <li>购物</li>
  </ul>

  <button id="addTask">添加任务</button>
  <button id="removeTask">移除任务</button>
  <p id="taskCount">任务数量:2</p>

  <script>
    const todoList = document.getElementById('todo-list');
    const taskCount = document.getElementById('taskCount');

    const observer = new MutationObserver((mutationsList, observer) => {
      mutationsList.forEach((mutation) => {
        if (mutation.type === 'childList') {
          updateTaskCount();
        }
      });
    });

    const config = { childList: true };

    observer.observe(todoList, config);

    document.getElementById('addTask').addEventListener('click', () => {
      const newTask = document.createElement('li');
      newTask.textContent = '新任务';
      todoList.appendChild(newTask);
    });

    document.getElementById('removeTask').addEventListener('click', () => {
      const tasks = todoList.getElementsByTagName('li');
      if (tasks.length > 0) {
        todoList.removeChild(tasks[0]);
      }
    });

    function updateTaskCount() {
      const tasks = todoList.getElementsByTagName('li');
      taskCount.textContent = `任务数量:${tasks.length}`;
    }
  </script>
</body>
</html>

2.3.2、预览

在线源码请点击【前往

2.3.3、效果

三、IntersectionObserver

IntersectionObserver用于监听一个元素的可见比例(一个DOM元素被另一个DOM元素遮挡百分比)变化。

3.1、基本使用

const target = document.getElementById('app');

const options = {
  root: rootTarget, // 相对于某个元素进行遮挡计算
  rootMargin: '0px', // 进行计算的边界范围,通过rootMargin可以实现提前计算或延迟计算(相对于root原本尺寸)的效果
  threshold: 0.5 // 触发callback时的遮挡比例,0.5代表元素被遮挡50%时触发callback。由于浏览器事件循环机制的影响,callback触发时遮挡比例通常不会是精确的50%。
};

const intersectionObserver = new IntersectionObserver((entries, observer) => {
  //和MutationObserver相同,也是产生一个array
  entries.forEach(entry => {
    console.log(entry)
  });
}, options);

intersectionObserver.observe(target);

3.2、API介绍

3.2.1、observe & options

observe方法用于启动一个ObserverDOM元素的监听。在创建IntersectionObserver时可以通过传入option改变监听的行为。

const options = {
  root: root, 
  rootMargin: '100px', 
  threshold: 0.7 
};

在上面的配置中,通过配置rootMargin100pxtarget距离root元素100px时即可判定为被遮挡,通过threshold设置为0.7,当遮挡比例查过70%时执行callback

3.2.2、entry

callback第一个paramentry对象构成的arrayentry包含了触发callbackDOM的位置信息

//被监听DOM元素的Rect信息
boundingClientRect:  {
  bottom: 208
  height: 200
  left: 8
  right: 208
  top: 8
  width: 200
  x: 8
  y: 8
}
intersectionRatio: 1 //交叉比例
// 被监听元素与Root元素交叉部分矩形的Rect信息。
intersectionRect: {
  bottom: 208,
  height: 200,
  left: 8,
  right: 208,
  top: 8,
  width: 200,
  x: 8,
  y: 8
},
// 是否处于交叉状态
isIntersecting: true,
isVisible: false,
// Root元素的Rect信息
rootBounds:  {
  bottom: 606,
  height: 606,
  left: 0,
  right: 476,
  top: 0,
  width: 476,
  x: 0,
  y: 0
},
// root元素
target: div#target,
time: 49.09999990463257

3.3、常见场景

乍一看IntersectionObserver好像没啥用,单这个Api在某些场景下十分好用。

比如我们有一个通过sticky固定在屏幕顶部的header元素,我们希望在触发sticky时给header加一个shadow(很多table都有这样的功能)

一种很常见的做法是监听scroll,当滚动一定距离时加上shadow即可。但是监听scroll本身会早成一定的渲染压力(scroll触发非常频繁),同时如果使用React这样的框架又会造成额外的render,在用户的视角看来更卡了。

此时使用IntersectionObserver就很合适了,因为我们需要监听的只是触发sticky的一瞬间,其他的滚动都是无效的,没必要进行计算。

3.3.1、源码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sticky Header with Shadow on Intersection</title>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    header {
      height: 80px;
      background-color: #3498db;
      color: white;
      text-align: center;
      line-height: 80px;
      position: sticky;
      top: 0;
      z-index: 100;
    }

    .header-shadow {
      transition: box-shadow 0.3s ease;
    }

    .header-shadow.shadow {
      box-shadow: 0 2px 5px black;
    }

    section {
      height: 1000px;
      background-color: #ecf0f1;
      padding: 20px;
    }
  </style>
</head>
<body>
  <div id="guard"></div>
  <header id="sticky-header" class="header-shadow">Sticky Header</header>

  <section>
    <p>向下滚动触发sticky时展示shadow</p>
  </section>

  <script>
    const header = document.getElementById('sticky-header');
    const section = document.querySelector('section');

    const options = {
      threshold: 1
    };
    //guard滚动到可视区域以外时认为触发了shadow
    const intersectionObserver = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          header.classList.remove('shadow');
        } else {
          header.classList.add('shadow');
        }
      });
    }, options);

    intersectionObserver.observe(document.getElementById('guard'));
  </script>

</body>
</html>

3.3.2、预览

在线源码请点击【前往

3.3.3、效果

四、ResizeObserver

ResizeObserver是用于监听DOM尺寸变化的observer,当DOM尺寸变化是执行callback

4.1、基本使用

和前面的api用法差不多,这里不过多介绍。

const box = document.getElementById('box');

const resizeObserver = new ResizeObserver(entries => {
  entries.forEach(entry => {
    console.log(entry)
  });
});

resizeObserver.observe(box);

4.2、API介绍

entry对象包含resize相关的信息,下面看一下entry的结构

{
  // 不同box-sizing下的尺寸
  borderBoxSize: [{
    blockSize: 200,
    inlineSize: 200,
  }],
  contentBoxSize: [{
    blockSize: 200,
    inlineSize: 200,
  }],
  contentRect: {
    bottom: 200,
    height: 200,
    left: 0,
    right: 200,
    top: 0,
    width: 200,
    x: 0,
    y: 0
  },
  // 在物理设备像素上的大小, 在不同的屏幕上尺寸不同例如Retina
  devicePixelContentBoxSize: [{
      blockSize: 300,
      inlineSize: 300
    }
  ],
  target: div#resizable-box
}

4.3、常见场景

可以基于ResizeObserver实现一个简单的resize-detector(参考react-resize-detector),在尺寸变化时返回尺寸信息,点击盒子就算拖拽有效。

4.3.1、源码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ResizeObserver Demo with Resizable Box</title>
  <style>
    #resizable-box {
      width: 200px;
      height: 200px;
      background-color: #3498db;
      color: white;
      text-align: center;
      line-height: 200px;
      font-size: 24px;
      transition: background-color 0.5s ease;
      resize: both;
      overflow: auto;
      cursor: pointer;
    }
  </style>
</head>
<body>

  <div id="resizable-box">Resize me!</div>

  <script>
    const resizableBox = document.getElementById('resizable-box');
    let isResizing = false;
    let startX, startY, startWidth, startHeight;

    const resizeObserver = new ResizeObserver(entries => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        console.log('宽度:', width, '高度:', height);
      }
    });

    resizeObserver.observe(resizableBox);

    resizableBox.addEventListener('mousedown', startResize);
    document.addEventListener('mousemove', handleResize);
    document.addEventListener('mouseup', stopResize);

    function startResize(e) {
      isResizing = true;
      startX = e.clientX;
      startY = e.clientY;
      startWidth = parseInt(document.defaultView.getComputedStyle(resizableBox).width, 10);
      startHeight = parseInt(document.defaultView.getComputedStyle(resizableBox).height, 10);
    }

    function handleResize(e) {
      if (!isResizing) return;
      const newWidth = startWidth + (e.clientX - startX);
      const newHeight = startHeight + (e.clientY - startY);

      resizableBox.style.width = newWidth + 'px';
      resizableBox.style.height = newHeight + 'px';
    }

    function stopResize() {
      isResizing = false;
    }
  </script>

</body>
</html>

4.3.2、预览

在线源码请点击【前往

4.3.3、效果

五、PerformanceObserver

PerformanceObserver用于监听浏览器的performance事件,方便在performance事件触发时作统一处理。

5.1、基本使用

// mdn demo
function perf_observer(list, observer) {
  console.log(list)
}
var observer2 = new PerformanceObserver(perf_observer);
// entryTypes用于指定要监听的事件类型
observer2.observe({ entryTypes: ["measure"] });

5.2、API介绍

下面列一下常见的entryTypes

  • mark:用于标记时间戳的事件
  • measureperformance.measure触发的事件
  • frame:网页渲染的事件
  • navigation:导航的事件,例如页面加载或重新加载
  • resource:资源加载事件
  • longtask:长任务事件
  • paint:绘制事件,例如FPFCP
  • layout-shift:用于监视布局变化的事件

对于对性能比较敏感的项目以及长期性能监控来说这个api还是比较方便的。

六、ReportingObserver

ReportingObserver用于监听浏览器报告的事件,例如废弃API,过时特性,网络错误。做监控SDK的同学应该经常能用到,日常业务代码用的比较少。

6.1、基本使用

这里就简单看一下使用方法吧,比较简单

const observer = new ReportingObserver((reports, observer) => {
  reports.forEach(report => {
    console.log(report);
  });
});

// 监听过时特性
observer.observe({ types: ['deprecation'] });

七、最后

到此这篇关于JavaScript中5个重要的Observer函数小结的文章就介绍到这了,更多相关JavaScript Observer内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaScript学习笔记(三):JavaScript也有入口Main函数

    JavaScript学习笔记(三):JavaScript也有入口Main函数

    大家都知道在c和java中,有main函数货main方法作为一个程序的入口函数或方法。在JS中从js源文件的头部开始运行的,我们仍然可以虚构出一个main函数来作为程序的起点,这样一来不仅可以跟其他语言统一了,而且说不定你会对JS有更深的理解。感兴趣的小伙跟着小编一起学习吧
    2015-09-09
  • 原生js实现日历效果

    原生js实现日历效果

    这篇文章主要为大家详细介绍了原生js实现日历效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • JavaScript实现商品评价五星好评

    JavaScript实现商品评价五星好评

    这篇文章主要为大家详细介绍了JavaScript实现商品评价五星好评,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • 微信小程序之几种常见的弹框提示信息实现详解

    微信小程序之几种常见的弹框提示信息实现详解

    这篇文章主要介绍了微信小程序之几种常见的弹框提示信息实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • js实现圆盘记速表

    js实现圆盘记速表

    本文分享了一个利用AmCharts制作的汽车速度计速表,通过设置不同的速度(数字)来动态改变计速表的指针。使用也非常简单,下面给出方法。
    2015-08-08
  • 使用three.js 绘制三维带箭头线的详细过程

    使用three.js 绘制三维带箭头线的详细过程

    遇到一个需求,在一个地铁场景里展示逃生路线,为了画这个箭头,我花费了很多时间精力,下面基于使用three.js 绘制三维带箭头线的详细过程分享给大家,感兴趣的朋友一起看看吧
    2021-10-10
  • 详解JavaScript中的4种类型识别方法

    详解JavaScript中的4种类型识别方法

    JavaScript中检测对象类型的运算符有:typeof、instanceof,还有对象的constructor属性: 1) typeof 运算符 typeof 是一元运算符,返回结果是一个说明运算数类型的字符串。下面由小编给大家分享JavaScript中的4种类型识别方法,需要的朋友可以参考下本文
    2015-09-09
  • js实现简单掷骰子小游戏

    js实现简单掷骰子小游戏

    这篇文章主要为大家详细介绍了js实现简单摇筛子小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • ECharts柱状图关闭鼠标hover时的高亮样式详解

    ECharts柱状图关闭鼠标hover时的高亮样式详解

    为了方便使用,echarts的饼图中给加入了默认的hover高亮效果,下面这篇文章主要给大家介绍了关于ECharts柱状图关闭鼠标hover时的高亮样式的相关资料,需要的朋友可以参考下
    2023-04-04
  • Node.js:Windows7下搭建的Node.js服务(来玩玩服务器端的javascript吧,这可不是前端js插件)

    Node.js:Windows7下搭建的Node.js服务(来玩玩服务器端的javascript吧,这可不是前端js插件

    什么是Node.js?还服务器端javascript?对于这个概念我在这篇文章不做解释,可以自己去搜索了解下,服务器端js不是新技术,只是最近的node.js的火爆让他爆发了,我会在以后的文章里解释什么是node.js。
    2011-06-06

最新评论