vueデータの双方向バインドソース分析

10805 ワード

VUEにおけるデータの双方向バインディングは、データハイジャックによって実現する、コアはobjectである.defineProperty()は、3つのセクションで構成されています.
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.1 walkメソッド:すべてのインスタンスプロパティを巡回し、defineReactiveメソッドを呼び出します.
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

1.2 observeArrayメソッドで、配列内の各要素に対してobserveメソッドを呼び出します.
 observeArray (items: Array) {
    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として付与し、この属性がデータ属性であり、2つのパラメータが入力されると、オブジェクトに対応するインスタンス属性値を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 addDepthメソッド: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メソッドが含まれているかどうかを判断し、含まれている場合は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, ,