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

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 方法进行相应的处理。这样的模式使得观察逻辑解耦,代码更加灵活和

相关推荐
木头没有瓜40 分钟前
vscode离线安装插件
ide·vue.js·vscode
伍哥的传说1 小时前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi9878381 小时前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
浪裡遊2 小时前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听12 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿3 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉3 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
归于尽3 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课3 小时前
React useEffect 详解与运用
前端·react.js
我想说一句3 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript