六月第三周(6.22-6.27)的安排

vue源码学习

  • vue初始化流程
  • vue响应式原理
  • vue中的mvvm
  • vue虚拟Dom
  • vue生命周期的理解
  • vue如何检测数组变化
  • vue中如何实现异步渲染?

vue运行机制

初始化流程

  • 创建Vue实例对象
  • init过程会初始化生命周期/初始化事件中心/初始化渲染,执行beforeCreate周期函数
  • 初始化调用 $mount 方法对Vue实例进行挂载(核心 模板编译 => 渲染函数 => 更新)
  • 如果没有定义render方法,而是定义了template,需要经历编译阶段,需要将模板字符串编译成 render function,步骤如下
    • parse正则解析成AST
    • optimize标记静态节点
  • 编译成render function之后,调用$mount的mountComponent方法,先执行beforeMount钩子函数,然后实例化一个watcher
  • 调用render方法将render function渲染成虚拟的VNode
  • 生成虚拟DOM树后,需要将虚拟DOM树转化成真实的DOM节点,此时需要调用update方法,update方法又会调用pacth方法把虚拟DOM转换成真正的DOM节点

vue生命周期的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
]

常规来说 created -> mounted -> updated -> destroyed 创建 挂载 更新 销毁
还有keep-alive组件的activated/deactivated 激活和停用还有最新的serverPrefetch,
允许我们在渲染过程中“等待”异步数据。可在任何组件中使用,而不仅仅是路由组件

生命周期的钩子函数合并策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Hooks and props are merged as arrays.
*/
function mergeHook (
parentVal,
childVal
) {
var res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
console.log(`res`, dedupeHooks(res));
return res
? dedupeHooks(res)
: res
}

先判断子组件是否含有对应名字的生命周期钩子,然后再合并父组件的生命周期钩子,还做了去重,生命周期钩子其实阔以写成数组

created && mounted(创建和挂载顺序)

根组件created -> 子组件created
子组件mounted -> 父组件mounted

vue响应式原理(vue双向绑定原理的理解)

answer:

综述

vue 采用数据劫持+发布者-订阅者模式的方式,通过Object.defineproperty()的方式劫持各个属性的setter/getter,在数据改变时发布消息给订阅者,触发响应的监听回调

描述

  • 监听器Observer 对需要监听的数据对象进行递归遍历,包括子属性对象的属性,利用Object.defineProperty()对给所有属性都加上setter/getter,一旦某个值赋值,就会触发setter,就能监听到数据变化

  • 解析器Compiler 解析Vue模板指令,将模板中的变量都替换成数据,然后初始化页面视图,并将每个指令对应的节点绑定更新函数,一旦数据有变动,收到通知,调用更新函数进行数据更新

  • 订阅者Watcher 订阅者是Observer和Compile之间的通信桥梁,主要任务是订阅Observer中的属性值变化的消息,当收到属性值变化的消息,触发解析器对应的更新函数 1.自身实例化的时候往订阅器Dep添加自己 2.自身必须有update方法 3.待属性变动Dep.notice通知时,调用自身的update方法,并触发Compile中绑定的回调函数

  • 订阅器Dep 收集订阅者,数据变动触发notify,再调用订阅者的update方法

Last MVVM作为数据绑定的入口,整合Observe/Compile/Watcher三者,通过Observe来监听自己的model数据变化,通过Compile解析模板编译指令,最终利用Watcher搭起通信桥梁,达到数据变化-> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果

在init的时候会利用Object.defineProperty方法(不兼容IE8)监听Vue实例的响应式数据的变化从而实现数据劫持能力(利用了JavaScript对象的访问器属性get和set,在未来的Vue3中会使用ES6的Proxy来优化响应式原理)。在初始化流程中的编译阶段,当render function被渲染的时候,会读取Vue实例中和视图相关的响应式数据,此时会触发getter函数进行依赖收集(将观察者Watcher对象存放到当前闭包的订阅者Dep的subs中),此时的数据劫持功能和观察者模式就实现了一个MVVM模式中的Binder,之后就是正常的渲染和更新流程。
当数据发生变化或者视图导致的数据发生了变化时,会触发数据劫持的setter函数,setter会通知初始化依赖收集中的Dep中的和视图相应的Watcher,告知需要重新渲染视图,Wather就会再次通过update方法来更新视图。

vue如何检测数组变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { def } from '../util/index'
// 原始Array未重写之前的API原型方法
const arrayProto = Array.prototype
// 拷贝原型
export const arrayMethods = Object.create(arrayProto)
// 重写原型的方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]

/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
//
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 如果插入的数据,将再次监听
if (inserted) ob.observeArray(inserted)
// 触发订阅,像页面更新响应就在这里触发
ob.dep.notify()
return result
})
})

重写之后的数组会在每次在执行数组的原始方法之后手动触发响应页面的效果。
Vue2.x中并没有实现将已存在的数组元素做监听,而是去监听造成数组变化的方法,触发这个方法的同时去调用挂载好的响应页面方法,达到页面响应式的效果。

1
2
3
4
5
6
7
8
9
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
// 监听数组元素
observe(items[i])
}
}

this.observeArray函数了,它的内部实现非常简单,它对数组元素进行了监听,什么意思呢,就是改变数组里的元素不能监听到,但是数组内的值是对象类型的,修改它依旧能得到监听响应,如改变list[0].val可以得到监听,但是改变list[0]不能,但是依旧没有对数组本身的变化进行监听。

Vue中如何实现异步渲染?

参考链接