0
点赞
收藏
分享

微信扫一扫

如何理解vue的computed?

这道考察computed属性的题蛮有意思的。

不仅仅考察了computed,而且还考察了vue的依赖收集以及脏检查。

computed : {
foo() {
if(this.a>0){ return this.a}
else { return this.b + this.c }
}
}
data() {
a: 1,
b: 1,
c: 1,
}

众所周知,首次a,b,c均为1时,foo()返回值为1。 以foo()返回值为1作为起始态,独立的执行下面以下3个操作,vue会如何计算foo呢?

  • 如果此时this.a = 0,foo()如何计算?
  • 如果此时this.b = 2,foo()如何计算?
  • 如果a的初始值为-1,执行this.a = 1,foo()如何计算?

目录

  • 执行表现
  • 源码分析
  • createComputedGetter
  • 关键的watcher.js
  • 关键的dep.js
  • 基于源码分析拆解执行表现
  • 初始化a,b,c均为1时,foo()如何计算?
  • 如果此时this.a = 0,foo()如何计算?
  • 如果此时this.b = 2,foo()如何计算?
  • 如果a的初始值为-1,执行this.a = 1,foo()如何计算?
  • 一句话总结

执行表现

  • 如果此时this.a = 0,foo()如何计算?
  • 如果此时this.b = 2,foo()如何计算?
  • 如果a的初始值为-1,执行this.a = 1,foo()如何计算?

如果此时this.a = 0,foo()如何计算?

foo()的返回值会从this.a变为this.b+this.c,2。 vue会重新执行一遍eval()如何计算?

foo()的返回值仍旧为this.a,1。 vue会跳过eval()如何计算?

foo()的返回值会从this.b+this.c变为this.a。 vue会重新执行一遍evaluate,得到返回值this.a。

为什么会是这样的呢?是否执行evaluate的条件是什么? 为什么a的初始值为-1了也可以重新evaluate?

源码分析

对于this.b = 2,vue跳过evaluate阶段,直接得到返回值this.a,是如何优化的呢? 下面我们来看源码: 源码地址:​​state.js​​ computed相关的有三个非常重要的函数:

  • createComputedGetter(脏检查,依赖收集)
  • 关键的watcher.js(Watcher)
  • 关键的dep.js(依赖)

先来看看最最核心的代码

// 脏检查, 执行计算
if (watcher.dirty) {
watcher.eval()
}
// Dep更新依赖
if (Dep.target) {
watcher.depend()
}

下面再看具体的源码

createComputedGetter

  • 脏检查, 重新计算 对__computedWatcher中的具体的属性做检查
  • Dep更新依赖

function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 脏检查, 执行计算
if (watcher.dirty) {
watcher.eval()
}
// Dep更新依赖
if (Dep.target) {
watcher.depend()
}
return

