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

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

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

理解观察者模式

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

简单的JavaScript观察者模式示例

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

目标角色(Subject)

js 复制代码
// 主题
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)

js 复制代码
// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }

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

使用示例:

js 复制代码
// 使用示例
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 收到通知,进行更新操作。

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

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

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

浏览器原生提供了多种观察者模式的实现,其中之一是 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:

js 复制代码
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;
},

处理滑动事件:

js 复制代码
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属性的变化

js 复制代码
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();
},

绑定观察者:

js 复制代码
bindObserver() {
    const  arrayDom = this.getPickerDom();
    for(let dom of arrayDom){
        this.mutationObserver(dom)
    }
},

启动:

js 复制代码
mounted(){
   this.bindObserver();
},

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

相关推荐
逐·風21 分钟前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫1 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦1 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子2 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享3 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
程序媛小果3 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
从兄3 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
凉辰4 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式
清灵xmf5 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询