Vue学习笔记(十八)——Vue数据双向绑定原理解析

参考文档:https://www.cnblogs.com/zhenfei-jiang/p/7542900.html

Vue的数据双向绑定的基础是通过Object.defineProperty()劫持作用域中变量的get和set方法实现的,示例如下:

示例中数据双向绑定的实现:
js对象变更触发dom更新:当执行virtualData.data = “default”;时输入框的信息同步更新为default字符串;
dom变更触发js对象更新:当改变输入框内的信息时,控制台打印的virtualData.data同步更新为输入框变更后的内容。


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>definePropertytitle>
head>
<body>
<div>
    <input type="text" id="data">
div>
body>
<script>
    let val = "";
    let virtualData = {};
    let dataDom = document.getElementById("data");
    Object.defineProperty(virtualData, "data", {
        get: function () {
            return val;
        },
        set: function (newVal) {
            /**
             * 在vue中,这里是一个订阅列表,页面上所有使用data变量的地方(如v-model、v-bind以及{{}})
             * 在初始化时都会在这里产生订阅(每处使用都会生成一个订阅者),用于触发视图更新
             *
             * 产生订阅:在set方法中增加dataDom.value = newVal;语句的过程
             * 订阅发布:data变更触发set方法执行dataDom.value = newVal;语句的过程
             *
             * 这里可以视为数据双向绑定中的js对象变更触发dom变更
             */
            dataDom.value = newVal;
            val = newVal;
        }
    });
    //不论页面触发的变量变更还是js中触发的变量变更,都会通过劫持的set方法触发订阅发布
    dataDom.addEventListener("input", function (e) {
        //这里可以视为数据双向绑定中的dom变更触发js对象变更
        virtualData.data = e.target.value;
        console.log("virtualData.data is " + virtualData.data);
    });
    virtualData.data = "default";
script>
html>

注意:本文后续代码Vue版本为2.6.7,如发现代码不匹配或功能与本文描述不符,请优先检查Vue版本。

当然,Vue真正的实现不会像上例那么简单,下面来看下Vue关于Object.defineProperty()的代码实现:

/**
 * Define a reactive property on an Object.
 */
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  //作用域中变量的生命周期由Dep对象统一管理
  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;
      //Dep.target用于验证当前查询的变量是否有页面dom在使用(如果有,那么Dep.target是一个Watcher对象,否则是undefined)
      if (Dep.target) {
        //产生订阅,这里可以通过shallow参数控制是浅订阅还是深订阅
        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 (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);
      //订阅发布,函数会遍历订阅列表(即Watcher列表,每个作用域变量都会生成一个Watcher),调用每个Wacher的update方法更新dom
      dep.notify();
    }
  });
}

接下来看一下Watcher对象:
在组件的整个生命周期中,Watcher对象会在以下3个过程中生成:

  1. 组件计算变量初始化时(initComputed);
  2. 组件mount时(mountComponent);
  3. 混入信息声明时(stateMixin)。

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.
 */
var Watcher = function Watcher (
  //vue对象
  vm,
  expOrFn,
  //真实dom列表,元素由Vue对html模版解析后生成(例:如果一个变量在页面上有两处使用,则cb会有两个元素),这里的解析逻辑会随着Vue的版本更新而变化,这是由于不同版本的Vue所支持的指令会略有差异
  cb,
  options,
  isRenderWatcher
) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  vm._watchers.push(this);
  // options
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$2; // uid for batching
  this.active = true;
  this.dirty = this.lazy; // for lazy watchers
  this.deps = [];
  this.newDeps = [];
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = process.env.NODE_ENV !== 'production'
    ? expOrFn.toString()
    : '';
  // 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();
};

在Watcher对象的run方法中会更新真实dom(Watcher对象的update方法最终会调用run方法完成更新动作,所以这里直接贴出run方法):

/**
 * Scheduler job interface.
 * Will be called by the scheduler.
 */
Watcher.prototype.run = function run () {
  if (this.active) {
    var 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
      var oldValue = this.value;
      this.value = value;
      //更新dom
      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);
      }
    }
  }
};

Vue解析html模版的代码在源码中逻辑较为松散,下面只贴出构建虚拟dom的部分源码供参考:

function genElement (el, state) {
  if (el.parent) {
    el.pre = el.pre || el.parent.pre;
  }

  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element
    var code;
    if (el.component) {
      code = genComponent(el.component, el, state);
    } else {
      var data;
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        data = genData$2(el, state);
      }

      var children = el.inlineTemplate ? null : genChildren(el, state, true);
      code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
    }
    // module transforms
    for (var i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code);
    }
    return code
  }
}

你可能感兴趣的:(Vue)