关于javascript:简要实现Vue

45次阅读

共计 4224 个字符,预计需要花费 11 分钟才能阅读完成。

实现:

/*
 * 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>

成果:

正文完
 0