如果你是一名前端从业者,并且在简历上写了会使用vue框架,那么在拿着这份简历去面试的时候,面试官有很大的概率会问你vue的数据双向绑定是如何实现的。
打开goole,输入vue双向绑定,有非常多优秀的博主已经对vue数据双向绑定作了一个全方位的刨析,阅读之后,你会大概了解,双向绑定涉及到javascript的核心api是Object.defineProperty
,通过set
与get
这俩个存取描述符来监听数据的实时改变,并且在对模版作出相应改变。
那么为了更加了解vue是如何实现数据双向绑定的,我花了一下午的时间阅读vue的源码,并将我的对vue实现数据双向绑定的方式理解记录了下来。
打开vue源码目录
这几个文件夹都是分别负责什么的,我们暂且不管(其实是我不知道),我们找到入口文件src/core/index.js
。
看到一大推第一次见并且不熟的代码,谁都会感动头疼。所以我看源码的基本方针是
- 不清楚应用方法的具体实现,先靠他的命名猜一下(所以英文好很关键,哭)。
- 如果有一大堆
if..else-if..else
,先找到按正常流程走的代码,其他分支先放一放…- 不用钻牛角尖,看的懂的代码就好好理解,看不懂的了解个大概足已!看源码的目的是更好的理解框架的实现原理,并不是要把整个框架吃透(
关键也吃不透啊,vue源代码那么多,咱也不是啥大神,难道看不懂去问尤雨溪吗,咱也不敢问讷)
1 | // src/core/index.js |
我就是以上面这种方式来一点点看源码的。根据上面得到的提示,我们应该去看看./instance/index
里写了啥。
1 | // src/core/instance/index |
其他初始化函数我们先不看,从initMixin
这个名字和第一个引入的骄傲位置来说,他应该和我们要找的data
属性有一腿。所以我们打开./init
看一下。
1 | // src/core/instance/init |
从命名上来讲,state
应该是与data
联系更多的,也许是因为在react里,初始化数据就叫作state
吧,所以我们打开./state
找到initState
方法
1 | // src/core/instance/state |
从上面的代码中,我们看到很多脸熟的代码了,并且终于找到我们想找的data
属性,顺水推舟继续往下走吧,找到initData
的方法定义。
1 | function initData (vm: Component) { |
到这里,我们已经很接近实现数据双向绑定的函数了,那就observe
,接下来去../observer/index
里看看,observe
函数到底写了些什么东西。
在export function observe()
的函数里,return出来的是一个名为Observer
的类
1 | // src/core/observer/index.js |
当我们调用new Oberver(value)
的时候,会执行this.walk(value)
这个方法,看方法里的作用应该是,遍历value
,执行defineReactive
方法,而在defineReactive
方法里主要就是通过Object.defineProperty
方法来定义响应式数据。
1 | // src/core/observer/index.js |
省略了部分代码后,我们注意到在get
和set
里分别执行了dep.depend()
和dep.notify()
,而Dep
就是我们常说的订阅发布管理中心,这时候我们来看一张,vue实现数据双向绑定的示例图。
大概解释一下上图,上图实现的设计模式为 订阅-发布 模式。可以从俩个入口分别说起
1.从
init Data
说起,比如我们在vue实例中定义了初始化的data
属性,接着会触发new Observer()
,data
里所有的数据都会通过上面介绍的那样,通过defineReactive
这个方法为每一个属性挂载Object.defineProperty
(也可以说在get
里为每一个属性都添加了一个订阅,在set
里做一个通知订阅者的操作),如果触发了setter
,也就是在业务代码里改变了data
里的值,会通知Watcher
,Wathcer
更新指令系统对应绑定的data
值
2.从编译侧说起,Dom 上通过指令或者双大括号绑定的数据,经过编译以后,会为数据进行添加观察者Watcher
,当实例化Watcher
的时候 会触发属性的getter
方法,此时会调用dep.depend()
,并且会将Watcher
的依赖收集起来。
那么我们可以看一下dep.depend()
和dep.depend()
1 | // src/core/observer/dep.js |
首先我们得先知道注入到Dep
里的一般都是Watcher
类,像Dep.target.addDep(this)
和subs[i].update()
这俩个方法是可以在定义Watcher
的文件下找到的。
1 | // src/core/observer/watcher.js |
一系列操作的主要作用就是让Dep
与Wathcer
建立双向的联系。
代码真是太多了,解释不完,感觉要烂尾了
最后vue有一个很关键的指令解析系统,在src/compiler/directives
文件中可以找到v-bind
,v-on
,v-model
相应的源码。能力有限,看不下去了。越挖越深。
说的我自己都乱了
言简意赅的总结一下,Observer
就是对data
里到所有值进行一个数据劫持,强行给每个数据注入set
(能监听到数据改变,没有return
)与get
(该数据具体呈现出来的值,能return
出数据)方法,Observer
操作完以后,data
可以理解成房子资源。然后Dep
是个订阅器(订阅管理中心,可以理解成房地产中介),Watcher
是订阅者(有钱买房的人),Watcher
把需求和联系方式通过dep.depend()
告诉中介dep
,dep
中介找到了合适的房子通过dep.notify()
打电话通知我们忽悠买房。那Wathcer
没有钱之前就是被绑定在dom上的一些数据,通过了v-model
,v-test
,双大括号等途径赚到了钱(也就是vue的compile编译系统),升级成了一个Wathcer
,赚钱和买房总是无穷无尽的,dom发生了更新(比如input事件),赚到钱了就去问中介dep
有没有房,同时如果房源发生了变化(data发生了更新),中介dep
会通知Wathcer
买房不?