目录
一、vue2响应式原理
对应常规源码?:
数组有哪些不同?:
总结下,vue2有哪些缺陷?
在以往的经验中,我们主技术栈为vue的前端开发,在面试的时候被问到响应式原理的概率是非常大的。而现在市场上,基本上老项目还是vue2,新项目大部分都会选择vue3来开发。
所以,我们有必要来总结对应的源码和面试题,come on ~
一、vue2响应式原理
简述:在组件初始化时,通过Object.defineProperty来进行对数据进行劫持,构造get set方法。get来进行依赖的收集dep,set用于通知dep.notify。
对应常规源码?:
- observe()函数返回一个ob实例。
export function observe (value: any, asRootData: ?boolean): Observer | void {// 观察者let ob: Observer | voidif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__ } else if (shouldObserve &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {// 创建观察者ob = new Observer(value)}if (asRootData && ob) {ob.vmCount++}return ob
}
2. ob对象根据不同的数据类型进行不同的响应化操作
export class Observer {value: any;dep: Dep; // 保存数组类型数据的依赖constructor (value: any) {this.value = valuethis.dep = new Dep()def(value, '__ob__', this) // 在getter中可以通过__ob__可获取ob实例if (Array.isArray(value)) { // 数组响应化protoAugment(value, arrayMethods) 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])}}/*** 对数组每一项执行observe*/observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}
}
3. defineReactive()函数定义get、set方法,并收集依赖。
export function defineReactive (obj: Object,key: string,val: any,customSetter?: ?Function,shallow?: boolean
) {const dep = new Dep() // 一个key一个Dep实例// 递归执行子对象响应化let childOb = !shallow && observe(val)// 定义当前对象getter/setterObject.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {// getter负责依赖收集if (Dep.target) {dep.depend()// 若存在子observer,则依赖也追加到子obif (childOb) {childOb.dep.depend()if (Array.isArray(value)) {dependArray(value) // 数组需特殊处理}}}return value},set: function reactiveSetter (newVal) {if (newVal === value || (newVal !== newVal && value !== value)) {return} val = newVal // 更新值childOb = !shallow && observe(newVal) // childOb更新dep.notify() // 通知更新}})
}
4. Dep管理一组watcher, dep管理的值更新时,通知对应的watcher进行更新
export default class Dep {static target: ?Watcher; // 依赖收集时的wacher引用subs: Array<Watcher>; // watcher数组constructor () {this.subs = [] }//添加watcher实例addSub (sub: Watcher) {this.subs.push(sub)}//删除watcher实例removeSub (sub: Watcher) {remove(this.subs, sub)}//watcher和dep相互保存引用depend () {if (Dep.target) {Dep.target.addDep(this)}}notify () {// stabilize the subscriber list firstconst subs = this.subs.slice()for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}}
}
5. watcher监控数据或者关联组件的一个更新函数,数据更新则执行回调或者更新函数背调用。
export default class Watcher {constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean) {this.vm = vm// 组件保存render watcherif (isRenderWatcher) {vm._watcher = this}// 组件保存非render watchervm._watchers.push(this)// options...// 将表达式解析为getter函数// 如果是函数则直接指定为getter,那什么时候是函数?// 答案是那些和组件实例对应的Watcher创建时会传递组件更新函数updateComponentif (typeof expOrFn === 'function') {this.getter = expOrFn} else {// 这种是$watch传递进来的表达式,它们需要解析为函数this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = noop}}// 若非延迟watcher,立即调用getterthis.value = this.lazy ? undefined : this.get()}/*** 模拟getter, 重新收集依赖re-collect dependencies.*/get () {// Dep.target = thispushTarget(this)let valueconst vm = this.vmtry {// 从组件中获取到value同时触发依赖收集value = this.getter.call(vm, vm)} catch (e) {} finally {// deep watching,递归触发深层属性if (this.deep) {traverse(value)}popTarget()this.cleanupDeps()}return value}addDep (dep: Dep) {const id = dep.idif (!this.newDepIds.has(id)) {// watcher保存dep引用this.newDepIds.add(id)this.newDeps.push(dep)// dep添加watcherif (!this.depIds.has(id)) {dep.addSub(this)}}}update () {// 更新逻辑if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()} else {//默认lazy和sync都是false,所以会走该逻辑queueWatcher(this)}}
}
数组有哪些不同?:
- 7个数组方法的打补丁和原型覆盖
// 数组原型 const arrayProto = Array.prototype // 修改后的原型 export const arrayMethods = Object.create(arrayProto) // 七个待修改方法 const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']/*** 拦截这些方法,额外发送变更通知*/ methodsToPatch.forEach(function (method) {// 原始数组方法const original = arrayProto[method]// 修改这些方法的descriptordef(arrayMethods, method, function mutator (...args) {// 原始操作const result = original.apply(this, args)// 获取ob实例用于发送通知const ob = this.__ob__// 三个能新增元素的方法特殊处理let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}// 若有新增则做响应处理if (inserted) ob.observeArray(inserted)// 通知更新ob.dep.notify()return result}) })
if (Array.isArray(value)) {// 替换数组原型protoAugment(value, arrayMethods) // value.__proto__ = arrayMethodsthis.observeArray(value) }
2. 数组响应式的循环o'berve
observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}
3. 依赖收集时做特殊处理,如果数组项中还是数组需要deepArray
//getter中 if (Array.isArray(value)) {dependArray(value) }// 数组中每一项也需要收集依赖 function dependArray (value: Array<any>) {for (let e, i = 0, l = value.length; i < l; i++) {e = value[i]e && e.__ob__ && e.__ob__.dep.depend()if (Array.isArray(e)) {dependArray(e)}} }
总结下,vue2有哪些缺陷?
- 不能监听数组长度的变化
- 不能监听数组根据下标修改
- 不能监听对象的新增和删除
- 所以vue2提供了修补方法,使用$set()
持续更新中~ 如有不足,敬请指出,共同进步~