2

组件化与MVVM

组件化

传统组件只是静态渲染,更新还要依赖于操作DOM。
数据驱动视图——Vue MVVM。更加关注数据和业务逻辑。

MVVM

MVC
Model 数据 → View 视图 → Controller 控制器
MVVM
MVVM —— Model View ViewModel,数据,视图,视图模型
model 对应 data,view 对应 template,vm 对应 new Vue({…})
三者的关系view 可以通过事件绑定的方式影响 modelmodel 可以通过数据绑定的形式影响到viewviewModel是把 modelview 连起来的连接器。
MVVM.png

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 的定义、diffpatch 等过程

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)
diff1.jpg

平层Diff的4种情况:
节点替换:节点改变了,例如将上面的 div 换成 h1;
顺序互换:移动、删除、新增子节点,例如上面 div 的子节点,把 p 和 ul 顺序互换;
属性更改:修改了节点的属性,例如把上面 li 的 class 样式类删除;
文本改变:改变文本节点的文本内容,例如将上面 p 节点的文本内容更改为 “Real Dom”;

差异对象(patches):我们能通过差异对象得到,两个虚拟 DOM 对象之间进行了哪些变化,从而根据这个差异对象(patches)更改原先的真实 DOM 结构,从而将页面的 DOM 结构进行更改。

diff过程**

主要是通过调用patchVnode方法进行的:

diff 过程中分了好几种情况,oldCholdVnode的子节点,chVnode的子节点:

  • 首先进行文本节点的判断,若 oldVnode.text !== vnode.text,那么就会直接进行文本节点的替换;
  • vnode 没有文本节点的情况下,进入子节点的 diff
  • oldChch 都存在且不相同的情况下,调用 updateChildren 对子节点进行 diff
  • oldCh不存在,ch 存在,首先清空 oldVnode 的文本节点,同时调用 addVnodes 方法将 ch 添加到elm真实 dom 节点当中;
  • oldCh存在,ch不存在,则删除 elm 真实节点下的 oldCh 子节点;
  • oldVnode 有文本节点,而 vnode 没有,那么就清空这个文本节点。
子节点diff流程分析

为什么使用v-for时必须添加唯一的key?
diff2.jpg
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:
diff3.jpg
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
diff4.jpg
所以,key的作用主要是为了高效的更新虚拟DOM。

3.将两个虚拟DOM对象的差异应用到真正的DOM树**


梁柱
135 声望12 粉丝