持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情
入口
关于响应式原理源码阅读的入口位置是在初始化阶段,处理数据响应式这一步,即调用 initState 方法
/src/core/instance/init.ts
initState方法是对props,methods,data,computed,watch进行初始化,包括响应式的处理。其中优先级为:props > methods > data > computed
/src/core/instance/state.ts
(本节主要看data的响应式处理)我们可以看到,如判断有options里有data,则调用initData初始化data
初始化data函数,获取options中的data,如果为函数,则执行函数,否则则直接赋值给vm上的data。接着就为data上的数据进行代理,这一步的好处是原本需要通过this.data.name访问,现在直接使用this.name访问即可。最后就是重点:调取observe,对data里的数据进行响应式处理。这里就是vue响应式的真正入口了。
真正入口 - observe、observer
/src/core/observer/index.ts
在这里,我们可以看非对象和vnode是不做响应式处理的。接着判断value是否存在_ob_属性,该属性表示我们的对象是否做过响应式处理了,如果存在,则直接返回_ob_属性。否则,创建观察者实例。接着,我们来看这个观察者类
可以看到,数组和对象是两种不同的处理方式 如果是对象,则递归遍历所有的key,对每一个属性调取defineReactive进行数据劫持,而数组则是通过操做数组原型,覆盖数组默认的七个原型方法,实现数组的响应式。因为对象的属性比较少,而数组则有可能有成千上万个元素,那如果同对象一样每个都做劫持,则会消耗太多性能。具体关于arrayMethods的代码在下篇文章中数组的响应式会讲到。
其中,可以看到58-70行是考虑到有些浏览器不支持_proto_属性,因此需要判断一下对象上是否存在_proto_属性,做一些兼容处理。
接下来就着重看vue是如何对数组和对象进行响应式处理的。
响应式的底层原理 - defineReactive
实例化了dep,给每个数据添加一个观察者。递归调用,保证对象中的所有key都会被观察。
通过get方法拦截数据的读取操作
依赖收集,在dep中添加watcher,也在wacher中添加dep。
其中childOb表示对象嵌套对象的规则者对象,如果存在也需要对其进行依赖收集。而如果是数组则触发数组的响应式。
通过set方法拦截数据的设置操作
如果新旧值一样则直接return。
如果不一致,则对新值进行观察,触发dep.notify,通知watcher的update方法,进行视图更新。
因为前面的递归阶段无法为数组中的对象元素添加依赖,因此需要遍历每个数组元素,递归处理数组项为对象的情况,为其添加依赖
总结
vue在init初始化,接受options传递进来的参数,递归调用defineReactive使用object.defineProperty对data进行get和set数据劫持,当访问响应式属性时触发get方法,执行dep.depend进行依赖收集,在响应式属性发生改变会触发set方法进行派发更新,执行dep.notify通知所有与属性相关的watch进行视图更新。
那么这还是其主要的思想原理,具体关于dep实例和watcher内部究竟如何进行依赖收集和派发更新,可以参考下一篇文章。