Vue.js(以下简称Vue)是时下流行的前端开发库,一般搭配其插件Vue-Router,Vuex一起使用,行业中称为Vue全家桶。

Vue使用了MVVM的理念,将表现层(DOM)和数据层进行了分离,其基本思想是数据和DOM的一体化,操作数据即可变更DOM,表单交互亦可通过v-model指令改变数据,将前端开发者从DOM、数据两手抓的泥潭中解放出来。

但是,使用这种方法的代价还是明显的,那就是Vue本身,因为这对大多数开发者而言是一个黑盒子。如果不能大概了解它做了哪些事情,那么有的时候遇到一些问题还是很头疼的。

本文关于Vue实现组件化的基本思路,总结了一些其中发生的故事,希望对今后的开发学习有所帮助。

最基本的模型

Vue2.0之后并没有将DOM和数据进行直接绑定,而是采用了VNode类,也就是常说的虚拟DOM。虚拟DOM其实并不神奇,它就是一个JS对象,描述了DOM元素的一些特征,但是它的属性要比真实DOM少得多,这就使得操作更加方便省时。

数据层位于Vue实例中(以下简称VM),该实例的渲染函数执行可得到VNode,然后Vnode通过patch过程渲染成真实DOM,真实DOM又通过注册事件改变VM,这就是一个基本的模型。

Vue实例的由来

那么VM从何而来呢?考虑以下实际问题:

// App.js
new Vue({
name: 'App',
components: { M },
template: `
<div>
<div>swamp</div>
<M />
</div>`
}).$mount('#app') // M.vue
<template>
<div :class="containerClass">
<div>I am M</div>
<ul>
<li v-for="item in listData">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'M',
data () {
return {
listData: ['A', 'B', 'C', 'D', 'E', 'F'],
containerClass: 'container'
}
}
}
</script>

通过Vue的模板解析(parse)和Vue-loader,上述两种形式的组件定义都会最终变成一个options对象,这个对象包含了所有我们定义的属性,template被解析成渲染函数render和静态渲染函数staticRenderFns(这是Vue针对静态模板的优化)。上例中App.js通过new Vue得到第一个VM,接着执行render函数得到VNode,紧接着此VNode进入patch阶段,模板中的html标签是可以直接生成dom元素的;但是其中有一个M标签,这不是一个html标签,然而它作为一个组件已被注册在数据的components属性中,于是Vue拿到这个组件(一个options对象),然后通过Vue.extend(options)生成一个Vue的子类Sub,然后通过实例化这个Sub类得到M组件的VM(第二个VM)。当然,这个VM也会执行render和patch,然后插入到dom中去。

初始化patch过程

Vue有两类重要的Vnode,一种是占位Vnode(placeholder Vnode, 即上文中的M节点),它是一个虚拟的节点,作为M实际内容的父节点存在,这类节点的tag属性一般为Vue-component-2-M这种形式,而普通Vnode(Actual Vnode)的tag属性则是普通的html节点名称,如div, span, ul等。

普通Vnode节点的patch过程很简单,递归patch其子元素(createChildren),然后创建实际节点插入dom即可。

占位Vnode节点的patch过程则比较复杂,包含子元素的递归创建过程,上文所述M节点的VM创建即是在这个阶段进行的。

数据变更时的patch

如果通过某种操作变更了VM的数据,比如上面例子中,我们在M组件中调用this.containerClass = 'common'; this.listData = ['C', 'B', 'F', 'D', 'E', 'G', 'A'],此时M组件会再次render得到全新的VNode(这里再次render的触发基于双向数据绑定,这是Vue的一大核心,但不是本文重点),这个全新的VNode将与之前存在的VNode进行比较,得到差异后再patch进dom,从而完成更新。patch的含义是打补丁,用在这种场景再合适不过了。

VNode拥有和DOM类似的树形结构,在patch过程中,新老VNode进行同层比较:父节点与父节点比,子节点与子节点比,不会跨层比较(这其实是Vue针对树的编辑距离问题的一种处理,将时间复杂度降低到可以接受的程度)。在本例中,我们主要分析第三层的比较,以此阐述patch过程中diff算法的核心。

设老VNode的序列为O,新VNode的序列为N,分别保留指向序列开头和结尾的指针,称为Os, Oe, Ns, Ne。

diff的目的是调整O使其变为N(调整老DOM得到新DOM),使用了双指针算法:

首先是四次比较,比较的目的是发现相同的节点,用修改操作取代创建从而提高效率,这四次比较分别是:

  1. Os - Ns, 如果VNode相同则把两个指针均向右移动,说明新老节点相同,不做处理;
  2. Oe - Ne, 如果VNode相同则把两个指针均向左移动,说明新老节点相同,不做处理;
  3. Os - Ne, 如果VNode相同说明在新的序列中这个节点应该被移动到Oe后边,直接在DOM层面处理移动,然后把Os右移,Ne左移;
  4. Oe - Ns, 如果VNode相同说明在新的序列中这个节点应该被移动到Os前面,直接在DOM层面处理移动,然后把Ns右移,Oe左移;
  5. 如果以上四种情况都没有找到能够匹配的VNode,则在序列O中寻找Ns。可知如果采取遍历O序列的方式,diff算法的时间复杂度为O(min{len(O), len(N)} * len(O));而如果各个VNode拥有key属性,Vue会事先建立一个[key]-[Vnode]的散列表,依据Ns的key去查表只需要O(1)时间,则diff算法的时间复杂度为O(max{len(O), len(N)})。如果查找成功,就将找到的节点移动到Os的前方,并将Ns右移;
  6. 如果上述情况均不能成功,那就说明O序列中并没有Ns处的节点,只能创建一个新的节点,把它插入Os之前,并将Ns右移。

    上述循环进行直到Os出现在Oe之前或者Ns出现在Ne之前,此时如果Oe仍然在Os之前或者同一位置,说明原始序列中的这些元素要被删除;如果Ne仍然在Ns之前或者同一位置,说明原始序列中的这些元素要被新添加,按情况执行对应的操作即可。