关键的watcher.js

  • get 收集依赖
  • udpate 更新dirty为true,从而可以重新eval(232, 232, 232); background: rgb(249, 249, 249);">

    export default class Watcher{
    lazy: boolean;
    dirty: boolean;
    constructor (
    ) {
    this.dirty = this.lazy // for lazy watchers,dirty用于懒监听
    this.value = this.lazy? undefined: this.get() // Dep的target设置为foo watcher
    }
    // Evaluate the getter, and re-collect dependencies.
    get () {
    pushTarget(this)
    value = this.getter.call(vm, vm)
    return value;
    }
    update () {
    if (this.lazy) {
    this.dirty = true
    }
    }
    eval() {
    this.value = this.get()
    this.dirty = false
    }
    depend () {
    let i = this.deps.length
    while (i--) {
    this.deps[i].depend()
    }
    }
    addDep (dep: Dep) {
    const id = dep.id
    if (!this.depIds.has(id)) {
    dep.addSub(this)
    }
    }
    }

    关键的dep.js

    • addSub:添加订阅
    • depend: 添加依赖
    • notify:通知更新

    export default class Dep {
    constructor () {
    this.subs = []
    }
    addSub (sub: Watcher) {
    this.subs.push(sub)
    }
    depend () {
    if (Dep.target) {
    Dep.target.addDep(this)
    }
    }
    notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
    }
    }
    }
    Dep.target

    基于源码分析拆解执行表现

    • 初始化a,b,c均为1时,foo()如何计算?
    • 如果此时this.a = 0,foo()如何计算?
    • 如果此时this.b = 2,foo()如何计算?
    • 如果a的初始值为-1,执行this.a = 1,foo()如何计算?

    computed : {
    foo() {
    if(this.a>0){ return this.a}
    else { return this.b + this.c }
    }
    }
    data() {
    a: 1,
    b: 1,
    c: 1,
    }
    created(){ this.b = 2; }

    初始化a,b,c均为1时,foo()如何计算?

    • 初始化watcher
    • 创建getter得到value并将dirty置为false
    • watcher帮助dep收集依赖,收集的是this.a
    • 依赖收集图
    初始化watcher

    _computedWatchers:{
    foo: Watcher {(vm, getter, null, { lazy: true })}
    }
    // watcher
    Watcher: { lazy: true, dirty: true, value: undefined, deps:[] }

    创建getter得到value并将dirty置为false

    // Watcher: { lazy: true, dirty: true, value: undefined }
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
    // 脏检查, 执行计算
    if (watcher.dirty) {
    watcher.eval() // 得到value,dirty置为false
    }
    // 返回this.a 1
    return watcher.value
    }

    // watcher.eval() 拆解
    eval() {
    // 从foo的getter get()得到value:this.a 1
    this.value = this.get()
    // 将dirty变为false
    this.dirty = false

    执行完毕后,结果为​​Watcher { lazy: true, dirty: false, value: this.a, deps: [Dep a]}​

    watcher帮助dep收集依赖

    if (watcher) {
    // Dep更新依赖
    if (Dep.target) {
    watcher.depend()
    }
    }
    // watcher.depend() 拆解
    depend () {
    let i = this.deps.length
    while (i--) {
    this.deps[i].depend()
    }
    }
    // dep.depend()拆解
    depend () {
    if (Dep.target) {
    Dep.target.addDep(this)
    }
    }
    // watcher.addDep拆解
    addDep (dep: Dep) {
    const id = dep.id
    if (!this.depIds.has(id)) {
    dep.addSub(this)
    }
    }
    // dep.addSub()拆解
    addSub (sub: Watcher) {
    this.subs.push(sub)
    }

    最终结果为: 计算属性foo仅仅收集了this.a作为dep。没有收集b和c。 ​​Watcher { lazy: true, dirty: false, value: this.a , deps: [Dep a]}​

    依赖收集图(dirty为false)

    ​deps: [Dep a(1)]​

    如何理解vue的computed?_初始化

    如果此时this.a = 0,foo()如何计算?

    • 执行computedGetter触发watcher.eval()
    • 在收集了a的基础上,再次收集到b和c的依赖
    • 依赖收集图

    当我们执行this.a = 0时,a的setter发出依赖更新,getter执行更新,dirty由false变为true。 由于dirty为true,所以执行eval()的返回值this.b+this.c。

    // 发出依赖更新
    notify () {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
    }
    }
    // dirty由false变为true
    update () {
    if (this.lazy) {
    this.dirty = true
    }
    }
    return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
    // 脏检查, 执行计算
    if (watcher.dirty) {
    // 执行eval()
    }
    // Dep更新依赖
    if (Dep.target) {
    watcher.depend()
    }
    return watcher.value
    }
    }
    eval() {
    this.value = this.get()
    this.dirty = false
    }
    get () {
    pushTarget(this)
    value = this.getter.call(vm, vm)
    return value;
    }
    // 收集b和c的依赖
    if

    最终收集到的依赖为​​Watcher: { lazy: true, dirty: true, value: this.b+this.c , deps: [Dep a, Dep b, Dep c]}​

    依赖收集图(dirty为true)

    ​deps: [Dep a(1), Dep b(2), Dep c(2)]​

    如何理解vue的computed?_Vue.js_02

    如果此时this.b = 2,foo()如何计算?

    • 执行computedGetter不会触发watcher.eval()
    • 依赖收集图
    执行computedGetter不会触发watcher.eval()

    为什么执行computedGetter不会触发watcher.eval()?

    因为仅收集了this.a的依赖 当我们执行this.b = 2时,b的setter发出依赖更新,getter执行更新。 但是,由于我们初始化的条件仅仅将this.a作为计算属性foo的依赖,所以不会有任何变化。

    // Watcher { lazy: true, dirty: false, value: this.a,deps: [Dep a(1) }
    return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
    // 此时watcher的dirty为false
    if (watcher.dirty) {
    watcher.eval()
    }
    // 返回this.a的值 1
    return

    依赖收集图(dirty为false)

    ​deps: [Dep a(1)]​

    如何理解vue的computed?_初始化

    如果a的初始值为-1,执行this.a = 1,foo()如何计算?

    • 每次get都会被收集到依赖中,收集a,b,c依赖到deps
    • 由于this.a的依赖被收集到,因此可以直接通过this.a = 0触发更新
    • 依赖收集图

    源码get()的注释:

    Evaluate the getter, and re-collect dependencies.

    computed : {
    foo() {
    // a的get()触发,收集到deps
    if(this.a>0){ return this.a}
    // b和c的get()触发,收集到deps
    else { return this.b + this.c }
    }
    }
    data() {
    a: -1,
    b: 1,
    c: 1,
    }

    如何收集的?

    get () {
    pushTarget(this)
    value = this.getter.call(vm, vm)
    }

    此时再触发this.a=1,由于this.a的依赖被收集到,因此可以直接触发更新。 最终返回1。

    依赖收集图(dirty为true)

    ​deps: [Dep a(1), Dep a(2), Dep a(2)]​

    如何理解vue的computed?_Vue.js_02

    一句话总结

    一个computed属性中,每个类似this.foo的调用,都会在get()中重新收集依赖。当依赖收集大于一次(不是一个)时,视为脏(dirty)计算属性,需要重新 eval(238, 238, 238); opacity: 0.6;">

      • 微信公众号: 生活在浏览器里的我们
    举报

    相关推荐

    0 条评论