Vue拦截数组变异方法的思路
在JavaScript的数组中,有一些可以改变原数组的方法,我们称之为数组的变异方法,包括splice、 sort、push、pop、reverse、shift、unshift
等,当我们在vue中通过这些方法更改数组的时候会触发视图更新,这是为什么呢?
在上一篇文章中,我说明了通过Object.defineProperty
可以实现对对象的监听,从而触发视图更新,JavaScript中数组也是对象的,为什么vue不使用Object.defineProperty
来完成对数组的监听呢?通过网上查阅,发现使用Object.defineProperty
监听数组性能很差,方便性得到的好处小于性能带来的损失,得不偿失。
那我们如何通过监听这些变异数组来触发视图更新呢?
试想下面的例子, 我如何不改变原函数的情况下让其先输出我就在这,等你来
function love() {
console.log('你爱来不来')
}
思索中。。。
let tmpLove = love
function love() {
console.log('我就在这,等你来')
tmpLove()
}
咦,上面代码就完美实现了我的要求,首先将love函数缓存在tmpLove变量中,之后再重写love函数。这样我们就保证了在不变love函数的情况下,实现对其自身的扩展。
目前的Vue中,正是利用这种技巧实现对数组变异方法的扩展,即在不改变自身的前提下扩展方法。我们都知道,JavaScript中这些变异方法都是存在于数组构造函数的原型上,如图
数组也是对象,所以实例对象的__proto
__ 属性指向的就是数组构造函数的原型,即array.__proto__ === Array.prototype
为true
,为了拦截到数组的变异方法,我们可以在实例和原型之间在插入一层,如下图
中间层我们给他起个名字arrayMethods
, 我们让数组实例array的__proto__
属性指向arrayMethods
, arrayMethods
的__proto__
属性指向Array.prototype
,这样,当我们通过实例去访问数组的变异方法时,根据原型链的查找规则,会先在arrayMethods
对象中查找, 这样,我们就实现了对数组变异方法的拦截, 代码如下
// 数组的变异方法
const variationMethods = ['splice', 'sort', 'push', 'pop', 'reverse', 'shift', 'unshift']
// 缓存数组的原型对象
const tmpArrayPrototype = Array.prototype
// middLayer继承数组的原型对象,即middleLayer.__proto__ = Array.prototype
const middleLayer = Object.create(Array.prototype)
variationMethods.forEach(method => {
// 变异方法全部在middleLayer对象上实现一遍
middleLayer[method] = function(...args) {
// 实际执行的还是Array.prototype对象上的方法
const result = tmpArrayPrototype[method].apply(this, args)
console.log(`拦截到了${method}方法的执行`)
return result
}
})
我们来验证一下
const arr = []
arr.__proto__ = middleLayer
arr.push('1')
执行以上代码,会输出拦截到了push方法的执行
, 同时arr数组的值变成了["1"]
,这说明我们拦截到了push方法,并且执行了push方法。
但是此时又会有一个问题,__proto__
属性并不是所有的浏览器都支持啊,所以就得另想一个办法去拦截,这次我们直接在实例上添加方法, 代码如下
const arr = []
const arrayKeys = Object.getOwnPropertyNames(middleLayer)
arrayKeys.forEach(method => {
arr[method] = middleLayer[method]
})
题外知识点
Object.keys
和Object.getOwnPropertyNames
都可以获取到属性名,但是它俩有什么区别呢?
Object.keys
用来获取对象自身可枚举的属性键
Object.getOwnPropertyNames
用来获取对象自身的全部属性名
我们继续
但是这样有一个问题,就是在实例上添加的方法是可以被枚举出来的,这和原来的方法表现不一致,所以我们可以通过Object.defineProperty
来定义相关方法,如下
const arr = []
const arrayKeys = Object.getOwnPropertyNames(middleLayer)
arrayKeys.forEach(method => {
Object.defineProperty(arr, method, {
enumerable: false,
writable: true,
configurable: true,
value: middleLayer[method]
})
})
我们同样可以验证一下
arr.push('1')
结果和上面一致,这样我们就完成了对数组变异方法的拦截,至于拦截后如何处理,如何触发相关视图变化,这就需要阅读vue源码了。(ps: 后期可能会补上,^_^)