本篇文章次要想记录一下自己在学习vue中的响应式原理中遇到的一些问题,如有谬误,请大佬们斧正!
1. Watcher
、Dep
、Observer
别离负责什么?
Observer
负责递归数据及所有子数据,通过Object.defineProperty
为属性定义 getter/setter,监听数据的变动,将数据定义为响应式。但仅仅是把Object.defineProperty封装起来,监听数据的变动,是没有任何作用的,次要的目标是为了收集依赖
。Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; /* 省略 */ if (Dep.target) { // 收集依赖 dep.depend(); } return value }, set: function reactiveSetter(newVal) { /* 省略 */ dep.notify(); // 触发依赖 }});// 总的来说就是,在`getter`中收集依赖,在`setter`中触发依赖。
- 为什么要收集依赖,是为了数据属性发生变化时,告诉应用了该数据的中央。
- 依赖收集在哪里?依赖收集在
Dep
中,Dep用来收集依赖、删除依赖和告诉依赖等。 - 那么依赖是谁?依赖就是
Watcher
。
为什么须要把依赖封装成Watcher类,
《深入浅出vue.js》
给出了很好的解释:当属性发生变化后,咱们要告诉用到数据的中央,而应用这个数据的中央有很多,而且类型还不一样,既有可能是模板,也有可能是用户写的一个watch,这时须要形象出一个能集中处理这些状况的类。而后,咱们在依赖收集阶段只收集这个封装好的类的实例进来,告诉也只告诉它一个。接着,它再负责告诉其余中央。
2. Dep和Watcher的关系?
在我刚开始学习Vue源码的时候,对这里也很困惑:
Dep.target
是干什么的?
Dep.target其实就是以后Watcher实例,是一个全局惟一的动态变量,读取数据的时候会触发这个数据的getter,触发了getter就会执行
if (Dep.target) { dep.depend();}
把本人收集到该数据的Dep中,大抵代码如下:
get () { /*将本身watcher观察者实例设置给Dep.target,用以依赖收集。*/ pushTarget(this) let value const vm = this.vm value = this.getter.call(vm, vm) /*将观察者实例从target栈中取出,置空Dep.target*/ popTarget() return value } /*将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中*/ function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) Dep.target = _target }
- 为什么Dep收集依赖是用Watcher中的addDep办法,并在Watcher中有记录dep的deps数组?
要答复这个问题,让咱们来举个例子,咱们来看一下Vue中的vm.$watch
,
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: Function, options?: Object ): Function { const watcher = new Watcher(vm, expOrFn, cb, options) /*省略*/ return function unwatchFn () { /*将本身从所有依赖收集订阅列表删除*/ watcher.teardown() } }}/* Watcher/teardown大抵代码如下 *//* 将本身从所有依赖收集订阅列表删除 */ function teardown () { let i = this.deps.length while (i--) { this.deps[i].removeSub(this) } this.active = false }
这个办法返回一个unwatchFn
办法用于勾销察看数据,实质就是要把Watcher
实例从以后正在察看数据的依赖列表中移除。
如果通过getter中的依赖收集,仅仅是把Watcher
记录到了该数据的Dep
(依赖列表)中,那么只是在Dep
中晓得了哪些Watcher
在监听这个数据,但在Watcher
中却不晓得本人都订阅了谁(Watcher实例被收集进了哪些Dep中),不晓得本人订阅了谁,那该告诉谁来勾销察看呢?所以咱们须要在Dep和Watcher中都记录一下对方,行成多对多的关系
。
所以咱们在Dep中收集依赖是用Dep.target.addDep(this)
function addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } }}
通过记录newDepIds,newDeps,既在Watcher中记录本人被收集进了哪些Dep中,最初又通过dep.addSub(this)把本人记录到了Dep中,是不是很奇妙呢!
3. __ob__作用是什么?Observer实例上的dep是干什么的?
首先,__ob__属性上保留的就是Observer实例,__ob__的第一个作用是能够用来标记以后value是否曾经被Observer转换成了响应式数据,防止后续反复操作。
其次,Vue对于Obejct
的变动侦测,收集依赖和触发依赖都在一个闭包内能够拜访到dep实例。
function defineReactive () { /*在闭包中定义一个dep对象*/ const dep = new Dep() Object.defineProperty(obj, key, { get: function reactiveGetter () { /*进行依赖收集*/ dep.depend() }, set: function reactiveSetter (newVal) { /*dep对象告诉所有的观察者*/ dep.notify() } })}
但对于Array
,是在getter中收集依赖,在拦截器上触发的依赖
,不相熟的小伙伴请自行看Vue解决数组的代码,大抵代码如下:
[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator () { /*数组新插入的元素须要从新进行observe能力响应式*/ const ob = this.__ob__ /*dep告诉所有注册的观察者进行响应式解决*/ ob.dep.notify() return result })})
咱们能够看到,在拦截器中,没有dep实例给咱们拜访,但因为拦截器是对原型的一种封装,所以能够在拦截器中拜访到以后正在操作的数组(this)
,所以要把__ob__属性设置到每个被侦测的数据上,这里就能够通过this.__ob__.dep获取到dep实例。
最初我想说,学习源码一方面是为了更加纯熟的使用Vue这个框架去开发,但更重要的是了解框架为什么这么去设计,并使用到当前的日常开发中去,更好的晋升本人!
结尾
我是周小羊,一个前端萌新,写文章是为了记录本人日常工作遇到的问题和学习的内容,晋升本人,如果您感觉本文对你有用的话,麻烦点个赞激励一下哟~