面试官: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({
// 具体传参按业务需求来传就可以了
});
}
});
然后,通过 mounted 和 unMounted 钩子函数,在元素挂载到DOM时开始观察它的尺寸变化,并在元素卸载时停止观察。这确保了只有当元素存在于页面上时才会对其进行尺寸监听,进一步优化了性能和资源管理。
export default {
mounted(el ,bindings){
ob.observe(el);
map.set(el ,bindings.value);
},
unMounted(el){
ob.unobserve(el);
}
}