记录--手写$forceUpdate,vm.$destroy方法
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
vm.$forceUpdate
(1)作用
迫使Vue.js实例重新渲染。注意它仅仅影响实例本身以及插入插槽内容的子组件,而不是所有子组件。
(2)实现
只需要执行watcher的update方法,就可以让实例重新渲染。
Vue.js的每一个实例都有一个watcher。当状态发生改变时,会通知到组件级别,然后组件内部使用虚拟DOM进行更详细的重新渲染操作。
事实上,组件就是Vue.js实例,所以组件几倍的watcher和Vue.js实例上的watcher说的是同一个watcher。
手动执行实例watcher的update方法,就可以使Vue.js实例重新渲染。
1 2 3 4 5 6 | Vue.prototype.$forceUpdate = function(){ const vm = this; if(vm._watcher){ vm._watcher.update(); } } |
vm._watcher就是Vue.js实例的watcher,每当组件内依赖的数据发生变化时,都会自动触发Vue.js实例中_watcher的update方法。
重新渲染的实现原理并不难,Vue.js的自动渲染通过变化侦测来侦测数据,即当数据发生变化时,Vue.js实例重新渲染。而vm.$forceUpdate是手动通知Vue.js实例重新渲染。
vm.$destroy
(1)作用
完全销毁一个实例,它会清理该实例与其他实例的连接,并解绑其全部指令及监听器,同时会触发beforeDestory和destroyed的钩子函数。
(2)这个方法并不是很常用,大部分场景下并不需要销毁组件,只需要使用v-if或则v-for等指令以数据驱动的方式控制子组件的生命周期即可。
(3)实现原理
1 2 3 4 5 6 7 8 | Vue.prototype.$destory = function(){ const vm = this; if(vm._isBeingDestroyed){ return; } callHook(vm,"beforeDestroy"); vm._isBeingDestroyed = = true; } |
1、为了防止vm.$destroy被反复执行,先对属性_isBeingDestroyed进行判断,如果它为true,说明Vue.js实例正在被销毁,直接使用return语句退出函数执行逻辑。因为销毁只需要销毁一次即可,不需要反复销毁。
2、然后调用callHook函数触发beforeDestroy的钩子函数(callHook会触发参数中提供的钩子函数)。
(4)销毁实例的逻辑1 首先,需要清理当前组件与父组件之间的连接。组件就是Vue.js实例,所以要清理当前组件与父组件之间的连接,只需要将当前组件实例从父组件实例的$children属性中删除即可。
说明:Vue.js实例的$children属性存储了所有子组件
1 2 3 4 | const parent = vm.$parent; if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){ remove(parent.$children,vm) } |
1、如果当前实例有父级,同时父级没有被销毁且不是抽象组件,那么将自己从父级的子列表中删除,也就是将自己的实例从父级的$children属性中删除。
2、事实上,子组件在不同父组件中是不同的Vue.js实例,所以一个子组件实例的父级只有一个,销毁操作也只需要从父级的子组件列表中销毁当前这个Vue.js实例。
1 2 3 4 5 6 7 8 | export function remove(arr,item){ if(arr.length){ const index = arr.indexOf(item); if(index>-1){ return arr.splice(index,1); } } } |
(5)销毁实例的逻辑2
1、父子组件间的链接断掉之后,需要销毁实例上的所有watcher,也就是说需要将实例上所有的依赖追踪断掉。
2、状态会收集一些依赖,当状态发生改变时会向这些依赖发送通知,而被收集的依赖就是watcher实例。因此,当Vue.js实例被销毁时,应该将实例所监听的状态都取消掉,也就是从状态的依赖列表中将watcher移除。
3、watcher的teardown方法,它的作用是从所有依赖项的Dep列表中将自己移除。即只要执行这个方法,就可以断掉这个watcher所监听的所有状态。
4、断掉Vue.js实例自身的watcher实例监听的所有状态。
1 2 3 | if(vm._watcher){ vm._watcher.teardown(); } |
5、执行了组件自身的watcher实例的teardown方法,从所有依赖项的订阅列表中删除watcher实例。删除之后,当状态发生变化时,watcher实例就不会再得到通知。
6、vm._watcher来源
当执行new Vue()时,会执行一系列初始化操作并渲染组件到实体上,其中就包括vm._watcher的处理
7、从Vue.js2.0开始,变化侦测的粒度调整为中等粒度,它只会发送通知到组件级别,然后组件使用虚拟DOM进行重新渲染。组件其实就是Vue.js实例。
8、怎么通知到组件级别
在Vue.js实例上,有一个watcher,也就是vm._watcher,它会监听这个组件中用到的所有状态,即这个组件内用到的所有状态的依赖列表中都会收集到vm._watcher。当这些状态发生变化时,也都会通知vm._watcher,然后这个watcher再调用虚拟DOM进行重新渲染。
(6)销毁实例的逻辑
1、只从状态的依赖列表中删除Vue.js实例上的watcher实例是不够的。Vue.js提供了vm.watch所创建的watcher实例。
2、从状态的依赖列表中销毁用户创建的watcher实例和销毁Vue实例上的watcher实例相同,只需要执行watcher的teardown方法。
3、问题:如何知道用户创建了多少个watcher?
1)Vue.js的解决方案是执行new Vue()时,在初始化的流程中,在this上添加一个_watchers属性
1 | vm._watchers = []; |
2)每当创建watcher实例时,都会将watcher实例添加到vm._watchers中
1 2 3 4 5 6 | export default class Watcher{ constructor(vm,expOrFn,cb){ <!-- 每当创建watcher实例时,都将watcher实例添加到vm._watchers中 --> vm._watchers.push(this); } } |
4、只需要遍历vm._watchers并依次执行每一项watcher实例的teardown方法,就可以将watcher实例从它所监听的状态的依赖列表中移除。
1 2 3 4 | let i = vm._watchers.length; while(i--){ vm._watchers[i].teardown(); } |
(7)向Vue.js实例添加_isDestroyed属性来表示Vue.js实例已经被销毁。
1 | vm._isDestroyed = true; |
(8)当vm.$destroy执行时,Vue.js不会将已经渲染到页面中的DOM节点移除,但会将模板中的所有指令解绑。
1 | vm._patch_(vm._vnode,null) |
(9)触发destroyed钩子函数
1 | callHook(vm,'destroyed') |
(10)最后,移除实例上的所有事件监听器。
1 | vm.$off() |
(11)完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Vue.prototype.$destory = function(){ const vm = this; <!-- 防止重复销毁 --> if(vm._isBeingDestroyed){ return; } <!-- 调用钩子函数beforeDestroy --> callHook(vm,"beforeDestroy"); vm._isBeingDestroyed = = true; <!-- 删除自己与父级之间的连接 --> const parent = vm.$parent; if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){ remove(parent.$children,vm); } <!-- 从watcher监听的所有状态的依赖列表中移除watcher --> if(vm._watcher){ vm._watcher.teardown(); } let i = vm._watchers.length; <!-- 将从vm.$watcher创建的watcher实例从它所监听的状态的依赖列表中移除 --> while(i--){ vm._watchers[i].teardown(); } <!-- 表示实例已经被销毁 --> vm._isDestroyed = true; <!-- 将模板中的所有指令解绑 --> vm._patch_(vm._vnode,null) <!-- 触发destroyed钩子函数 --> callHook(vm,'destroyed') <!-- 移除实例上的所有事件监听器 --> vm.$off(); } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
2020-11-14 小程序修改原生标题栏返回键的方法