最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写一个简单伪MVVM模式

    正文概述 掘金(小桂summer)   2020-12-16   614

    前言

    我将参考下面这张图 实现一个简单的伪MVVM模式 主要针对compile, observer, watcher, dep 四个文件 手写一个简单伪MVVM模式

    目录结构

    • js
      • compile.js
      • dep.js
      • observer.js
      • vue.js
      • watcher.js
    • index.html

    index.html

    <div id="app">
      <section>
        <input type="text" v-model="message.a">
        <div>
          {{message.a}} - {{message.a}} - {{message.b}}
        </div>
      </section>
    
      <section>
        <div>123</div>
        <p><span></span></p>
        <ul>
          <li></li>
          <li></li>
          <li></li>
        </ul>
      </section>
    
      <section>
        <button v-on:click="clickMe">click me !</button>
        <h2>{{title}}</h2>
      </section>
    </div>
    
    <script src="./js/dep.js"></script>
    <script src="./js/watcher.js"></script>
    <script src="./js/observer.js"></script>
    <script src="./js/compile.js"></script>
    <script src="./js/vue.js"></script>
    <script>
      let vm = new Vue ({
        el: '#app',
        data: {
          message: {
            a: 'hello MVVM',
            b: 'new vue'
          },
          title: 'vue'
        },
        mounted() {
          setTimeout(() => {
            this.title = 'vue 3000'
          }, 3000)
        },
        methods: {
          clickMe() {
            console.log('clickMe ~ title', this.title)
            this.title = 'vue code'
          }
        }
      })
    </script>
    

    vue.js

    class Vue {
      constructor(options) {
        this.$el = options.el
        this.$data = options.data
        this.methods = options.methods
    
        
        if (this.$el) {
          /** 核心-数据劫持 */
          new Observer(this.$data)
          /** 核心-编译模板 */
          new Compile(this.$el, this)
    
          /** 辅助-数据代理到this */
          this.proxyKeys()
          /** 辅助-生命周期mounted(所有的事情处理好执行) */
          options.mounted.call(this)
        }
    
      }
    
      proxyKeys() {
        Object.keys(this.$data).forEach(key => {
          Object.defineProperty(this, key, {
            enumerable: false,
            configurable: true,
            get() {
              return this.$data[key]
            },
            set(newValue) {
              this.$data[key] = newValue
            }
          })
        })
      }
    }
    

    compile.js

    class Compile {
      constructor(el, vm) {
        this.el = this.isNodeElement(el) ? el : document.querySelector(el)
        this.vm = vm
    
        if (this.el) {
          let fragment = this.nodeToFragment(this.el)
          this.compile(fragment)
          this.el.appendChild(fragment)
        }
      }
    
      /** 辅助方法 */
      isNodeElement(node) { return node.nodeType === 1 }
      isDirective(name) { return name.includes('v-') }
      isEventDirective(name) { return name.indexOf('on:') > -1 }
    
      /** 核心方法 */
      /**
       * @description: 编译元素
       */
      compileElement(node) {
        let nodeAttrs = node.attributes
        Array.from(nodeAttrs).forEach(attr => {
          if (this.isDirective(attr.name)) {
            if (this.isEventDirective(attr.name)) {
              const [, eventType] = attr.name.split(':')
              let method = attr.value
              compileUtil['event'](this.vm, node, eventType, method)
            } else { // v-model
              let expr = attr.value
              const [, type] = attr.name.split('-')
              compileUtil[type](this.vm, node, expr)
            }
          }
          
        })
      }
      /**
       * @description: 编译文本
       */
      compileText(node) {
        let textExpr = node.textContent
        if (RegText.test(textExpr)) {
          compileUtil['text'](this.vm, node, textExpr)
        }
      }
      
      /**
       * @description: 编译
       */
      compile(fragment) {
        let childNodes = fragment.childNodes
        Array.from(childNodes).forEach(node => {
          if (this.isNodeElement(node)) {
            this.compileElement(node)
            this.compile(node)
          } else {
            this.compileText(node)
          }
        })
      }
    
      /**
       * @description: 转为文档碎片
       */
      nodeToFragment(el) {
        let fragment = document.createDocumentFragment()
        let firstChild = el.firstChild
        while(firstChild) {
          fragment.appendChild(firstChild)
          firstChild = el.firstChild
        }
        return fragment
      }
    
    }
    
    const RegText = /\{\{([^}]+)\}\}/g
    
    /** 指令工具包 */
    const compileUtil = {
      /** 辅助方法 */
      getValue(vm, expr) {
        expr = expr.split('.')
        return expr.reduce((prev, next) => {
          return prev[next]
        }, vm.$data)
      },
      getTextValue(vm, textExpr) {
        return textExpr.replace(RegText, (...argments) => {
          let expr = argments[1]
          return this.getValue(vm, expr)
        })
      },
      setModelValue(vm, expr, value) {
        expr = expr.split('.')
        return expr.reduce((prev, next, currentIndex) => {
          if (currentIndex == expr.length-1) {
            prev[next] = value
          }
          return prev[next]
        }, vm.$data)
      },
      /** 核心方法 */
      text(vm, node, textExpr) {
        let textValue = this.getTextValue(vm, textExpr)
        let updaterFn = this.updater['textUpdater']
        updaterFn && updaterFn(node, textValue)
        
        textExpr.replace(RegText, (...argments) => {
          new Watcher(vm, argments[1], () => {
            updaterFn && updaterFn(node, this.getTextValue(vm, textExpr))
          })
        })
      },
      model(vm, node, expr) {
        let value = this.getValue(vm, expr)
        let updaterFn = this.updater['modelUpdater']
        updaterFn && updaterFn(node, value)
    
        new Watcher(vm, expr, () => {
          updaterFn && updaterFn(node, this.getValue(vm, expr))
        })
    
        // 监听事件
        node.addEventListener('input', e => {
          let newValue = e.target.value
          this.setModelValue(vm, expr, newValue)
        })
    
      },
      event(vm, node, eventType, method) {
        node.addEventListener(eventType, (e) => {
          e.preventDefault && e.preventDefault()
          let methodFn = vm.methods[method]
          methodFn && methodFn.call(vm)
        })
        
      },
      updater: {
        modelUpdater(node, value) {
          node.value = value
        },
        textUpdater(node, value) {
          node.textContent = value
        }
      }
    }
    

    dep.js

    class Dep {
      constructor() {
        this.subs = []
      }
    
      addSub(watcher) {
        this.subs.push(watcher)
      }
    
      notify() {
        this.subs.forEach(watcher => watcher.update())
      }
    }
    
    Dep.target = null
    

    obsever.js

    class Observer {
      constructor(data) {
        this.data = data
        this.observer(data)
      }
    
      observer(data) {
        if (!data || typeof data !== 'object') { return }
    
        Object.keys(data).forEach(key => {
          this.defineReactive(data, key, data[key])
          this.observer(data[key])
        })
      }
    
      defineReactive(data, key, value) {
        let self = this
        let dep = new Dep()
        Object.defineProperty(data, key, {
          configurable: true,
          enumerable: true,
          get() {
            Dep.target && dep.addSub(Dep.target)
            return value
          },
          set(newValue) {
            if (newValue != value) {
              self.observer(newValue)
              value = newValue
              dep.notify()
            }
          }
        })
      }
    
    }
    

    watcher.js

    class Watcher {
      constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
    
        // 存储老值
        this.oldValue = this.getValue()
      }
    
      getObserverValue(vm, expr) {
        expr = expr.split('.') // [message.a]
        return expr.reduce((prev, next) => {
          return prev[next]
        }, vm.$data)
      }
    
      getValue() {
        Dep.target = this
        let value = this.getObserverValue(this.vm, this.expr)
        Dep.target = null
        return value
      }
    
      update() {
        let newValue = this.getObserverValue(this.vm, this.expr)
        if (newValue != this.oldValue) {
          this.oldValue = newValue
          this.cb()
        }
      }
    }
    

    效果图

    手写一个简单伪MVVM模式


    起源地下载网 » 手写一个简单伪MVVM模式

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元