-
Notifications
You must be signed in to change notification settings - Fork 3
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源码拆解(一):实现计算属性和侦听器 #10
Labels
Comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
前言
Vue被称为“框架”,而jQuery被称为“库”,区别在于库解决某一类问题,而框架解决了多类问题。
想要深入了解Vue,以读“库”的方式去逐行看源码,很容易迷失在成千上万行代码里。
本文抱着拆出指定功能模块的目的来读源码,以脱离框架的方式实现功能模块。
虽然代码会与源码不同,但模块功能的实现原理是相同的,逻辑纯粹的代码更容易理解其原理。
defineProperty
Vue教程 - 深入响应式原理 - 如何追踪变化一节提到,Vue 遍历对象所有的属性,并使用
Object.defineProperty
把这些属性全部转为getter/setter
。先了解
defineProperty
功能:MDN文档 - defineProperty。defineProperty
的主要能力是定义或修改属性描述符,在Vue源码中有两种用途:定义不可枚举的属性或方法
设置
getter/setter
,在属性被访问和修改时通知变化依赖收集
什么是依赖?
当变化发生的时候,要通知变化,但通知给谁呢?因此,接收通知的单位应当是变化发生前先明确的。
如果通知到所有接收通知的单位,那么在遍历进行通知时,就会有不需要通知的冗余单位,这些冗余单位仍会参与遍历并消耗性能。
性能优化考虑,应当有条件地选择接收通知的单位,这些单位称之为依赖(依赖者、订阅者,变化发生的单位称之为被依赖者、被观察者、发布者)。
如何收集依赖?
是个令人头疼的问题,无论
getter
还是setter
执行时,传入的参数里都不包含这个发生变化的对象属性有哪些依赖。为了解决这个问题,Vue给每个对象添加一个不可枚举的观察者(Observer),存储属于它的依赖。
observe()
Vue通过
observe()
函数为对象添加Observer
:Observer
Observer
类定义如下:在为对象添加观察者的过程中,Vue 遍历了对象所有的属性,使用
defineReactive()
设置getter/setter
,并递归地为嵌套对象添加观察者。defineReactive()
defineReactive
中又定义了一个私有dep
,在getter/setter
中被使用,形成闭包。闭包dep
与Observer
实例的dep
属性相比,两者有什么区别?提前看下
Dep
类中depend()
定义:可见
depend()
不是一个静态方法,而是和实例上下文相关的。defineReactive
设置的setter
中通知变化调用用的是闭包dep
,因而真正与响应式直接相关的是闭包dep
。而Observer
实例的dep
属性,会执行与闭包dep
一样的depend()
行为,但不会通知变化。因此实例上的
dep
是只读的,有用的是闭包dep
。Dep
下面具体看
Dep
类:static
标志的属性和方法是Dep
所有实例共享的,其中target
取值为空或者Watcher
实例,targetStack
、pushTarget
、popTarget
形成对多个watcher
的栈操作。在
Dep
的constructor
中定义的subs
属性,负责存储接收变化通知的订阅者列表,由addSub()
、removeSub()
方法操作列表,notify()
方法遍历列表通知订阅者更新。需要理解的是
depend()
方法。dep.depend()
执行的前提是Dep.target
有真值,而Dep.target
在注释中说明了真值为Watcher
实例,因此执行内容是调用Watcher
实例的addDep()
方法。提前看下
Watcher
类中addDep()
定义:addDep(dep)
去重后调用dep.addSub(this)
,此处this
指向当前Watcher
实例。综上,
dep.depend()
的逻辑可以理解为:“收集依赖”的过程小结:
当Vue通过
defineReactive()
设置对象属性obj.key
的getter
被触发时,如果存在正在执行的watcher
(即Dep.target
有真值),将watcher
加入到defineReactive()
创建的闭包dep.subs
列表和obj.__ob__.dep.subs
中。效果如下:
问题是,什么时候
Dep.target
才有真值,也就是pushTarget()
被传入真值并调用的时候。Watcher
下面具体看
Watcher
类Watcher
实例化需要至少三个参数,用法如下:其中
obj
是被侦听的对象;keyOrFn
是被侦听属性名(侦听器watch使用)或函数(计算属性computed使用),在constructor
中被转换为能够触发被侦听属性的getter
的函数,并赋值给this.getter
;cb
是变化后回调的函数。Dep.pushTarget(this)
在Watcher.get()
中用到,此时Dep.target
有真值。此外,
get()
方法还调用this.getter
触发被侦听属性的getter
,getter
内容即执行dep.depend()
,将当前watcher
实例加入到被侦听对象obj
的依赖列表中,完成依赖收集。watcher.get()
方法在constructor()
、update()
、evaluate()
三处被使用。constructor()
在Watcher
实例化时执行;update()
在dep.notify()
通知变化时执行;evaluate()
尚未提到,是在实现computed
计算属性中使用的。三处的区别与联系都和
this.lazy
相关:lazy
为假Watcher
实例化时立即执行get()
收集依赖,dep.notify()
通知变化时立即执行get()
更新依赖,并调用回调函数cb
lazy
为真Watcher
实例化时不执行get()
,dep.notify()
通知变化时将this.dirty
赋值为真,直到wathcer.evalute()
执行时才会执行get()
收集依赖并执行computed
定义的计算函数。上述
lazy
两种取值,形成了两种数据响应式更新的方式,下面来具体实现这两种方式。侦听器 - $watch
设计
$watch
的使用方式如下:$watch
接收二个参数:obj
watch
实现
$watch()
函数:计算属性 - $compute
设计
$compute
的使用方式如下:$compute
接收二个参数:obj
computed
实现
$compute
函数:至此实现了计算属性和侦听器,可以运行的完整源码。
流程图
$watch
$watch
函数流程$watch
响应更新流程$compute
$compute
函数流程$compute
取值流程总结
The text was updated successfully, but these errors were encountered: