VUE MVVM实现

VUE MVVM实现

详细代码请参考: https://github.com/osorso/VUE_MVVM

理解MVVM

mvvm - Model View ViewModel 数据 视图 视图模型

其中Model ---> data, View ---> template, ViewModel ---> new Vue({...})

view通过绑定事件的方式将model联系在一起, model可以通过数据绑定的形式影响view, 而这两种联系则是通过viewModel实现的

mvvm.png

实现原理

创建html



  
    
    
    vue响应式实现
  

  
    

{{ person.name }} ----- {{ person.age }}

{{ person.fav }}

  • 123
  • 123
  • 123

{{ msg }}

创建一个VUE实例类

class Vue{
  constructor(options) {
    this.$el = options.el
    this.$data = options.data
    this.$options = options
    if (this.$el) {
      new Observer(this.$data)
      new Compile(this.$el, this)
    }
  }
}

通过创建vue实例,将data, el存在时, 创建数据观察者Observe以及指令解析器Compile

实现compile(指令解析器)

class Compile{
  constructor(el, vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el)
    this.vm = vm
    const fragment = this.nodeFragment(this.el)
    this.compile(fragment)
    this.el.appendChild(fragment) // 追加子元素到根元素
  }
}

​ fragment此方法为获取文档碎片对象,将其放入内存中,减少回流重绘

nodeFragment(el) {
    // 创建文档碎片
    const f = document.createDocumentFragment()
    let firstChild;
    while (firstChild = el.firstChild) {
        f.appendChild(firstChild)
    }
    return f
}

​ compile(编译模版)

compile(fragment) {
    // 获取子节点
    const childNodes = fragment.childNodes;
    [...childNodes].forEach(child => {
        if (this.isElementNode(child)) {
            this.compileElement(child)
        } else {
            this.compileText(child)
        }
        if (child.childNodes && child.childNodes.length) {
            this.compile(child)
        }
    })
}

compileElement(node) {
    const attributes = node.attributes
    ;[...attributes].forEach(attr => {
        const { name, value } = attr
        if (this.isDirective(name)) { // 获取到指令v-html v-text等
            const [,dirctive] = name.split('-') // text, html, model on:click等
            const [dirName, eventName] = dirctive.split(':') // text html model on
            // 更新数据, 数据驱动视图
            compileUtil[dirName](node, value, this.vm, eventName)
            // 删除有指令的标签上的属性
            node.removeAttribute('v-' + dirctive)
        } else if(this.isEventName(name)) {
            let [,eventName] = name.split('@')
            compileUtil['on'](node, value, this.vm, eventName)
        }
    })
}

compileText(node) {
    const content = node.textContent
    if (/\{\{(.+?)\}\}/.test(content)) {
        compileUtil['text'](node, content, this.vm)
    }
}

isEventName(attrName) {
    return attrName.startsWith('@')
}

nodeFragment(el) {
    // 创建文档碎片
    const f = document.createDocumentFragment()
    let firstChild;
    while (firstChild = el.firstChild) {
        f.appendChild(firstChild)
    }
    return f
}

isDirective(attrName) {
    return attrName.startsWith('v-')
}

isElementNode(node) {
    return node.nodeType === 1
}

compile中解析模板的方法

