In Vue2, the core of the reactive implementation is Object. DefineProperty (obj, prop, Descriptor) of ES5. Object.defineproperty () hijacks the getters and setters for the properties of data and props. The getters do dependency collection, and the setters distribute updates. Overall, it is a data hijacking + publishing-subscriber model.

  • During the vue initialization phase (beforeCreate after and beforeCreate), iterate over the data/props and call object.defineproperty to add getters and setters to each property.
  • Each component, each computed instantiates a Watcher (and, of course, each custom Watcher), subscribing to the data/props/computed used for rendering/computation
  • Whenever the data changes, the setter is called and tells the rendering Watcher to recalculate and update the component.

Simple implementation of responsive principle, design five classes:

  • Vue: Processes parameters passed in by options, converting members in data to getters/setters
  • Observer: Recursively processes all attributes of data, hijacking data get/set to add dependencies/update views
  • Dep: Adds watcher, notifies all Watchers of data changes
  • Watcher: When instantiating, you add yourself to the DEP, and the DEP informs Watcher of data changes to update the view
  • Compiler: Parses instructions/interpolation expressions, encounters template-dependent data, adds Watcher

Vue

Function:

  • Log the incoming options and set data,data, data, EL
  • Inject properties from Data into Vue instances and convert them into getters/setters
  • Call observer to listen for changes to all properties in data
  • Call compiler to parse instructions/interpolation expressions

Class attribute methods:

  • $options
  • $el
  • $data
  • _proxyData()
class Vue {
  constructor(options) {
    this.$options = options || {};
    this.$data = options.data || {};
    this.$el =
      typeof options.el === "string"
        ? document.querySelector(options.el)
        : options.el;
    this._proxyData(this.$data);
    this.initMethods(options.methods || {});
    new Observer(this.$data);
    new Compiler(this);
  }

  _proxyData(data) {
    Object.keys(data).forEach((key) = > {
      Object.defineProperty(this, key, {
        configurable: true.enumerable: true.get() {
          return data[key];
        },
        set(newValue) {
          if (newValue === data[key]) return;
          console.log("set -> newValue", newValue); data[key] = newValue; }}); }); }initMethods(methods) {
    Object.keys(methods).forEach((key) = > {
      this[key] = methods[key]; }); }}Copy the code

Observer

Function:

  • The data was hijacked
    • Is responsible for converting members in data to getters/setters
    • Is responsible for converting layers of properties into getters/setters
    • If you assign a property to a new object, set the member of the new object to getter/setter
  • Add the Dep and Watcher dependencies
  • Send notifications of data changes

Class attribute methods:

  • Walk traversal data (data)
  • DefineReactive (data, key, value) reactive processing
class Observer {
  constructor(data) {
    this.walk(data);
  }

  walk(data) {
    if (typeofdata ! = ="object" || data === null) {
      return;
    }
    Object.keys(data).forEach((key) = > {
      this.defineReactive(data, key, data[key]);
    });
  }

  defineReactive(obj, key, val) {
    let self = this;
    const dep = new Dep();
    this.walk(val);
    Object.defineProperty(obj, key, {
      enumerable: true.configurable: true.get() {
        Dep.target && dep.addSub(Dep.target);
        return val;
      },
      set(newValue) {
        if (newValue === val) return; val = newValue; self.walk(newValue); dep.notify(); }}); }}Copy the code

Dep

Function:

  • Collect dependencies, add watcher
  • Notify all observers

Class attribute methods:

  • Subs holds all observers
  • AddSub () adds an observer
  • When the notify() event occurs, the update method for all observers is called
class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub); }}notify() {
    this.subs.forEach((sub) = >{ sub.update(); }); }}Copy the code

Watcher

Function:

  • When data changes trigger dependencies, DEP notifies all Watcher instances to update the view
  • Add yourself to the DEP object when instantiating itself

Class attribute methods:

  • Vue vm instance
  • The name of the attribute in key Data
  • cb
  • oldValue
  • update()
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    this.key = key;
    this.cb = cb;
    Dep.target = this;
    this.oldValue = vm[key];
    Dep.target = null;
  }
  update() {
    const newValue = this.vm[this.key];
    if (newValue === this.oldValue) {
      return;
    }
    this.cb(newValue); }}Copy the code

