本章涉及知识点
1、前言
2、ZVue架构设计
3、框架的功能列表
4、ZVue类的属性定义
5、_observer方法定义
6、_complie方法定义
7、v-model和v-bind指令解析
8、v-for循环指令解析
9、v-click点击指令解析
10、Watcher类的属性定义
11、Watcher动态批量删除DOM方法
12、Watcher动态批量添加DOM方法
13、Watcher动态更新DOM方法
14、变异数组类的属性定义
15、变异数组切面调用回调函数
16、案例页面引用ZVue框架
17、案例结果的展示分析
18、后记
一、前言
写完了几篇数学算法的推导,突发奇想来换个角度写一点关于前端的框架原理算法,于是就尝试写了这个轻量的框架—ZVue,大致上模拟了Vue渐进式框架的MVVM逻辑流程,也是自己对Vue框架原理的一些感悟和心得体会
该框架可以实现动态的data劫持监听和指令解析、增删改查整个数组字面量或者若干数组项字面量的时候,同时刷新view对应的视图、事件的动态绑定,以及data和view的双向绑定。在page层实例ZVue的语法,html指令模板语法,包括渲染到浏览器端后的dom的结构和原生Vue基本上相同
二、ZVue整体架构设计
梳理一下MVVM流程,需要动态劫持,解析指令,更新视图,变异数组处理等,设计出的架构图如下
根据架构图,类和函数的设计清单如下:
1:ZVue类
(1)constructor:初始化ZVue实例的数据集合、方法集合、template映射表和render映射表等
(2)_toNewArray:实例变异数组及注入回调函数
(3)_defineProperty:属性劫持到变化,通知相应的watcher对象刷新view
(4)_observer:深度遍历所有属性,维护render映射表,并分派劫持监听
(5)_complie:遍历dom结构的所有指令,维护template映射表,分派到各自的解析函数
(6)_parsing_v_model:解析v-model指令,绑定input事件和watcher对象
(7)_parsing_v_click:解析v-click指令,绑定click事件,解析事件的参数列表
(8)_parsing_v_bind:解析v-bind指令和对应的watcher对象
(9)_parsing_v_for:解析v-for指令,作用于整个数组,循环生成动态dom,为父节点绑定watcher对象
(10)_parsing_v_for_item:遍历v-for下的动态dom,绑定watcher对象
(11)_deleteArrayWatcher:动态维护删除render的映射表
(12)_maintainTemplate:动态维护template映射表,并为真实dom设置v-data唯一标志
二、Watcher类
(1)constructor:初始化data绑定的视图片段,包含真实的dom,dom的属性,data的key,ZVue的指针等
(2)getTemplateDom:根据dom字符串,动态组装出包含标签、类名、内容以及自定义指令的dom模板
(3)deleteBatchDom:动态批量删除dom,并同步维护render映射表
(4)addBatchDom:动态批量添加dom,并同步劫持监听新的属性值以及解析新的指令
(5)update_Array:更新数组类型的视图片段
(6)update_Text:更新普通标签中的视图片段
(7)update:set钩子通知触发,负责通知属性对应的更新函数来刷新视图
三、NewArray类(继承Array类)
(1)constructor:接受参数列表和ZVue注入的回调函数,调用父级的构造函数
(2)push:重载Array类的push方法,并在切面调用ZVue的回调函数
(3)pop:重载Array类的pop方法,并在切面调用ZVue的回调函数
(4)splice:重载Array类的splice方法,并在切面调用ZVue的回调函数
三、架构的功能列表
有了架构图和函数设计列表,此架构的功能列表为:
(1)页面ZVue对象的data和methods接管
(2)页面的模板节点结构、插值表达式的正确渲染和节点事件的绑定
(3)实现v-model、v-bind、v-click、v-for四个指令的正确解析渲染
(4)Input输入测试v-model双向绑定,页面的值随着Input的输入值变化而同步变化
(5)dl列表测试v-for循环生成若干个dd(包含class和若干指令),且插值表达式包含常量+索引变量+数组项数值变量
(6)button点击测试更新整个数组的字面量,页面的列表同步变化
(7)button点击测试局部更新数组的某一项字面量,页面的列表同步变化
(8)button点击测试push数组,页面的列表同步变化
(9)button点击测试pop数组,页面的列表同步变化
(10)button点击测试splice数组,页面的列表同步变化
四、ZVue类的属性定义
需要注意原始数组类型需要赋值为变异数组,才能进行数组API的切面编程
五、_observer方法定义
需要注意遍历到数组类型的时候,需要初始化整个数组指针的render映射表并深度遍历,到遍历到数组项的时候,render映射表需要额外deep一层,显然普通类型、数组指针和数组任意一项都被劫持,渲染后data结果为
六、_complie方法定义
分派到各自的指令函数解析后,render映射表为
七、v-model和v-bind指令解析
可以看到指令解析里,注入了与data对应的watcher对象负责更新对应的view
八、v-for循环指令解析
需要注意的是_parsing_v_for需要解析参数列表(可能包含数组索引或者数值,或者同时都包含),最后是给节点的父节点绑定了watcher对象,该对象只负责删除原来旧列表和生成新列表,而生成的列表节点还没有被劫持和解析指令,所以需要重新劫持和解析新的节点
可以看到_parsing_v_for_item给每一个新节点注入了对应的watcher对象,同时还检测了新节点是否包含v-click指令,如果包含则调用其的解析函数,这也就是架构图中_parsing_v_for_item需要在watcher动态添加节点之后调用
九、v-click点击指令解析
可以看到v-click指令需要先解析待调用的函数名字和参数列表,同时要注意向上检测父节点来判断当前节点是否是循环生成的这种情况,因为如果是循环生成的节点,需要二次从v-for指令中解析转义参数列表为迭代数组的索引和数组项,最后在调用原生的方法并传入解析转义的参数列表
十、Watcher类的属性定义
需要注意repalce是保存了迭代数组的key名称集合,来更新替换循环dom的插值表达式
十一、Watcher动态批量删除DOM方法
该方法是配合v-for指令使用,注意需要在删除旧dom的同时,需要维护ZVue的render映射表
十二、Watcher动态批量添加DOM方法
该方法是配合v-for指令使用,注意需要先从template映射表中读取到dom的字符串模板,在动态组装成dom节点,并根据新数组的长度循环生成新dom,而这些新dom此刻并没有被ZVue劫持属性和解析指令,所以在循环结束后需要同步劫持新的属性值和解析新dom的指令
其中动态根据dom字符串来正则解析组装dom的方法定义为
十三、Watcher动态更新DOM方法
需要判断当数据属于数组类型,而数组类型的更新需要动态删除、添加、维护render映射表、重新劫持和解析指令,所以单独封装出数组的更新方法为
十四、变异数组类的属性定义
需要注意到如splice方法的底层实现也许使用了中间数组来桥接原数组,这样会导致重复执行回调函数,而变异数组的声明是在ZVue的构造函数或者set钩子里,所以才可以带上具名的回调函数,为此,需要判断如果是变异数组内部构造了另一个变异数组,则不会带上回调函数的情况
十五、变异数组切面调用回调函数
这样调用变异数组的push、pop、splice方法都会在执行完原生数组的这些方法之后调用我们的回调函数,而这个回调函数要做的,就是更新ZVue中对应的数组,并且是数组本身字面量的直接覆盖,才能让数组本身发生变化,从而进入其set钩子里去通知相应的watcher对象刷新其视图片段
下面是我们ZVue回调函数的定义
我们只希望数组本身被注入set和get钩子,为此我们将data的名称当做变量作为回调函数的名称,使用eval来声明具名回调函数,并为该回调函数注入静态变量—ZVue实例指针,这样就可以在回调函数内部访问到ZVue的属性
十六、案例页面引用ZVue框架
下面我们编写案例页面来引用ZVue
页面html代码部分和Vue非常相似~可以写不同的指令和插值表达式
下面我们在页面内引入ZVue并实例
可以看到,页面的js代码部分和原生Vue非常相似~其中我们用随机数来作为数组项来模拟测试增删改查
十七、案例结果的展示分析
观察渲染的dom结构为
可以看到渲染后dom结构和Vue非常接近。下面我们输入input或者点击增加减少按钮来测试页面的双向绑定
下面我们来测试数组的变化,随意push三个随机值到数组中,观察数组的set和render映射表
可以看到新加入的数组项都被劫持和映射到render表中,下面我们点击局部更新按钮来更新数组的第二项
下面我们pop出四个值,得到
下面我们splice删除数组第二项,得到
最后我们更新整个数组字面量为一个长度为4的新数组,得到
可以看到我们更新覆盖完整个数组字面量后,data中list里又动态注入了新数据的set钩子且删除了旧数据的set钩子,render映射表也动态维护了新节点对应的watcher对象且删除了旧节点的watcher对象
十八、后记
写完ZVue,主要目的是从原理出发用原生JS来实现数据的劫持和指令的解析,以及数组的动态CURD映射到视图的动态变化,并没有加入虚拟dom的diff算法,同时也是向Vue的作者尤雨溪致敬!
案例代码见:原生JS实现轻量Vue+指令解析+变异数组监听