MVVM

147 阅读4分钟

一、什么是大前端

简单来说是所有前端的统称。特点是一次开发,兼容所有平台。如果我们要做web端、客户端就是,一套代码适用所有平台。正是大前端时代的出现,才孕育出了MVVM架构模式

二、什么是MVC

MVC:架构模式的一种,Model、View、Controller(控制器),目的是将M和V的代码分离。数据传输方向是单向的,视图需要通过控制器,控制器改变数据,数据直接在视图上反馈出来

原理:Controller它是指页面业务逻辑,将V和M的代码分离。V和M需要通过C来承上启下

image.png C可以直接引用M、V,但反过来不可以。M和V直接不可以相互引用(说白点就是,C中可以写M、V的代码,但是M、V只可以写自己的代码)

三、MVVM的诞生

随着数据的复杂,数据解析花费的时间就越长,如果放在c中,c就会越来越臃肿,而且c的定义没有解析这一项。所以,根据面对对象的思想,出现了新类:VIewModel。也因此诞生了一个新的架构模式:MVVM。

MVVM:架构模式的一种,Model、View、VIewModel,数据传输方向是双向的,视图通过DOM事件改变数据,数据通过数据绑定显示视图。(还是有C的,但是C的存在感被降低了)

原理:ViewModel中有一个observe观察者,当数据变化时,VIewModel可以监听到数据的变化,通知视图更新;当用户操作视图时,VIewModel能监听到视图的变化,然后通知Model改变。VIew和VIewModel可以直接相互改变,但是Model和VIewModel则需要observe进行通信 img

四、MVC和MVVM的区别

1、MVC数据单向通信、MVVM是双向通信

2、MVVM中VIewModel没有取代Controller,他是抽离了C中的业务逻辑,其他还是放在C中,这样就实现了业务逻辑组件的复用

五、vue中如何实现的双向绑定

监听-订阅模式

image.png

1、model直接反馈到view上: object.defineProperty给每个属性添加set、get方法,数据更新时触发observe,这时通知Dep(订阅器,管理所有的订阅者),dep去更新并告知是哪个watcher(订阅者),watcher知道是自己改变时,去触发update(),更新视图

2、视图去改变model,两种情况:指令解析、dom事件触发

指令解析:compile循环遍历解析每个指令,解析一次去创建一个新的watcher,并绑定update方法,watcher添加到dep中,同时更新初始化视图

dom事件:去触发属性的set的方法,observe监听到去触发dep.notify(),之后和第一种一样

六、用js实现v-model、v-if和v-show

1.用vue的方式先把html写完
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <input type="text" name="input" v-model="{{name}}"><br/>
    <p>{{name}}</p>
  </div>
  <script src="vm.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
          name: '李鑫'
      }
    })
    
  </script>
</body>
</html>
2.定义vue类
class Vue{
    constructor(options){
        this.el = document.querySelector(options.el)
        this.data = options.data
    }
}
3.为data中的每个数据添加数据劫持
class Vue{
    constructor(options){
        this.initData()
    }
    initData() {
        var _data = {}
        var _this = this
        for(let key in this.data){
        Object.defineProperty(_data, key, {
            set(newValue) {
                _this.data[key] = newValue
            },
            get(){
                return _this.data[key]
            }
        })
    }
  }
}
4.遍历节点,找到v-model及{{name}}
class Vue{
    constructor(options){
        this.initDom()
    }
    initDom() {
        let childNodes = this.el.childNodes
        const _this = this
        for(let item of childNodes){
            console.log('child', item)
            if(item.nodeType === 1){
                const value = item.getAttribute('v-model')
                if(value){
                    const modelName = value.match(/{{(.+?)}}/)[1]
                    item.value = this.data[modelName]
                    item.addEventListener('input', function (e) {
                    _this.data[modelName] = e.target.value
                })
            }
        }
        if(item.childNodes.length != 0){
            const value = item.childNodes[0].data.trim()
            const modelName = value.match(/{{(.+?)}}/)
            if(modelName){
                item.innerHTML = this.data[modelName[1]]
            }
        }
    }
}

到这里就实现了,v-model和{{name}}显示

5.添加订阅器,获取到{{name}}时,存入key对应的item,input不用,因为value等于的值是响应式的
if(item.childNodes.length != 0){
    if(modelName){
        this.dep[modelName[1]] = item
    }
}
set(newValue) {
    _this._data[key] = newValue
    console.log('setProperty', _this.dep[key])
    if(_this.dep[key]) {
        _this.dep[key].innerHTML = newValue
    }          
}
6.所有的代码
class Vue {
  constructor(options) {
    this.el = document.querySelector(options.el)
    this._data = options.data
    this.dep = {}
    this.initData()
    this.initDom()
​
  }
​
  initDom() {
    let childNodes = this.el.childNodes
    const _this = this
    for(let item of childNodes){
      console.log('child', item)
      if(item.nodeType === 1){
        const value = item.getAttribute('v-model')
        if(value){
          const modelName = value.match(/{{(.+?)}}/)[1]
          item.value = _this.data[modelName]
          item.addEventListener('input', function (e) {
            _this.data[modelName] = e.target.value
            console.log('aaaa', _this.data[modelName])
          })
        }
      }
      if(item.childNodes.length != 0){
        const value = item.childNodes[0].data.trim()
        const modelName = value.match(/{{(.+?)}}/)
        if(modelName){
          console.log('sssss', modelName)
          item.innerHTML = this.data[modelName[1]]
          this.dep[modelName[1]] = item
        }
      }
    }
  }
​
  initData() {
    this.data = {}
    let _this = this
    for(let key in _this._data){
      Object.defineProperty(_this.data, key, {
        set(newValue) {
          _this._data[key] = newValue
          console.log('setProperty', _this.dep[key])
          if(_this.dep[key]) {
            _this.dep[key].innerHTML = newValue
          }          
        },
        get(){
          return _this._data[key]
        }
      })
    }
    this.data['name'] = '11'
  }
}

七、实现v-show\v-if

1.用vue的方式先把html写完
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="change">切换</button>
    <div v-if='isIf'>v-if</div>
    <div v-show='isShow'>v-show</div>
  </div>
  <script src="vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        isShow: false,
        isIf: false
      },
      methods:{
        change() {
          this.isIf = !this.isIf 
          this.isShow = !this.isShow
        }
      }
    })
  </script>
