面试官:Vue中封装一个监听元素尺寸变化的指令

156 阅读2分钟

面试官:Vue中封装一个监听元素尺寸变化的指令

也是去年面试的时候遇到的一个手撕题,记录一下,主要是考查ResizeObserver这个内容。

如果你对于这些原生的Observer API不太熟悉的话,可以参考这篇文章:Web中原生内置的Observer方法们:MutationObserver、IntersectionObserver与ResizeObserver

需求

在某些场景下,我们可能需要监听某个DOM元素的尺寸变化以执行特定的操作,例如重新布局或更新样式。为了解决这些问题并提高代码的可复用性,可以将其封装成一个 Vue 自定义指令。

先放整体代码

vue中调用:

<script setup> 
    const reiszeHandler = () => { 
        console.log('Element size changed!'); 
    } 
</script> 

<template> 
    <div v-resize-ob="reiszeHandler"> Watch my size change! </div> 
</template>

指令实现:

const map = new WeakMap(); // 存储el和对应回调的映射。让下面mounted里的内容能在ob里拿到,使用weakmap防止内存泄漏

const ob = new ResizeObserver((entries)=>{
  for(const entry of entries){
    const handler = map.get(entry.target);
    handler && handler({
      // 具体传参按业务需求来传就可以了
    });
  }
});

export default {
  mounted(el ,bindings){
    ob.observe(el);
    map.set(el ,bindings.value);
  },

  unMounted(el){
    ob.unobserve(el);
  }
}

实现细节

使用一个 WeakMap 实例 map 来存储元素(el)及其对应的回调函数之间的映射关系。这样做的好处是可以有效地防止内存泄漏,因为 WeakMap 只会弱引用其键值对中的键。

const map = new WeakMap(); // 存储el和对应回调的映射。让下面mounted里的内容能在ob里拿到,使用weakmap防止内存泄漏

创建一个 ResizeObserver 实例 ob,用于监听元素尺寸的变化。每当被监听的元素尺寸发生变化时,ResizeObserver 就会触发回调函数,并传入相应的 entries 对象数组。这些 entries 包含了每个被观察元素的各种尺寸信息,如边框盒尺寸和内容盒尺寸等。

这里问到了为什么打印出来的entry对象的边框盒尺寸属性是一个数组,是因为一个元素其实是可以有多个边框盒的,比如li元素,前面那个点是一个单独的边框盒,内容又是一个。

const ob = new ResizeObserver((entries)=>{
  for(const entry of entries){
    const handler = map.get(entry.target);
    handler && handler({
      // 具体传参按业务需求来传就可以了
    });
  }
});

然后,通过 mountedunMounted 钩子函数,在元素挂载到DOM时开始观察它的尺寸变化,并在元素卸载时停止观察。这确保了只有当元素存在于页面上时才会对其进行尺寸监听,进一步优化了性能和资源管理。

export default {
  mounted(el ,bindings){
    ob.observe(el);
    map.set(el ,bindings.value);
  },

  unMounted(el){
    ob.unobserve(el);
  }
}