Vue的响应原理:Observe、Watcher、Dep关系
Opened this issue · 0 comments
前面两篇讲述了Vue运行时---第一步扩展实例方法(点击链接)和Vue运行时---第二步扩展静态方法(点击链接),今天第三节解读## Vue中双向数据绑定的原理。
我们先回到’instance/index’文件中,如下图
我们第一节已经说到 initMixin(Vue)只做了一件事,就是给Vue扩展了实例方法_init
定义了data和template,我们这节就要搞清楚data值变化和template生成的html是如何相应变化的。如图一,new Vue之后,进入function Vue构造函数,执行this._init(options),这节其实也就是主要要搞清楚_init 方法,我们现在看到图二中_init 方法,
1、 我们先看到第一个红框处,mergeOptions是方法是用来合并options参数的,第一句resolveConstructorOptios(vm.constructor)将得到第二节中说道buildInComponents默认参数,并用传入的options取代默认参数。
2、 initLifecycle(vm)
我们找到’instance/lifeCycle’文件如下:
我们可以看到是初始化工作,第一句options.parent在buildInComponent中没有,如果自己传递了那么vm.$parent就是你传递额parent参数。如果没有传递那么vm.$parent就是null而且vm.$vm就是vm本身。接着又设置vm.$refs ={},vm._watch = null,vm._inactive = null,vm._directInactive = false,vm._isMounted = false,vm._isDestroyed = false,vm._isBeingDestroyed = false。
3、 initEvents(vm)、initRender(vm)、initInjections(vm)我就不一一写了,都是初始化,我们主要说initState(vm)
4、 initState(vm)这里面就是定义了Observe、Watcher、Dep的关系,进入看到initState(vm)函数内容
我主要讲解initData(vm)这个方法,其他initiativeProps、initMethods、iniComputed、initWatch后续再说。进入initData(vm)函数,如下图
看到observe(data,true)这一句,这里的data还是之前的的
进入observe方法后,如下图
这里出现了new observer(value)其实就是要把data传入了new Observer中。进入Observer中的构造函数,如下图
把data数据又传入了this.walk方法中,如下图
这里data对象{a:1,b:2}被取出a、b属性和各自值分别执行了
进入defineDeactive方法后,如下图出现最重要部分了。这也是为什么Vue不支持IE8的原因了,因为IE8中不支持Object.defineProperty
1、 先实例化 var dep = new Dep();这里使用了闭包,因为同一个data属性会对应到html中该data属性。
2、 取得data[‘a’]的属性描述,判断该属性是否能配置。这里先插入一句对象属性的描述规则,比如person对象的name属性是这样定义的。如下:
3、 接着取出原声getter和setter方法,我们先看第二个红框set方法当我们写{a:1}的时候,会自动自定元素的set方法,其中set方法的过程为:
一、 取出现有值,比较现有值和跟新值是否一样,
二、 先调用setter.call(obj.newVal)将obj的属性a修改为值1
三、 如果新值是一个负责类型递归执行observe(newVal)
四、 调用dep.notify方法,订阅-发布模型,通知所有元素更新操作
4、 进入dep.notify内容,看定于-发布模型的具体内容如下:
取出属性a对应的所有Watcher对象数组,循环所有对象挨个出发update函数,其实subs是一个Watcher实例数组。我们可以看Dep类的内容。
现在我们讲述了set方法的内容以及触发订阅者更新。那么subs的watcher监听又是哪里来的呢,这就要回到上上上图的get方法了,我们新截一张get方法图片如下,方便说明
说明get前,我们先要简单说明一下template编译为javascript函数的结果,我们回到举例
这里的template是一个字符串,会编译为javascript函数,过程我们后面会用一节来讲解,我们就只说这个例子中template编译后的结果。如下:
如果这就是将template字符串编译为javascript函数的过程,如果你对_c、_v、_s函数有疑问,你可以回忆第一节扩展实例方法的最后一个图renderMixin中添加的实例方法。
好了,现在tempate已经转换为javascript函数了,转换过程我以后章节再详细说明,第一句with(this)就是with(vm),那么其中的a、b、a出现就是wm.a和wm.b和wm.a了,这样就会触发data函数中的a、b属性的get方法了,当然这里是做了代理的,wm.a等同于wm.data.a,这样我们编译模版后,就会自动触发属性的get方法,我们再回到get方法中如下:
比如第一个wm.a进入get方法后,dep.depend方法执行,如下
这个this就是dep实例,而Dep.target是watcher,所以每次执行vm.a都会将一个dep实例压入watcher中,即订阅目的,而在修改wm.a值时,又会循环所有watcher通知所有元素更新,即发布目的。这就是Watcher、Dep的关系。
总结:
1、 每个data中属性元素会有一个闭包Dep的实例,里面存放Watcher实例
2、 当template编译为render函数时,会调用属性的get方法,将每一次调用都生成一个watcher实例,并压入dep中,产生订阅效果。
3、 当设置data中属性值的时候,调用属性的set方法通知所有订阅该属性的元素更新
4、 而且props,computed,watch内同理是增加watcher产生订阅和发布的效果。