《手写一个vue.js》文章笔记

前言

引用文章: Vue.js 是如何实现 MVVM 的?
上文中对mvvm的讲解很有一套,收获颇多. 文章最后实现了vue.js. 自己敲了五六遍,最后做了三点优化:

  1. compile.js 文件中 createHandleElement函数
  2. 删除xvue.js 中的proxydata代理函数.
  3. 修复多级侦听

ps1: 强烈建议自己可以纯手打实现这个代码. 也建议深读一下,原作者写的注释很好.
ps2: 作者在代码中有一句console.log(dep.deps)注释掉了,各位可以想一下会输出什么.

代码用到的知识点

这个代码用到的知识点,我总结一下:

  1. 闭包
  2. Object.defienPrototype
  3. bind的使用
  4. 观察者模式
  5. fragement优化渲染

在实现的过程中,也可以顺便把这些知识点学习一下。

我的代码

1. index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"  />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" >
    <title>123</title>
  </head>
  <body>
    <div id="app">
      {
   
   {test}}
      <div v-text='test'></div>
      <p>
        <input type="text" v-model='test' />
      </p>
      <p v-html='html'></p>
      <p>
        <button @click='onClick'>按钮</button>
      </p>
    </div>
    <script src="./compile.js"></script>
    <script src="./XVue.js"></script>
    <script>
      var o = new XVue({
        el:'#app',
        data:{
          test:'123',
          foo:{
            bar:'bar'
          },
          html:'<button>test html</button>'
        },
        methods:{
          onClick(){
            alert('点击按钮!')
          }
        }
      })
      console.log(o.$data.test)
      o.$data.test = 'hello vue'
      console.log(o.$data.test)
    </script>
  </body>
</html>

2. XVue.js

class XVue {
  constructor(option) {
    this.$data = option.data;
    this.$options = option;
    this.observe(this.$data)
    new Compile(option.el, this)
  }

  observe(obj) {
    if (!obj || typeof obj !== 'object') {
      return
    }

    Object.keys(obj).forEach(key => {
      this.defineReactive(obj, key, obj[key])
    })
  }

  defineReactive(obj, key, value) {
    this.observe(value);

    var dep = new Dep()

    Object.defineProperty(obj, key, {
      get() {
        Dep.target && dep.addDep(Dep.target)
        return value
      },
      set(newVal) {
        value = newVal
        dep.notify()
      }
    })
  }
}


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

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

  notify() {
    this.deps.forEach(dep => {
      dep.update()
    })
  }
}

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

    Dep.target = this;

    if (this.key.indexOf('.') > 0) {
      var value = this.key.split('.').reduce((initial, value) => { return initial[value] }, vm)
      console.log(value)
    } else {
      this.vm[this.key];
    }
    Dep.target = null
  }

  update() {
    if (this.key.indexOf('.') > 0) {
      var value = this.key.split('.').reduce((initial, value) => { return initial[value] }, this.vm)
      this.cb.call(this.vm, value)
    } else {
      this.cb.call(this.vm, this.vm[this.key])
    }
  }
}

3. compile.js

var arr1 = [];

class Compile {
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = document.querySelector(el);
    this.$fragement = this.node2Fragement(this.$el)
    this.compile(this.$fragement)
    this.$el.appendChild(this.$fragement)
  }

  node2Fragement(el) {
    var fragement = document.createDocumentFragment();
    var child
    while (child = el.firstChild) {
      fragement.appendChild(child)
    }
    return fragement;
  }

  compile(el) {
    var childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      if (this.isNodeType(node)) {
        this.compileNode(node)
      } else if (this.isElementType(node) &&
        /\{\{(.*)\}\}/.test(node.textContent)) {
        this.compileElement(node, RegExp.$1)
      }

      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }

  isNodeType(node) {
    return node.nodeType == 1;
  }

  isElementType(node) {
    return node.nodeType == 3;
  }

  compileNode(node) {
    var attrs = node.attributes;
    Array.from(attrs).forEach(attr => {
      var name = attr.name;
      var exp = attr.value;
      if (this.isDirective(name)) {
        const dir = name.substr(2)

        this[dir] && this[dir](node, this.$vm, exp)
      } else if (this.isDirectiveEvent(name)) {
        const dir = name.substr(1)
        this.createHandleElement(node, this.$vm, exp, dir)
      }
    })
  }

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

  html(node, vm, exp) {
    this.update(node, vm, exp, 'html')
  }

  model(node, vm, exp) {
    this.update(node, vm, exp, 'model')

    node.addEventListener('input', function (el) {
      vm.$data[exp] = el.target.value;
    })
  }

  update(node, vm, exp, dir) {
    var fn = this[dir + 'Updater'];

    if (exp.indexOf('.') > 0) {
      var value = exp.split('.').reduce((initial, value) => { return initial[value] }, vm.$data)
      fn && fn(node, value);
    } else {
      fn && fn(node, vm.$data[exp]);
    }

    new Watcher(vm.$data, exp, function (value) {
      fn && fn(node, value)
    })
  }

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

  htmlUpdater(node, value) {
    node.innerHTML = value
  }

  modelUpdater(node, value) {
    node.value = value
  }

  isDirective(str) {
    return str.indexOf('v-') === 0;
  }

  isDirectiveEvent(str) {
    return str.indexOf('@') === 0;
  }

  compileElement(node, value) {
    this.text(node, this.$vm, value)
  }

  createHandleElement(node, vm, exp, dir) {
    var fn = vm.$options && vm.$options.methods && vm.$options.methods[exp]
    if (dir && fn) {
      node.addEventListener(dir, fn.bind(vm), false)
    }
  }
}

猜你喜欢

转载自blog.csdn.net/a519991963/article/details/103899490
今日推荐