通过图带你深入了解vue的响应式原理
前端  /  管理员 发布于 4年前   304
前言
如果自己去实现数据驱动的模式,如何解决一下几个问题:
数据劫持――obvserver
我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:
Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {// ...},set: function reactiveSetter (newVal) {// ...}})
通过Object.defineProperty这个方法,我们可以在数据发生改变或者获取的时候,插入一些自定义操作。同理,vue也是在这个方法中做依赖收集和派发更新的。
绑定和更新视图――watcher
从初始化开始,我们渲染视图的时候,便会生成一个watcher,他是监视视图中参数变化以及更新视图的。代码如下:
// 在mount的生命钩子中new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)
当然,我们可以保留疑问:
具体的绑定和更新的流程,我们到后续的依赖收集中讲解。
我们先来讲讲响应式系统中涉及到的设计模式。
发布订阅模式
在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件
以此避免发布者和订阅者之间产生依赖关系
vue的响应式流程
vue的响应式系统借鉴了数据劫持和发布订阅模式。
Vue用Dep作为一个中间者,解藕了Observer和Watcher之间的关系,使得两者的职能更加明确。
那具体是如何来完成依赖收集和订阅更新的呢?
依赖收集过程
依赖收集的流程
举个例子
{{ message }}{{ message1 }}改变messagevar app = new Vue({el: '#app',data: {message: '1',message1: '2',},methods: {changeMessage() {this.message = '2'}},watch: {message: function(val) {this.message1 = val}}})依赖收集流程图:
如何看懂这个依赖收集流程?关键在watcher代码中:
get () {pushTarget(this)let valueconst vm = this.vmtry {value = this.getter.call(vm, vm)} catch (e) {// 省略} finally {if (this.deep) {traverse(value)}popTarget()this.cleanupDeps()}return value}调用的这个this.getter有两种,一种是key值的getter方法,还有一种是expOrFn,比如mounted中传入的updateComponent。
如何防止重复收集
我们不妨想想什么才算是重复收集了?
笔者想到一种情况:就是dep数组中,出现了多个一样的watcher。
比如renderWatch就容易被重复收集,因为我们在html模版中,会重复使用data中的某个变量。那他是如何去重的呢?
1、只有watch在执行get时,触发的取数操作,才会被收集
Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : valif (Dep.target) {dep.depend()// ...}return value},set: function reactiveSetter (newVal) {// ...dep.notify()}})当只有Dep.target这个存在的时候才进行依赖收集。Dep.target这个值只有在watcher执行get方法的时候才会存在。
2、在dep.depend的时候会判断watch的id
depend () {if (Dep.target) {Dep.target.addDep(this)}}addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {this.newDepIds.add(id)this.newDeps.push(dep)if (!this.depIds.has(id)) {dep.addSub(this)}}}我们会发现,在depend过程中,会有一个newDepIds去记录已经存入的dep的id,当一个watcher已经被该dep存过时,便不再会进行依赖收集操作。
派发更新过程
收集流程讲完了,不妨在听听更新流程。
订阅更新的流程
老例子
{{ message }}{{ message1 }}改变messagevar app = new Vue({el: '#app',data: {message: '1',message1: '2',},methods: {changeMessage() {this.message = '3'}},watch: {message: function(val) {this.message1 = val}}})依赖收集的最终结果:
当触发click事件的时候,便会触发订阅更新流程。
订阅更新流程图:
当renderWatch执行更新的时候,回去调用beforeUpdate生命钩子,然后执行patch方法,进行视图的变更。
如何防止重复更新
如何去防止重复更新呢?renderWatch会被很多dep进行收集,如果视图多次渲染,会造成性能问题。
其实问题的关在在于――queueWatcher
在queueWatcher中有两个操作:去重和异步更新。
function queueWatcher (watcher) {const id = watcher.idif (has[id] == null) {has[id] = truequeue.push(watcher)// ...if (!waiting) {waiting = true// ...nextTick(flushSchedulerQueue)}}}其实queueWatcher很简单,将所有watch收集到一个数组当中,然后去重。
这样至少可以避免renderWatch频繁更新。
比如上述例子中的,message和message1都有一个renderWatch,但是只会执行一次。
异步更新也可以保证当一个事件结束之后,才会触发视图层的更新,也能防止renderWatch重复更新
结尾
文章讲述了响应式流程的原因,代码细节并未深入,
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
您可能感兴趣的文章:
- Vue响应式原理Observer、Dep、Watcher理解
- 浅谈Vue的响应式原理
- vue.js响应式原理解析与实现
- Vue实现双向绑定的原理以及响应式数据的方法
- 浅谈Vue 数据响应式原理
- 代码详解Vuejs响应式原理
上一条:
10种JavaScript最常见的错误(小结)
下一条:
简单了解JavaScript中常见的反模式Top
- 相关文章
- 使用 Alpine.js 排序插件对元素进行排序(0个评论)
- 在js中使用jszip + file-saver实现批量下载OSS文件功能示例(0个评论)
- 在vue中实现父页面按钮显示子组件中的el-dialog效果(0个评论)
- 使用mock-server实现模拟接口对接流程步骤(0个评论)
- vue项目打包程序实现把项目打包成一个exe可执行程序(0个评论)
- 近期文章
- 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
- 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
- gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
- 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
- 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
- Laravel从Accel获得5700万美元A轮融资(0个评论)
- 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
- 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
- PHP 8.4 Alpha 1现已发布!(0个评论)
- Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
- 近期评论
122 在
学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..123 在
Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..原梓番博客 在
在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..博主 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..1111 在
佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
Copyright·© 2019 侯体宗版权所有·
粤ICP备20027696号