手写版VUE1.0

159 阅读1分钟

vue

class vue {
    constructor(options) {
        // 保存选项
        this.$options = options;

        // 保存data
        this.$data = options.data;

        // 对传入data对象执行响应化处理
        this.observe(this.$data);

        new Compile(options.el, this);

        if(options.created) {
            options.created.call(this)
        }
    }

    observe(value) {
        // 参数必须是对象
        if (!value || typeof value !== 'object') {
            return;
        }

        Object.keys(value).forEach(key => {
            // 执行响应化
            this.defineReactive(value, key, value[key])

            // 执行代理
            this.proxyData(key);
        })
    }

    defineReactive(obj, key, val) {
        // 递归判断
        this.observe(val);

        // 创建Dep,它和key 1:1关系
        const dep = new Dep();

        // 定义属性
        // 参数3是属性描述符,定义配置型、遍历性、可读、可写
        Object.defineProperty(obj, key, {
            get() {
                // 依赖收集
                Dep.target && dep.addDep(Dep.target)
                return val;
            },
            set(newVal) {
                if (newVal === val) {
                    return
                }
                val = newVal;
                // 通知更新
                dep.notify();
                
            }
        })
    }

    proxyData(key) {
        // 想Vue实例上面定义属性key
        Object.defineProperty(this, key, {
            get() {
                return this.$data[key]
            },
            set(newVal) {
                this.$data[key] = newVal;
            }
        })
    }
}

Dep: 管理若干Watcher实例,通知它们更新

class Dep {
    constructor() {
        this.deps = [];
    }

    addDep(dep) {
        this.deps.push(dep);
    }

    notify() {
        // set函数调用
        this.deps.forEach(dep => dep.update())
    }
}

Watcher: 执行具体更新操作

class Watcher {
    constructor(vm, key, updater) {
        
        this.vm = vm;
        this.key = key;
        this.updater = updater;


        Dep.target = this; // 依赖收集时要用到
        this.vm[this.key];
        Dep.target = null;
    }

    update() {
        // console.log('属性'+this.key+'更新了');
        this.updater.call(this.vm, this.vm[this.key])
    }
}

compiler:编译器

class Compile {
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = document.querySelector(el);

    if (this.$el) {
      // 执行编译
      this.compile(this.$el);
    }
  }

  compile(el) {
    // 遍历el
    const childNodes = el.childNodes;
    // 每次拿出一个dom节点
    Array.from(childNodes).forEach(node => {
      // 判断节点类型
      if (this.isElement(node)) {
        // 3. 元素节点:访问节点特性,截获k-和@开头内容并解析
        // console.log('编译元素'+node.nodeName);
        this.compileElement(node);
      } else if (this.isInter(node)) {
        // 2. 文本节点:获取{{}}格式的内容并解析
        // console.log('编译插值文本'+node.textContent);
        this.compileText(node);
      }

      // 递归
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }

  isElement(node) {
    return node.nodeType === 1;
  }
  // 判断是否是插值表达式
  isInter(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  }

  // 编译插值文本
  compileText(node) {
    // 获取表达式
    // {{a+b()}}
    const exp = RegExp.$1;
    this.update(node, exp, "text");
  }

  // 通用方法 update(node, 'xxx', 'text')
  update(node, exp, dir) {
    // 构造更新函数并执行:相当于首次赋值
    let updaterFn = this[dir + "Updater"];
    updaterFn && updaterFn(node, this.$vm[exp]);

    // 创建watcher,执行后续更新操作
    // 额外传递一个更新函数:能够更新指定dom元素
    new Watcher(this.$vm, exp, function(value) {
      updaterFn && updaterFn(node, value);
    });
  }

  textUpdater(node, value) {
    node.textContent = value;
  }

  compileElement(node) {
    // 获取属性
    const nodeAttrs = node.attributes;
    Array.from(nodeAttrs).forEach(attr => {
      //   k-text="test"
      const attrName = attr.name; // k-text
      const exp = attr.value; // test

      if (attrName.indexOf("k-") === 0) {
        // 指令 k-text k-model
        const dir = attrName.substring(2); // text
        this[dir] && this[dir](node, exp);
      }
    });
  }

  text(node, exp) {
    this.update(node, exp, "text");
  }
}