MVVM -Model View ViewModel

  • M:模型(Model) :对应 data 中的数据

  • V:视图(View) :模板

  • VM:视图模型(ViewModel) : Vue 实例对象

最核心的就是 ViewModelViewModel 包含 DOM ListenersData Bindings

Data Bindings 用于将数据绑定到 View 上显示,DOM Listeners 用于监听操作。

  • ModelView 的映射,也就是 Data Bindings 。这样可以大量省略我们手动 update View 的代码和时间。

  • ViewModel 的事件监听,也就是 DOM Listeners 。这样我们的 Model 就会随着 View 触发事件而改变。

在Vue中的mvvm:

  • data中所有的属性、computed的计算属性、methods中的方法等,最后都出现在了vue实例vm身上。

  • vue实例vm身上所有的属性 及 Vue原型上所有属性,在Vue模板{{}}中都可以直接使用。

<div id="app">
  {{num}}
</div>
​
<script>
let vm = new Vue({
  el:'#app',
  data:{
    num: 10,
  }
});
</script>

MVVM思想有两个方向

一是将模型转换成视图,即将后端传递的数据转换成看到的页面。实现方式是:数据绑定。

二是将视图转换成模型,即将看到的页面转换成后端的数据。实现的方式是:DOM 事件监听。

这两个方向都实现的,就称为数据的双向绑定。

MVC 和 MVVM 的区别(关系)

MVC - Model View Controller( controller: 控制器 ),M 和 V 和 MVVM 中的 M 和 V 意思一样,C 指页面业务逻辑。使用 MVC 的目的就是将 M 和 V 的代码分离,但 MVC 是单向通信,也就是将 Model 渲染到 View 上,必须通过 Controller 来承上启下。

MVC 和 MVVM 的区别(关系)并不是 ViewModel 完全取代了 Controller 。

ViewModel 目的在于抽离 Controller 中的数据渲染功能,而不是替代。其他操作业务等还是应该放在 Controller 中实现,这样就实现了业务逻辑组件的复用。

常见关于Vue的面试题

什么是MVVM思想?

MVVM -Model View ViewModel,它包括 DOM Listenters 和 Data bindings,前者实现了页面与数据的绑定,当页面操作数据的时候 DOM 和 Model 也会发生相应的变化。后者实现了数据与页面的绑定,当数据发生变化的时候会自动渲染页面。

MVVM相对于MVC的优势?

  1. MVVM 实现了数据与页面的双向绑定,MVC 只实现了 Model 和 View 的单向绑定。

  2. MVVM 实现了页面业务逻辑和渲染之间的解耦,也实现了数据与视图的解耦,并且可以组件化开发。

VUE是如何体现MVVM思想的?

  1. 胡子语法(Mustache 语法, {{}} 长的比较像胡子,命名为胡子语法),实现了数据与视图的绑定。

  2. v-on 事件绑定,通过事件操作数据时,v-model 会发生相应的变化。

响应式原理

数据代理

数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)

let o1 = {x: 100};
let o2 = {y: 200};
​
Object.defineProperty(o2, 'x', {
  get(){
    return o1.x;
  },
  set(value){
    o1.x = value;
  }
});

o2对象代理了o1对象的属性x,当修改o1.x会影响o2.x,修改o2.x也会影响o1.x

响应式 reactive

响应式原理:在改变数据的时候,视图会跟着更新。

<input type="text">
<p class="text"></p>
​
<script>
  let input = document.querySelector('input');
  let text = document.querySelector('p');
  let obj = {};
​
  Object.defineProperty(obj, 'message', {
    set (val) {
      // 当修改obj.mesage值时 同时设置给 input/p 设置对应的值
      input.value = val
      text.innerText = val
    },
    get(){
      return input.value;
    }
  });
​
  input.oninput = function (ev){
    obj.message = ev.target.value;
  }
</script>

Vue给data里所有的属性加上set,get这个过程就叫做响应式。

Vue中的数据代理

Vue中的数据代理:通过vue实例来代理data对象中属性的操作(读/写)

Vue中数据代理的好处:更加方便的操作data中的数据

响应式原理

  • vue在实例化时,将data中的所有属性都通过Object.defineProperty添加到vue实例上

  • 为每一个添加到vue实例上的属性都指定setter和getter,在getter/setter内部去操作(读/写)data中对应的属性

  • 每个vue实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据记录为依赖。之后当依赖属性的 setter 触发时,会通知 watcher,从而使页面中绑定这个属性的部分重新渲染。

总结

第一步:组件初始化的时候,先给每一个Data属性都注册getter,setter,也就是reactive化。然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里。

第二步:当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件。

响应式属性

vue的响应式属性

由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值。

var vm = new Vue({
  data:{
    // 声明 message 为一个空值字符串
    message: ''
  }
})
// 之后设置 `message` 是响应式的
vm.message = 'Hello!'

由于 Vue 会在初始化实例时对data中的属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

var vm = new Vue({
  data:{
    a:1,
    zhangsan: {
      name: '张三'
    },
    items: [1, 2, 3],
  }
})
​
// `vm.a` 是响应式的
​
vm.b = 2
// `vm.b` 是非响应式的

给已有对象添加属性

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如:

Vue.set(vm.someObject, 'b', 2);
​
// 或者
​
this.$set(this.someObject,'b',2);

添加多个属性

this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Vue.set(app.zhangsan, 'age', 18);//age是响应式的
​
vm.zhangsan = Object.assign({}, app.zhangsan, {weight:100, height:180});

数组的响应式

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

  2. 当你修改数组的长度时,例如:vm.items.length = newLength

vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

为了解决第一类问题,以下两种方式都可以实现和 vm.items[indexOfItem] = newValue 相同的效果,同时也将在响应式系统内触发状态更新:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)  
//或者 
vm.$set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

为了解决第二类问题vm.items.length = newLength,你可以使用 splice

vm.items.splice(newLength)

nextTick

Vue 在更新 DOM 时是异步执行的:当data中数据变化是通过异步操作渲染更新DOM

<p ref="p">num:{{num}} </p>
<button @click="btnClick">按钮</button>
<script>
  var vm = new Vue({
    data:{
      num: 6,
    },
    methods: {
      btnClick () {
        this.num = 666;
        // Vue 在更新 DOM 时是异步执行的:当data中数据变化是通过异步操作渲染更新DOM
        console.log(this.$refs.p.innerHTML);// num:6

        // 为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用
        this.$nextTick(function(){
        	console.log(this.$refs.p.innerHTML);// num:666
        });
      }
    },
	})
</script>
Logo

前往低代码交流专区

更多推荐