Vue原理
主要考察范围
- 组件化
- 响应式
- vdom和diff
- 模板编译
- 渲染过程
- 前端路由
一、组件化基础
1. 组件化的理解
- 以前的组件化,如
include
导入模块 - 数据驱动视图(MVVM)
2. 数据驱动视图
- 传统组件,只是静态渲染,更新还要依赖于手动操作DOM
- 数据驱动视图-MVVM,数据变化,通知视图更新
3. MVVM
View,ViewModel,Model
二、Vue响应式
- 组件data的数据一旦发生变化,立刻触发视图的更新
- 实现数据驱动视图的第一步
- 考察Vue原理的第一题
1. Vue响应式的实现
- 核心API -
Object.defineProperty
- 如何实现响应式,代码演示
Object.defineProperty
的一些缺点(在Vue3.0中使用Proxy解决)
(1)Object.defineProperty的基本用法
const data = {}
const name = 'zhangsan'
Object.defineProperty(data, "name", {
get: function () {
console.log('get')
return name
},
set: function (newVal) {
console.log('set')
name = newVal
}
})
// 测试
console.log(data.naem) // get
data.name = 'lisi' // set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(2)Object.defineProperty实现响应式
- 监听对象和数组
- 复杂对象的深度监听
- 缺点
1. 监听对象和数组
- 对象的监听,对对象的key进行迭代,通过
Object.defineProperty
设置set
和get
方法进行监听 - 数组的监听,通过重新定义数组的原型方法进行监听
const originalArrayPrototype = Array.prototype
// 创建一个新的对象,原型指向 originalArrayPrototype,再扩展新的方法不会影响到原型
const arrProto = Object.create(originalArrayPrototype)
const methods = ['push', 'pop', 'shift', 'unshift', 'slice', ...]
methods.forEach(methodName => {
arrProto[methodName] = function () {
updateView()
originalArrayPrototype[methodName].call(this, ...arguments)
}
})
// 测试
const target = []
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2. 复杂对象的深度监听
递归所有对象属性进行监听
3. 缺点
- 深度监听,需要递归所有属性,一次性计算量大
- 无法监听新增和删除属性(
Vue.set
和Vue.delete
) - 无法监听数组变化,需要特殊处理
三、vdom和diff算法
1. vdom
(1)vdom是什么
- vdom即virtual dom,虚拟dom
- 是用js模拟的dom结构
(2)vdom解决方案出现的背景
- DOM操作非常消耗性能
- DOM变化的比对,放到js层来做,可以提升效率
- 提供重绘性能
(3)使用js模拟DOM
{
tag: 'div',
props: {
id: 'test',
className: 'test',
on: {
click: Function
},
style: {
background: '#fff'
}
...
},
text: '',
childern: [
{
tag: 'h1',
props: {},
text: 'hello world',
childern: []
},
...
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(4)snabbdom vdom框架重点总结
- h函数(生成vnode)
- vnode数据结构
- patch函数(渲染)- 首次渲染:
patch(el, props, text)
,更新:patch(oldVnode, newVnode)
,销毁:patch(oldVnode, null)
(5)vdom总结
- 用JS模拟的DOM结构(vnode)
- 新旧vnode对比,得出最小的更新范围,最后更新DOM
- 数据驱动视图的模式下,有效控制DOM操作
2. diff算法
四、模板编译
with
语法的使用- 流程:
template -> render -> vnode ->渲染更新
render
函数的使用,vue组件可以使用render
函数代替template
五、组件渲染和更新过程
- 初次渲染过程
- 更新过程
- 异步更新
1. 初次渲染过程
- 解析template为render函数(或在开发环境已完成,vue-loader)
- 触发响应式,监听data属性getter setter
- 执行render函数,生成vnode,patch(elem, vnode)
2. 更新过程
- 修改data,触发setter(此前在getter中已经被监听)
- 重新执行render函数,生成newVnode
- patch(vnode, newVnode)
3. 异步渲染
$nextTick
- 汇总data的修改,一次性更新视图
- 减少DOM操作次数,提高性能
六、路由原理
1. hash实现路由
(1)网页url组成部分
(2)hash的特点
- hash变化会触发网页跳转,即浏览器的前进和后退
- hash变化不会刷新页面,SPA必备特点
- has永远不会提交到server端
(3)代码演示
// 监听hash变化
window.onhashchange = (event) => {
console.log('old url', event.oldURL)
console.log('new url', event.newURL)
console.log('hash:', location.hash)
}
1
2
3
4
5
6
2
3
4
5
6
2. H5 history实现路由
(1)H5 history的特点
- 用url规范的路由(hash带
#
),跳转时页面不刷新 - 需要server端支持,设置为:总是返回index.html
history.pushState
window.onpopstate
(2)代码演示
// 如首页 http://www.abc.com/index.html
const state = { name: 'page1' }
history.pushState(state)
console.log(location.pathname) // index.html/page1
// 监听浏览器前进、后退
window.onpopstate = event => {
console.log('onpopstate', event.state, location.pathname)
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3. 场景
- to B 的系统推荐使用 hash,简单易用,对url规范不敏感
- to C 的系统(如需要SEO),可以考虑选择H5 history,但需要服务端支持