Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【源码】watch 源码解析 #42

Open
YIngChenIt opened this issue Jul 3, 2020 · 0 comments
Open

【源码】watch 源码解析 #42

YIngChenIt opened this issue Jul 3, 2020 · 0 comments

Comments

@YIngChenIt
Copy link
Owner

【源码】watch 源码解析

前言

watchcomputed一直是我们开发和面试时候经常用的一个点,接下来我们通过源码的解析深入的了解watch的内部机制

源码解析

我们知道Vue加载的时候会调用initState来初始化state

// vue/src/core/instance/init.js
  Vue.prototype._init = function (options?: Object) {
      ...
      initState(vm) // 初始化state
      ...
  }

其实我们的watch的初始化在initState内部实现

// vue/src/core/instance/state.js
export function initState (vm: Component) {
  ... 初始化props、methods、data、computed等
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

我们接下来看下initWatch是什么名堂

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) { // 如果是数组,遍历调用createWatcher
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}

我们会发现一个很有趣的事情,这里做了一个是否是数组的判断,这是因为mixin机制可以让watch是一个数组的形式

上述代码就是遍历数组或者对象,然后调用createWatcher方法

我们接下来看下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)
}

那么问题来了,为什么createWatcher方法中还要进行一层类型判断呢?

那是因为我们有3种watch的用法

watch: {
    name: {
        handler() {}
    },
    name() {},
    name: 'getName',
}

如果是对象的话获取对象的handler方法,如果是字符串则去实例上取这个方法

最后我们来看最关键的一个方法vm.$watch(代码做了删减)

Vue.prototype.$watch = function(
    // expOrFn 是 监听的 key,cb 是监听回调,opts 是所有选项
    expOrFn, cb, opts
){    
    // expOrFn 是 监听的 key,cb 是监听的回调,opts 是 监听的所有选项
    var watcher = new Watcher(this, expOrFn, cb, opts);    

    // 设定了立即执行,所以马上执行回调
    if (opts.immediate) {
        cb.call(this, watcher.value);
    }
};

首先我们可以发现一点,当我们使用watch的时候,如果设置immediate为true,会立刻将handler执行

然后我们会发现watch的核心, new Watcher(), 没错,watch就是通过Vue的发布订阅机制来实现的一个功能点,在源码中给
watch中的属性新建一个观察者watcher,然后就可以实现属性变化的时候,执行cd也就是watch中的handler了,如果对这一块不了解的同学可以看下我的关于Vue中MVVM原理的文章手写mvvm 之 实现数据双向绑定

最后还有一个小知识点,watch是如何实现深度监听的,也就是我们使用的时候设置的deep(代码有删减)

Watcher.prototype.get = function() {
  Dep.target= this
  var value = this.getter(this.vm)    
  if (this.deep)  traverse(value)
  Dep.target= null
  return value
};

我们可以发现,当设置deep为true的时候会走traverse方法

function _traverse (val, seen) {
  var i, keys;
  var isA = Array.isArray(val);
  if (isA) { // 如果是数组
    i = val.length;
    while (i--) { _traverse(val[i], seen); }
  } else { // 如果是对象
    keys = Object.keys(val);
    i = keys.length;
    while (i--) { _traverse(val[keys[i]], seen); }
  }
}

我们可以发现就是一直递归往下查找,这里有一个比较有趣的事情是它通过val[i]val[keys[i]] 变相的获取值,因为data里面的数据是具有响应式能力的,每一次获取都会触发getter,然后往对应的dep中添加watcher, 然后就实现深度监听的能力了

总结

  • watch 内部通过为设置的属性生成一个watcher的方式实现数据劫持

  • 深度监听的原理是通过不断的递归,变相的调用data的getter,然后往对应的dep里面添加watcher

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant