Vue 2.0的数据依赖实现原理代码简析
首先让我们从最简单的一个实例Vue入手: 通过查阅文档,我们可以知道这个
具体未展开的内容请自行查阅相关文档,接下来让我们来看看传入的 我们使用Vue这个构造函数去实例化了一个vue实例app。传入了 那Vue的构造函数到底是怎么实现的呢?Vue // 对Vue这个class进行mixin,即在原型上添加方法
// Vue.prototype.* = function () {} initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) 当我们调用new Vue的时候,事实上就调用的Vue原型上的 let startTag,endTag
// a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow,and none of the // internal component options needs special treatment. initInternalComponent(vm,options) } else { // 将传入的这些options选项挂载到vm.$options属性上 vm.$options = mergeOptions( // components/filter/directive resolveConstructorOptions(vm.constructor),// this._init()传入的options options || {},vm ) } / istanbul ignore else / if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 自身的实例 // 接下来所有的操作都是在这个实例上添加方法 initLifecycle(vm) // lifecycle初始化 initEvents(vm) // events初始化 vm._events,主要是提供vm实例上的$on/$emit/$off/$off等方法 initRender(vm) // 初始化渲染函数,在vm上绑定$createElement方法 callHook(vm,'beforeCreate') // 钩子函数的执行,beforeCreate initInjections(vm) // resolve injections before data/props initState(vm) // Observe data添加对data的监听,将data转化为getters/setters initProvide(vm) // resolve provide after data/props callHook(vm,'created') // 钩子函数的执行,created // vm挂载的根元素 其中在 initProps我们在实例化app的时候,在构造函数里面传入的options中有props属性: // 将这个key对应的值转化为getter/setter
defineReactive(props,key,value) // static props are already proxied on the component's prototype // during Vue.extend(). We only need to proxy props defined at // instantiation here. // 如果在vm这个实例上没有key属性,那么就通过proxy转化为proxyGetter/proxySetter,并挂载到vm实例上,可以通过app._props[key]这种形式去访问 if (!(key in vm)) { proxy(vm, _props ,key)} } observerState.shouldConvert = true } 接下来看下 Vue提供了一个observe方法,在其内部实例化了一个Observer类,并返回Observer的实例。每一个Observer实例对应记录了props中这个的default value的所有依赖(仅限object类型),这个Observer实际上就是一个观察者,它维护了一个数组this.subs = []用以收集相关的subs(订阅者)(即这个观察者的依赖)。通过将default value转化为getter/setter形式,同时添加一个自定义__ob__属性,这个属性就对应Observer实例。 说起来有点绕,还是让我们看看我们给的demo里传入的options配置: 在往上数的第二段代码里面的方法 此外,还需要了解下在Vue中管理依赖的一个非常重要的类: Dep 在Vue的整个生命周期当中,你所定义的响应式的数据上都会绑定一个Dep实例去管理其依赖。它实际上就是观察者和订阅者联系的一个桥梁。 刚才谈到了对于依赖的管理,它的核心之一就是观察者Observer这个类: constructor (value: any) {
this.value = value // dep记录了和这个value值的相关依赖 this.dep = new Dep() this.vmCount = 0 // value其实就是vm._data,即在vm._data上添加ob属性 def(value,'ob',this) // 如果是数组 if (Array.isArray(value)) { // 首先判断是否能使用proto属性 const augment = hasProto ? protoAugment : copyAugment augment(value,arrayMethods,arrayKeys) // 遍历数组,并将obj类型的属性改为getter/setter实现 this.observeArray(value) } else { // 遍历obj上的属性,将每个属性改为getter/setter实现 this.walk(value) } } /**
/**
walk方法里面调用defineReactive方法:通过遍历这个object的key,并将对应的value转化为getter/setter形式,通过闭包维护一个dep,在getter方法当中定义了这个key是如何进行依赖的收集,在setter方法中定义了当这个key对应的值改变后,如何完成相关依赖数据的更新。但是从源码当中,我们却发现当getter函数被调用的时候并非就一定会完成依赖的收集,其中还有一层判断,就是Dep.target是否存在。 // 或者属性描述符
const property = Object.getOwnPropertyDescriptor(obj,key) // 如果这个属性是不可配的,即无法更改 if (property && property.configurable === false) { return } // cater for pre-defined getter/setters // 递归去将val转化为getter/setter 在上文中提到了Dep类是链接观察者和订阅者的桥梁。同时在Dep的实现当中还有一个非常重要的属性就是Dep.target,它事实就上就是一个订阅者,只有当Dep.target(订阅者)存在的时候,调用属性的getter函数的时候才能完成依赖的收集工作。 export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target) Dep.target = _target } export function popTarget () { 那么Vue是如何来实现订阅者的呢?Vue里面定义了一个类: Watcher,在Vue的整个生命周期当中,会有4类地方会实例化Watcher:
Watcher接收的参数当中expOrFn定义了用以获取watcher的getter函数。expOrFn可以有2种类型:string或function.若为string类型,首先会通过parsePath方法去对string进行分割(仅支持.号形式的对象访问)。在除了computed选项外,其他几种实例化watcher的方式都是在实例化过程中完成求值及依赖的收集工作:this.value = this.lazy ? undefined : this.get().在Watcher的get方法中: !!!前方高能 一进入get方法,首先进行pushTarget(this)的操作,此时Vue当中Dep.target = 当前这个watcher,接下来进行value = this.getter.call(vm,vm)操作,在这个操作中就完成了依赖的收集工作。还是拿文章一开始的demo来说,在vue实例化的时候传入了watch选项: 在Vue的initState()开始执行后,首先会初始化props的属性为getter/setter函数,然后在进行initWatch初始化的时候,这个时候初始化watcher实例,并调用get()方法,设置Dep.target = 当前这个watcher实例,进而到value = this.getter.call(vm,vm)的操作。在调用this.getter.call(vm,vm)的方法中,便会访问props选项中的a属性即其getter函数。在a属性的getter函数执行过程中,因为Dep.target已经存在,那么就进入了依赖收集的过程: dep是一开始初始化的过程中,这个属性上的dep属性。调用dep.depend()函数: Dep.target也就刚才的那个watcher实例,这里也就相当于调用了watcher实例的addDep方法: watcher.addDep(this),并将dep观察者传入。在addDep方法中完成依赖收集: 这个时候依赖完成了收集,当你去修改a属性的值时,会调用a属性的setter函数,里面会执行dep.notify(),它会遍历所有的订阅者,然后调用订阅者上的update函数。 initData过程和initProps类似,具体可参见源码。 initComputed以上就是在initProps过程中Vue是如何进行依赖收集的,initData的过程和initProps类似,下来再来看看initComputed的过程. 在computed属性初始化的过程当中,会为每个属性实例化一个watcher: function initComputed (vm: Component,computed: Object) {
// 新建_computedWatchers属性 const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { // component-defined computed properties are already defined on the 但是这个watcher在实例化的过程中,由于传入了{lazy: true}的配置选项,那么一开始是不会进行求值与依赖收集的: this.value = this.lazy ? undefined : this.get().在initComputed的过程中,Vue会将computed属性定义到vm实例上,同时将这个属性定义为getter/setter。当你访问computed属性的时候调用getter函数: 在watcher存在的情况下,首先判断watcher.dirty属性,这个属性主要是用于判断这个computed属性是否需要重新求值,因为在上一轮的依赖收集的过程当中,观察者已经将这个watcher添加到依赖数组当中了,如果观察者发生了变化,就会dep.notify(),通知所有的watcher,而对于computed的watcher接收到变化的请求后,会将watcher.dirty = true即表明观察者发生了变化,当再次调用computed属性的getter函数的时候便会重新计算,否则还是使用之前缓存的值。 initWatchinitWatch的过程中其实就是实例化new Watcher完成观察者的依赖收集的过程,在内部的实现当中是调用了原型上的Vue.prototype.$watch方法。这个方法也适用于vm实例,即在vm实例内部调用this.$watch方法去实例化watcher,完成依赖的收集,同时监听expOrFn的变化。 总结:以上就是在Vue实例初始化的过程中实现依赖管理的分析。大致的总结下就是:
这篇文章主要从初始化的数据层面上分析了Vue是如何管理依赖来到达数据的动态响应。下一篇文章来分析下Vue中模板中的指令和响应式数据是如何关联来实现由数据驱动视图,以及数据是如何响应视图变化的。 感谢阅读,希望能帮助到大家,谢谢大家对本站的支持! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |