深入了解MVVM

1,731 阅读4分钟

前言

  • 如何理解 MVVM
  • 如何实现 MVVM
  • 是否解读过 vue 的源码(理解能力 学习能力 自学能力)

面试问到的题目

  • 说一下使用jQuery和使用MVVM框架的区别
  • 说一下对 MVVM 的理解
  • vue 中如何实现响应式
  • vue 中如何解析模板
  • vue 的整个实现流程

实现一个todo-list

jQuery版

vue版

两者的区别:

  • 数据和视图的分离
  • 以数据驱动视图

说一下使用jQuery和使用MVVM框架的区别

  • 数据和视图的分离,解耦(开放封闭原则)
  • 以数据驱动视图,只关心数据变化,DOM 操作被封装

什么是MVVM

  • MVC
  • MVVM
  • 关于 ViewModel

MVC

  • M:Model 数据
  • V:View 视图、界面
  • C:Controller 控制器、逻辑处理

MVVM

  • Model:模型、数据
  • View:视图、模板(视图和模型是分离的)
  • ViewModel:连接 Model 和 View

关于ViewModel

  • MVVM 不算是一种创新
  • 但其中的 ViewModel 确实一种创新
  • 真正结合前端场景应用的创建

解答

  • MVVM:Model View ViewModel
  • 三者之间的联系、以及如何对应到各段代码
  • ViewModel 的理解,联系 View 和 Model,数据驱动视图,视图修改驱动数据变化

vue的MVVM

  • 响应式:vue 如何监听到 data 的每个属性变化
  • 模板引擎:Vue 的模板如何被解析,指令如何处理
  • 渲染:vue 的模板如何被渲染成html?以及渲染过程

vue 中如何实现响应式

什么是响应式

  • 修改 data 属性之后,vue 立刻监听到
  • data 属性被代理到 vm 中
  • Object.defineProperty
  • 模拟
// var obj = {
//     name: 'zhangsan',
//     age: 25
// }
// console.log(obj) // {name: 'zhangsan',age: 25}

// var obj = {}
// var _name = 'shangsan'
// Object.defineProperty(obj, 'name', {
//     get: function () {
//         console.log('get', _name) // 监听
//         return _name
//     },
//     set: function (newVal) {
//         console.log('set', newVal)  // 监听
//         _name = newVal
//     }
// })


// var vm = new Vue({
//     el: '#app',
//     data: {
//         name: 'zhangsan',
//         age: 20
//     }
// })

var vm = {}
var data = {
  name: 'zhangsan',
  age: 20
}

var key, value
for (key in data) {
  (function (key) { // 保证 key 的独立作用域
    Object.defineProperty(vm, key, {
      get: function () {
        console.log('get', data[key]) // 监听
        return data[key]
      },
      set: function (newVal) {
        console.log('set', newVal) // 监听
        data[key] = newVal
      }
    })
  })(key)
}
  • 关键是理解 Object.defineProperty
  • 将 data 的属性代理到 vm 上

vue 中如何解析模板

模板是什么

  • 本质:字符串
  • 有逻辑,如 v-if v-for 等
  • 与 html 格式很想,但有很大区别
  • 最终还要转换为 html 来显示
  • 分割线
  • 模板最终必须转换成 JS 代码,因为
  • 有逻辑(v-if v-for),必须用 JS 才能实现
  • 转换为 html 渲染页面,必须用 JS 才能实现
  • 因此,模板最终要转换成一个 JS 函数(render 函数)

render 函数

  • 模板中所有信息都包含在了 render 函数中
  • this 即 vm
  • price 即 this.price 即 vm.price,即 data 中的 price
  • _c 即 this._c 即 vm._c
  • 从哪里可以看到 render 函数?(源码查找code.render 打印出来)
  • 复杂一点的例子,render 函数是什么样子的?(参考下面的 todo-list)
  • vm._c 是什么?(创建元素)

render 函数与 vdom

  • vm._c 其实就相当于 snabbdom 中的 h 函数
  • render 函数执行之后,返回的是 vnode
  • updateComponent 中实现了 vdom 的 patch
  • 页面首次渲染执行 updateComponent
  • data 中每次修改属性,执行 updateComponent

// jquery
// html

// <div>
//   <input type="text" name="" id="txt-title">
//   <button id="btn-submit">submit</button>
// </div>
// <div>
//   <ul id="ul-list"></ul>
// </div>
var $txtTitle = $('#txt-title')
var $btnSubmit = $('#btn-submit')
var $ulList = $('#ul-list')
$btnSubmit.click(function () {
  var title = $txtTitle.val()
  if (!title) {
    return
  }
  var $li = $('<li>' + title + '</li>')
  $ulList.append($li)
  $txtTitle.val('')
})



// vue

// <div id="app">
//   <div>
//     <input v-model="title">
//     <button v-on:click="add">submit</button>
//   </div>
//   <div>
//     <ul>
//       <li v-for="item in list">{{item}}</li>
//     </ul>
//   </div>
// </div>

// data 独立
var data = {
  title: '',
  list: []
}
// 初始化 Vue 实例
var vm = new Vue({
  el: '#app',
  data: data,
  methods: {
    add: function () {
      this.list.push(this.title)
      this.title = ''
    }
  }
})
/*
    
with(this){  // this 就是 vm
    return _c(
        'div',
        {
            attrs:{"id":"app"}
        },
        [
            _c(
                'div',
                [
                    _c(
                        'input',
                        {
                            directives:[
                                {
                                    name:"model",
                                    rawName:"v-model",
                                    value:(title),
                                    expression:"title"
                                }
                            ],
                            domProps:{
                                "value":(title)
                            },
                            on:{
                                "input":function($event){
                                    if($event.target.composing)return;
                                    title=$event.target.value
                                }
                            }
                        }
                    ),
                    _v(" "),
                    _c(
                        'button',
                        {
                            on:{
                                "click":add
                            }
                        },
                        [_v("submit")]
                    )
                ]
            ),
            _v(" "),
            _c('div',
                [
                    _c(
                        'ul',
                        _l((list),function(item){return _c('li',[_v(_s(item))])})
                    )
                ]
            )
        ]
    )
}

*/

vue 的整个实现流程

  • 模板:字符串,有逻辑,嵌入 JS 变量……
  • 模板必须转换为 JS 代码(有逻辑、渲染 html、JS 变量)
  • render 函数是什么样子的
  • render 函数执行是返回 vnode
  • updateComponent

流程:

第一步:解析模板成 render 函数

  • with 的用法
  • 模板中的所有信息都被 render 函数包含
  • 模板中用到的 data 中的属性,都变成了 JS 变量
  • 模板中的 v-model v-for v-on 都变成了 JS 逻辑
  • render 函数返回 vnode

第二步:响应式开始监听

  • Object.defineProperty
  • 将 data 的属性代理到 vm 上

第三步:首次渲染,显示页面,且绑定依赖

  • 初次渲染,执行 updateComponent,执行 vm._render()
  • 执行 render 函数,会访问到 vm.list vm.title
  • 会被响应式的 get 方法监听到(后面详细讲)
  • 执行 updateComponent ,会走到 vdom 的 patch 方法
  • patch 将 vnode 渲染成 DOM ,初次渲染完成

第四步:data 属性变化,触发 rerender

  • 修改属性,被响应式的 set 监听到
  • set 中执行 updateComponent
  • updateComponent 重新执行 vm._render()
  • 生成的 vnode 和 prevVnode ,通过 patch 进行对比
  • 渲染到 html 中

可以参考 深入了解Vue的双向数据绑定 + 深入了解virtual dom