「vue.jsの手書き」記事ノート

序文

引用記事:Vue.jsはどのようにMVVMを実装しますか?
上記のmvvmの説明は非常に優れており、多くのことを学びました。記事はついにvue.jsを実現し、5〜6回ノックし、最後に3つの最適化を行いました。

  1. compile.jsファイルのcreateHandleElement関数
  2. xvue.jsのproxydataプロキシ関数を削除します。
  3. マルチレベルリスニングを修正

ps1:このコードは純粋に手作業で実装することを強くお勧めします。また、深く読むことをお勧めします。元の作成者のコメントは非常に優れ
ています。ps2:作成者はconsole.log(dep.deps)をコメントアウトしています。コード、あなたはそれについて考えることができます何が出力されるのか。

コードで使用される知識ポイント

このコードで使用されている知識のポイントを要約します。

  1. 閉鎖
  2. Object.defienPrototype
  3. バインドの使用
  4. オブザーバーモード
  5. フラグメント最適化レンダリング

ちなみに、実現の過程で、これらの知識のポイントを学ぶこともできます。

私のコード

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