Compiler

Function:

  • Responsible for compiling templates, parsing instructions/interpolation expressions
  • Responsible for the first rendering of the page
  • Re-render the view when the data changes

Class attribute methods:

  • el
  • vm
  • Compile (EL) compiles native instructions
  • compileElement(node)
  • CompileText (node) compiles difference expressions
  • IsDirective (attrName) Checks whether the directive starts with v –
  • isTextNode(node)
  • isElementNode(node)
class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.compile(this.el);
  }
  compile(el) {
    let childNodes = el.childNodes;
    Array.from(childNodes).forEach((node) = > {
      if (this.isTextNode(node)) {
        this.compileText(node);
      } else if (this.isElementNode(node)) {
        this.compileElement(node);
      }
      if (node.childNodes && node.childNodes.length) {
        this.compile(node); }}); }isTextNode(node) {
    return node.nodeType === 3;
  }
  isElementNode(node) {
    return node.nodeType === 1;
  }
  compileText(node) {
    let reg = / \ {\ {(. +) \} \} /;
    let value = node.textContent;
    if (reg.test(value)) {
      let key = RegExp.$1.trim();
      console.log("compileText -> key", key);
      node.textContent = value.replace(reg, this.vm[key]);

      new Watcher(this.vm, key, (newValue) = >{ node.textContent = newValue; }); }}compileElement(node) {
    Array.from(node.attributes).forEach((attr) = > {
      let attrName = attr.name;
      if (this.isDirective(attrName)) {
        attrName = attrName.substr(2);
        if (attrName.includes("on:")) {
          const tmp = attrName.split(":");
          const name = tmp[1];
          this.onHandler(node, attr.value, name);
        } else {
          this.update(node, attr.value, attrName); }}}); }isDirective(attrName) {
    return attrName.startsWith("v-");
  }
  update(node, key, attrName) {
    let fn = this[attrName + "Updater"];
    fn && fn.call(this, node, this.vm[key], key);
  }
  textUpdater(node, value, key) {
    node.textContent = value;
    new Watcher(this.vm, key, (newValue) = > {
      node.textContent = newValue;
    });
  }
  modelUpdater(node, value, key) {
    node.value = value;
    new Watcher(this.vm, key, (newValue) = > {
      node.value = newValue;
    });
    node.addEventListener("input".() = > {
      this.vm[key] = node.value;
    });
  }
  htmlUpdater(node, value, key) {
    node.innerHTML = value;
    new Watcher(this.vm, key, (newValue) = > {
      node.innerHTML = newValue;
    });
  }
  onHandler(node, value, name) {
    let modifier = "";
    if (name.includes(".")) {
      const tmp = name.split(".");
      name = tmp[0];
      modifier = tmp[1].trim();
    }

    // Dynamic timing: v-on:[event]="doThis"
    if (name.startsWith("[")) {
      name = name.slice(1, -1);
      name = this.vm[name];
    }

    let third_params = false;
    if (modifier === "capture") {
      third_params = true;
    } else if (modifier === "passive") {
      third_params = { passive: true };
    }

    const cb = (e) = > {
      if (modifier === "stop") {
        e.stopPropagation();
      }
      if (modifier === "prevent") {
        e.preventDefault();
      }
      let methodName = value;
      let args = [e];
      // Handle inline statements passing extra arguments
      if (value.endsWith(")")) {
        const tmp = value.split("(");
        methodName = tmp[0];
        args = tmp[1]
          .slice(0, -1)
          .split(",")
          .map((item) = > {
            item = item.trim();
            console.log("onHandler -> item", item, typeof item);
            if (item === "$event") {
              return e;
            }
            if (item.startsWith('"') || item.startsWith("'")) {
              console.log("onHandler -> item", item);
              return item.slice(1, -1);
            }
            return this.vm[item];
          });
      }
      this.vm[methodName](... args);if (modifier === "once") { node.removeEventListener(name, cb, third_params); }}; node.addEventListener(name, cb, third_params); }}Copy the code

Yards cloud address