实现:

/*
 * Time: 2021/09/09
 * Author: tony_cyt
 * About: 简要实现Vue
 */

class Vue {
  /**
   * @description: Vue构造函数
   * @param {*} options
   * @return {*}
   */
  constructor(options) {
    // 赋值el和data
    const { el, data, computed } = options;
    this.$el = el;
    this.$data = data;
    // 如果$el存在,就走编译流程
    if (this.$el) {
      new Observer(this.$data);
      for (const key in computed) {
        Object.defineProperty(this.$data, key, {
          get: () => {
            return computed[key].call(this);
          }
        })
      }
      this.proxyData(this.$data);
      new Compiler(this.$el, this);
    }
  }
  proxyData(data) {
    for (const key in data) {
      Object.defineProperty(this, key, {
        get: () => {
          return data[key];
        }
      })
    }
  }
}

/**
 * @description: 自定义指令集
 * @param {*}
 * @return {*}
 */
CompilerUtil = {
  getVal(vm, expr) {
    return expr.split('.').reduce((data, current) => {
      return data[current];
    }, vm.$data)
  },
  setVal(vm, expr, value) {
    expr.split('.').reduce((data, current, index, arr) => {
      if (index === arr.length - 1) {
        return data[current] = value;
      }
      return data[current];
    }, vm.$data);
  },
  model(node, expr, vm) {
    const value = this.getVal(vm, expr);
    const fn = this.updater['modeUpdater'];
    new Watcher(vm, expr, newValue => {
      fn(node, newValue);
    })
    node.addEventListener('input', e => {
      this.setVal(vm, expr, e.target.value);
    })
    fn(node, value);
  },
  getContentValue(vm, expr) {
    return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      return this.getVal(vm, args[1]);
    })
  },
  text(node, expr, vm) {
    const fn = this.updater['textUpdater'];
    const content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
      new Watcher(vm, args[1], () => {
        fn(node, this.getContentValue(vm, expr));
      })
      return this.getVal(vm, args[1]);
    })
    fn(node, content);
  },
  html() {

  },
  updater: {
    modeUpdater(node, value) {
      node.value = value;
    },
    textUpdater(node, value) {
      node.textContent = value;
    },
    htmlUpdater() {

    }
  }
}

class Compiler {
  /**
   * @description: 编译器构造函数
   * @param {*} el
   * @param {*} vm
   * @return {*}
   */
  constructor(el, vm) {
    this.vm = vm;
    this.el = this.isElementNode(el) ? el : document.querySelector(el);
    const fragment = this.nodeToFragment(this.el);
    this.compile(fragment);
    this.el.append(fragment);
  }
  /**
   * @description: 判断是不是元素节点
   * @param {*} node
   * @return {*}
   */
  isElementNode(node) {
    return node.nodeType === 1;
  }
  /**
   * @description: 生成文档片段
   * @param {*} node
   * @return {*}
   */
  nodeToFragment(node) {
    const fragment = document.createDocumentFragment();
    let firstChild;
    while (firstChild = node.firstChild) {
      fragment.append(firstChild);
    }
    return fragment;
  }
  /**
   * @description: 根据类型编译
   * @param {*} node
   * @return {*}
   */
  compile(node) {
    [...node.childNodes].forEach(child => {
      if (this.isElementNode(child)) {
        this.compileElement(child);
        // 递归节点遍历
        this.compile(child);
      } else {
        this.compileText(child);
      }
    })
  }
  /**
   * @description: 编译文本节点
   * @param {*} node
   * @return {*}
   */
  compileText(node) {
    const reg = /\{\{(.+?)\}\}/;
    if (reg.test(node.textContent)) {
      CompilerUtil['text'](node, node.textContent, this.vm);
    }
  }
  /**
   * @description: 判断是否是指令
   * @param {*} attrName
   * @return {*}
   */
  isDirective(attrName) {
    return attrName.startsWith('v-');
  }
  /**
   * @description: 编译元素节点
   * @param {*} node
   * @return {*}
   */
  compileElement(node) {
    [...node.attributes].forEach(attr => {
      const { name, value: expr } = attr;
      if (this.isDirective(name)) {
        const [, directive] = name.split('-');
        CompilerUtil[directive](node, expr, this.vm);
      }
    })
  }
}

/**
 * @description: 实现数据响应式
 * @param {*}
 * @return {*}
 */
class Observer {
  constructor(data) {
    this.observer(data);
  }
  observer(data) {
    if (data && typeof data === 'object') {
      for (const key in data) {
        this.defineReactive(data, key, data[key]);
      }
    }
  }
  defineReactive(obj, key, value) {
    this.observer(value);
    const dep = new Dep();
    Object.defineProperty(obj, key, {
      get() {
        Dep.target && dep.subs.push(Dep.target);
        return value;
      },
      set: (newValue) => {
        if (newValue !== value) {
          this.observer(newValue);
          value = newValue;
          dep.notify();
        }
      }
    })
  }
}

/**
 * @description: 存储观察者
 * @param {*}
 * @return {*}
 */
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(watcher) {
    this.subs.push[watcher];
  }
  notify() {
    this.subs.forEach(watcher => {
      watcher.update();
    })
  }
}

class Watcher {
  constructor(vm, expr, cb) {
    this.vm = vm;
    this.expr = expr;
    this.cb = cb;
    this.oldValue = this.get();
  }
  get() {
    Dep.target = this;
    const value = CompilerUtil.getVal(this.vm, this.expr);
    Dep.target = null;
    return value;
  }
  update() {
    const newValue = CompilerUtil.getVal(this.vm, this.expr);
    if (newValue !== this.oldValue) {
      this.cb(newValue);
    }
  }
}

使用:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>MVVM</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model='school.name'>
    <div>{{school.name}}</div>
    <div>{{school.age}}</div>
    {{newName}}
    <ul>
      <li>1</li>
      <li>2</li>
    </ul>
  </div>

  <script src="VueMVVM.js"></script>
  <script>
    let vm = new Vue({
      el: "#app",
      data: {
        school: {
          name: "qinghua",
          age: 20
        }
      },
      computed: {
        newName() {
          return this.school.name + 'new'
        }
      }
    })
  </script>
</body>

</html>

效果:
image.png


tony_cyt
15 声望0 粉丝