Vue2 的 Watcher解析以及简单实现

Watcher

上回说到,有一个Watcher会一直去相应数据的notify。

今天说说Vue的第二个Watcher也就是$watch
  • 这个watcher的回调函数不是updateComponent,不会去执行render等相关函数,只会执行自身定义的函数

  • 那问题来了,如果是我们设计。我们可以怎么设计这个函数能让在数据变化的时候执行他呢

  • 我们想到的就是,如果我也把他丢到数据的Dep的subs中,那么数据相应是Dep中的sub.notify不就可以一起执行updateComponent和$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++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
// createWatcher
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
//$watch
 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 || {}
    options.user = true
    // 定义$watcher 的 Watcher 并且user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        // 是否立即执行
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
 }
  • 这时候我们看看Watcher (注意user=true) 并且参数的 expOrFn是一个字符串,这时候我们回想,定义数据的Wacher的第二参数expOrFn是什么,是 new Watcher(vm, updateComponent, noop,fn,fn)也就是一个函数
export default class Watcher {
  // 省略定义
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
  // 这里删除了不必要的东西
    this.vm = vm
     this.user = !!options.user
    this.id = ++uid // uid for batching
    // parse expression for getter
    // $watcher 的第二个参数并不是一个函数 走下面
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    this.value = this.lazy
      ? undefined
      : this.get()
}
// 老规矩 改掉Target指向 
get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 执行expOrFn方法 这个方法其实就是调用数据的get然后让他们Dep绑定这个Watcher
      value = this.getter.call(vm, vm) 
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  // 返回一个函数,这个函数里返回的obj其实相当于this.xxxx,
  // 那么之后怎么说,就是调用数据的get(),绑定Watcher!
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}
  • 我就省略get的过程啦,有兴趣可以打个断点进去试试
    在这里插入图片描述
  • 这个时候就绑定进去$watcher
  • 最后看看notify后怎么执行
run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
     	 //老的值
        const oldValue = this.value
        // 新的值
        this.value = value
        // 当user为true的时候就执行cb
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
 }
  • 最后就响应该方法了
  • 如果是deep = true 呢 , Vue的处理是 其实跟parsePath 差不多,只不过就是所有子孙元素都加上$watch而已
export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    // 通过回调来不断出发子孙元素的get()方法
    while (i--) _traverse(val[keys[i]], seen)
  }
}
  • 下面就是我自己简单实现$watch(有点小问题😆 😆)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id='text'>o.b</div>
    <script>
        // Watcher
        var _this = this
        class Watcher {
            constructor(expOrFn, cb, user, deep) {
                this.newDepIds = new Set()
                this.newDeps = []
                this.depIds = new Set()
                this.getter = expOrFn;
                this.value = this.get()
                this.cb = cb
                this.user = user
                this.deep = deep
            }
            addDep(dep) {
                const id = dep.id
                if (!this.newDepIds.has(id)) {
                    // watcher保存和它有关的dep
                    this.newDepIds.add(id)
                    this.newDeps.push(dep)
                    // 反过来
                    if (!this.depIds.has(id)) {
                        dep.addSub(this)
                    }
                }
            }
            get() {
                var val = ''
                setTarget(this)
                val = this.getter.call()
                if(this.deep){
                    _traverse(val)
                }
                removeTarget(this)
                return val
            }
            update() {
                this.run()
            }
            run() {
                if (this.user) {
                    const oldValue = this.value
                    this.value = this.get()
                    this.cb(this.value, oldValue)
                } else {
                    this.get()
                }
            }
        }
        function $watch(func, data) {
            // 调用一下这个方法,已添加这个方法的Watcher
            new Watcher(data, func, true, true)
        }
        //Dep
        var uid = 0
        class Dep {
            constructor() {
                this.id = uid++;
                this.subs = []
            }
            addSub(Watcher) {

                this.subs.push(Watcher)
            }
            depend() {
                Dep.target.addDep(this)
            }
            notify() {
                for (let i = 0, l = this.subs.length; i < l; i++) {
                    this.subs[i].update()
                }
            }
        }
        function setTarget(Watcher) {
            Dep.target = Watcher
        }
        function removeTarget(Watcher) {
            Dep.target = null
        }
        // Observer
        var data = {
            o: { b: '666' },
            c: '333'
        }

        function _traverse(val) {
            let i, keys
            const isA = Array.isArray(val)
            if (!(isA || val instanceof Object)) {
                return
            }
            keys = Object.keys(val)
            i = keys.length
            while (i--) _traverse(val[keys[i]])
        }

        class Observer {
            constructor(value) {
                this.value = value
                this.dep = new Dep()
                if (value instanceof Object) {
                    this.walk(value);
                }
            }
            observe(value) {
                // 初始化时创建一个
                let ob;
                if (value instanceof Object) {
                    ob = new Observer(value)
                }
                return ob
            }
            defineReactive(obj, key, value) {
                const dep = new Dep();
                if (arguments.length == 2) {
                    value = obj[key]
                    // console.log(value)
                }
                this.observe(obj[key])
                // 递归遍历
                Object.defineProperty(obj, key, {
                    enumerable: true,
                    configurable: true,
                    get: function reactiveGetter() {
                        if (Dep.target) {
                            dep.depend()
                        }
                        // console.log(obj, key, value)
                        return value
                    },
                    set: function reactiveSetter(newVal) {
                        value = newVal
                        dep.notify()
                    }
                })
            }
            walk(obj) {
                const keys = Object.keys(obj)
                for (let i = 0; i < keys.length; i++) {
                    this.defineReactive(obj, keys[i])
                }
            }
        }
        //模拟Vue执行的过程 暂时用setTimeout函数
        setTimeout(() => {
            // initData
            new Observer(data)
            // initWatch
            this.$watch((val, oldValue) => {
                console.log('发生变化', 'newValue:', val, 'oldValue:', oldValue)
            }, () => {
            	//这里是需要相应的值
                // 这个其实相当于parsePath, parsePath 相当于解析字符串然后调用每个数据的get,这个方法直接去调用get,简化了过程
                // 源码在lang.js parsePath()方法
                // 这是有个bug,如果没有另提出一份空间,会导致对象新旧值一直一样,可能和Vue的定义的data作用域不一样导致,如果懂得请跟我解释下
                return JSON.parse(JSON.stringify(this.data.o))
            })
            new Watcher(() => {
                // .. render 渲染
                console.log('...', '发生改变继续调用')
                document.getElementById('text').innerText = this.data.o.b
            })
        })
        setTimeout(() => {
            this.data.o.b =888
            this.data.c = 666
        }, 5000)
    </script>
</body>
</html>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值