有echarts使用经验的同窗可能遇到过这样的场景,在window.onresize事件回调里触发echartsBox.resize()方法来达到重绘的目的,resize事件是连续触发的这意味着echarts实例会连续的重绘这是很是耗性能的。还有一个常见的场景在input标签的input事件里请求后端接口,input事件也是连续触发的,假设我输入了“12”就会请求两次接口参数分别是“1”和“12”,比浪费网络资源更要命的是若是参数为“1”的请求返回数据的时间晚于参数为“12”的接口,那么咱们获得的数据是和指望不符的。固然基于axios能够作不少封装能够取消上一个请求或者经过拦截作处理,但仍是从防抖入手比较简单。html
解释:当持续触发某事件时,必定时间间隔内没有再触发事件时,事件处理函数才会执行一次,若是设定的时间间隔到来以前,又一次触发了事件,就从新开始延时。
案例:持续触发scroll事件时,并不当即执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发一次handle函数。vue
function debounce(fn, wait) {
let timeout = null
return function() {
if(timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait);
}
}
function handle() {
console.log(Math.random())
}
window.addEventListener('scroll', debounce(handle, 1000))
复制代码
addEventListener的第二个参数其实是debounce函数里return回的方法,let timeout = null
这行代码只在addEventListener的时候执行了一次 触发事件的时候不会执行,那么每次触发scroll事件的时候都会清除上次的延时器同时记录一个新的延时器,当scroll事件中止触发后最后一次记录的延时器不会被清除能够延时执行,这是debounce函数的原理node
解释:当持续触发事件时,有规律的每隔一个时间间隔执行一次事件处理函数。
案例:持续触发scroll事件时,并不当即执行handle函数,每隔1000毫秒才会执行一次handle函数。react
function throttle(fn, delay) {
var prev = Date.now()
return function() {
var now = Date.now()
if (now - prev > delay) {
fn()
prev = Date.now()
}
}
}
function handle() {
console.log(Math.random())
}
window.addEventListener('scroll', throttle(handle, 1000))
复制代码
原理和防抖相似,每次执行fn函数都会更新prev用来记录本次执行的时间,下一次事件触发时判断时间间隔是否到达预先的设定,重复上述操做。ios
防抖和节流均可以用于 mousemove、scroll、resize、input等事件,他们的区别在于防抖只会在连续的事件周期结束时执行一次,而节流会在事件周期内按间隔时间有规律的执行屡次。element-ui
在vue中实现防抖无非下面这两种方法axios
把上面的案例改造一下就能封装一个简单的utils工具后端
utils.js
let timeout = null
function debounce(fn, wait) {
if(timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
export default debounce
app.js
<input type="text" @input="debounceInput($event)">
import debounce from './utils'
export default {
methods: {
debounceInput(E){
debounce(() => {
console.log(E.target.value)
}, 1000)
}
}
}
复制代码
至于组件的封装咱们要用到$listeners、$attrs
这两个属性,他俩都是vue2.4新增的内容,官网的介绍比较晦涩,咱们来看他俩究竟是干啥的:api
$attrs:
父组件在调用子组件的时候会在子组件上绑定不少属性,而后在子组件里经过props注册使用,那么没有被props注册的就会放在$attrs
里,固然不包括class和style,而且能够经过 v-bind="$attrs"
传入子组件的内部组件。数组
$listeners:
父组件在子组件上绑定的不含.native修饰器的事件会放在$listeners里
,它能够经过 v-on="$listeners"
传入内部组件。
简单来讲$listeners、$attrs
他俩是作属性和事件的承接,这在对组件作二次封装的时候很是有用。
咱们以element-ui的el-input组件为例封装一个带防抖的debounce-input组件
debounce-input.vue
<template>
<el-input v-bind="$attrs" @input="debounceInput"/>
</template>
<script>
export default {
data() {
return {
timeout: null
}
},
methods: {
debounceInput(value){
if(this.timeout !== null) clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.$emit('input', value)
}, 1000)
}
}
}
</script>
app.vue
<template>
<debounce-input placeholder="防抖" prefix-icon="el-icon-search" @input="inputEve"></debounce-input>
</template>
<script>
import debounceInput from './debounce-input'
export default {
methods: {
inputEve(value){
console.log(value)
}
},
components: {
debounceInput
}
}
</script>
复制代码
上面组件的封装用了$attrs,虽然不须要开发者关注属性的传递,可是在使用上仍是不方便的,由于把el-input封装在了内部这样对样式的限定也比较局限。有接触过react高阶组件的同窗可能有了解,react高阶组件本质上是一个函数经过包裹被传入的React组件,通过一系列处理,最终返回一个相对加强的React组件。那么在vue中能够借鉴这种思路吗,咱们来了解一下vue的函数式组件。
函数式组件是指用一个Function来渲染一个vue组件,这个组件只接受一些 prop,咱们能够将这类组件标记为 functional,这意味着它无状态 (没有响应式数据),也没有实例 (没有this上下文)。
一个函数式组件大概向下面这样:
export default () => {
functional: true,
props: {
// Props 是可选的
},
// 为了弥补缺乏的实例, 提供第二个参数做为上下文
render: function (createElement, context) {
return vNode
}
}
复制代码
注意:在 2.3.0 以前的版本中,若是一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你能够省略 props 选项,全部组件上的特性都会被自动隐式解析为 prop。可是你一旦注册了 prop 那么只有被注册的 prop 会出如今 context.prop 里。
render函数的第二个参数context用来代替上下文this他是一个包含以下字段的对象:
slots用来访问被插槽分发的内容。每一个具名插槽 有其相应的属性 (例如:v-slot:foo 中的内容将会在 vm.$slots.foo 中被找到)。default 属性包括了全部没有被包含在具名插槽中的节点,或 v-slot:default 的内容。
你可能想知道为何同时须要 slots() 和 children。slots().default 不是和 children 相似的吗?在一些场景中,是这样——但若是是以下的带有子节点的函数式组件呢?
<my-functional-component>
<p v-slot:foo>
first
</p>
<p>second</p>
</my-functional-component>
复制代码
对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。同时拥有 children 和 slots(),所以你能够选择让组件感知某个插槽机制,仍是简单地经过传递 children,移交给其它组件去处理。
假设有一个a组件,引入了 a1,a2,a3 三个组件,a组件的父组件给a组件传入了一个type属性根据type的值a组件来决定显示 a1,a2,a3 中的那个组件。这样的场景a组件用函数式组件是很是方便的。那么为何要用函数式组件呢?一句话:渲染开销低,由于函数式组件只是函数。
由于业务关系该防抖组件的封装同时支持 input、button、el-input、el-button 的使用,若是是input类组件对input事件作防抖处理,若是是button类组件对click事件作防抖处理。
const debounce = (fun, delay = 500, before) => {
let timer = null
return (params) => {
timer && window.clearTimeout(timer)
before && before(params)
timer = window.setTimeout(() => {
// click事件fun是Function input事件fun是Array
if (!Array.isArray(fun)) {
fun = [fun]
}
for (let i in fun) {
fun[i](params)
}
timer = null
}, parseInt(delay))
}
}
export default {
name: 'Debounce',
functional: true, // 静态组件 当不声明functional时该组件一样拥有上下文以及生命周期函数
render(createElement, context) {
const before = context.props.before
const time = context.props.time
const vnodeList = context.slots().default
if (vnodeList === undefined){
console.warn('<debounce> 组件必需要有子元素')
return null
}
const vnode = vnodeList[0] || null // 获取子元素虚拟dom
if (vnode.tag === 'input') {
const defaultFun = vnode.data.on.input
const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
vnode.data.on.input = debounceFun
} else if (vnode.tag === 'button') {
const defaultFun = vnode.data.on.click
const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
vnode.data.on.click = debounceFun
} else if (vnode.componentOptions && vnode.componentOptions.tag === 'el-input') {
const defaultFun = vnode.componentOptions.listeners.input
const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
vnode.componentOptions.listeners.input = debounceFun
} else if (vnode.componentOptions && vnode.componentOptions.tag === 'el-button') {
const defaultFun = vnode.componentOptions.listeners.click
const debounceFun = debounce(defaultFun, time, before) // 获取节流函数
vnode.componentOptions.listeners.click = debounceFun
} else {
console.warn('<debounce> 组件内只能出现下面组件的任意一个且惟一 el-button、el-input、button、input')
return vnode
}
return vnode
}
}
复制代码
<template>
<debounce time="300" :before="beforeFun">
<input type="text" v-model="inpModel" @input="inputChange"/>
</debounce>
</template>
<script>
import debounce from './debounce'
export default {
data() {
return {
inpModel: 1
}
},
methods: {
inputChange(e){
console.log(e.target.value, '防抖')
},
beforeFun(e){
console.log(e.target.value, '不防抖')
}
},
components: {
debounce
}
}
</script>
复制代码
原理也很简单就是在vNode中拦截on下面的click、input事件作防抖处理,这样在使用上就很是简单了。
咱们来思考一个问题,函数式组件封装防抖的关节是获取vNode,那么咱们经过自定义指令一样能够拿到vNode,甚至还能够获得原生的Dom,这样用自定义指令来处理会更加方便。。。。。。
$attrs or $listeners
cn.vuejs.org/v2/api/#vm-…
函数式组件
cn.vuejs.org/v2/guide/re…
自定义指令
cn.vuejs.org/v2/guide/cu…