vue 与 throttle 的坑
Lodash 一直是我十分喜欢的一个工具库,其中 throttle 节流函数适用于控制类似 scroll 事件回调这种极其频繁场景上。可惜的是在配合 vue 使用上变得不那么好用,让我觉得非常可惜,当然预先说一句,这实际上不是 throttle ,Lodash 设计的问题,更多的是 vue 的问题。
问题一 绑定不便
事实上不只是 throttle ,相当一类工具类函数,特指 Lodash 中那一类接受一个函数返回一个新的函数的函数,会遇到无法直接绑定到 component 的 methods 上,首先是无法使用对象方法的简写语法,即
|
|
这样的方法,读取到 vue 挂载 component 的时候为 methods 中的方法绑定的 this,
解决方案
这个问题好解决,本身 methods 用对象方法也只是一个简写,把 function
定义的方法的 this 指向当前组件实例。所以解决方法很简单
|
|
这个其实没什么好说的,在官网上面都有说明,只是我没有想到而已。我没有注意到还是因为惯性思维,习惯了用箭头函数跟对象方法的简写定义,忘记直接用 function 才是最基本的做法。
但是要留意的一点是这里可以 throttle 里的 function 可以绑定到组件实例的 this,一个重要的原因是,throttle 返回的包裹函数调用的时候一般会使用 apply 来将 this 传入调用的 function ,没有用 apply 直接调用的话 this 就不是组件实例了 。
类似 Lodash ,都会细心的处理到这一点,如果你自己定义一些类似的工具函数,不要忘记在包裹函数里通过 apply 去调用。
问题二 实例共享
如果说第一个问题还是个小问题,那么第二个问题要烦人许多。这不是单个特例,而同样的是一类特定函数会遇到,其中同样可以以 throttle 作为例子。
让我们回看问题一的解决方法,其中解决了 this 的指向问题,不过,请考虑以下代码
|
|
按照预期,我们会希望子组件 com 的点击事件每隔 2s 触发一次,然而在上面的有两个 com 子组件的例子中,假如你在 2s 内分别点击了两个 com 子组件,就会发现只有第一个被点击的 com 子组件正常执行了 num + 1 操作。而第二个被点击的组件无法触发 num + 1 操作,这就是第二问题了。
我们知道 throttle 可以控制函数的调用频率,即使不去看其实现,我们都知道它必然是通过闭包保存了一个计时器,以判断是否应该执行传入的函数。
闭包当然保证了每个 throttle 都是独立的,这点毋庸置疑。问题依然在于 vue 的 methods 的定义方式。
当我们如上例所示定义了一个函数的时候,需要注意的是我们的 throttle 实际上只在定义组件的时候执行了一次,赋值给 click ,因此这个组件的所有实例共享同一个静态的 click 方法,共享了同一个 throttle 返回的闭包计时器。
这就导致了上面的问题,存在多个组件实例时,不是每个实例一个 throttle 计时器,而是共用了一个计时器,相互影响。
从这里可以推论出,凡是形如 throttle 那样借助闭包保存私有变量比如计时的函数,如果用上面的使用方法,都会遇到闭包私有变量被多个组件实例共享的问题。
目前来说,只要还是想要将方法都静态定义于 methods 中就没有什么解决方案。
解决方法
所以,如果你想要避免这个问题,那就只能考虑在组件实例化的时候才去定义这些方法了。
你可以在mounted
及之前的生命周期中,又或者是 data
函数中去定义这些方法
|
|
不过,这样就失去了在 methods 定义函数的直观这一点,其次是在这两者中定义的时候,需要考虑到生命周期的问题,是否在定义的时候会使用到一些还未初始化的数据。
比如这样就不行
|
|
总结
总的来说,本文展示了 vue component 的 methods 定义方式下的两个问题,分别是通过函数返回函数时,函数中 this 指向问题,以及通过函数返回函数时,闭包私用变量被共享的问题。
第一个问题可以通过 function
关键字解决,第二个问题,如果不涉及到多实例会同时使用的情况,那么就没有问题,不然则只能通过在组件实例化的时候在各个生命周期中或者data
函数中定义方法。