</body>
</html>
2.创建vue类+初始化data
class Vue {
  constructor(options) {
    this.el = document.querySelector(options.el)
    this.data = options.data
    this.methodName = options.methods
    this.initData()
  }
  initData() {
    this._data = {}
    let _this = this
    for(let key in this.data) {
      Object.defineProperty(_this._data, key, {
        set(newValue) {
          _this.data[key] = newValue                  
        },
        get() {
          return _this.data[key]
        }
      })
    }
  }
}
3.循环遍历v-show\v-if v-show时判断是否显示还是隐藏,v-if建立comment节点
initDom() {
    let _this = this
    let childNodes = this.el.childNodes
    for(let item of childNodes) {
      if(item.nodeType === 1) {
        if(item.getAttribute('v-show')) {
          const modelName = item.getAttribute('v-show')
          if(!_this.data[modelName]){
            item.style.display = 'none'
          }
        }
        if(item.getAttribute('v-if')) {
          var newDiv = document.createComment('v-if')
          const modelName = item.getAttribute('v-if')
          if(!_this.data[modelName]){
            item.parentNode.replaceChild(newDiv, item)
          }
        }
        if(item.getAttribute('@click')) {
          const modelName = item.getAttribute('@click')
          item.addEventListener('click', function(){
          })
        }
      }
    }
  }
5.为item添加click事件,改变this指向
item.addEventListener('click', function(){
    console.log('+--+')
    _this.methodName[modelName].call(_this._data)
})
6.添加订阅器
set(newValue) {
          _this.data[key] = newValue   
          if(_this.dep[key].item) { //说明是v-if
            if(newValue){
              _this.dep[key].parent.replaceChild(_this.dep[key].item, _this.dep[key].value)
            }else {
              _this.dep[key].parent.replaceChild(_this.dep[key].value, _this.dep[key].item)
            }
          } else{
            console.log('这是v-show', key, _this.dep[key])
            if(newValue){
              _this.dep[key].style.display = 'block'
            }else {
              _this.dep[key].style.display = 'none'
            }
          }
                
        }
7.所有
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <button @click="change">切换</button>
    <div v-if='isIf'>v-if</div>
    <div v-show='isShow'>v-show</div>
  </div>
  <script src="vue.js"></script>
  <script>
    let vm = new Vue({
      el: '#app',
      data: {
        isShow: false,
        isIf: false
      },
      methods:{
        change() {
          console.log(this, this.isShow, '+++++') 
          this.isIf = !this.isIf 
          this.isShow = !this.isShow
        }
      }
    })
  </script>
</body>
</html>
<script>
class Vue {
  constructor(options) {
    this.el = document.querySelector(options.el)
    this.data = options.data
    this.methodName = options.methods
    this.dep = {}
    this.initData()
    this.initDom()
  }
  initDom() {
    let _this = this
    let childNodes = this.el.childNodes
    for(let item of childNodes) {
      if(item.nodeType === 1) {
        if(item.getAttribute('v-show')) {
          const modelName = item.getAttribute('v-show')
          _this.dep[modelName] = item
          if(!_this.data[modelName]){
            item.style.display = 'none'
          }
        }
        if(item.getAttribute('v-if')) {
          var newDiv = document.createComment('v-if')
          const modelName = item.getAttribute('v-if')
          _this.dep[modelName] = {item, value: newDiv, parent: item.parentNode}
          if(!_this.data[modelName]){
            item.parentNode.replaceChild(newDiv, item)
          }
          console.log(_this.dep[modelName])
        }
        if(item.getAttribute('@click')) {
          const modelName = item.getAttribute('@click')
          item.addEventListener('click', function(){
            console.log('+--+')
            _this.methodName[modelName].call(_this._data)
          })
        }
      }
    }
  }
​
  initData() {
    this._data = {}
    let _this = this
    for(let key in this.data) {
      Object.defineProperty(_this._data, key, {
        set(newValue) {
          _this.data[key] = newValue   
          if(_this.dep[key].item) { //说明是v-if
            console.log('这是v-if', key, newValue)
            if(newValue){
              _this.dep[key].parent.replaceChild(_this.dep[key].item, _this.dep[key].value)
            }else {
              _this.dep[key].parent.replaceChild(_this.dep[key].value, _this.dep[key].item)
            }
          } else{
            console.log('这是v-show', key, _this.dep[key])
            if(newValue){
              _this.dep[key].style.display = 'block'
            }else {
              _this.dep[key].style.display = 'none'
            }
          }
                
        },
        get() {
          return _this.data[key]
        }
      })
    }
  }
}
</script>