前端面试题八@杨志刚

vue中双向数据绑定是如何实现的


    数据双向绑定,一个是监听页面的数据变化,一个是将数据变化映射到页面。

分成两个进程,一个进程是对挂载目标元素模板里的v-model和{{ }}这两个指令进行编译(绿色)。另一个进程是对传进去的data对象里面的数据进行监听(红色)。

红色:

当你把一个普通的 JavaScript 对象传给 Vue 实例的data选项,Vue 将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部加上set和get访问器,这样在设置data的属性值的时候,会触发set方法,那么set方法主要有两个作用,一是改变data里面的属性值,二是发出数据变化的通知。Observer作为数据的观察者,让数据对象的读写操作都处于自己的监管之下,Dep作为Watcher(订阅器)的收集者,当数据发生变化set会发出通知,会被Observer观察到,然后由Dep通知到Watcher,最后更新视图。

绿色:

指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,同样由Dep进行收集,然后由Dep通知到Watcher,最后更新视图。

节点介绍

数据监听器观察者Observer,能够对数据对象的所有属性进行监听,让数据对象的读写操作都处于自己的监管之下,当数据发生变化set会发出通知,会被Observer观察到,然后由Dep通知到Watcher,最后更新视图。

实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性

Watcher将数据监听器和指令解析器连接起来,数据的属性变动时,执行指令绑定的相应回调函数,

1.如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。

指令解析器Compile,

对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher

Dep:因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的

详述虚拟DOM中的diff算法

Vue的核心是双向绑定和虚拟DOM(下文我们简称为vdom),vdom是树状结构,其节点为vnode,vnode和浏览器DOM中的Node一一对应,通过vnode的elm属性可以访问到对应的Node。

vdom因为是纯粹的JS对象,所以操作它会很高效,但是vdom的变更最终会转换成DOM操作,为了实现高效的DOM操作,一套高效的虚拟DOM diff算法显得很有必要。

vdom: 双向绑定虚拟dom。下面是vue的diff算法参考图

need-to-insert-img

一、简单的diff设计:

遍历newVdom的节点,找到他在oldVdom中的位置,找到就移动对应的dom元素,没找到说明是新增节点,则创建一个节点插入。遍历完后如果oldVdom中还有没有处理过的节点,说明这些节点在newVdom中被删除了,删除它们即可。

二、Vue的diff实现:

1、优先处理特殊场景

(1)、头部的同类型节点、尾部的同类型节点

这类节点更新前后位置没有发生变化,所以不用移动它们对应的DOM

(2)、头尾/尾头的同类型节点

这类节点位置很明确,不需要再花心思查找,直接移动DOM就好

处理了这些场景之后,一方面一些不需要做移动的DOM得到快速处理,另一方面待处理节点变少,缩小了后续操作的处理范围,性能也得到提升

2、“原地复用”

“原地复用”是指Vue会尽可能复用DOM,尽可能不发生DOM的移动。Vue在判断更新前后指针是否指向同一个节点,其实不要求它们真实引用同一个DOM节点,实际上它仅判断指向的是否是同类节点

虚拟DOM对比时,会用到diff算法

虚拟DOM什么时候会被比对?

当数据发生变化的时候就会被比对

那什么时候数据会发生改变呢?

要么改变了state,要么改变了props(props的改变其实是他的父组件的state发生了改变)

setState方法,其实是异步的,为什么是异步的?实际为了提升React底层的性能,假设:调用三次setState变更三组数据,大家想页面会怎么做或者说React会怎么做?我们想的是React可能会做三次比对更新三次视图。又假设三次更新间隔非常小,这样会耗费性能,React可以把三次合并为一次,只去做一次虚拟DOM的比对,然后更新一次视图,这样的话就可以省去两次比对性能上的耗费。

同层比对,如果一致,那么继续比对第二层,如果比对一样了,继续往下比对。

如果比对到不一样了,React会这么做,它不会再继续往下比对了,而是从不一样的这一层开始直接用新的覆盖掉就得DOM节点,这样的话岂不是性能并未得到最大提升?这样的话会造成重复节点的浪费,。那这样比对会有什么好处呢?同层比对带来的好处就是比对的算法特别简单,虽然可能会造成DOM上的重新渲染的浪费,但是大大的减少了虚拟DOM之间比对的算法上的性能消耗,所以React中采用了同层比对的算法。

遍历时候key的问题:

