• 手写一个MVVM


    最近看了珠峰的架构课——实现一个MVVM。

    首先,我们来了解一下什么是MVVM。

    MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。

    先贴一下代码,然后再做分析。

    <!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>myMVVM</title>
    </head>
    <body>
      <div id="root">
        <input type="text" v-model='person.name' >
        <h1>{{message}}</h1>
        <ul>
          <li>1</li>
          <li>2</li>
        </ul>
        {{person.age}}
        <br />
        <button v-on:click="change">点我</button>
        <br />
        {{getNewName}}
        <div v-html="message"></div>
      </div>
      <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
      <script src="Vue.js"></script>
      <script>
          let vm = new Vue({
            el: '#root',
            data: {
              message: 'she is a bad girl',
              person: {
                age: 20,
                name: 'zhangsan'
              }
            },
            computed: {
              getNewName() {
                return this.person.name + 'hahaha'
              }
            },
            methods: {
              change() {
                console.log("11111")
              }
            }
            
          })
      </script>
    </body>
    </html>

    MVVM的简易实现(还有很多功能没有涉及到)

    /**
     * 订阅发布 调度中心
     */
    class Dep {
      constructor() {
        this.subs = [] // 存放所有的watcher
      }
      // 添加watcher, 订阅
      addSub(watcher) {
        this.subs.push(watcher)
      }
      // 发布
      notify() {
        this.subs.forEach(watcher => watcher.update())
      }
    }
    
    /**
     *  观察者模式 观察者,被观察者
     */
    class Watcher {
      constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
    
        // 默认先存放旧值
        this.oldValue = this.get(vm, expr)
      }
      // 获取旧的值
      get() {
        Dep.target = this
        let value = CompileUtil.getVal(this.vm, this.expr)
        Dep.target = null
        return value
      }
      // 数据更新
      update() {
        let newVal = CompileUtil.getVal(this.vm, this.expr)
        if(newVal != this.oldValue) {
          this.cb(newVal)
        }
      }
    }
    
    /**
     * 实现数据劫持
     */
    class Observer {
      constructor(data) {
        this.observe(data)
      }
      observe(data) {
        if(data && typeof data == 'object') {
          for(let key in data) {
            this.defineReactive(data, key, data[key])
          }
        }
      }
    
      defineReactive(obj, key, value) {
        this.observe(value)
        let dep = new Dep() // 给每一个属性都加上一个具有发布订阅的功能
        Object.defineProperty(obj, key, {
          get() {
            Dep.target && dep.addSub(Dep.target)
            return value
          },
          set: (newValue) => {
            if(newValue != value) {
              this.observe(newValue)
              value = newValue
              dep.notify()
            }
          }
        })
      }
    }
    
    /**
     * 编译模板
     */
    class Compiler {
      constructor(el, vm) {
        this.vm = vm
        // 判断el是否是个元素,如果不是就获取它
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // console.log(this.el)
        // 把当前节点中的元素放到内存中
        let fragment = this.node2fragment(this.el)
        
        // 把节点中的内容进行替换
    
        // 编译模板 用数据来编译
        this.compile(fragment)
    
        // 把内容再塞回页面中
        this.el.appendChild(fragment)
      }
    
      // 是否是指令 v-开头的
      isDirective(attrName) {
        // startsWith('v-') 或 split('-')
        return attrName.indexOf('v-') !== -1
      }
      // 编译元素
      compileElement(node) {
        let attributes = node.attributes
        // [...attributes] 或 Array.from 等价 Array.prototype.slice.call
        Array.from(attributes).forEach(attr => {
          let { name, value: expr } = attr
          if(this.isDirective(name)) {
            // 
            let [, directive] = name.split('-')
            // console.log(name,node, expr, directive)
            if(directive.indexOf(':' !== -1)) {
              let [directiveName, eventName] = directive.split(':')
              CompileUtil[directiveName](node, expr, this.vm, eventName)
            } else {
              CompileUtil[directive](node, expr, this.vm)
            }
            
          }
        })
      }
      // 编译文本 找{{}}
      compileText(node) {
        let content = node.textContent
        if(/{{(.+?)}}/.test(content)) {
          // console.log(content) // 找到所有文本
          CompileUtil['text'](node, content, this.vm)
        }
      }
    
      // 编译内存中的dom节点 核心的编译方法
      compile(node) {
        let childNodes = node.childNodes
        Array.from(childNodes).forEach(child => {
          if(this.isElementNode(child)) {
            this.compileElement(child)
            // 如果是元素的话,需要除去自己,再去遍历子节点
            this.compile(child)
          } else {
            this.compileText(child)
          }
        })
      }
      // 把节点移动到内存中 appendChild方法
      node2fragment(node) {
        let fragment = document.createDocumentFragment()
        let firstChild
        while(firstChild = node.firstChild) {
          fragment.appendChild(firstChild)
        }
    
        return fragment
      }
      // 判断是否为元素节点
      isElementNode(node) {
        return node.nodeType === 1
      }
    }
    
    /**
     * 编译工具函数
     */
    CompileUtil = {
      // 根据表达式取对应的数据
      getVal(vm, expr) {
        return expr.split('.').reduce((data, current) => {
          return data[current]
        }, vm.$data)
      },
      getContentVal(vm, expr) {
        return expr.replace(/{{(.+?)}}/g, (...args) => {
          return this.getVal(vm, args[1])
        })
      },
      setVal(vm, expr, value) {
        expr.split('.').reduce((data, current, index, arr) => {
          if(index === arr.length - 1) {
            return data[current] = value
          }
          return data[current]
        }, vm.$data)
      },
      // 解析v-model指令
      model(node, expr, vm) {
        // node.value
        let fn = this.updater['modelUpdater']
        new Watcher(vm, expr, (newVal) => { // 给输入框加一个观察者,稍后数据更新,触发此方法,用新值给输入框赋值
          fn(node, newVal)
        })
        node.addEventListener('input', e => {
          let value = e.target.value
          this.setVal(vm, expr, value)
        })
        let value = this.getVal(vm, expr)
        fn(node, value)
      },
      on(node, expr, vm, eventName) {
        node.addEventListener(eventName, e => {
          vm[expr].call(vm, e)
        })
      },
      text(node, expr, vm) {
        let fn = this.updater['textUpdater']
        let content = expr.replace(/{{(.+?)}}/g, (...args) => {
          // 给表达式每个变量都加上观察者
          new Watcher(vm, args[1], () => {
            fn(node, this.getContentVal(vm, expr)) // 返回一个全的字符串
          })
          return this.getVal(vm, args[1])
        })
        fn(node, content)
      },
      html(node, expr, vm) {
        // node.innerHTML
        let fn = this.updater['htmlUpdater']
        new Watcher(vm, expr, (newVal) => { // 给输入框加一个观察者,稍后数据更新,触发此方法,用新值给输入框赋值
          fn(node, newVal)
        })
        let value = this.getVal(vm, expr)
        fn(node, value)
    
      },
      updater: {
        modelUpdater(node, value) {
          node.value = value
        },
        textUpdater(node, value) {
          node.textContent = value
        },
        htmlUpdater(node, value) {
          node.innerHTML = value
        }
      }
    }
    
    /**
     * Vue构造函数
     */
    class Vue {
      constructor(options) {
        this.$el = options.el
        this.$data = options.data
    
        let computed = options.computed
        let methods = options.methods
    
        if(this.$el) {
          //  做数据劫持
          new Observer(this.$data)
          // console.log(this.$data)
    
          for(let key in computed) { // 依赖关系
            Object.defineProperty(this.$data, key, {
              get: () => {
                return computed[key].call(this)
              }
            })
          }
    
          for(let key in methods) {
            Object.defineProperty(this, key, {
              get() {
                return methods[key]
              }
            })
          }
    
          // vm上的取值操作都代理上vm.$data上
          this.proxyVm(this.$data)
    
          // 编译模板
          new Compiler(this.$el, this)
        }
      }
      // 代理
      proxyVm(data) {
        for(let key in data) {
          Object.defineProperty(this, key, {
            get() {
              return data[key]
            },
            set(newVal) {
              data[key] = newVal
            }
          })
        }
      }
    }

    未完待续......

  • 相关阅读:
    VS2008 Ajax Toolkit 控件(修正后转载)
    AD操作类[修改版]
    java中使用Rhino执行js文件中的function
    XSTL中单引号的问题
    [转]ADSearch User Property Attributes
    2008我的健身计划
    spring执行定时任务
    分页存储过程,支持多表查询,效率还行
    今天写一个读卡器程序,操作AD时的一个Exception:The authentication mechanism is unknown
    httpclient模拟windows认证
  • 原文地址:https://www.cnblogs.com/zyl-Tara/p/11263687.html
Copyright © 2020-2023  润新知