依据此算法,上面例子中的情形可以图示为:



以上就是最基本的Vue从定义到组件化到生成DOM的过程。

Vue实现组件化的基本思路的更多相关文章

  1. VUE.JS组件化

    VUE.JS组件化 前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎 ...

  2. vue的组件化运用(数据在两个组件互传,小问题总结)

    一.vue的组件化应用 首先,知道有哪些相关的属性需要用到,再慢慢去理解,运用. 1.两个vue页面 2. slot占位符(可用可不用) 3.props内置属性 4.watch监听函数 5.impor ...

  3. 【06】Vue 之 组件化开发

    组件其实就是一个拥有样式.动画.js逻辑.HTML结构的综合块.前端组件化确实让大的前端团队更高效的开发前端项目.而作为前端比较流行的框架之一,Vue的组件和也做的非常彻底,而且有自己的特色.尤其是她 ...

  4. Vue中组件化编码使用(实战练习一)

    Vue中组件化编码的大致流程(初接触).组件之间的参数传递(最基础的形式).组件之间的配合完成一个需求 1.在Vue中进行组件化编码 1.1.组件化编码流程: (1).拆分静态组件:组件要按照功能点拆 ...

  5. iOS 组件化 —— 路由设计思路分析

    原文 前言 随着用户的需求越来越多,对App的用户体验也变的要求越来越高.为了更好的应对各种需求,开发人员从软件工程的角度,将App架构由原来简单的MVC变成MVVM,VIPER等复杂架构.更换适合业 ...

  6. vue.js组件化开发实践

    前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎么实现,技术选型自然 ...

  7. Vue中组件化编码使用、实现组件之间的参数传递(实战练习二)

    上一章节实现的是静态页面的设计.这一章节实现将数据抽取出来.通过组件间参数的传递来实现 上一章节链接地址:https://blog.csdn.net/weixin_43304253/article/d ...

  8. Vue中组件化编码 完成任务的添加、删除、统计、勾选需求(实战练习三完结)

    上一个章节实现数据在组件之间的传递 .这一章主要是完成添加任务到任务栏.删除任务栏.统计任务完成情况.主要还是参数在各个组件之间的传递. 上一章节的链接地址:https://blog.csdn.net ...

  9. 大话大前端时代(一) —— Vue 与 iOS 的组件化

    序 今年大前端的概念一而再再而三的被提及,那么大前端时代究竟是什么呢?大前端这个词最早是因为在阿里内部有很多前端开发人员既写前端又写 Java 的 Velocity 模板而得来,不过现在大前端的范围已 ...

  10. vue学习笔记(1)—— 组件化实现todoList

    一.环境搭建 1.npm 大型应用时推荐用npm安装,npm能很好的和webpack等模块打包器配合使用.具体安装步骤请参考网上的诸多教程.完成后使用如下命令安装vue. $ npm install ...

随机推荐

  1. python3 - Django3.2框架

    提示:web开发已有php.java,而python在这方面,没有优势,python的优势在于:爬虫.人工智能.大数据分析等,python在web开发这方面,没必要掌握:版本:稳定版本:3.2(py3 ...

  2. oracle学习之redo

    Oracle的重做日志基本概念及原理 重做日志文件 redo log file 通常也称为日志文件,它是保证数据库安全和数据库备份与恢复的文件,是数据库安全和恢复的最基本的保障.管理员可以根据日志文集 ...

  3. 百题计划-6 codeforces 651 div2 E. Binary Subsequence Rotation 01序列集合划分,2个队列处理

    https://codeforces.com/contest/1370/problem/E 队列元素以末尾字符为结尾的序列就好了,这里队列里的元素不重要,队列size重要 #include<bi ...

  4. 关于什么时候用get请求和什么时候用post请求

    转载自:(19条消息) 关于什么时候用get请求和什么时候用post请求_火兰的博客-CSDN博客_什么时候用get什么时候用post get和post方法功能类似的,使用建议:1.get方式的安全性 ...

  5. laravel常用集合的使用

    创建集合: //数组1 $connection = collect([ ['name'=>'jack','age'=>'18','email'=>'ceshi@qq.com'], [ ...

  6. One-Shot Transfer Learning of Physics-Informed Neural Networks

    本文提出了一种将迁移学习应用到PINN的方法.可以极大的缩短训练PINN所用的时间,目前,PINN所需要的训练次数往往都在成千上万次, 作者通过批量训练PINN,来学习丰富的潜在空间用来执行迁移学习. ...

  7. SAP BPC全面预算及合并解决方案.pdf 电子版

    SAP BPC全面预算及合并解决方案.pdf 电子版 有需要的联系 wx :erpworld

  8. 查看当前执行的sql

    SELECT TOP 100 t.hostname, t.loginame, percent_complete, [session_id] , der.[request_id] , [start_ti ...

  9. input点击焦点后阴影

    input[type=text]:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow:inset 0 1p ...

  10. CF1534F2 Falling Sand (Hard Version)

    个人思路: 每个点向相邻沙子连边,向本列和相邻 \(2\) 列下方第一个沙子连边. 对于一个 DAG,所有入度为 \(0\) 的点会覆盖全部点.我们缩点即可通过 F1. 但是这样做是过不了 F2 的. ...