const compileUtil = {
  getVal(expr, vm) { // 获取div v-text="person.name">
中的person.name这个属性--使得可以在$data顺利取得此值 return expr.split('.').reduce((data, currentVal) => { currentVal = currentVal.trim() return data[currentVal]; }, vm.$data) }, setVal(expr, vm, inputVal) { return expr.split('.').reduce((data, currentVal) => { currentVal = currentVal.trim() data[currentVal] = inputVal }, vm.$data) }, getContentVal(expr, vm) { return expr.replace(/\{\{(.+?)\}\}/g, (...args) => { return this.getVal(args[1], vm) }) }, text(node, expr, vm) { // expr---msg let value; if (expr.indexOf('{{') !== -1) { value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => { // 绑定观察者,数据发生变化时触发 进行更新 new Watcher(vm, args[1], () => { this.updater.textUpdater(node, this.getContentVal(expr, vm)) }) return this.getVal(args[1], vm) }) } else { value = this.getVal(expr, vm) new Watcher(vm, expr, (newVal) => { this.updater.textUpdater(node, newVal) }) } this.updater.textUpdater(node, value) }, html(node, expr, vm) { const value = this.getVal(expr, vm) new Watcher(vm, expr, (newVal) => { this.updater.htmlUpdater(node, newVal) }) this.updater.htmlUpdater(node, value) }, // 实现双向数据绑定 model(node, expr, vm) { const value = this.getVal(expr, vm) // 绑定更新函数 数据=> 视图 new Watcher(vm, expr, (newVal) => { this.updater.modelUpdater(node, newVal) }) // 视图 => 数据 => 视图 node.addEventListener('input', (e) => { this.setVal(expr, vm, e.target.value) }) this.updater.modelUpdater(node, value) }, on(node, expr, vm, eventName) { let fn = vm.$options.methods && vm.$options.methods[expr] node.addEventListener(eventName, fn.bind(vm), false) }, bind(node, expr, vm, attr) {}, // 更新的函数 updater: { textUpdater(node, value) { node.textContent = value }, htmlUpdater(node, value) { node.innerHTML = value }, modelUpdater(node, value) { node.value = value } } }

实现Observe(数据劫持)

class Observer{
  constructor(data) {
    this.observer(data)
  }

  observer(data) {
    if (data && typeof data === 'object') {
      Object.keys(data).forEach(key => {
        this.defineReactive(data, key, data[key])
      })
    }
  }

  defineReactive(obj, key, value) {
    // 递归遍历
    this.observer(value)
    const dep = new Dep()
    // 劫持并监听所有属性
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: false,
      get() {
        // 订阅数据变化时,往Dep中添加观察者
        Dep.target && dep.addSub(Dep.target)
        return value
      },
      set: newVal => {
        this.observer(value) // 如果设置新值时需重新劫持新值,确保不会因为改变值而导致未能劫持数据
        if (newVal !== value) {
          value = newVal
        }
        // 通知变化
        dep.notify()
      }
    })
  }
}

observe通过对数据对象的递归遍历, 结合Object.defineProperty()从而实现劫持各个属性的setter,getter, 从而实现当属性对应的数据变化时,发布消息给订阅者, 通知其变化!

订阅者实现

class Dep{
  constructor() {
    this.sub = []
  }
  // 收集观察者
  addSub(watcher) {
    this.sub.push(watcher)
  }
  // 通知观察者更新
  notify() {
    this.sub.forEach(w => w.update())
  }
}

订阅者的功能主要是收集变化并通知观察者更新,架起了Observe与Watcher之间的桥梁

Watcher(监听器实现)

class Watcher{
  constructor(vm, expr, cb) {
    this.vm = vm
    this.expr = expr
    this.cb = cb
    this.oldVal = this.getOldVal()
  }

  getOldVal() {
    Dep.target = this // 将watcher挂在到dep上
    const oldVal = compileUtil.getVal(this.expr, this.vm)
    Dep.target = null // 获取到值后清除所挂载的watcher
    return oldVal
  }

  update() {
    const newVal = compileUtil.getVal(this.expr, this.vm)
    if (newVal !== this.oldVal) {
      this.cb(newVal)
    }
  }
}

通过Watcher,则可实现Observe与Compile之间的联系,从而实现数据变化驱动视图

实现原理: 往订阅者中添加Watcher,与Observe建立联系, 而Watcher自身有个update()方法,此方法与Compile建立联系,当属性变化时, Observe则会通知Watcher,从而Watcher调用update()方法, 触发Compile中的回调,实现更新

你可能感兴趣的:(VUE MVVM实现)