手写一个miniVue

108 阅读1分钟

new Vue 做了什么

  1. 传入options配置项,通过属性保存传入的属性
  2. 将传入的data成员转化为响应式数据,并挂载到vue实例中
  3. 调用Observer对象,监听数据变化,添加watcher
  4. 通用compiler对象,解析指令和插值表达式
  5. compiler解析中遇到数据更新调用watcher,更新视图
class Vue {
  constructor(options) {
    // 1. 通过属性保存选项中的数据
    this.$options = options || {};
    this.$data = options.data || {};
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el;
    // 2. 把data中的成员转换成getter合setter,注入到vue实例中
    this._definePropertyHandler(this.$data);
    // 3. 调用observer对象, 监听数据的变化
    new Observer(this.$data);
    // 4. 调用compiler对象,解析指令和插值表达式
    new Compiler(this);
  }

  _definePropertyHandler(data) {
    Object.keys(data).forEach((key) => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key];
        },
        set(newValue) {
          if (newValue === data[key]) return;
          data[key] = newValue;
        }
      })
    })
  }
}

创建observe

class Observer {
  constructor(data) {
    this.walk(data);
  }
  walk(data) {
    if (!data || typeof data !== 'object') return;
    Object.keys(data).forEach((key) => {
      this.defineReactive(data, key, data[key])
    })
  }
  defineReactive(obj, key, value) {
    const _this = this;
    let dep = new Dep(); // 负责收集依赖
    // 需要传递第三个参数的原因是  如果通过obj[key] 去访问的话会造成死循环  一直访问get方法 局部变量没有被释放掉的原因是
    // 外部因为了get这个方法  就相当于造成了闭包  所以value这个局部变量不会被释放掉

    // 再次调用walk方法, 让data中的对象属性也变成响应式对象
    this.walk(value);
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 添加依赖
        Dep.target && dep.addSub(Dep.target);
        return value
      },
      set(newValue) {
        if (newValue === value) return;
        value = newValue;
        _this.walk(newValue);
        // 发送通知
        dep.notify();
      }
    })
  }
}

创建一个观察者模式用于发送更新通知及添加依赖

class Dep {
  constructor() {
    // 存储所有的观察者
    this.subs = [];
  }
  // 添加观察者
  addSub(sub) {
    (sub && sub.update) && this.subs.push(sub);
  }
  // 发送通知
  notify() {
    this.subs.forEach((sub) => {
      sub.update();
    })
  }
}

最后创建一个compiler文件用来进行解释指令及插值表达式

// 负责编译模板  解析指令和插值表达式  负责页面的首次渲染  当数据变化后重新渲染视图
class Compiler {
  constructor(vm) {
    this.el = vm.$el;
    this.vm = vm;
    this.compile(this.el);
  }
  // 编译模板 处理文本节点和元素节点
  compile(el) {
    let childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      if (this.isTextNode(node)) {
        // 处理文本节点
        this.compilerText(node);
      }
      if (this.isElementNode(node)) {
        // 处理元素节点
        this.compileElement(node);
      }

      // 判断node节点, 是否有子节点, 如果有子节点,要递归调用compiler
      if (node.childNodes && node.childNodes.length) {
        this.compile(node);
      }
    })
  }

  // 编译元素节点  处理指令
  compileElement(node) {
    console.log(node.attributes)
    // 遍历所有的属性节点
    Array.from(node.attributes).forEach((attr) => {
      let attrName = attr.name;
      // 判断是否是指令
      if (this.isDirective(attrName)) {
        // v-text --- > text
        attrName = attrName.substr(2);
        let key = attr.value;
        this.update(node, key, attrName)
      }
    })
  }

  update(node, key, attrName) {
    let updateFn = this[`${attrName}Updater`]
    updateFn && updateFn.call(this, node, this.vm[key], key)
  }

  // 处理v-text指令
  textUpdater(node, value, key) {
    node.textContent = value;
    new Watcher(this.vm, key, (newValue) => {
      node.textContent = newValue;
    })
  }

  // 处理v-model
  modelUpdater(node, value, key) {
    node.value = value;
    new Watcher(this.vm, key, (newValue) => {
      node.value = newValue;
    })
    // 双向绑定
    node.addEventListener('input', () => {
      this.vm[key] = node.value
    })
  }

  // 编译文本节点  处理插值表达式
  compilerText(node) {

    // ()有分组的意思  .+?是用来匹配{{}}中间的内容
    let reg = /\{\{(.+?)\}\}/;
    let value = node.textContent;
    if (reg.test(value)) {
      // 调用正则构造函数 $1是第一个分组的值 $2是第二个分组的值
      let key = RegExp.$1.trim();
      node.textContent = value.replace(reg, this.vm[key])

      // 创建watcher对象 当数据改变更新视图
      new Watcher(this.vm, key, (newValue) => {
        node.textContent = newValue;
      })
    }
    console.dir(node)
  }

  // 判断是不是指令
  isDirective(attrName) {
    return attrName.startsWith('v-');
  }

  // 判断是不是文本节点
  isTextNode(node) {
    return node.nodeType === 3;
  }

  // 判断节点是否是元素节点
  isElementNode(node) {
    return node.nodeType === 1;
  }
}

watcher代码

class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    // data中的属性名称
    this.key = key;
    // 回调函数负责更新视图
    this.cb = cb;

    // 把watcher对象记录到dep类的静态属性target
    Dep.target = this;
    // 触发get方法  在get方法中会调用addSub
    this.oldValue = vm[key];
    // 防止重复添加
    Dep.target = null;
  }
  // 当数据变化的时候更新视图
  update() {
    let newValue = this.vm[this.key];
    if (this.oldValue === newValue) {
      return;
    }
    this.cb(newValue);
  }
}

只是实现了数据的更新及简单指令 gitee代码链接:gitee.com/xuxuqingson…