带着问题开始

咱们重点阐明,几个问题的源码实现

1、computed 的 月老身份的起源2、computed 怎么计算3、computed 的缓存是怎么做的4、computed 什么时候初始化5、computed 是怎么能够间接应用实例拜访到的

问题不会按程序解析,因为这些问题会相互关联,在摸索源码的过程中,你天然会失去答案

初始化过程

function Vue(){    ... 其余解决    initState(this)    ...解析模板,生成DOM 插入页面}function initState(vm) {        var opts = vm.$options;        if (opts.computed) {         initComputed(vm, opts.computed);     }    .....}

没错,当你调用 Vue 创立实例过程中,会去解决各种选项,其中包含解决 computed

解决 computed 的办法是 initComputed,上面就呈上 源码

initComputedfunction initComputed(vm, computed) {        var watchers = vm._computedWatchers = Object.create(null);        for (var key in computed) {                var userDef = computed[key];                var getter = typeof userDef === 'function' ? userDef: userDef.get;                              // 每个 computed 都创立一个 watcher        // watcher 用来存储计算值,判断是否须要从新计算        watchers[key] =         new Watcher(vm, getter, {              lazy: true         });                        // 判断是否有重名的属性        if (! (key in vm)) {            defineComputed(vm, key, userDef);        }    }}

initComputed 这段代码做了几件事

1、每个 computed 配发 watcher2、defineComputed 解决3、收集所有 computed 的 watcher

好的,这三件事,一件一件说哈

1、每个 computed 配发 watcher

computed 到底和 watcher 有什么猫腻呢?

1、保留 computed 计算函数2、保留计算结果3、管制缓存计算结果是否无效

看下 Watcher 源码构造函数

function Watcher(vm, expOrFn, options) {        this.dirty = this.lazy = options.lazy;        this.getter = expOrFn;        this.value = this.lazy ? undefined: this.get();};

从这段源码中,咱们再看 computed 传了什么参数

new Watcher(vm, getter, { lazy: true })

于是,咱们就晓得了下面说的三个猫腻是怎么回事

1、保留设置的 getter。

把用户设置的 computed-getter,寄存到 watcher.getter 中,用于前面的计算

2、watcher.value 寄存计算结果,然而这里有个条件,因为 lazy 的起因,不会新建实例并马上读取值

这里能够算是 Vue 的一个优化,只有你再读取 computed,再开始计算,而不是初始化就开始计算值了

尽管没有一开始计算,然而计算 value 还是这个 watcher.get 这个办法,来看下源码(已省略局部代码,上面讲其余问题,会更具体展现进去)

这个办法,其实就是执行 保留的 getter 函数,从而失去计算值,灰常简略

Watcher.prototype.get = function() {        // getter 就是 watcher 回调    var value = this.getter.call(vm, vm);        return value};

3、computed 新建 watcher 的时候,传入 lazy

没错,作用是把计算结果缓存起来,而不是每次应用都要从新计算

而这里呢,还把 lazy 赋值给了 dirty,为什么呢?

因为 lazy 示意一种固定形容,不可扭转,示意这个 watcher 须要缓存

而 dirty 示意缓存是否可用,如果为 true,示意缓存脏了,须要从新计算,否则不必

dirty 默认是 false 的,而 lazy 赋值给 dirty,就是给一个初始值,示意 你管制缓存的工作开始了

所以记住,【dirty】 是真正的管制缓存的要害,而 lazy 只是起到一个开启的作用

具体,怎么管制缓存,上面会说

2、defineComputed 解决

请看源码

function defineComputed(    target, key, userDef) {        // 设置 set 为默认值,防止 computed 并没有设置 set    var set = function(){}          //  如果用户设置了set,就应用用户的set    if (userDef.set) set = userDef.set       Object.defineProperty(target, key, {                // 包装get 函数,次要用于判断计算缓存后果是否无效        get:createComputedGetter(key),                set:set    });}

源码曾经被我简短很多,然而意思是不变的

1、应用 Object.defineProperty 在 实例上computed 属性,所以能够间接拜访

2、set 函数默认是空函数,如果用户设置,则应用用户设置

3、createComputedGetter 包装返回 get 函数

重点就在第三点,为什么重要?

两大问题都在这里失去解决,【月老牵线问题+缓存管制问题】

马上呈上 createComputedGetter 源码

function createComputedGetter(key) {        return function() {                // 获取到相应 key 的 computed-watcher        var watcher = this._computedWatchers[key];                // 如果 computed 依赖的数据变动,dirty 会变成true,                    从而从新计算,而后更新缓存值 watcher.value        if (watcher.dirty) {            watcher.evaluate();        }                // 这里是 月老computed 牵线的重点,让单方建设关系        if (Dep.target) {            watcher.depend();        }                return watcher.value    }}

1、缓存管制

上面这段代码作用就是缓存管制,请往下看

if (watcher.dirty) {           watcher.evaluate()}

1、watcher.evaluate 用来从新计算,更新缓存值,并重置 dirty 为false,示意缓存已更新

上面是源码

Watcher.prototype.evaluate = function() {        this.value = this.get();        // 执行完更新函数之后,立刻重置标记位    this.dirty = false;};

2、只有 dirty 为 true 的时候,才会执行 evaluate

所有说通过 管制 dirty 从而管制缓存,然而怎么管制dirty 呢?

先说一个设定,computed数据A 援用了 data数据B,即A 依赖 B,所以B 会收集到 A 的 watcher

当 B 扭转的时候,会告诉 A 进行更新,即调用 A-watcher.update,看下源码

Watcher.prototype.update = function() {        if (this.lazy)  this.dirty = true;    ....还有其余无关操作,已被省略};

当告诉 computed 更新的时候,就只是 把 dirty 设置为 true,从而 读取 comptued 时,便会调用 evalute 从新计算

2、月老牵线

月老牵线的意思,在文言版中也说分明了,这里简略说一下

现有 页面-P,computed- C,data- D

1、P 援用了 C,C 援用了 D

2、实践上 D 扭转时, C 就会扭转,C 则告诉 P 更新。

3、实际上 C 让 D 和 P 建立联系,让 D 扭转时间接告诉 P

没错,就是上面这段代码搞的鬼

if (Dep.target) {   watcher.depend();}

你别看这段代码短啊,波及的内容真不少啊且须要点脑筋的,看源码分分钟绕不过去,真的服尤大怎么写进去的

来看看 watcher.depend 的源码

Watcher.prototype.depend = function() {        var i = this.deps.length;        while (i--) {                // this.deps[i].depend();        //dep就是D 的依赖收集器        dep.addSub(Dep.target)    }};

这段的作用就是!(仍然应用下面的例子 PCD 代号来阐明)

让 D 的依赖收集器收集到 Dep.target,而 Dep.target 以后是什么?

没错,就是 页面 的 watcher!

所以这里,D 就会收集到 页面的 watcher 了,所以就会间接告诉 页面 watcher

看累了吗.....

你会问了,为什么 Dep.target 是 页面 watcher?

你好,这里内容就有点多了有点繁冗了,坐好了兄die

先送你一份源码大礼,收好了

Watcher.prototype.get = function() {        // 扭转 Dep.target    pushTarget()        // getter 就是 watcher 回调    var value = this.getter.call(this.vm, this.vm);        // 复原前一个 watcher    popTarget()        return value};Dep.target = null;var targetStack = [];function pushTarget(_target) {        // 把上一个 Dep.target 缓存起来,便于前面复原    if (Dep.target) {        targetStack.push(Dep.target);    }    Dep.target = _target;//将computed的watch赋值给Dep.target}function popTarget() {    Dep.target = targetStack.pop();//还原成页面watch}

注解几个词

1、页面 watcher.getter 保留 页面更新函数,computed watcher.getter 保留 计算getter

2、watcher.get 用于执行 watcher.getter 并 设置 Dep.target

3、Dep.target 会有缓存

上面开始 月老牵线的 具体流程

1、页面更新,读取 computed 的时候,Dep.target 会设置为 页面 watcher。

2、computed 被读取,createComputedGetter 包装的函数触发,第一次会进行计算

computed-watcher.evaluted 被调用,进而 computed-watcher.get 被调用,Dep.target 被设置为 computed-watcher,旧值 页面 watcher 被缓存起来。

3、computed 计算会读取 data,此时 data 就收集到 computed-watcher

同时 computed-watcher 也会保留到 data 的依赖收集器 dep(用于下一步)。

computed 计算结束,开释Dep.target,并且Dep.target 复原上一个watcher(页面watcher)

4、手动 watcher.depend, 让 data 再收集一次 Dep.target,于是 data 又收集到 复原了的页面watcher

再额定记一个data扭转后续流程

综上,此时 data 的依赖收集器=【computed-watcher,页面watcher】

data 扭转,正序遍历告诉,computed 先更新,页面再更新,所以,页面能力读取到最新的 computed 值

3、收集所有 computed 的 watcher

从源码中,你能够看出为每个computed 新建watcher 之后,会全副收集到一个对象中,并挂到实例上

为什么收集起来,我临时的想法是

为了在 createComputedGetter 获取到对应的 watcher

其实能够通过传递 watcher ,然而这里的做法是传递 key,而后应用key 去找到对应 watcher