Vue.js 是如何实现 MVVM 的?
框架到底为我们做了什么?
- 数据和视图分离,解耦(开放封闭原则)
- 所有数据和视图不分离的,都会命中开放封闭原则
-
Vue
数据独立在data
里面,视图在template
中
- 以数据驱动视图,只关心数据变化,
dom
操作被封装- 使用原生js是直接通过操作
dom
来修改视图,例如ducument.getElementById('xx').innerHTML="xxx"
- 以数据驱动视图就是,我们只管修改数据,视图的部分由框架去帮我们修改,符合开放封闭模式
- 使用原生js是直接通过操作
如何理解 MVVM ?
- MVC
-
Model
数据 →View
视图 →Controller
控制器
-
- MVVM
-
MVVM
不算是一种创新 - 但是其中的
ViewModel
是一种创新 -
ViewModel
是真正结合前端应用场景的实现
-
- 如何理解MVVM
-
MVVM - Model View ViewModel
,数据,视图,视图模型 - 三者与
Vue
的对应:view
对应template
,vm
对应new Vue({…})
,model
对应data
- 三者的关系:
view
可以通过事件绑定的方式影响model
,model
可以通过数据绑定的形式影响到view
,viewModel
是把model
和view
连起来的连接器
-
如何实现 MVVM - 以 Vue.js 为例
MVVM
框架的三大要素
- 响应式:
Vue
如何监听到data
的每个属性变化 - 模板引擎:
Vue
的模板如何被解析,指令如何处理 - 渲染:
Vue
的模板如何被渲染成html
,渲染过程是怎样的
Vue 如何实现响应式
- 什么是响应式
- 修改
data
属性之后,Vue
立刻监听到,立刻渲染页面 -
data
属性被代理到vm
上
- 修改
- Object.defineProperty
- 将对象属性的值的设置和访问 (get,set) 都变成函数,可以在当中加入我们自己的逻辑(进行监听)
- 普通的
JavaScript
对象,做属性修改,我们监听不到,所以需要用到Object.defineProperty
- 既能get,又能set,才是双向数据绑定
Vue 如何解析模板
- 模板是什么
- 本质:模板就是字符串
- 与html格式很像,但是模板中是有逻辑的,可以嵌入JS变量,如v-if, v-for等
- 视图最终还是需要由模板生成
html
来显示 - 模板必须先要转换成JS代码
- 有逻辑(v-if, v-for),必须用JS才能实现(图灵完备)
- 转换为html渲染页面,必须用JS才能实现
- 因此,模板要转换成render函数
- render函数
- render函数包含了模板中所有的信息,返回
vnode
,解决了模板中的逻辑(v-if, v-for)问题 - 如何找到最终生成的render函数
- 找到vue源码,搜索
code.render
,将code打印出来,就是生成的render函数
- 找到vue源码,搜索
- render函数包含了模板中所有的信息,返回
- render函数与vdom
- 模板生成
html
:vm._c
-
vm._c
和snabbdom
中的h
函数的实现很像,都是传入标签,属性,子元素作为参数 -
Vue.js
的vdom
实现借鉴了snabbdom
-
updateComponent
中实现了vdom
的patch
- 页面首次渲染执行
updateComponent
-
data
中每次修改属性,都会执行updateComponent
- 模板生成
Vue.js 运行机制
- 第一步:解析模板成
render
函数- 因为在打包的时候就已经生成了render函数,所以编译是第一步;响应式监听是在代码执行的时候才开始监听。
- 模板中的所有信息都被render函数包含
- 模板中用到的data中的属性,都变成了js变量
- 模板中的 v-model v-for v-on都变成了js逻辑
- render函数返回vnode
- 第二步:响应式开始监听
- 通过Object.definedProperty监听到对象属性的get和set
- 将data的属性代理到vm上
- 第三步:首次渲染,显示页面,且绑定依赖
- 初次渲染,执行
updateComponent
,执行vm._render()
- 执行
render
函数,会访问到data
中的值,访问时会被响应式的get
方法监听到 - 执行
updateComponent
,会走到vdom
的patch
方法 -
patch
将vnode
渲染成dom
,初次渲染完成 - 疑问:为何要监听
get
,而不是直接监听set
?- 因为
data
中有很多属性,有些被用到,有些可能不被用到 - 只有被用到的才会走
get
- 没有走到
get
中的属性,set
的时候我们也无需关心 - 避免不必要的重新渲染
- 因为
- 初次渲染,执行
- 第四步:
data
属性变化,触发re-render
- 修改属性,被响应式的
set
监听到 -
set
中执行updateComponent
-
updateComponent
重新执行vm._render()
- 生成的
vnode
和prevVnode
,通过patch
进行对比 - 渲染到
html
中
- 修改属性,被响应式的