美文网首页
JS 简易实现 Vue 的 Watcher,Observer,D

JS 简易实现 Vue 的 Watcher,Observer,D

作者: 风之化身呀 | 来源:发表于2019-12-01 17:23 被阅读0次

    整体思路:Observer 作为发布者,Watcher 作为订阅者,Compiler 将二者连接起来

    1、实现 Observer

    function Obsever(data) {
      Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
    }
    
    function observe(data) {
      if (typeof data !== 'object' || data === null) {
        return
      }
      return new Obsever(data)
    }
    
    function defineReactive(obj, key, val) {
      observe(val)
      var dep = new Dep()
      Object.defineProperty(obj, key, {
        configurable: false,
        enumerable: true,
        get: function () {
          Dep.target && dep.add(Dep.target)
          return val
        },
        set: function (newValue) {
          if (newValue !== val) {
            val = newValue
            dep.notify(newValue)
          }
        }
      })
    }
    
    function Dep() {
      this.subs = []
    }
    
    Dep.target = null
    
    Dep.prototype = {
      constructor: Dep,
      add: function (sub) {
        this.subs.push(sub)
      },
      notify: function (value) {
        this.subs.forEach(sub => sub.update(value))
      }
    }
    

    2、实现 Watcher

    function Watcher(vm, exp, cb) {
      this.vm = vm;
      this.exp = exp;
      this.cb = cb;
      this.val = this.get()
    }
    
    Watcher.prototype = {
      constructor: Watcher,
      get: function () {
        Dep.target = this;
        var val = this.vm[this.exp];
        Dep.target = null;
        return val
      },
      update: function (newValue) {
        this.cb(newValue)
      }
    }
    

    3、实现 Compiler

    function Compiler(vm, el) {
      var dom = document.querySelector(el);
      this.vm = vm;
      this.$el = dom;
      this.init();
    }
    
    Compiler.prototype = {
      constructor: Compiler,
      init: function () {
        var childNodes = this.$el.childNodes;
        [].slice.call(childNodes).forEach(child => this.compile(child));
      },
      compile: function (child) {
        if (child.nodeType === 3) {
          var val = child.textContent;
          var reg = /\{\{(.*)\}\}/;
          var result = val.match(reg);
          if (!result) return;
          this.bind(val.match(reg)[1], "text", child);
        } else {
          var attrs = child.attributes;
          [].slice.call(attrs).forEach(attr => {
            var directiveReg = /v-/,
              eventDirectiveReg = /v-on/,
              attrName = attr.name,
              attrValue = attr.value;
            if (attrName.match(directiveReg)) {
              if (attrName.match(eventDirectiveReg)) {
                this.event(attrName.slice(5), attrValue, child);
              } else {
                this.bind(attrValue, attrName.slice(2), child);
              }
              child.removeAttribute(attrName);
            }
          });
          [].slice.call(child.childNodes).forEach(child => this.compile(child));
        }
      },
      bind: function (exp, type, node) {
        var me = this;
        if (type === "model") {
          node.addEventListener("input", function (e) {
            me.vm[exp] = e.target.value;
          });
        }
        var updateFn = updateUtil[type];
        console.log('me.vm[exp]', me.vm[exp])
        updateFn && updateFn(node, me.vm[exp]);
        new Watcher(me.vm, exp, function () {
          updateFn && updateFn(node, me.vm[exp]);
        });
      },
      event: function (eventType, exp, node) {
        var handler = this.vm.options.methods[exp].bind(this.vm)
        node.addEventListener(eventType, handler, false);
      }
    };
    
    var updateUtil = {
      text: function (node, newValue) {
        node.textContent = newValue;
      },
      model: function (node, newValue) {
        node.value = newValue;
      }
    };
    

    4、实现 mvvm

    function MVVM(options) {
      this.options = options
      this.proxy(options.data)
      this.init(options)
    }
    MVVM.prototype = {
      constructor: MVVM,
      init: function (opt) {
        observe(opt.data)
        this.initComputed()
        this.initWatch()
        this.$compiler = new Compiler(this, opt.el)
      },
      initComputed() {
        var computed = this.options.computed
        Object.keys(computed).forEach(key => {
          var valueFn = computed[key].bind(this)
          Object.defineProperty(this, key, {
            configurable: false,
            enumerable: true,
            get: function () {
              return valueFn()
            },
            set: function (newValue) {
              return newValue
            }
          })
        })
      },
      initWatch() {
        var watch = this.options.watch
        Object.keys(watch).forEach(key => {
          new Watcher(this, key, watch[key].bind(this))
        })
      },
      proxy: function (data) {
        Object.keys(data).forEach(key => {
          Object.defineProperty(this, key, {
            configurable: false,
            enumerable: true,
            get: function () {
              return data[key]
            },
            set: function (newValue) {
              return data[key] = newValue
            }
          })
        })
      }
    }
    

    5、测试

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>MVVM 框架测试</title>
      <script src="./obsever.js"></script>
      <script src="./watcher.js"></script>
      <script src="./compiler.js"></script>
      <script src="./mvvm.js"></script>
    </head>
    
    <body>
      <div id="app">
        <input type="text" v-model="message">
        <span>{{message}}</span>
        <span>{{messages}}</span>
        <button v-on:click="handleClick">改变model</button>
      </div>
    
      <script>
        var vm = new MVVM({
          el: '#app',
          data: {
            message: 'I am test message'
          },
          methods: {
            handleClick: function () {
              this.message = 'test'
            }
          },
          computed: {
            messages: function () {
              return this.message + '!'
            }
          },
          watch: {
            message: function () {
              console.log('watched!', this.message)
            }
          }
        })
      </script>
    </body>
    
    </html>
    

    参考

    相关文章

      网友评论

          本文标题:JS 简易实现 Vue 的 Watcher,Observer,D

          本文链接:https://www.haomeiwen.com/subject/sgazwctx.html