框架与框架
MVVM
View:用户视图
Model:数据
传统的 MVC
架构通常是使用控制器更新模型,视图从模型中获取数据去渲染。当用户有输入时,会通过控制器去更新模型,并且通知视图进行更新
但是 MVC
有一个巨大的缺陷,控制器承担的责任太大。随着项目愈加的复杂,控制器中的代码也会越臃肿,导致不利于维护。
所以引入 MVVM
模式。添加了 ViewModel
概念。 ViewModel
只关心数据和业务的处理,不关心 View
如何处理数据,在这种情况下, View
和 Model
都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可以复用的逻辑放在一个 ViewModel
中,让多个 View
复用这个 ViewModel。
以 Vue
来举例,ViewModel
就是组件的实例。View
就是模板,Model
引入 Vuex
的情况下完全可以和组件分离。
对于 MVVM
来说,其实最重要的并不是通过双向绑定或者其他的方式将 View
与 ViewModel
绑定起来,而是通过 ViewModel
将视图中的状态和用户的行为分离出一个抽象,这才是 MVVM
的精髓。
Virtual 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>
可以通过 JS 对象渲染出对应的 DOM,但是如何判断新旧两个 JS 对象的最小差异且实现局部更新 DOM。
DOM 是一个多叉树结构,如果需要完整的对比两颗树的差异,那么需要的时间复杂度会是 O(n ^ 3)
,这个复杂度肯定是不能接受的。于是 React 优化了算法,实现 O(n)
的复杂度来对比差异。实现 O(n)
复杂度的关键就是只对比同层的节点,而不是跨层对比
先上至下,坐到右遍历对象,树的深度遍历,给每个节点索引,便于最后渲染差异
一旦节点有子元素,就去判断子元素是否有不同
第一步中判断新旧节点 tagName
是否相同,如果不相同就代表节点被替换;如果没有更改,就要判断是否有子元素,存在即进行第二步计算。
在第二步中,需要判断原本的列表中是否有节点被移除,在新的列表中要判断是否有新的节点加入,还需要判断节点是否移动。
例如:页面中有一个列表,对列表中元素进行了变更
// 假设这里模拟一个 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
。
window.addEventListener('hashchange', () => {
// code
})
Hash
模式兼容性更好。
History 模式
History 模式是 HTML5 功能,主要使用 history.pushState
和 history.replaceState
监听 URL
变化。
// 新增历史记录
history.pushState(stateObject, title, URL)
// 替换当前历史记录
history.replaceState(stateObject, title, URL)
例如:
const state = { 'page_id': 1, 'user_id': 5 }
const title = ''
const url = 'hello-world.html'
history.pushState(state, title, url)
当用户做出浏览器动作时,比如点击后退按钮时会触发 popState 事件
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
的依赖追踪,页面渲染已内部最优。
React
的 JSX
,存在上手成本,更加灵活,选择周边工具完全需要靠用户自身对技术的把控。