手撸秀珍 vue 响应式框架

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <style>
  </style>
  <div id="app" style="width:500px;height:500px;border:1px solid red;position:relative">
    <h1>{{msg}}</h1>
    <div class="pge">我的年龄 最大是</div>
    <h1>{{age.max}}</h1>
    <h2>{{hello}}</h2>
    <button @click="beBiggerAge">修改最大年龄</button>
    <input type="text" v-model="age.max">
  </div>
</body>
</html>
复制代码
 class Vue {
    constructor(options) {
      this.$options = options;
      this._init();
    }
    _init() {
      this.$data = this.initData();
      this.$methods = this.$options.methods;
      this.$computed = this.$options.computed;
      this.$watch = this.$options.watch;
      new Observer(this.$data)
      this.proxyData(this.$data);
      this.proxyData(this.$methods);
      this._watch();
      this.$options.created.apply(this)
      if (this.el) this.$options.$mount(this.el)
      this.$options.mounted.apply(this)
    }
    _watch() {
      Object.keys(this.$watch).forEach(key => {
        new Watcher(key, this, (newValue, oldValue) => {
          this.$watch[key].call(this, newValue, oldValue)
        })
      })
    }
    //代理this 使得可以直接访问 this.data this.method 
    proxyData(proxy) {
      Object.keys(proxy).forEach(key => {
        Object.defineProperty(this, key, {
          get() {
            return proxy[key]
          },
          set(newValue) {
            if (newValue !== proxy[key]) proxy[key] = newValue
          }
        })
      })
    }
    //初始化 data
    initData() {
      const type = typeof this.$options.data;
      return type === 'function' ? this.$options.data() : this.$options.data
    }
    //挂载 element
    $mount(el) {
      if (typeof el === 'string') this.$el = document.querySelector(el)
      else if (el.nodeType === 1) this.$el = el
      else throw new Error('节点错误')
      new Compiler(this.$el, this)
    }
  }
  class Compiler {
    constructor(el, vm) {
      this.$vm = vm
      this.$el = el;
      let fragment = this.vNodeFragment(el)
      this.compilerFragment(fragment);
      this.$el.appendChild(fragment)
    }
    vNodeFragment(el) {
      let fragment = document.createDocumentFragment(); //创建文档碎片
      while (el.firstChild) fragment.appendChild(el.firstChild);
      return fragment
    }
    compilerFragment(fragment) {
      const childNodes = fragment.childNodes;
      childNodes.forEach(node => {
        if (node.nodeType === 1) {
          this.compileElement(node);
          this.compilerFragment(node);
        } else {
          this.compileText(node)
        }
      })
    }
    compileText(node) {
      let reg = /\{\{(.+?)\}\}/g;
      let text = node.textContent;

      if (reg.test(text)) {
        let variable = CompilerUtils.getTextVariable(text);
        const isComputed = this.$vm.$computed[variable];
        //编辑 computed 属性
        if (isComputed) {
          CompilerUtils.computed(variable, node, this.$vm)
          // console.log(isComputed, variable)
        } else {
          CompilerUtils.text(variable, node, this.$vm)
        }
      }
    }
    compileElement(node) {
      const attrs = [...node.attributes];
      attrs.forEach(attr => {
        const name = attr.name
        if (name.includes('v-')) {
          //处理指令
          const value = attr.value;
          const [, type] = name.split('v-');
          CompilerUtils[type](value, node, this.$vm)
        } else if (name.includes('@')) {
          //处理事件
          const [, event] = name.split('@')
          const method = attr.value;
          // console.log(method, event, node)
          CompilerUtils['addEvent'](method, event, node, this.$vm)
        }
      })
    }
  }
  //计算属性
  class ComputedWathcer {
    constructor(variable, vm, cb) {
      this.variable = variable;
      this.vm = vm;
      this.cb = cb;
      this.value = this.getValue();
    }
    getValue() {
      Dep.target = this;
      let value = this.vm.$computed[this.variable].call(this.vm);
      Dep.target = null;
      return value;
    }
    update() {
      let newValue = this.vm.$computed[this.variable].call(this.vm);
      if (newValue !== this.value) this.cb(newValue, this.value)
    }
  }
  //模板编译工具
  const CompilerUtils = {
    addEvent(method, event, node, vm) {
      node.addEventListener(event, (...args) => {
        vm[method].apply(vm, args)
      })
    },
    textUpdater(node, value) {
      node.textContent = value
    },
    computed(variable, node, vm) {
      let fn = this.textUpdater;
      let computedIns = new ComputedWathcer(variable, vm, (nv, ov) => {
        fn && fn(node, nv)
      })
      fn && fn(node, computedIns.value)
    },
    text(variable, node, vm) {
      let fn = this.textUpdater;
      let value = this.getValue(variable, vm);
      new Watcher(variable, vm, (newValue) => {
        fn && fn(node, newValue)
      })
      fn && fn(node, value)
    },
    getTextVariable(variable) {
      let reg = /\{\{(.+?)\}\}/g;
      let res = variable.replace(reg, ($0, $1) => $1)
      return res;
    },
    getValue(variable, vm) {
      return variable.split('.').reduce((prev, next) => {
        return prev[next]
      }, vm.$data)
    },
    setValue(variable, vm, newValue) {
      const keys = variable.split('.')
      const len = keys.length;
      keys.reduce((prev, next, index) => {
        if (index === len - 1) prev[next] = newValue
        return prev[next]
      }, vm.$data)
    },
    inputUpdater(node, value) {
      node.value = value;
    },
    model(variable, node, vm) {
      const value = this.getValue(variable, vm);
      const fn = this.inputUpdater
      fn && fn(node, value)
      node.addEventListener('input', (event) => {
        const newValue = event.target.value
        if (newValue !== value) this.setValue(variable, vm, newValue)
      })
      new Watcher(variable, vm, (newValue) => {
        fn && fn(node, newValue)
      })
    }
  }
  //劫持数据 双向绑定
  class Observer {
    constructor(data) {
      this.observe(data)
    }
    observe(data) {
      if (!data || typeof data !== 'object') return;
      Object.keys(data).forEach(key => {
        this.defineReactive(key, data[key], data);
        if (typeof data[key] === 'object') this.observe(data[key])
      })
    }
    defineReactive(key, value, data) {
      let dep = new Dep();
      let _this = this;
      Object.defineProperty(data, key, {
        get() {
          Dep.target && dep.addSub(Dep.target)
          return value
        },
        set(newValue) {
          if (newValue !== value) {
            value = newValue;
            _this.observe(newValue);
            dep.notify();
          }
        }
      })
    }
  }
  class Dep {
    constructor() {
      this.subs = []
    }
    addSub(wathcer) {
      this.subs.push(wathcer)
    }
    notify() {
      this.subs.forEach(w => w.update())
    }
  }
  class Watcher {
    constructor(variable, vm, cb) {
      this.variable = variable
      this.vm = vm
      this.cb = cb
      this.value = this.get();
    }
    get() {
      Dep.target = this;
      const value = CompilerUtils.getValue(this.variable, this.vm);
      Dep.target = null;
      return value
    }
    update() {
      let newValue = CompilerUtils.getValue(this.variable, this.vm);
      let oldValue = this.value;
      if (newValue !== oldValue) this.cb(newValue, oldValue)
    }
  }


  new Vue({
    data() {
      return {
        msg: '第一次测试',
        name: 'mike',
        age: {
          max: 100,
          min: 10
        }
      }
    },
    watch: {
      msg(newV, oldV) {
        console.log(newV, oldV)
      }
    },
    computed: {
      hello() {
        return `你好我是mike, 今年${this.age.max * 2}岁。`;
      }
    },
    methods: {
      beBiggerAge() {
        this.msg = '我可以活到 200 岁了';
        // this.age.max = 200;
      }
    },
    created() {
      this.msg = 'created 已创建';
      console.log('created')
    },
    mounted() {
      console.log('mounted')
    }
  }).$mount('#app');
复制代码

可以直接测试 欢迎指正,后期会加上注释.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享