让前端飞Vuejs

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

2018-10-28  本文已影响6人  passMaker

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>

单向绑定总结

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

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

上一篇下一篇

猜你喜欢

热点阅读