美文网首页让前端飞Vuejs
实现 MVVM (四)- MVVM 单向绑定实现

实现 MVVM (四)- MVVM 单向绑定实现

作者: passMaker | 来源:发表于2018-10-28 16:06 被阅读6次

    MVVM 单向绑定实现

    回顾 MVVM

    MVVM 是一种用于把数据和 UI 分离的设计模式。Model 表示应用程序使用的数据,View 是与用户进行交互的桥梁。ViewModel 充当数据转换器,将 Model 信息转换为 View 的信息,将命令从 View 传递到 Model

    MVVM 单向绑定 Demo

    JSbin

    代码实现

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <title>MVVM 单向绑定</title>
    </head>
    <body>
    
    <div id="app" >
      <h1>{{name}}  {{number}}</h1>
    </div>
    
    <script>
    // 观察数据
    function observe(data) {
      if(!data || typeof data !== 'object') return
      for(var key in data) {
        let val = data[key]
        let subject = new Subject()   //遍历属性的过程中,对于每一个属性 new Subject()
        Object.defineProperty(data, key, {
          enumerable: true,
          configurable: true,
          get: function() {
            console.log(`get ${key}: ${val}`)
    
            /*** 如果 currentObserver 出现,将观察者订阅到该主题  ***/
            if(currentObserver){
              console.log('has currentObserver')
              currentObserver.subscribeTo(subject)
            }
    
            return val
          },
          set: function(newVal) {
            val = newVal
            console.log('start notify...')
            //通知订阅者 执行 notify()
            subject.notify()
          }
        })
        if(typeof val === 'object'){
          observe(val)
        }
      }
    }
    
    let id = 0
    let currentObserver = null
    
    class Subject {
      constructor() {
        this.id = id++
        this.observers = []
      }
      addObserver(observer) {
        this.observers.push(observer)
      }
      removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if(index > -1){
          this.observers.splice(index, 1)
        }
      }
      notify() {
        this.observers.forEach(observer => {
          observer.update()
        })
      }
    }
    
    // 观察者
    class Observer{
      constructor(vm, key, callback) {
        this.subjects = {}  // 订阅主题
        this.vm = vm   // mvvm 对象
        this.key = key  // data 的属性
        this.callback = callback  //callback
        this.value = this.getValue()
      }
      // 更新值
      update(){
        let oldVal = this.value
        let value = this.getValue()
        // 彻底更新时,才会做改变
        if(value !== oldVal) {
          this.value = value
          this.callback.bind(this.vm)(value, oldVal)
        }
      }
      subscribeTo(subject) {
        if(!this.subjects[subject.id]){
          console.log('subscribeTo.. ', subject)
           subject.addObserver(this)
           this.subjects[subject.id] = subject
        }
      }
      /*** 观察者变为全局 currentObserver ***/
      getValue(){
        currentObserver = this
        let value = this.vm.$data[this.key]    //相当于new Observer 之后会调用 observe 中的 get,然后执行其中的 if(currentObserver)
        currentObserver = null
        return value
      }
    }
    
    
    
    class mvvm {
      constructor(opts) {
        this.init(opts)  //初始化
        observe(this.$data)  //监听
        this.compile()  //解析模板
      }
      init(opts){
        this.$el = document.querySelector(opts.el)    //获取当前元素 el: 下的id
        this.$data = opts.data
        this.observers = []
      }
      // 解析 el 模板
      compile(){
        this.traverse(this.$el)
      }
      // 递归遍历情况 DOM nodeType 属性
      traverse(node){
        if(node.nodeType === 1){
          node.childNodes.forEach(childNode=>{
            this.traverse(childNode)
          })
        }else if(node.nodeType === 3){ //文本
          this.renderText(node)   // 渲染
        }
      }
      // 渲染
      renderText(node){
        let reg = /{{(.+?)}}/g   //正则取双大括号里面的内容
        let match
        while(match = reg.exec(node.nodeValue)){
          let raw = match[0]  // 原始
          let key = match[1].trim()  // 插值表达式内的值
          node.nodeValue = node.nodeValue.replace(raw, this.$data[key])   // 替换大括号插值表达式里的内容为 data里面的数据
    
          // Observer 方法 创建观察者
          new Observer(this, key, function(val, oldVal){
            node.nodeValue = node.nodeValue.replace(oldVal, val)
          })
        }
      }
    
    }
    
    let vm = new mvvm({
      el: '#app',
      data: {
        name: 'hello world',
        number: 0
      }
    })
    
    setInterval(function(){
      vm.$data.number++
    }, 1000)
    
    
    </script>
    </body>
    </html>
    

    单向绑定总结

    实现单向数据绑定,只需要做两件事。

    • 观察 data 数据:通过 observe()
    • 解析模板:通过 class mvvm 构造函数中的 compile()

    当数据发生改变的时候,就通过观察者 和 发布/订阅 来进行改变。

    相关文章

      网友评论

        本文标题:实现 MVVM (四)- MVVM 单向绑定实现

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