Vuejs 数据驱动原理

443 阅读2分钟

1.入口Observer函数

  • data中的属性会被传入Observer函数
  • Observer函数,递归为每一个属性执行defineReactive函数后,每一个节点都会被包装为一个观察者。
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();
  this.vmCount = 0;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);
  }
};
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
};

2.defineReactive实现数据劫持

function defineReactive (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  // 创建派发器
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if ("development" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}
  • defineReactive函数中会创建一个派发器(new Dep)
  • data中属性触发defineProperty.get时会调用 Dep.depend()来收集依赖
  • data中属性触发defineProperty.set时会调用 Dep.notify()

3. 订阅者watcher

那么Dep.notify()是如何驱动视图变化的呢?

我从官网找了一张图很清晰的描述了这一过程 image

Watcher负责做的事情就是订阅Dep当Dep发出消息传递(notify)时,订阅者Dep的Watchers会进行update操作。

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    this.cb = cb
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 解析表达式
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
      }
    }
    this.value = this.get()
  }

  get () {
    // 将目标收集到目标栈
    pushTarget(this)
    const vm = this.vm
    
    let value = this.getter.call(vm, vm)
    // 删除目标
    popTarget()
    
    return value
  }

  // 订阅 Dep,同时让 Dep 知道自己订阅着它
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // 收集订阅者
        dep.addSub(this)
      }
    }
  }

  // 订阅者'消费'动作,当接收到变更时则会执行
  // 最终让视图更新就是使用的该方法
  update () {
    this.run()
  }

  run () {
    const value = this.get()
    const oldValue = this.value
    this.value = value
    this.cb.call(this.vm, value, oldValue)
  }
}

看完代码得知watcher在监听到dep传递来的消息后调用update()函数去触发了视图的变化

Dep 负责收集所有数据变化,通过notify发布消息。

Watcher负责订阅Dep并在订阅的时候让Dep进行收集,接收到 Dep 发布的消息时,做update操作

两者相互独立又相互依赖配合实现了vue中的数据驱动

文章写得比较简单,只是把整个流程的概念讲了一遍。具体的还是得认真研读源码。

end