面试官问你Vue2的响应式原理该如何回答?
清风丶 人气:0前言
可能很多小伙伴之前都了解过 Vue2实现响应式的核心是利用了ES5的Object.defineProperty
但是面对面试官时如果只知道一些模糊的概念,回答肯定是虎头蛇尾的,只有深入底层了解响应式的原理,才能在关键时刻对答如流,百毒不侵。
响应式对象
Object.defineProperty
方法的官方解释是可以直接在一个对象上定义一个新属性或者修改一个对象的现有属性
let object1 = {}; Object.defineProperty(object1, 'property1', { value: 42, }); console.log(object1); //{property1: 42}
经过Object.defineProperty定义后,object1就有了一个property1属性
并且通过这种方式能为属性添加get
与set
方法,
当一个对象的属性都拥有get和set方法时,就可以称这个对象为响应式对象
let object1 ={} Object.defineProperty(object1, "name", { get() { return 1; }, set(x) { console.log("数据变化了",1+x) } }); console.log(object1) 当我们为object1添加name属性以及get和set方法时 //{ // name:1 // get name:function()... // set name:function()... //} console.log(object1.name) //1 object1.name = 1 //数据变化了 2
当我们读取object1的name值会触发get
方法 这里会打印出1,
当我们修改object1的name值会触发set
方法 这里会打印出 ”数据变化了 2“
响应式开始的地方
vue源码中在初始化data的方法initData
有一句 observe(data)
这就是梦开始的地方,让我们看一下observe具体实现
function observe(value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else{ ob = new Observer(value) } return ob }
首先对传入的值做了一些类型校验,如果不是引用类型或者是vNode实例就直接return返回
接下来判断对象下是否有_ob_
属性,如果有直接返回否则执行new Observer(value)
那么这个_ob_
以及 Observer
是什么东西呢?我们接着往下看
Observer
class Observer { value: any; dep: Dep; vmCount: number; constructor(value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { this.observeArray(value) } else { this.walk(value) } } walk(obj: Object) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } observeArray(items: Array<any>) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
Observer
的逻辑也非常简单,首先为传入的这个引用类型new了一个dep
,这个dep主要是为了后续的$set方法使用暂且不看并将当前的observer实例储存在目标的_ob_
里,上面说的observe
方法会根据这个_ob_
进行判断,这样是为了防止data里的属性相互引用导致多次生成新实例接下来判断如果是对象类型则对每个属性执行defineReactive
方法
如果是数组类型则遍历数组对每个子项执行observe
方法,observe
方法上面我们说过,它会根据值的类型进行判断如果是数组或者对象就执行new Observer
这一层的套娃实际上是对数组的层层解析,目的就是为了让数组里的对象都执行defineReactive方法
实现响应式的defineReactive
vue2的源码中是通过递归调用defineReactive
方法将所有对象变为响应式对象接下来我们简单看一下defineReactive
的主要逻辑
function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() ... 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) { dep.depend() if (childOb) { childOb.dep.depend() } } return value }, 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() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
首先为每一个对象属性都添加的get
与set
方法
并且为每个属性都new
一个dep,这个dep接下来会介绍,值得一提的是这句let childOb = !shallow && observe(val)
observe我们上面说了它会对所有对象以及数组里嵌套的对象执行defineReactive
这段逻辑就是在递归调用defineReactive
方法,这样不管我们对象套了多少层,它都能实现响应vue的响应式实际上的经典的观察者模式,dep
在get方法里实现对观察者watcher
进行收集,在set方法里通知每个观察者watcher执行 update
方法,想要了解过程,接下来我们重点看一下dep
与watcher
的定义
dep
dep.js export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { this.id = uid++ this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
dep
充当一个中间桥梁的作用,收集以及维护观察者,在目标属性发生变化时调用自己的notify
方法,对每个观察者都执行update
方法通知观察者需要更新
watcher
watcher.js export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; ... constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (typeof expOrFn === 'function') { this.getter = expOrFn } if (this.computed) { this.value = undefined this.dep = new Dep() } else { this.value = this.get() } } } get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { dep.addSub(this) } } } update () { if (this.computed) { if (this.dep.subs.length === 0) { this.dirty = true } else { this.getAndInvoke(() => { this.dep.notify() }) } } else if (this.sync) { this.run() } else { queueWatcher(this) } } }
watcher
中的逻辑不适合单独拆开解析,接下来我们结合流程分析,watcher
首次创建实例的场景,这是在第一次渲染页面组件的时候,我们传入的expOrFn
参数对应的是updateComponent
,updateComponent
是vue定义的用于重新渲染页面组件的函数,在代码中updateComponent
又被赋值给了this.getter
new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') } } }, true )
接着往下看,由于当前不是计算属性所以this.computed
是false,执行this.value = this.get()
在执行get()的时候会执行到pushTarget
方法
Dep.target = null function pushTarget (_target: ?Watcher) { ... Dep.target = _target }
这个方法实际上是将当前的watcher
赋值给了Dep.target
,Dep.target
是一个全局变量,为啥要这么干呢
因为会有组件嵌套的情况,所以可能会有多个渲染watcher
,但是通过这种方式就这样保证了 Dep.target
指向的是最新创建的watcher
,接下来执行了value = this.getter.call(vm, vm)
,上面说了这个this.getter就是传入的updateComponent
,这个updateComponent
就是页面组件重新渲染的方法,
流程分别是:生成vnode->根据vnode树生成真实的dome树->挂载到页面上,在使用rander
生成vnode的时候就会读取到模版语法中的值,当访问到值时就触发了我们通过defineReactive
方法添加的get
方法,就触发了依赖收集过程。
依赖收集
function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() ... 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) { dep.depend() if (childOb) { childOb.dep.depend() } } return value }, ... }) }
刚刚我们说到Dep.target
就是当前的watcher,在上面
加载全部内容