vue数据的双向绑定源码分析

VUE中的数据双向绑定是通过数据劫持的方式实现的,核心的便是object.defineProperty(),它内部分为三个部分:

observer 可以递归地监听对象上的所有属性,当属性改变时触发相应的watcher。

watcher 观察者,当监听的数据值修改时,执行相应的回调函数,更新模板内容。

dep 连接observer watcher,每一个observer对应一个dep,内部维护一个数组,保存与该observer相关的watcher。

由初始化数据进入到observe(value)方法,为给定的数据绑定observer实例,在observe方法中的核心就是

ob = new Observer(value)

在getter方法中,把watcher添加到dep中,在setter方法中,触发watcher执行回调(生成render函数,生成虚拟dom,映射在页面上)。

1.observer class中属性dep定义为new Dep(),对数组和对象类型分别处理。对数组调用observeArray方法,对对象调用walk方法。

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

1.1walk方法:遍历所有实例属性,将之调用defineReactive方法。

  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

1.2observeArray方法,对数组中的每个元素调用observe方法。

 observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }

1.11 深入查看defineReactive方法。

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()

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

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

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const 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) {
      const 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()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

传入方法中的参数为 obj key ,分别表示对象本身和 实例属性名。

(1)在此方法中首先创建dep实例,之后获取对于实例属性的描述,通过Object.getOwnPropertyDescriptor,获取实例属性是否可以修改,若configurable为false,那么从方法中返回。

(2)获取属性的get方法,若属性为数据属性而不为访问器属性,则get值为undefined,并将结果赋值为getter,如果此属性为数据属性,并且传入了两个参数,则把对象对应的实例属性值赋值为val。

(3)获取属性的set方法值。若为数据属性则为undefined,并将其赋值为setter。

(4)若参数中未包含true false,则调用observe(val)方法,并将其赋值为childOb  这里涉及到observe方法。会创建observer实例,并返回。

(5)利用Object defineProperty定义obj的key实例属性描述为可遍历,可修改,设置其get,set方法。

get方法:a.若此属性已包含get方法,则在obj作用域下调用get方法。若无get方法,则获取其实例属性值val。将get方法返回值或者obj[key]赋值为value。b.如果Dep.target有值时,也就是目前存在唯一的watcher正在起作用时,通过dep实例调用depend方法,来为watcher和此dep实例添加依赖。此处的depend方法接下来细谈。

    b.1 depend方法:若当前存在watcher,则调用Watcher 类型的addDep方法。

export default class Dep {
  static target: ?Watcher;

depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
       b.11 深入addDep方法:若Watcher的新dep中未包含此dep实例,也就是此属性头一次被监听,那么将此dep实例添加进newDeps中,若果depId中未包含此dep实例,则调用dep实例的addSub方法
/**
 * Add a dependency to this directive.
 */
Watcher.prototype.addDep = function addDep (dep) {
  var id = dep.id;
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id);
    this.newDeps.push(dep);
    if (!this.depIds.has(id)) {
      dep.addSub(this);
    }
  }
};

      b.111调用addSub方法。为dep实例的subs数组中添加watcher。

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};
if (childOb) {
  childOb.dep.depend()
  if (Array.isArray(value)) {
    dependArray(value)
  }

此处在研究observe后进行补充。



set方法

(1)首先判断此属性是否包含get方法,如果包含则获取get方法在obj对象的作用域下调用的返回值,如果没有get方法,则获取实例属性的值,并将其赋值为value。

(2)判断为实例属性新赋的值是否全等于value值,也就是实例属性已有的值和新赋值是否全等,或者value自身与自身就不全等或者新值与自身不全等的情况下,从set方法中返回。

(3)如果已有set方法,则在obj作用域下调用set方法,否则将val定义为新赋的值

(4)根据是否传入true false 调用observe(newval)方法,将返回值赋值为childOb

(5)调用dep实例的notify方法。对dep实例的subs数组中的watcher调用update方法,此处延伸下去。

 notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }

a.watcher的update方法会调用run方法。

 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);
        }
      }
    }
 get方法:先将目前正在执行工作的watcher添加到Dep的targetStack中,设置全局变量Dep.target,之后touch Watcher初始化时传入的参数expOrFn中
涉及到的每一项数据,然后触发该数据项的getter函数;设置dep.target是依赖收集过程中的重要一步,getter函数中就是通过判断Dep.target的
有无来判断是Watcher初始化时调用的还是普通数据读取,如果有则进行依赖收集。

get () {
  pushTarget(this);//设置全局变量Dep.target,将watcher保存在这个全局变量中。
  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
}
class Dep中
Dep.target = null;
const targetStack = [];

function pushTarget (_target) {
  if (Dep.target) targetStack.push(Dep.target);
  Dep.target = _target;
}

watcher对象的run方法中会调用cb方法,而该方法会进而调用patch方法,进行diff,比较新旧值差距,进而渲染页面



猜你喜欢

转载自blog.csdn.net/u012516440/article/details/79910415