-
Notifications
You must be signed in to change notification settings - Fork 473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vue 2.0 的数据依赖实现原理简析 #17
Comments
作者高能 |
i have nothing to say except NB!!! |
so diffcult |
谢谢~看懂了很多之前没明白的地方。特别是那个Dep.target |
之前一直在看vue vuex vue-router axios的源码,也来分享一发 https://github.com/jimwmg/JiM-Blog/tree/master/VueLesson |
process.env.NODE_ENV !== 'production'这段代码啥意思 |
@guoying2026 判断是否生产模式 |
// 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)
} 在什么情况下才会走if里面,去代理到_props的key,能给举一个例子么。 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Vue版本: 2.3.2
首先让我们从最简单的一个实例
Vue
入手:通过查阅文档,我们可以知道这个
options
可以接受:具体未展开的内容请自行查阅相关文档,接下来让我们来看看传入的
选项/数据
是如何管理数据之间的相互依赖的。我们使用
Vue
这个构造函数去实例化了一个vue
实例app
。传入了props
,data
,watch
,methods
等属性。在实例化的过程中,Vue
提供的构造函数就使用我们传入的options
去完成数据的依赖管理,初始化的过程只有一次,但是在你自己的程序当中,数据的依赖管理的次数不止一次。那
Vue
的构造函数到底是怎么实现的呢?Vue当我们调用
new Vue
的时候,事实上就调用的Vue
原型上的_init
方法.其中在
this._init()
方法中调用initState(vm)
,完成对vm
这个实例的数据的监听,也是本文所要展开说的具体内容。initProps
我们在实例化
app
的时候,在构造函数里面传入的options
中有props
属性:接下来看下
validateProp(key, propsOptions, propsData, vm)
方法内部到底发生了什么。Vue
提供了一个observe
方法,在其内部实例化了一个Observer
类,并返回Observer
的实例。每一个Observer
实例对应记录了props
中这个的default value
的所有依赖(仅限object
类型),这个Observer
实际上就是一个观察者,它维护了一个数组this.subs = []
用以收集相关的subs(订阅者)
(即这个观察者的依赖)。通过将default value
转化为getter/setter
形式,同时添加一个自定义__ob__
属性,这个属性就对应Observer
实例。说起来有点绕,还是让我们看看我们给的
demo
里传入的options
配置:在往上数的第二段代码里面的方法
obervse(value)
,即对{key1: 'a', key2: {a: 'b'}}
进行依赖的管理,同时将这个obj
所有的属性值都转化为getter/setter
形式。此外,Vue
还会将props
属性都代理到vm
实例上,通过vm.a
就可以访问到这个属性。此外,还需要了解下在
Vue
中管理依赖的一个非常重要的类:Dep
在
Vue
的整个生命周期当中,你所定义的响应式的数据上都会绑定一个Dep
实例去管理其依赖。它实际上就是观察者
和订阅者
联系的一个桥梁。刚才谈到了对于依赖的管理,它的核心之一就是观察者
Observer
这个类:walk
方法里面调用defineReactive
方法:通过遍历这个object
的key
,并将对应的value
转化为getter/setter
形式,通过闭包维护一个dep
,在getter
方法当中定义了这个key
是如何进行依赖的收集,在setter
方法中定义了当这个key
对应的值改变后,如何完成相关依赖数据的更新。但是从源码当中,我们却发现当getter
函数被调用的时候并非就一定会完成依赖的收集,其中还有一层判断,就是Dep.target
是否存在。在上文中提到了
Dep
类是链接观察者
和订阅者
的桥梁。同时在Dep
的实现当中还有一个非常重要的属性就是Dep.target
,它事实就上就是一个订阅者,只有当Dep.target
(订阅者)存在的时候,调用属性的getter
函数的时候才能完成依赖的收集工作。那么
Vue
是如何来实现订阅者
的呢?Vue
里面定义了一个类:Watcher
,在Vue
的整个生命周期当中,会有4类地方会实例化Watcher
:Vue
实例化的过程中有watch
选项Vue
实例化的过程中有computed
计算属性选项Vue
原型上有挂载$watch
方法: Vue.prototype.$watch,可以直接通过实例调用this.$watch
方法Vue
生成了render
函数,更新视图时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
:但是这个
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
函数的时候便会重新计算,否则还是使用之前缓存的值。initWatch
initWatch
的过程中其实就是实例化new Watcher
完成观察者的依赖收集的过程,在内部的实现当中是调用了原型上的Vue.prototype.$watch
方法。这个方法也适用于vm
实例,即在vm
实例内部调用this.$watch
方法去实例化watcher
,完成依赖的收集,同时监听expOrFn
的变化。总结:
以上就是在
Vue
实例初始化的过程中实现依赖管理的分析。大致的总结下就是:initState
的过程中,将props
,computed
,data
等属性通过Object.defineProperty
来改造其getter/setter
属性,并为每一个响应式属性实例化一个observer
观察者。这个observer
内部dep
记录了这个响应式属性的所有依赖。setter
函数时,通过dep.notify()
方法去遍历所有的依赖,调用watcher.update()
去完成数据的动态响应。这篇文章主要从初始化的数据层面上分析了
Vue
是如何管理依赖来到达数据的动态响应。下一篇文章来分析下Vue
中模板中的指令和响应式数据是如何关联来实现由数据驱动视图,以及数据是如何响应视图变化的。The text was updated successfully, but these errors were encountered: