Skip to content
On this page

框架与框架

MVVM

  • View:用户视图

  • Model:数据

传统的 MVC 架构通常是使用控制器更新模型,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模型,并且通知视图进行更新

mvc

但是 MVC 有一个巨大的缺陷,控制器承担的责任太大。随着项目愈加的复杂,控制器中的代码也会越臃肿,导致不利于维护。

所以引入 MVVM 模式。添加了 ViewModel 概念。 ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下, ViewModel 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可以复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel。

mvvm

Vue 来举例,ViewModel 就是组件的实例。View 就是模板,Model 引入 Vuex 的情况下完全可以和组件分离。

对于 MVVM 来说,其实最重要的并不是通过双向绑定或者其他的方式将 View ViewModel 绑定起来,而是通过 ViewModel 将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM 的精髓。

Virtual DOM

由于直接操作 DOM 会存在性能缺陷,操作 JS 对象会快很多,所以通过 JS 来模拟 DOM

js
const ul = {
  tag: 'ul',
  props: {
    class: 'list'
  },
  children: {
    tag: 'li',
    children: '1'
  }
}

对应真实的 DOM 结构

html
<ul class='list'>
  <li>1</li>
</ul>

可以通过 JS 对象渲染出对应的 DOM,但是如何判断新旧两个 JS 对象的最小差异且实现局部更新 DOM。

DOM 是一个多叉树结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3),这个复杂度肯定是不能接受的。于是 React 优化了算法,实现 O(n) 的复杂度来对比差异。实现 O(n) 复杂度的关键就是只对比同层的节点,而不是跨层对比

  • 先上至下,坐到右遍历对象,树的深度遍历,给每个节点索引,便于最后渲染差异

  • 一旦节点有子元素,就去判断子元素是否有不同

第一步中判断新旧节点 tagName 是否相同,如果不相同就代表节点被替换;如果没有更改,就要判断是否有子元素,存在即进行第二步计算。

在第二步中,需要判断原本的列表中是否有节点被移除,在新的列表中要判断是否有新的节点加入,还需要判断节点是否移动。

例如:页面中有一个列表,对列表中元素进行了变更

js
// 假设这里模拟一个 ul,其中包含了 5 个 li
[1, 2, 3, 4, 5]
// 这里替换上面的 li
[1, 2, 5, 4]

可以看到 ul 中的第三个 li 被移除,四和五替换了位置。

那么在实际的算法中,我们如何去识别改动的是哪个节点呢?

这就引入了 key 属性,大家在列表中都使用过这个属性。这个属性是用来给每一个节点打标记的,用于判断是否是同一个节点,节点的属性是否存在变化。

差异被判断,记录差异,当对比完两颗树后,就可以通过差异去局部更新 DOM,实现性能的最优化。

Virtual DOM 的最大优势:

  • 兼容,实现跨端开发;渲染到其他平台

  • 组件高度抽象

也不是万能的。直接操作 DOM 没有 diff 算法损耗。

前端路由是如何跳转的

前端路由本质是监听 URL 的变化,匹配路由规则,显示相应的页面,无需刷新页面。两种路由实现方式:

  • Hash 模式

  • History 模式

Hash 模式

test.com/#/ 就是 Hash URL,当 # 后面的哈希值发生变化后,可以使用 hashchange 事件监听 URL 的变化,进行页面跳转,无论哈希值如何变化,服务端收到的 URL 请求一直是 test.com

js
window.addEventListener('hashchange', () => {
  // code
})

Hash 模式兼容性更好。

History 模式

History 模式是 HTML5 功能,主要使用 history.pushStatehistory.replaceState 监听 URL 变化。

js
// 新增历史记录
history.pushState(stateObject, title, URL)
// 替换当前历史记录
history.replaceState(stateObject, title, URL)

例如:

js
const state = { 'page_id': 1, 'user_id': 5 }
const title = ''
const url = 'hello-world.html'

history.pushState(state, title, url)

当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件

js
window.addEventListener('popstate', e => {
  // e.state 就是 pushState(stateObject) 中的 stateObject
  console.log(e.state)
})

两种模式的差异:

  • Hash 模式只能修改 # 后的内容,History 模式可以通过 API 设置任意同源 URL

  • Hash 模式只能改 # 后的哈希值,History 模式可以添加任意类型数据到历史数据中

  • Hash 模式无需后端配置,兼容性好,History 模式在手动输入地址或刷新页面时会发起 URL 请求,后端需要支持配置 index.html 访问。

React 和 Vue 之间的区别

Vue 双向数据绑定,内部实现了数据的更新,修改数据相对 React 要简单,不用向 React 使用 setState 来改变状态。React 需要手动优化部分操作,而 Vue 的依赖追踪,页面渲染已内部最优。

ReactJSX,存在上手成本,更加灵活,选择周边工具完全需要靠用户自身对技术的把控。