组件化与MVVM
组件化
传统组件只是静态渲染,更新还要依赖于操作DOM。
数据驱动视图——Vue MVVM。更加关注数据和业务逻辑。
MVVM
MVCModel
数据 → View
视图 → Controller
控制器
MVVMMVVM
—— Model View ViewModel
,数据,视图,视图模型model
对应 data
,view
对应 template
,vm
对应 new Vue({…})
三者的关系:view
可以通过事件绑定的方式影响 model
,model
可以通过数据绑定的形式影响到view
,viewModel
是把 model
和 view
连起来的连接器。
MVVM
框架的三大要素
- 响应式:
Vue
如何监听到data
的每个属性变化 - 模板引擎:
Vue
的模板如何被解析,指令如何处理 - 渲染:
Vue
的模板如何被渲染成html
,渲染过程是怎样的
响应式
响应式:组件data的数据一旦变化,立刻触发视图的更新。data 属性被代理到 vm 上。
实现数据驱动视图的第一步。
Object.definedProperty
监听data变化的核心API
Object.definedProperty 基本用法
它可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。vue.js正式利用这种方法实现数据的双向绑定,以达到响应式的目的。通过Object.definedProperty监听到对象属性的get和set
语法
Object.defineProperty(obj, prop, descriptor)
object:要在其上添加或修改属性的对象。
propertyname:一个包含属性名称的字符串。就是需要定义的属性和方法。
descriptor:可以包含以下属性,默认情况下, writable, enumerable,configurable值为false
还有两个方法 (双向数据绑定正是利用了这两个方法,即访问器 )get() 和 set()
访问器 set和get
set();一旦属性被重新赋值,此方法被自动调用。
get();一旦属性被访问读取,此方法被自动调用。
let data = {};
let name = 'zhangsan';
Object.defineProperty(data, "name", {
get: function () {
console.log("get");
},
set: function (newVal) {
console.log("set");
name = newVal;
}
});
console.log(data.name);
//get
// undefined
data.name = "list";//set
vue.js采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Object.definedProperty 的几个缺点
深度监听,需要递归到底,一次性计算量大。
无法监听新增属性/删除属性。
无法原生监听数组,需要特殊处理。
Vdom 和 diff
vdom 是实现vue 的重要基石。diff 算法是vdom中最核心、最关键的部分。
Vdom(虚拟DOM)
背景:
用我们传统的开发模式,原生JS或JQ操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。
操作DOM的代价是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验。所以提出:
用js模拟DOM结构,计算出最小的变更,操作DOM
详述:
若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。
意义:
vdom 的真正意义是为了实现跨平台,服务端渲染,以及提供一个性能还算不错 Dom 更新策略。
Virtual DOM 渲染成真实的 DOM 实际上要经历 VNode
的定义、diff
、patch
等过程
VNode
Virtual DOM 是用 VNode
这个 Class
去描述,
VNode 的核心属性
-
tag
属性即这个vnode的标签属性 -
data
属性包含了最后渲染成真实dom节点后,节点上的class,attribute,style以及绑定的事件 -
children
属性是vnode的子节点 -
text
属性是文本属性 -
elm
属性为这个vnode对应的真实dom节点 -
key
属性是vnode的标记,在diff过程中可以提高diff的效率
算法实现
1.用JS
对象模拟DOM
树**
<div id="div1" class="container">
<p>vdom</p>
<ul style="font-size: 20px">
<li>a</li>
</ul>
</div>
{
tag:"div",
props:{}
children:[
{
tag:'p',
children:'vdom'
},
{
tag:'ul',
props:{style:'font-size:20px'},
children: [
{
tag:"li",
children:'a'
}
]
}
]
}
2.比较两棵虚拟 DOM 树的差异 — diff 算法**
diff
算法用来比较两棵 Virtual DOM
树的差异,如果需要两棵树的完全比较,那么 diff
算法的时间复杂度为O(n^3)
。但是在前端当中,你很少会跨越层级地移动 DOM
元素,所以 Virtual DOM
只会对同一个层级的元素进行对比, div
只会和同一层级的 div
对比,第二层级的只会跟第二层级对比,这样算法复杂度就可以达到 O(n)
。
平层Diff的4种情况:
节点替换:节点改变了,例如将上面的 div 换成 h1;
顺序互换:移动、删除、新增子节点,例如上面 div 的子节点,把 p 和 ul 顺序互换;
属性更改:修改了节点的属性,例如把上面 li 的 class 样式类删除;
文本改变:改变文本节点的文本内容,例如将上面 p 节点的文本内容更改为 “Real Dom”;
差异对象(patches):我们能通过差异对象得到,两个虚拟 DOM 对象之间进行了哪些变化,从而根据这个差异对象(patches)更改原先的真实 DOM 结构,从而将页面的 DOM 结构进行更改。
diff
过程**
主要是通过调用patchVnode
方法进行的:
diff
过程中分了好几种情况,oldCh
为 oldVnode
的子节点,ch
为 Vnode
的子节点:
- 首先进行文本节点的判断,若
oldVnode.text !== vnode.text
,那么就会直接进行文本节点的替换; - 在
vnode
没有文本节点的情况下,进入子节点的diff
; - 当
oldCh
和ch
都存在且不相同的情况下,调用updateChildren
对子节点进行diff
; - 若
oldCh
不存在,ch
存在,首先清空oldVnode
的文本节点,同时调用addVnodes
方法将ch
添加到elm
真实dom
节点当中; - 若
oldCh
存在,ch
不存在,则删除elm
真实节点下的oldCh
子节点; - 若
oldVnode
有文本节点,而vnode
没有,那么就清空这个文本节点。
子节点diff
流程分析
为什么使用v-for时必须添加唯一的key?
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
所以,key的作用主要是为了高效的更新虚拟DOM。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。