Vue双向绑定解析

聊聊来龙去脉

作者 Trekerz 日期 2020-02-15
Vue双向绑定解析

一、准备知识

  1. [].slice.call(list):将伪数组转换为数组

  2. node.nodeType:得到节点类型

    Node接口是所有节点类型的父类,节点从这个接口继承了属性和方法。

  3. Object.defineProperty(obj, propertyName, descriptor):给对象添加属性(指定描述符)

    • 属性描述符
      • 数据描述符(4个)
      • 访问描述符(2个)
  4. Object.keys():得到对象自身可枚举属性组成的数组

  5. obj.hasOwnProperty(prop):判断props是否是obj自身属性

  6. DocumentFragment:文档碎片(内存中保存n个element的容器对象,可用于高效批量更新多个节点)

二、数据代理

Object.defineProperty

  1. 举例

    已知a.b.xxx,实现a.xxx,即a直接访问xxx

    // 举例
    const vm = new Vue({
    el: '#app',
    data: {
    name: 'zjf'
    }
    })
    console.log(vm.name) // zjf

三、模板解析

  1. 去除el元素中所有子节点保存到一个fragment对象中
    // node2fragment(el)

    // 1. 创建空的fragment
    var fragment = document.createDocumentFragment(),
    child

    // 2. 将el中所有子节点转移到fragment
    while (child = el.firstChild) {
    fragment.appendChild(child)
    }
  2. 编译fragment中所有层次子节点(递归)
    • { { } }表达式文本节点进行解析:使用正则解析,替换文本
    • 对元素节点的指令属性进行解析
      • 事件指令:拆解指令类型和指令值,调用node.addEventListener
      • 一般指令:拆解指令类型和指令值,根据指令类型确定要操作的属性
    // compileElement(fragment)

    // 1. 去除最外层所有子节点
    var childNodes = el.childNodes,
    // compile对象
    me = this

    // 2. 遍历所有子节点
    [].slice.call(childNodes).forEach(function(node) => {
    var text = node.textContent
    var reg = /\{\{(.*)\}\}/

    if (me.isElementNode(node)) {
    // element节点
    me.compile(node)
    } else if (me.isTextNode(node) && reg.test(text)) {
    // text节点
    me.compileText(node, RegExp.$1)
    }

    // 递归遍历子节点
    if (node.childNodes && node.childNodes.length) {
    me.compileElement(node)
    }
    })
  3. 将编译好的fragment添加到页面的el元素中

四、数据绑定

  1. 解释

    一旦更新了data中的某个属性数据,所有界面上直接使用或间接使用了此属性的节点都会更新。

  2. 数据劫持

    用来实现数据绑定的一种技术(Object.defineProperty

  3. 过程

    vm.name = 'a' –> data中的name属性值变化 –> nameset()调用 –> dep –> 相关的所有watcher –> cb() –> updater

  4. 四个重要对象
    Dep:

    (1) 实例创建的时机:初始化时给data的属性进行数据劫持时创建的;

    (2) 实例个数:与data中的属性一一对应;

    (3) 实例结构:

    id:标识

    subs:n个相关的watcher的容器

    Watcher:

    (1) 实例创建的时机:初始化时解析模板语法时创建的;

    (2) 实例个数:与模板中表达式(不包含事件指令)一一对应;

    (3) 实例结构:

    • cb:用于界面更新的回调
    • vmvm
    • exp:对应的表达式
    • depIds:相关的n个dep的容器对象
    • value:当前表达式对应的value
    Dep与Watcher之间的关系(n:n的关系)
    • data属性 –> Dep –> n个watcher(场景:模板中有多个表达式使用了此属性({ { a } } v-text="a"))

    • 表达式 –> Watcher –> n个dep(场景:多层表达式(a.b.c))

    Dep与Watcher如何建立关系

    (1) Observer遍历data属性value

    • 每个value里创建一个与value对应的dep

    • valueget操作中调用depdepend方法去绑定依赖

    (2) 每个模板表达式建立与表达式对应的watcher

    • watcher建立时会根据表达式值去取属性值value

    • Dep的静态属性target暂时置为本watcher

    • 触发一次valueget操作

    • valueget操作执行depdepend方法,进而触发watcherdep互相绑定


–end–