百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

浏览器原生提供的观察者,是真的好用!

bigegpt 2024-10-12 06:23 4 浏览

JavaScript中的观察者模式(Observer pattern)是一种设计模式,用于定义一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这种模式在实现松耦合和可维护性方面非常有用。

监控滑动是否style,调整样式,实现效果:

理解观察者模式

观察者模式又名发布-订阅(Publish/Subscribe)模式,它允许观察者在接收到通知后执行各种操作,具体取决于其实现的 update 方法和业务需求。

简单的JavaScript观察者模式示例

按照自定义观察者模式,我们可以分为两个主要角色:

目标角色(Subject)

// 主题
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

  notify() {
    this.observers.forEach(observer => observer.update());
  }
}

观察者角色(Observer)

// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }

  update() {
    console.log(`${this.name} 收到通知,进行更新操作。`);
  }
}

使用示例:

// 使用示例
const subject = new Subject();

const observer1 = new Observer('观察者1');
const observer2 = new Observer('观察者2');

subject.addObserver(observer1);
subject.addObserver(observer2);

// 主题状态发生变化时,通知所有观察者(轮询调用了Observer中的update(),)
subject.notify();

// 输出:
// 观察者1 收到通知,进行更新操作。
// 观察者2 收到通知,进行更新操作。

当调用Subject的notify方法时,所有注册的观察者会收到通知并执行其更新操作。

这是一个简单的自定义观察者模式的例子,但在实际开发中,我们通常会使用浏览器原生提供的观察者模式来处理特定的场景。

浏览器原生提供的观察者模式

浏览器原生提供了多种观察者模式的实现,其中之一是 MutationObserver。它具有以下优点:

  1. 实时捕捉变化: MutationObserver 允许你在 DOM 发生变化的瞬间得到通知,而不需要定期轮询 DOM 元素。这样可以确保你在最早的时候获取到变化。
  2. 性能优化: 相较于其他检测 DOM 变化的方法,如定时器轮询或事件监听,MutationObserver 更为高效,因为仅在真正有变化时才触发回调。
  3. 支持灵活的配置: 可以配置 MutationObserver 以观察特定类型的变化,比如属性变化、子节点变化等,非常灵活适应不同监测需求。

除了 MutationObserver,还有其他一些观察者模式的实现,如 IntersectionObserver 用于观察目标元素与其祖先元素或视窗的交叉情况,以及 ResizeObserver 用于观察元素的大小变化。

在现代Web开发中,这些观察者模式通常被用于实现更高效和性能友好的交互和动态效果。

应用示例:Vant中的PickerNumber

以Vant中的 PickerNumber 组件为例,该组件在滑动 column 时,实际上是通过 ul 中的 transform 进行动画处理。

我们的目标是在触摸滑动的时候,能让我们滑动到中间的文字,进行选中变粗等样式修改,比如下面这样:

我们接下来就使用浏览器原生提供的观察者模式(Observer pattern)来完成这些处理,具体以 MutationObserver 为例:

获取DOM:

getPickerDom() {
  // 变白色
  const pickerColumnsDom = document.querySelector(".picker-number .van-picker .van-picker__columns");
  const pickerColumnDom = pickerColumnsDom.querySelectorAll(".van-picker-column");
  const ulPickerColumnDom = Array.from(pickerColumnDom).map(res => {
     return res.querySelector(".van-picker-column__wrapper");
  });
  return ulPickerColumnDom;
},

处理滑动事件:

handleTouchMove(dom) {
  // 处理滑动事件的函数

  // 获取当前滑动的位置
  const translateY = this.getTranslateY(dom);
  // 获取所有列表项
  const items = dom.querySelectorAll(".van-picker-column__item");
  // 获取单个列表项的高度
  const itemHeight = items.length > 0 ? items[0].offsetHeight : 32;
  // 当前初始化的高度
  const itemOffset = dom.offsetHeight / 2;
  // 计算当前滑动位置对应的列表项索引
  // this.initCount picker-nunber默认首次出现的位置
  const currentIndex = (Math.round((itemOffset - translateY) / (itemHeight))) - this.initCount + 3;
  // 移除所有列表项的选中状态
  items.forEach((item, index) => {
    item.classList.remove("van-picker-column__item--selected");
    // 添加选中状态给当前索引的列表项
    if (index === currentIndex) {
      item.classList.add("van-picker-column__item--selected");
    }
  });
},
getTranslateY(element) {
  // 获取元素的纵向位移值
  const transform = window.getComputedStyle(element).getPropertyValue("transform");
  const translateYMatch = transform.split(", ");
  const translateY = translateYMatch[translateYMatch.length - 1].slice(0, -1);
  return translateYMatch ? translateY : 0;
}

创建观察者实例:

监听ul中style中transfrom的变化,配置只观察style属性的变化

