Vue 响应式的原理
每次面试逃不过的一道面试题,那是什么呢?那就是 噔噔噔噔~~~ Vue的响应式,下面我们模拟一下面试的场景,看看大家是否感同身受,哈哈哈哈哈!!!!
面试官:看过Vue源码吗?
求职者:看过一点。
面试官:那你简单讲一下 Vue 的响应式原理
求职者:那什么,好的....
面试官:那就开始吧!
求职者:Vue的响应式原理主要经历了 3 个过程
1. 响应式处理的核心过程
2. 收集依赖的过程,
3. 数据改变时,watcher的执行过程
面试官:额,点点头,可以具体说一下嘛?比如响应式处理的核心过程是什么样的过程?是如何执行的?
求职者:好的,那我先讲一下 【 响应式处理的核心过程 】 ,比如 写一个Vue 的 data 数据
const vm = new Vue({
el: '#app',
data: {
NumberGroup: [1, 2, 3],
StringName: 'XiaoMing Tongxue',
userInfo: {
name: 'DaYa Gao',
sex: 'Girl'
}
}
})
1. 首先 通过 _init方法初始化实例成员,其中 initState下的 initData 来初始化Vue的data属性,同时通过observe将data转化成响应式数据
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm) // 初始化data属性
} else {
observe(vm._data = {}, true /* asRootData */) // 将 data 转换为响应式数据
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
2. observe方法 内 将data属性下添加 Dep 属性,并添加__ob__ 属性, 标识为 响应式数据
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep() // 添加依赖
this.vmCount = 0
def(value, '__ob__', this) // 添加__ob__属性,表示将其设置为响应式标识
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
3. 判断 data属性,如果是对象,则调用 walk方法,将data的值进行遍历,通过 defineReactive 转化为响应式数据。
4. 其中 defineReactive 中分别对 NumberGroup, StringName, userInfo, 添加 Dep 属性,并对其子属性也添加 Dep 属性,
5. 添加 Dep 属性后,通过 Object.definedProperty 将所有属性 (父级,和子级 ) 转换为响应式数据
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep() // 给属性添加依赖
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val) // 给属性的子级设置依赖
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) { // 是否有依赖 ( watcher )
dep.depend() // 将 watcher 存储 在 dep.subs 中
if (childOb) {
childOb.dep.depend() // 属性子级 也存储 watcher 到 dep.subs 中,注意 属性子级的 subs 与属性 subs 不同
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
}
})
}
求职者:到这里第一个阶段 【 响应式处理的核心过程 】结束了。
面试官:请继续你的表演~
求职者:第二阶段是 【 收集依赖的过程 】
5. 通过Object.definePrototype内的 get方法 使用 dep.depend() 收集依赖,对每个属性加入对应的watcher
6. 如果此处属性是数组,则封装 js 原生数组方法,并通过 watcher 与 js 数组方法结合
7. vm._render 渲染虚拟Dom
8. vm._update 将虚拟 Dom 转换为真实 Dom
9. 将真实 Dom 展示到界面上
面试官:额,继续
求职者:第三阶段是 【 数据改变时,watcher的执行过程 】
10. 比如修改 vm.StringName="Hahaha"
11. 因为 vm.StringName 是一个 Object.definedPrototype 方法,会直接调用 set 方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal) // 新值变为响应式数据
dep.notify() // watcher 通知数据修改
}
})
}
12. set 方法 先去判断新值与旧值的区别,如果不相同,则新值覆盖就值,并将新值 通过 observe转换为响应式对象
13. 修改成功后,调用 Dep.notify() 通知 watcher 的 update 实现更新,通过 nextTick 延时 更新,
14. 并通过 vm._render 渲染虚拟 Dom
15. vm.update 将虚拟 Dom 转换 为真实 Dom
updateComponent = () => {
vm._update(vm._render(), hydrating) // 将虚拟 Dom 转换为真实 Dom
}
16. 将真实 Dom 展示到界面上
面试官:Object.definePrototype 有几个属性?分别是什么?代表什么?
求职者:共有 3 个属性
Object.defineProperty(obj, prop, desc)
1. obj 需要定义属性的当前对象
2. prop 需要定义的属性名
3. desc 属性描述符
例如:
const obj = { name: 'hhahha' }
Object.defineProperty(obj, 'name', {
enumerable: true, // 可枚举
configurable: true, // 可配置,false = 不可以修改,不可以删除
get: function (params) {
console.log('get', params)
return obj['name']
},
set: function (params) {
console.log('set', params)
}
})