27 MVVM/虚拟DOM/前端路由
27.1 MVVM
涉及面试题:什么是
MVVM?比之MVC有什么区别?
首先先来说下 View 和 Model
View很简单,就是用户看到的视图Model同样很简单,一般就是本地数据和数据库中的数据
基本上,我们写的产品就是通过接口从数据库中读取数据,然后将数据经过处理展现到用户看到的视图上。当然我们还可以从视图上读取用户的输入,然后又将用户的输入通过接口写入到数据库中。但是,如何将数据展示到视图上,然后又如何将用户的输入写入到数据中,不同的人就产生了不同的看法,从此出现了很多种架构设计。
传统的
MVC架构通常是使用控制器更新模型,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模型,并且通知视图进行更新

- 但是
MVC有一个巨大的缺陷就是控制器承担的责任太大了,随着项目愈加复杂,控制器中的代码会越来越臃肿,导致出现不利于维护的情况。 - 在
MVVM架构中,引入了ViewModel的概念。ViewModel只关心数据和业务的处理,不关心View如何处理数据,在这种情况下,View和Model都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个ViewModel中,让多个View复用这个ViewModel。

- 以
Vue框架来举例,ViewModel就是组件的实例。View就是模板,Model的话在引入Vuex的情况下是完全可以和组件分离的。 - 除了以上三个部分,其实在
MVVM中还引入了一个隐式的Binder层,实现了View和ViewModel的绑定

- 同样以
Vue框架来举例,这个隐式的Binder层就是Vue通过解析模板中的插值和指令从而实现View与ViewModel的绑定。 - 对于
MVVM来说,其实最重要的并不是通过双向绑定或者其他的方式将View与ViewModel绑定起来,而是通过ViewModel将视图中的状态和用户的行为分离出一个抽象,这才是MVVM的精髓
27.2 Virtual DOM
涉及面试题:什么是
Virtual DOM?为什么Virtual DOM比原生DOM快?
- 大家都知道操作
DOM是很慢的,为什么慢的原因以及在「浏览器渲染原理」章节中说过,这里就不再赘述了- 那么相较于DOM来说,操作JS对象会快很多,并且我们也可以通过JS来模拟DOM
const ul = {
tag: 'ul',
props: {
class: 'list'
},
children: {
tag: 'li',
children: '1'
}
}
上述代码对应的 DOM 就是
<ul class='list'>
<li>1</li>
</ul>
- 那么既然
DOM可以通过JS对象来模拟,反之也可以通过JS对象来渲染出对应的DOM。当然了,通过JS来模拟DOM并且渲染对应的DOM只是第一步,难点在于如何判断新旧两个JS对象的最小差异并且实现局部更新DOM
首先
DOM是一个多叉树的结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是O(n ^ 3),这个复杂度肯定是不能接受的。于是React团队优化了算法,实现了O(n)的复杂度来对比差异。 实现O(n)复杂度的关键就是只对比同层的节点,而不是跨层对比,这也是考虑到在实际业务中很少会去跨层的移动DOM元素。 所以判断差异的算法就分为了两步
- 首先从上至下,从左往右遍历对象,也就是树的深度遍历,这一步中会给每个节点添加索引,便于最后渲染差异
- 一旦节点有子元素,就去判断子元素是否有不同
在第一步算法中我们需要判断新旧节点的
tagName是否相同,如果不相同的话就代表节点被替换了。如果没有更改tagName的话,就需要判断是否有子元素,有的话就进行第二步算法。
在第二步算法中,我们需要判断原本的列表中是否有节点被移除,在新的列表中需要判断是否有新的节点加入,还需要判断节点是否有移动。
举个例子来说,假设页面中只有一个列表,我们对列表中的元素进行了变更
// 假设这里模拟一个 ul,其中包含了 5 个 li
[1, 2, 3, 4, 5]
// 这里替换上面的 li
[1, 2, 5, 4]
从上述例子中,我们一眼就可以看出先前的
ul中的第三个li被移除了,四五替换了位置。
那么在实际的算法中,我们如何去识别改动的是哪个节点呢?这就引入了
key这个属性,想必大家在Vue或者React的列表中都用过这个属性。这个属性是用来给每一个节点打标志的,用于判断是否是同一个节点。
- 当然在判断以上差异的过程中,我们还需要判断节点的属性是否有变化等等。
- 当我们判断出以上的差异后,就可以把这些差异记录下来。当对比完两棵树以后,就可以通过差异去局部更新
DOM,实现性能的最优化。
当然了
Virtual DOM提高性能是其中一个优势,其实最大的优势还是在于:
- 将
Virtual DOM作为一个兼容层,让我们还能对接非Web端的系统,实现跨端开发。 - 同样的,通过
Virtual DOM我们可以渲染到其他的平台,比如实现SSR、同构渲染等等。 - 实现组件的高度抽象化
27.3 路由原理
涉及面试题:前端路由原理?两种实现方式有什么区别?
前端路由实现起来其实很简单,本质就是监听
URL的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式
Hash模式History模式
1. Hash 模式
www.test.com/#/就是Hash URL,当#后面的哈希值发生变化时,可以通过hashchange事件来监听到URL的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的URL请求永远是www.test.com
window.addEventListener('hashchange', () => {
// ... 具体逻辑
})
Hash模式相对来说更简单,并且兼容性也更好
2. History 模式
History模式是HTML5新推出的功能,主要使用history.pushState和history.replaceState改变URL
- 通过
History模式改变URL同样不会引起页面的刷新,只会更新浏览器的历史记录。
// 新增历史记录
history.pushState(stateObject, title, URL)
// 替换当前历史记录
history.replaceState(stateObject, title, URL)
当用户做出浏览器动作时,比如点击后退按钮时会触发
popState事件
window.addEventListener('popstate', e => {
// e.state 就是 pushState(stateObject) 中的 stateObject
console.log(e.state)
})
两种模式对比
Hash模式只可以更改#后面的内容,History模式可以通过API设置任意的同源URLHistory模式可以通过API添加任意类型的数据到历史记录中,Hash模式只能更改哈希值,也就是字符串Hash模式无需后端配置,并且兼容性好。History模式在用户手动输入地址或者刷新页面的时候会发起URL请求,后端需要配置index.html页面用于匹配不到静态资源的时候
27.4 Vue 和 React 之间的区别
Vue的表单可以使用v-model支持双向绑定,相比于React来说开发上更加方便,当然了v-model其实就是个语法糖,本质上和React写表单的方式没什么区别- 改变数据方式不同,
Vue修改状态相比来说要简单许多,React需要使用setState来改变状态,并且使用这个API也有一些坑点。并且Vue的底层使用了依赖追踪,页面更新渲染已经是最优的了,但是React还是需要用户手动去优化这方面的问题。 React 16以后,有些钩子函数会执行多次,这是因为引入Fiber的原因React需要使用JSX,有一定的上手成本,并且需要一整套的工具链支持,但是完全可以通过JS来控制页面,更加的灵活。Vue使用了模板语法,相比于JSX来说没有那么灵活,但是完全可以脱离工具链,通过直接编写render函数就能在浏览器中运行。- 在生态上来说,两者其实没多大的差距,当然
React的用户是远远高于Vue的