mutationObserver(targetElement) {
    // 创建一个MutationObserver实例
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.attributeName === 'style') {
                // 处理咱们事件 比如我自己的处理事件
                 this.handleTouchMove(targetElement);
            }
        });
    });

    // 配置观察选项
    const observerConfig = {
        attributes: true, // 观察属性的变化
        attributeFilter: ['style'] // 只观察style属性的变化
    };

    // 开始观察目标元素
    observer.observe(targetElement, observerConfig);
    // 停止观察
    // observer.disconnect();
},

绑定观察者:

bindObserver() {
    const  arrayDom = this.getPickerDom();
    for(let dom of arrayDom){
        this.mutationObserver(dom)
    }
},

启动:

mounted(){
   this.bindObserver();
},

我们使用 MutationObserver 充当观察者,通过监听 style 属性的变化来实时捕捉滑动事件,然后调用 handleTouchMove 方法进行相应的处理。这样的模式使得观察逻辑解耦,代码更加灵活



浏览器原生提供的观察者,是真的好用!
原文链接:https://juejin.cn/post/7321587195327741978

相关推荐

Redis集群对比:主从复制、哨兵模式、Cluster一文看懂所有优缺点

在分布式系统中,Redis作为高性能的内存数据库,其集群方案的选择直接影响到系统的稳定性、可用性和扩展性。本文将全面对比Redis的三种主流集群方案:主从复制、哨兵模式和Cluster模式,帮助开发者...

redis的主从复制,读写分离,主从切换

当数据量变得庞大的时候,读写分离还是很有必要的。同时避免一个redis服务宕机,导致应用宕机的情况,我们启用sentinel(哨兵)服务,实现主从切换的功能。redis提供了一个master,多个sl...

# Redis 入门到精通(九)-- 主从复制(3)

#Redis入门到精通(九)--主从复制(3)##一、redis主从复制-常见问题(1)###1、伴随着redis系统的运行,master的数据量会越来越大,一旦master重启...

redis - 主从复制(Redis主从复制时序图)

1引言在上一篇文章中,我们了解了Redis两种不同的持久化方式,Redis服务器通过持久化,把Redis内存中持久化到硬盘当中,当Redis宕机时,我们重启Redis服务器时,可以由RDB文件或AO...

# Redis 入门到精通(九)-- 主从复制(2)

#Redis入门到精通(九)--主从复制(2)##一、redis主从复制--数据同步阶段注意事项###1、数据同步阶段master说明1)如果master数据量巨大,数据同步阶段应...

Redis主从复制(redis主从复制主节点挂了)

介绍Redis有两种不同的持久化方式,Redis服务器通过持久化,把Redis内存中持久化到硬盘当中,当Redis宕机时,我们重启Redis服务器时,可以由RDB文件或AOF文件恢复内存中的数据。不过...

深入解析 Redis 集群的主从复制实现方式

在互联网大厂的后端开发领域,Redis作为一款高性能的内存数据库,被广泛应用于缓存、消息队列等场景。而Redis集群中的主从复制机制,更是保障数据安全、实现读写分离以及提升系统性能的关键所在。今...

Redis主从架构详解(redis主从架构高可用如何实现)

Redis主从架构搭建Redis主节点配置创建主节点目录(/opt/redis-master),复制redis.conf到该目录下,redis.conf配置项修改#后台启动daemonizeyes...

抖音“四大包塘战神”:承包了全网的快乐

在抖音钓鱼垂类领域,"包塘战神"军团正掀起一场黑色幽默风暴。空军华、大表坑、李赔光、透心良四位创作者,以承包鱼塘为舞台,用连续翻车的钓鱼直播构筑起流量奇观。当钓鱼佬在抖音集体转型喜剧人...

ORACLE 11G RAC 安装-通过VM配置共享磁盘

简介:在自己的电脑上通过VM软件搭建Oracle11GRAC,通过修改VM的参数文件来实现磁盘共享!目标:搭建RAC环境实现:使用VMwareWorkstation8.0.0+ORACLE...

Linux操作系统安全配置(linux系统安全配置包括)

一、服务相关命令systemctlenable服务名#开机自启动systemctldisable服务名#禁用开机自启动systemctlstop服务名#停止服务systemctls...

关于Linux性能调优中网络I/O的一些笔记

写在前面和小伙伴分享一些Linux网络优化的笔记,内容很浅,可以用作入门博文内容结合《Linux性能优化》读书笔记整理涉及内容包括常用的优化工具(mii-tool,ethtool,ifconfig,i...

从 Sonatype Nexus Repository Manager 迁移到 Artifactory

1.Nexus1.1下载下载链接:https://help.sonatype.com/repomanager3/product-information/download/download-archiv...

Ubuntu20安装zabbix5.0企业监控系统亲测教程

前言示例主机:zabbix10.0.100.10,将安装在UbuntuServer上教程说明:因使用官方教程无法安装成功,所以本教程与官方教程有所不同安装前提:已安装UbuntuServer2...

Linux内核设计与实现—进程管理(linux内核程序设计)

进程进程就是处于执行期的程序(目标码存放在某种存储介质上)。进并不仅仅局限于一段可执行程序代码(Unix称其为代码段,textsection)。通常进程还要包含其他资源,像打开的文件,挂起的信号,...