面试题答案
一键面试对象和数组响应式实现的不同
- 对象:Vue通过
Object.defineProperty()
方法来进行响应式的实现。遍历对象的属性,为每个属性定义getter
和setter
。当访问属性时,getter
会被触发用于依赖收集;当属性值改变时,setter
会被触发从而通知相关依赖进行更新。 - 数组:由于JavaScript中无法拦截对数组索引的直接赋值以及
length
属性的改变,Vue对数组不能像对象那样简单地使用Object.defineProperty()
。Vue采用覆盖数组原型的方式,对数组的变异方法(如push
、pop
、shift
、unshift
、splice
、sort
、reverse
)进行拦截,在这些方法被调用时触发视图更新。同时,Vue还提供了$set
方法来对数组的指定索引进行响应式更新。
对数组变异方法的拦截实现响应式
- 原型覆盖:Vue创建了一个新的数组原型
arrayMethods
,它继承自原生的Array.prototype
。在arrayMethods
上重新定义了数组的变异方法。 - 方法拦截:当Vue实例中的数组调用这些变异方法时,实际调用的是
arrayMethods
上重新定义的方法。在这些重新定义的方法内部,首先调用原生的数组方法,保证数组行为的正确性。然后,通过发布 - 订阅模式通知依赖(如视图),告知数组发生了变化,从而触发视图更新。例如,对于push
方法的实现:
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
})
})
在上述代码中,mutator
函数在调用原生push
等方法后,会对新插入的元素进行响应式处理(如果有新元素),并且通过ob.dep.notify()
通知依赖更新。