面试题答案
一键面试Vue对数组变异方法的拦截实现响应式原理
- 重写数组原型方法
- Vue在初始化时,通过
Object.create
创建了一个新的对象,它的__proto__
指向原生数组的原型Array.prototype
。 - 然后在这个新对象上重新定义了数组的变异方法,如
push
、pop
、shift
、unshift
、splice
、sort
、reverse
。例如:
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] Object.defineProperty(arrayMethods, method, { value: 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 }, enumerable: false, writable: true, configurable: true }) })
- 当Vue实例中的数组被创建时,会将该数组的
__proto__
指向arrayMethods
。这样,当调用数组的变异方法时,实际调用的是arrayMethods
上重写的方法。
- Vue在初始化时,通过
- 依赖收集与通知
- 在重写的变异方法中,首先调用原生数组的对应方法(如
original.apply(this, args)
)来执行真正的数组操作。 - 然后,对于
push
、unshift
、splice
这些可能会插入新元素的方法,会对新插入的元素进行响应式处理(通过ob.observeArray(inserted)
)。这里的ob
是数组的观察者实例,observeArray
方法会递归地为新插入的数组元素创建观察者。 - 最后,调用
ob.dep.notify()
来通知依赖该数组的所有订阅者(即视图相关的Watcher),让它们知道数组发生了变化,从而触发视图更新。
- 在重写的变异方法中,首先调用原生数组的对应方法(如
自定义数组方法让Vue响应式系统感知变化
- 手动触发依赖通知
- 如果自定义一个数组方法,假设为
customMethod
,在方法内部,首先要调用原生数组的相关操作来改变数组。例如,如果是类似push
的功能,可以这样写:
Array.prototype.customMethod = function (newItem) { const originalPush = Array.prototype.push originalPush.call(this, newItem) const ob = this.__ob__ if (ob) { ob.dep.notify() } }
- 这里通过获取数组的观察者实例
ob
,然后调用ob.dep.notify()
来手动触发依赖通知,这样Vue的响应式系统就能感知到数组变化并更新视图。
- 如果自定义一个数组方法,假设为
- 使用Vue.set方法
- 另一种方式是利用Vue提供的
Vue.set
方法(在组件内可以直接用this.$set
)。例如:
Array.prototype.customMethod = function (newItem) { const originalPush = Array.prototype.push originalPush.call(this, newItem) this.$set(this, this.length - 1, newItem) }
Vue.set
方法不仅会触发响应式更新,还会处理一些边界情况,比如在对象中添加新属性时确保其响应式等。在数组场景下,它能保证新添加的元素也被正确地设置为响应式,并且通知视图更新。
- 另一种方式是利用Vue提供的