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)Object.defineProperty实现响应式

  • 监听对象和数组
  • 复杂对象的深度监听
  • 缺点
1. 监听对象和数组
  1. 对象的监听,对对象的key进行迭代,通过Object.defineProperty设置setget方法进行监听
  2. 数组的监听,通过重新定义数组的原型方法进行监听
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. 缺点
  1. 深度监听,需要递归所有属性,一次性计算量大
  2. 无法监听新增和删除属性(Vue.setVue.delete
  3. 无法监听数组变化,需要特殊处理

三、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

(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)

9b3e01621554fb8905cea4d64ca2eea9.png

3. 异步渲染

  • $nextTick
  • 汇总data的修改,一次性更新视图
  • 减少DOM操作次数,提高性能

六、路由原理

1. hash实现路由

(1)网页url组成部分

333be4ac07073e93f7fea2905e18e6b0.png

(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. 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

3. 场景

  • to B 的系统推荐使用 hash,简单易用,对url规范不敏感
  • to C 的系统(如需要SEO),可以考虑选择H5 history,但需要服务端支持