假如:数组中有五条数据,渲染到页面,然后生成五个虚拟DOM树,接下来我往里面增加了一条数据于是数据发生变化会生成一个新的虚拟DOM树,然后我们会做两个虚拟DOM的比对也就是上下进行比对匹配关系,如果每一个虚拟DOM的节点没有一个key值,它就没有一个自己的名字,当我们在做两个虚拟DOM树的比对的时候节点和节点之间的关系就很难被确立,我们得做两层循环的比较,这样的话比较起来就很麻烦了,当然也是很耗费性能的。

我们可以这样优化,假如我们在做DOM节点的循环的时候,我们可以给每个节点起个名字,A、B、C、D、E在第二次循环的时候我们有六个,以前的ABCDE还存在还是叫做ABCDE,我又增加了一个节点Z进来这个时候比对就很简单了,我们根据他们的名字进行比对,马上就能知道ABCDE都一致,可以继续复用,只有Z不同,我们快速的建立关联后把Z增加到这个DOM树上就可以了。所以极大的提升了虚拟DOM比对的性能。

如果提升性能有个前提我们尽量不要用下标,因为大家看按照下标的话右图ABCDE,下面新的DOM树ABCDE和上面的其实不再是对应的关系了,对导致key值不稳定,key值是变化的,失去了存在的意义了。那用什么比较合适呢?唯一不变化的、稳定的值。

大神总结

Vue的双向数据绑定是通过数据劫持结合发布者订阅者模式来实现的

个人理解:在new Vue的时候,在Observer中通过Object.defineProperty()达到数据劫持,代理所有数据的getter和setter属性,在每次触发setter的时候,都会通过Dep来通知Watcher,Watcher作为Observer数据监听器与Compile模板解析器之间的桥梁,当Observer监听到数据发生改变的时候,通过Updater来通知Compile更新视图

而Compile通过Watcher订阅对应数据,绑定更新函数,通过Dep来添加订阅者,达到双向绑定

vue提供了几种脚手架模板

vue-cli + vue2.0 + vuex + vue-router + axios + element-ui(不知道这道题指什么,所以写了vue全家桶)

mvvm开发模式讨论

1.标准MVVM(由View实例化Model)

need-to-insert-img

这种由View调用Model, 并具体由View负责Model实例化的方式是最为普遍的,非常适合于需要跨平台的应用。当然,Model并不知道View的存在,因此View要承担所有的界面逻辑,好在WPF已经给出了足够多的解决方案,触发器,模板。基本绝大多数需求都能满足。

2.插件结构(Model实例化View)

这种做法的最常见场合应该是插件系统。一个个的Model其实是一个个的插件,它们应该具备自治性。因此,应该由自身负责界面的产生。

   它的好处是可以通过Model更加精细的调节View的行为,你可以在任何时候获得View内部ListBox的SelectIndex, 而不用麻烦的用Binding。 打个比方说,游戏开发中,你需要随时控制物体的运动速度和方向,这样Model就必须控制View. 绑定很难解决这类问题。

need-to-insert-img

 每一个功能库都有完整的自治性,当你将该功能库拷贝到主框架之下时,它就会自动加载,由Model负责View的生成。一切合情合理。

3. 组装车间(第三方组装View和Model)

这种思路来自于工厂方法,类似于装配车间,View和Model都不负责互相的实例化。而有一个“管理器”负责组装它们。这样的好处在于可配置。你可以通过配置文件动态的改变View.

这种做法彻底的隔绝了View和Model, 同时通过配置选项,可以随时修改View。可谓是一种不错的设计。 但是,必需看到,对于View来说,Model没有任何管理的权限。下图展示了它的基本逻辑:

need-to-insert-img

如果最终你依旧需要两边互相控制,可以考虑采用dynamic关键字。

名称组装逻辑适用场合缺点备注

标准MVVMView实例化Model常用的跨平台场合Model无法控制任何View适用于自底向上的分层设计

Model实例化ViewModel实例化View插件结构或用于游戏开发存在一定的耦合适用于按功能划分的插件型类库设计,或要求Model大量控制View的场合

组装车间第三方管理器实例化和组装Model和View可动态替换所有View两者彻底隔绝,没有控制灵活性大型系统的严格设计

常见的几种mvvm的实现方式

1.发布者-订阅模式(backbone.js)

2.脏值检查(angular.js)

3.数据劫持(vue.js)

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容