最近看了不少vue原理的文章,在这些文章的帮助下,我也多次尝试自己理解vue的源码。终于,我觉得是时候自己输出一下内容了,希望可以从不同于其他文章的角度带大家熟悉vue。
depvar dep = function dep() { this.id = uid++ this.subs = []}
dep 的含义,自然就是 dependency(也就是依赖,一个计算机领域的名词)。
就像编写 node.js 程序,常会使用 npm 仓库的依赖。在 vue 中,依赖具体指的是响应式处理后的数据。后面会提到,响应式处理的关键函数之一是在很多 vue 原理文章都会提到的 definereactive。
dep 与每个响应式数据绑定后,该响应式数据就会成为一个依赖(名词),下面介绍 watcher 时会提到,响应式数据可能被 watch、computed、在模板中使用 3 种情况依赖(动词)。
subs
dep 对象下有一个 subs 属性,是一个数组,很容易猜出,就是 subscriber(订阅者)列表的意思咯。订阅者可能是 watch 函数、computed 函数、视图更新函数。
watcherwatcher 是 dep 里提到的订阅者(不要和后面的 observer 观察者搞混)。
因为 watcher 的功能在于及时响应 dep 的更新,就像一些 app 的订阅推送,你(watcher)订阅了某些资讯(dep),资讯更新时会提醒你阅读。
deps
与 dep 拥有 subs 属性类似,watcher 对象也有 deps 属性。这样构成了 watcher 和 dep 就是一个多对多的关系,互相记录的原因是当一方被清除的时候可以及时更新相关对象。
watcher 如何产生
上面多次提到的 watch、computed、渲染模板产生 watcher,在 vue 源码里都有简明易懂的体现:
mountcomponent 的 vm._watcher = new watcher(vm, updatecomponent, noop);initcomputed 的 watchers[key] = new watcher(vm, getter || noop, noop, computedwatcheroptions)$watcher 的 var watcher = new watcher(vm, exporfn, cb, options);observerobserver 是观察者,他负责递归地观察(或者说是处理)响应式对象(或数组)。在打印出的实例里,可以注意到响应式的对象都会带着一个 __ob__,这是已经被观察的证明。观察者没有上面的 dep 和 watcher 重要,稍微了解下就可以了。
walk
observer.prototype.walk 是 observer 初始化时递归处理的核心方法,不过此方法用于处理对象,另外还有 observer.prototype.observearray 处理数组。
核心流程按照上面几个概念的关系,如何搭配,该如何实现数据响应式更新?
首先定下我们的目标:自然是在数据更新时,自动刷新视图,显示最新的数据。
这就是上面提到的 dep 和 watcher 的关系,数据是 dep,而 watcher 触发的是页面渲染函数(这是最重要的 watcher)。
但是新问题随之而来,dep 怎么知道有什么 watcher 依赖于他?
vue 采用了一个很有意思的方法:
在运行 watcher 的回调函数前,先记下当前 watcher 是什么(通过 dep.target)
运行回调函数中用到响应式数据,那么必然会调用响应式数据的 getter 函数
在响应式数据的 getter 函数中就能记下当前的 watcher,建立 dep 和 watcher 的关系
之后,在响应式数据更新时,必然会调用响应式数据的 setter 函数
基于之前建立的关系,在 setter 函数中就能触发对应 watcher 的回调函数了
代码上述逻辑就在 definereactive 函数中。这个函数入口不少,这里先讲比较重要的 observe 函数。
在 observe 函数中会 new observer 对象,其中使用 observer.prototype.walk 对对象中的值进行逐个响应式处理,使用的就是 definereactive 函数。
因为 definereactive 函数太重要了,而且也不长,所以直接贴到这边讲比较方便。
function definereactive(obj, key, val, customsetter, shallow) { var dep = new dep() depsarray.push({ dep, obj, key }) var property = object.getownpropertydescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get var setter = property && property.set var childob = !shallow && observe(val) object.defineproperty(obj, key, { enumerable: true, configurable: true, get: function reactivegetter() { var value = getter ? getter.call(obj) : val if (dep.target) { dep.depend() if (childob) { childob.dep.depend() if (array.isarray(value)) { dependarray(value) } } } return value }, set: function reactivesetter(newval) { var value = getter ? getter.call(obj) : val // 后半部分诡异的条件是用于判断新旧值都是 nan 的情况 if (newval === value || (newval !== newval && value !== value)) { return } // customsetter 用于提醒你设置的值可能存在问题 if ('development' !== 'production' && customsetter) { customsetter() } if (setter) { setter.call(obj, newval) } else { val = newval } childob = !shallow && observe(newval) dep.notify() }, })}
首先每个响应式的值都是一个“依赖",所以第一步我们先借闭包的能力给每个值造一个 dep。(到 vue 3 就不需要闭包啦)
接着看核心的三个参数:
obj 当前需要响应式处理的值所在的对象
key 值的 key
val 当前的值
这个值还可能之前就定义了自己的 getter、setter,所以在做 vue 的响应式处理时先处理原本的 getter、setter。
上面在核心流程中提到在 getter 函数会建立 dep 和 watcher 的关系,具体来说依靠的是 dep.depend()。
下面贴一下 dep 和 watcher 互相调用的几个方法:
dep.prototype.depend = function depend() { if (dep.target) { dep.target.adddep(this) }}watcher.prototype.adddep = function adddep(dep) { var id = dep.id if (!this.newdepids.has(id)) { this.newdepids.add(id) this.newdeps.push(dep) if (!this.depids.has(id)) { dep.addsub(this) } }}dep.prototype.addsub = function addsub(sub) { this.subs.push(sub)}
通过这几个函数,可以领略到了 dep 和 watcher 错综复杂的关系……不过看起来迂回,简单来说,其实做的就是上面说的互相添加到多对多列表。
你可以在 dep 的 subs 找到所有订阅同一个 dep 的 watcher,也可以在 watcher 的 deps 找到所有该 watcher 订阅的所有 dep。
但是里面还有一个隐藏问题,就是 dep.target 怎么来呢?先放一放,后会作出解答。先接着看看 setter 函数,其中的关键是 dep.notify()。
dep.prototype.notify = function notify() { // stabilize the subscriber list first var subs = this.subs.slice() for (var i = 0, l = subs.length; i < l; i++) { subs[i].update() }}
不难理解,就是 dep 提醒他的订阅者列表(subs)里的所有人更新,所谓订阅者都是 watcher,subs[i].update() 调用的也就是 watcher.prototype.update。
那么来看一下 watcher 的 update 做了什么——
watcher.prototype.update = function update() { if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queuewatcher(this) }}
在这里我觉得有两个点比较值得展开,所以挖点坑
以上就是vue的响应式原理是什么?的详细内容。