vue2源码浅读(四):computed和watch的原理与实现

vue2源码浅读(四):computed和watch的原理与实现

computed的原理与实现

  • 在初始化initState方法中,如果用户传入的配置有computed,调用initComputed方法。
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // 服务端渲染处理方法
  const isSSR = isServerRendering()
  //遍历 computed 
  for (const key in computed) {
    const userDef = computed[key]
    // computed有两种写法,直接是function的return或者get()的return
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // 非服务端渲染
    if (!isSSR) {
      // 为计算属性创建内部watcher
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }
	// key不能在vm实例上
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } 
  }
}
  • 为key创建watcher后,调用defineComputed方法,代理到vm实例上。如下:
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}
function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  //传入的是方法
  if (typeof userDef === 'function') {
  // 不是服务端渲染,调用createComputedGetter
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
  // 传入的是get()和set()
  // 不是服务端渲染,调用createComputedGetter
  // 注意,如果computed中某个key不需要缓存,可将cache设置为false
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  // 代理到vm实例上
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
	// 模板上访问计算属性
  return function computedGetter () {
  	// 取出创建的 computedWatchers
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
      //渲染的时候会进入这里
        watcher.evaluate()
      }
      // 此时渲染还未结束,computed 函数内部的变量收集渲染 watcher
      if (Dep.target) {
        watcher.depend()
      }
      //计算好的参数返回给用户
      return watcher.value
    }
  }
}
class Watcher {
    constructor(vm,expOrFn,cb,options) {
        this.vm = vm
        this.expOrFn = expOrFn //传入的 computed 方法
        this.cb = cb
        if (options) {
            this.lazy = !!options.lazy // true 默认不执行 这是一个 computed
        }
        this.dirty = this.lazy// computed 看是否需要从新求值
        this.deps = [];
        this.set = {}
        this.id = id++
        if (typeof expOrFn === 'function') {
            this.getters = expOrFn
        }
        //留住value
        this.value = this.lazy? undefined : this.get();
    }
    get() {
        //标记 computed target
        pushTarget(this)
        // 调用 computed 函数得到计算的值
        let value = this.getters.call(this.vm, this.vm);
        //弹出 target 防止data上每个属性都产生依赖,只有页面上使用的变量需要依赖
        popTarget()
        return value
    }
    run() {
        let newValue = this.get()
    }
    addDep(dep) {
        let id = dep.id
        //去重防止dep添加watch多次
        if (!this.set[id]) {
            //watcher添加dep
            this.deps.push(dep)
            //给dep添加watch
            dep.addSub(this)
            this.set[id] = true;
        }
    }
    upDate() {
    	//修改属性计算属性依赖的变量重置 dirty 
        if (this.lazy) {
            this.dirty = true
        }
    }
    evaluate() {
    	//当走到这里时,页面正在渲染中 Dep.target, 已经有一个渲染 watcher 了
        this.value = this.get();
        //修改了计算属性里面脏值
        this.dirty = false
    }
    depend () {
    	//给 computed 函数内部的属性添加渲染 watcher
        let i = this.deps.length
        while (i--) {
            this.deps[i].depend()
        }
    }
}

watch的原理与实现

  • 在初始化initState方法中,如果用户传入的配置有watch,调用initWatch方法。
function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    // 监听的是数组
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
       // 为数组中每个元素创建watcher
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
// 如果是对象
  if (isPlainObject(handler)) {
    options = handler
    // 如果是: test:{handler(old,new){}}
    handler = handler.handler
  }
// 监听a.b.c
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object 
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    // 标识这个是用户 watch
    options.user = true
    // 同样,创建内部watcher
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 立即监听
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
 
class Watcher {
    constructor(vm,expOrFn,cb,options) {
        this.vm = vm
        this.expOrFn = expOrFn //监听的属性 如:a.b.c
        this.cb = cb // watch 回调
        if (options) {
            this.user = !!options.user //这是个 watch
            this.deep = !!options.deep //深度监听
        }
        this.deps = [];
        this.set = {}
        this.id = id++
        function parsePath(path) {
		    path = path.split('.')
		    return function (obj) {
		        path.forEach((key) => {
		            obj = obj[key]
		        })
		        return obj
		    }
		}
        if (typeof expOrFn === 'function') {
            this.getters = expOrFn
        } else {
        	//访问监听的变量 如:a.b.c.d
            this.getters = parsePath(this.expOrFn)
        }
        //留住 value
        this.value = this.get();
    }
    get() {
        //标记target
        pushTarget(this)
        //访问监听的属性
        let value = this.getters.call(this.vm, this.vm);
        if (this.deep) {
            traverse(value)
        }
        //弹出target防止data上每个属性都产生依赖,只有页面上使用的变量需要依赖
        popTarget()
        return value
    }
    run() {
        let newValue = this.get()
        //取出旧值
        const oldValue = this.value
        //留住新值
        this.value = newValue
        //用户自己传入的watch
        if (this.user) {
        	//这里的 cb 就是传入的 watch 回调函数
             this.cb.call(this.vm, newValue, oldValue)
        }
    }
    addDep(dep) {
        let id = dep.id
        //去重防止dep添加watch多次
        if (!this.set[id]) {
            //watcher添加dep
            this.deps.push(dep)
            //给dep添加watch
            dep.addSub(this)
            this.set[id] = true;
        }
    }
    upDate() {
        this.run()
    }
    // 从所有依赖项的订阅者列表中删除自身
    teardown() {
  		if (this.active) {
			// vm实例的watch列表中移除,这是一个开销较大的操作,所以如果vm实例正在被销毁,就跳过
		    if (!this.vm._isBeingDestroyed) {
		      remove(this.vm._watchers, this)
		    }
		    let i = this.deps.length
		    while (i--) {
		      this.deps[i].removeSub(this)
		    }
		    this.active = false
	}
}

//深度访问对象内部每一个值
function traverse(val) {
  let isA = Array.isArray(val);
  if (!isA) {
    let key = Object.keys(val);
    let i = key.length
    while (i--) {
      traverse(val[key])
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值