-
Notifications
You must be signed in to change notification settings - Fork 128
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
Vue2.x源码解析系列四:数据响应之Observer #25
Comments
我们在获取一个数据的时候,比如 this.msg 并是不直接去 this._data.msg 上取,而是先创建一个watcher,然后通过 watcher.value来取 ------ 这个是针对computed来说的。 data 上面声明的属性并不是取watcher.value, 事实上所有data 声明的属性都会关联初始化声明的那个watcher 用来渲染视图的, watcher.value 是undefined |
@eltonchan 你的说法非常正确,在 |
const getter = property && property.get let childOb = !shallow && observe(val) 大佬 看源码 有疑问 请问 if ((!getter || setter) && arguments.length === 2) { 这个怎么比较细致的理解的?有点困惑 不胜感激!!谢谢 |
有getter没setter说明是只读属性,在get方法中通过getter获取value的值,set中也是一样。至于依赖收集之类的功能也不需要了。具体看这句: 以上是个人的理解。 相关的issue: |
this.msg取的就是data上的值吧? if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
} 这个val就是存的属性的值,至于watch里面的value,只是回调的时候传参的时候用到。 watch: {
msg (newVal, oldVal) {
// oldVal就是watch上保存的value
console.log(newVal, oldVal)
} |
下一章:Vue2.x源码解析系列五:数据响应之Watcher 最后一行的链接失效了。可能是手滑删了?应该是下面这个吧,博主修复一下?
|
感谢指正,已更新 |
如果你之前看过我的这一篇文章 Vue1.0源码解析系列:实现数据响应化 ,那么你可以很轻松看懂 Vue2.x版本中的响应化,因为基本思路以及大部分代码其实都没有变化。当然没看过也没关系,不用去看,因为这里我会讲的非常详细。
数据响应我会分两章来讲,本章讲
Observer
相关,下一章讲Watcher
。从data开始
state 的初始化是从
initState
函数开始的,下面是initState
的完整代码:core/instance/state.js
这里包括了四个部分:
props
,methods
,data
和watch
,为了方便起见,让我们从最简单的,但是也能完整揭示数据响应化原理的data
作为切入点。为什么选它呢,因为props
还涉及到如何从模板中解析,而另外两个其实是函数。让我们先看一下
initData
的完整代码:看起来并不算短,不过我们可以先把开发模式下的一些友好警告给忽略掉,毕竟对我们分析源码来说这些警告不是很重要,其中有三段警告,让我们分别看看:
上面这段的意思是,如果发现
data
竟然不是一个平凡对象,那么就打印一段警告,告诉你必须应该返回一个对象。大的循环体都是在循环
data
上的key
,上面这一段是说,如果发现methods
中有和data
上定义重复的key,那么就打印一个警告。上面这一段是说,如果发现
props
中发现了重复的key
,那么也会打印一段警告。当然上述两种警告都只有在开发模式下才有的。弄懂了这两段警告的意思,让我们把它删了,然后在看看代码变成这样了:是不是简单了很多,我们把上面这段代码拆成三段来分别看看。其中最上面的一段代码是:
首先把
vm.$options.data
取个别名,免得后面这样写太长了,然后判断了它的类型,如果是函数,就通过getData
获取函数的返回值。然后还有一个操作就是把data
放到了this._data
上,至于为什么这么做,下一段代码我们就会明白。这里大家会有另一个疑问了,为什么不是直接调用函数获得返回值,而是需要一个
getData
呢,它除了调用函数肯定还做了别的事,让我们看看getData
的源码:其实它确实是调用了函数,并获得了返回值,除了一段异常处理代码外,他在调用我们的
data
函数前进行了一个pushTarget
操作,而在结束后调用了一个popTarget
操作。我们继续来看这两个函数,他们在 **core/observer/dep.js`中有定义,而且异常简单。虽然看起来代码很简单,就是在一个全局的
Dep.target
中把自己记录了一下,也就是在data
函数调用前记录了一下,然后调用后又恢复了之前的值。这里暂时理解起来会比较困难,因为我们要结合本文后面讲到的内容才能理解。简单的说,在getData
的时候,我们调用pushTarget
却没有传参数,目的是把Dep.target
给清空,这样不会在获取data
初始值的过程中意外的把依赖记录下来。我们再回到
initState
的第二段代码:就是遍历了
data
的key,然后做了一个proxy
,我们来看proxy
的代码:这里target是就是我们的
vm
也就是我们的组件自身,sourceKey
就是_data
,也就是我们的data
,这段代码会把对vm
上的数据读写代理到_data
上去。哈哈,我们这样就明白了一个问题,为什么我们是通过data.msg
定义的数据,却可以通过this.msg
访问呢?原来是这里做了一个代理。到目前为止虽然说了这么多,但是做的事情很简单,除了一些异常处理之外,我们主要做了三件事:
getData
把options中传入的data取出来,这期间做了一些依赖
的处理this._data = data
data
上的key,都在vm
上做一个代理,实际操作的是this._data
这样结束之后,其实vm会变成这样:
弄懂了这个之后我们再看最后一段代码:
observe
是如何工作的?我们来看看他的代码,这是响应式的核心代码。深入 Observer
observer
的定义在core/observer/index.js
中,我们看看 代码:其中有一些很多if的判断,包括对类型的判断,是否之前已经做过监听等。我们暂且抛开这些,把代码精简一下,就只剩下两行了:
可以看到主要逻辑就是创建了一个
Observer
实例,那么我们再看看Observer
的代码:这个类包括构造函数在内,总共有三个函数。
构造函数代码如上,主要做了这么几件事:
这里记录了
value
,dep
,vmCount
, 和__ob__
四个值,其中值得注意的是这两个:this.dep
是 明显是记录依赖的,记录的是对这个value
的依赖,我们在下面马上就能看到怎么记录和使用的__ob__
其实是把自己记录一下,避免重复创建这一段代码会判断
value
的类型,进行递归的observe
,对数组来说,就是对其中每一项都进行递归observe
:显然,直到碰到数组中非数组部分后,最终就会进入
walk
函数,在看walk
函数之前,我们先看看这一段代码:这里我不打算详细讲解每一行,如果你看源码其实很容易看懂。这里的作用就是把 数组上的原生方法进行了一次
劫持
,因此你调用比如push
方法的时候,其实调用的是被劫持
一个方法,而在这个方法内部,Vue会进行notify
操作,因此就知道了你对数组的修改了。不过这个做法没法劫持直接通过下标对数组的修改。好,让我们回到
walk
函数:walk
函数会对每一个key
进行defineReactive
操作,在这个函数内部其实就会调用getter/setter
拦截读写操作,实现响应化。那么这时候可能有人会有一个疑问了,如果某个key
的值也是一个对象呢?难道不能进行深度的依赖么?当然可以的,不过对对象嵌套的递归操作不是在这里进行的,而是在defineReactive
中进行了递归。让我们看看defineReactive
函数:终于看到了传说中的
getter/setter
,上面是完整的代码,有些长,按照惯例我们分别进行讲解。这段代码中,第一步是创建了一个
dep
来收集对当前obj.key
的依赖,这里可能大家又会问:之前new Observer
的时候不是已经创建了吗,这里怎么又创建一次?这是一个深度依赖的问题,为了回答这个问题我们还得先往下看代码。在
dep
之后是获取了getter/setter
,比较简单,我们再往下看:这一段代码非常重要,如果
val
是一个对象,那么我们要递归进行监听。也就是又回到了new Observer
中,可以知道,childOb 返回的是一个observer
实例。有了这个对孩子的监听器之后,当孩子改变的时候我们就能知道了。让我们继续往下看最重要的一段代码getter
:首先,我们自定义的
getter
中,会把需要取出的值拿出来,通过原来的getter
。然后会判断Dep.target
存在就进行一个dep.depend()
操作,并且如果有孩子,也会对孩子进行dep.depend()
操作。dep.depend()
的代码如下:也就是把当前这个
dep
加入到target
中。那么这个
target
就非常重要了,他到底是什么呢?我们在getData
的时候设置过Dep.target
,但当时我们目的是清空,而不是设置一个值。所以这里我们依然不知道target
是什么。代码看到当前位置其实是肯定无法理解target
的作用的,没关系,我们可以带着这个疑问继续往下看。但是这里我简单说明一下,这个target其实是一个
watcher
,我们在获取一个数据的时候,比如this.msg
并是不直接去this._data.msg
上取,而是先创建一个watcher
,然后通过watcher.value
来取,而watcher.value === msg.getter
所以在取值的时候,我们就知道watcher
是依赖于当前的dep
的,而dep.depend()
相当于watcher.deps.push(dep)
。如果你面试的时候被问到
Vue
的原理,那么有一个常见的考点是问你 Vue 是怎么收集依赖的,比如computed
中有如下代码:Vue 是如何知道
info
依赖name
和age
呢?是因为在第一次获取info
的值的时候,会取name
和age
的值,因此就可以在他们的getter
中记录依赖。当然由于我们现在还没有看 Watcher 的代码,所以这一块并不能理解的很透彻,没关系,让我们暂且继续往下看。这里只要记住**Vue
在第一次取值的时候收集依赖 就行了**。再看看
setter
函数,我删除了部分不影响整体逻辑的代码:抛开一些异常情况的处理,主要代码其实做了两件事,第一件事是设置值,不过这里的
setter
是什么呢?其实是我们自定义的setter
,如果我们有自定义,那么就调用我们的setter
,否则就直接设置。然后如果发现我们设置的新值是一个对象,那么就递归监听这个对象。
最后,通过
dep.notify
来通知响应的target
们,我更新啦。还记得上面我们留了一个深度依赖的问题吗?我们举个栗子说明,假设我们的
data
是这样的:我们对
people
进行defineReactive
的时候,我们当然可以处理this.people={}
的操作。但是如果我进行了this.people.name='xx'
的操作的时候要怎么办呢?显然我们此时是无法检测到这个更新的。所以我们会创建对{name:123}
再创建一个childObj
,然后我们的target
也依赖于这个孩子,就能检测到他的更新了。到这里我们就讲完
Observer
了,总结一下,Observer就是通过getter/setter
监听数据读写,在getter
中记录依赖, 在setter
中通知哪些依赖们。让我们把之前的一张图完善下,变成这样:下一章 我们看看 什么是 Watcher
下一章:Vue2.x源码解析系列五:数据响应之Watcher
The text was updated successfully, but these errors were encountered: