vue源码分析(二)

淺唱寂寞╮ 提交于 2020-01-15 04:31:28

观察者模式

观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

vue的响应式原理采用的就是观察者设计模式。下面是观察者模式在vue中的运用解析。
在这里插入图片描述
图片来源:https://blog.csdn.net/github_36369819/article/details/79201314

  • Observer:监听者,通过Obeject.defineProperty对数据进行劫持,数据变更时通知Dep。
  • Dep:订阅器,用一个数组收集watcher依赖,当数据变更时,通知数组中的watcher进行更新。
  • Watcher:观察者,收到Dep发来的数据变更通知,获取新数据,更新视图。

下面来看vue中的具体实现。

Observer

var Observer = function Observer (value) {
    this.value = value;
    this.dep = new Dep();
    this.vmCount = 0;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  };
  
  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  };
  
  /**
   * Observe a list of Array items.
   */
  Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  };

最终都会调用walk函数,walk函数又会调用defineReactive$$1。

/**
 * Define a reactive property on an Object.
 */
function defineReactive$$1 (
    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 (process.env.NODE_ENV !== 'production' && customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }
    });
  }

get

get中我们会返回value值,同时当Dep.target存在时会调用dep.depend,如果有子对象,子对象也调用dep.depend,是数组也会调用dependArray方法。这边Dep.target就是一个watcher实例,也就是判断当前有没有观察者,如果有则depend方法会把当前的Dep.target添加到dep中的存放watcher的数组中,这边就是把关联的依赖收集起来了。在下面的Dep源码中我们会看到depend方法的具体实现。

set

set方法中我们会对value进行重新赋值,如果新值和旧值则不做任何操作,直接返回。如果有变化会对新值进行observe监听。同时会调用dep.notify()方法通知dep收集的依赖进行更新,notify详细代码请看下面的Dep源码。

Dep

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget () {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

depend

depend方法在上面get方法中调用,实现很简单,还是判断Dep.target这个值存不存在,如果存在调用targetaddDep方法。addDep的具体实现我们会在Watcher源码中介绍。

notify

notify方法中会遍历subs数组,subs数组存放的就是一个个watcher实例,也就是收集到的依赖,调用每一个watcherupdate方法,该方法的目的就是获取新的变更数据,更新视图。

pushTarget和popTarget

我们看到默认对target进行赋值为null,同时有一个targetStack栈用来存放历史target,pushTarget方法的作用是对Dep.target进行赋值,同时塞入targetStack栈,popTarget方法是把当前target出栈同时把Dep.target值取栈最上层target

Watcher

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
   	省略...
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
 * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      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
  }

  /**
 * Add a dependency to this directive.
   */
  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)
      }
    }
  }
  省略...
  /**
 * Subscriber interface.
 * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
 * Scheduler job interface.
 * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        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)
        }
      }
    }
  }
  省略...
  }
}

constructor

我们着重介绍构造函数的三个参数

  • vm: vue实例;
  • expOrFn: 表达式或者对象,用于取值触发上面defineReactive中定义的get方法;
  • cb: callback,用数据变更时的update中的回调。

构造函数中对getter进行赋值,如果expOrFnFunction直接赋值,如果不是则调用parsePath对表示进行解析,parsePath会返回一个函数,该函数会解析expOrFn表达式进行取值。

/**
 * Parse simple path.
 */
var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  var segments = path.split('.');
  return function (obj) {
    for (var i = 0; i < segments.length; i++) {
      if (!obj) { return }
      obj = obj[segments[i]];
    }
    return obj
  }
}

get

构造函数的最后会调用get方法,我们看下get的实现,该方法先是调用了上面我们说到的pushTarget方法把当前watcher赋值给Dep.target,当执行this.getter.call(vm, vm)时会触发exp表达式对应Observer监听的data中数据的get方法。get方法中会把当前的Dep.taregt收集依赖。最后调用popTarget方法释放这个Dep.Target

addDep

add方法我们可以看到最后会调用dep.addSub(this),这个可以回到Dep源码中看到这个方法的实现,就是把当前的watcher添加到subs数组中了,看到这边整个依赖的收集就串联起来了。

update和run

updatedepnotify方法中进行调用,这个方法最终会执行run方法,该方法会调用构造函数中watcher的回调函数,进行页面数据的更新。

总结

这节我们分析了Vue响应式原理中核心代码的实现,分为Observer,Dep和Watcher三个部分,这三个部分共通实现了观察者模式。大家在看源码时着重分析这三个部分代码可以更好的理解vue的响应式原理,再下一节我们来参照vue的响应式原理,实现一个简易版的vue。
跳转上一篇

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!