你好,我是终身学习的阿飞。
我做了个青柠檬读书会
的公众号,每天分享我的学习、读书的内容,同时也会分享我的一些学习方式和一些软件推荐。
我最近在读《深度工作》,有点相见很晚的感觉。预计在下周会写一个读后感,发表在青柠檬读书会
的公众号。欢迎你前来阅读
如果您有任何问题,可以在博客下方留言,我们一起探讨。
有兴趣可以关注,我们一起进步!
序
在昨天的文章探讨vue2.x的数据劫持是怎么实现的?中,我们探讨了vue是如何对对象劫持的。今天继续探讨另外一个问题。
vue中是如何对数组进行劫持的。
在使用vue的过程中,我们知道以下几点:
-
通过index对数组项进行改变,
vue无法监听
-
通过.length操作数组,
vue无法监听
-
vue中对数组操作需要使用变异方法
- push
- pop
- shift
- unshift
- sort
- revers
- splice
-
数组中的对象发生变化,是可以被监听的,比如
arr[1].a = 1
-
直接替换数组,可以被监听
我们今天探讨的就是在vue中,这些劫持是如何实现的。对数组的操作,又为什么会有这两个缺陷呢?
在vue对data进行监听的时候,如果发现需要监听的对象是个数组…
如果发现需要监听的对象是个数组,那么就不能通过defineProperty来对数据进行监听了。
如果是数组,就需要对数组的原方法进行重写。也就是所谓的变异方法。
class Observer {
constructor(data) {
if (Array.isArray(data)) {
// 注意这里的方法
data.__proto__ = arrayMethods
} else {
this.walk(data)
}
}
walk(data) {
// 对非数组的值进行劫持
}
}
我们新建一个array.js
, 用于对数组进行处理
let oldArrayProtoMtthods = Array.prototype
export let arrayMethods = Object.create(oldArrayProtoMtthods)
let methods = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
methods.forEach(m => {
arrayMethods[m] = function (...args) {
let r = oldArrayProtoMtthods[m].apply(this, args)
let inserted
switch (m) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
if(inserted) observerArray(inserted)
console.log('调用了数组的更新方法')
return r
}
})
接下来对这段代码逐段分析:
let oldArrayProtoMtthods = Array.prototype
export let arrayMethods = Object.create(oldArrayProtoMtthods)
这里通过Object.create
重新得到一个新的对象,这个对象包含Arrar的所有方法。
Object.create(proto, [propertiesObject])
这个方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。
之所以这样做,是为了不影响「VUE」以外的数组方法。
let methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice' ]
methods.forEach(m => {
arrayMethods[m] = function (...args) {
let r = oldArrayProtoMtthods[m].apply(this, args)
}
})
这里,对新的arrayMethods
(就是赋值的新对对象,包含所有的原数组上面的方法)上的七个需要变异的方法进行处理.
oldArrayProtoMtthods[m].apply(this, args)
- 这里的apply是改变this的指向,如果没有修改,则this指向的是Array.prototype,修改之后,this指向的是调用方。比如:
arr.push(a)
, this指向的就是arr - args指的是传入的数据,是一个数组
如果变异方法中,对数组添加对象
我们在导读里面说:数组中的对象发生变化,是可以被监听的,比如 arr[1].a = 1
当我们在使用变异方法去修改数组的时候,假如给数组添加了一个对象,这里面的对象是没有被监听的。这里需要怎么处理呢?
methods.forEach(m => {
arrayMethods[m] = function (...args) {
let r = oldArrayProtoMtthods[m].apply(this, args)
let inserted
switch (m) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
if(inserted) observerArray(inserted)
console.log('调用了数组的更新方法')
return r
}
})
在这里,定义一个inserted的变量。
七个变异方法中,只有三个方法可以向数组插入值。他们分别是:push
、unshift
、splice
let inserted
switch (m) {
case 'push':
case 'unshift':
inserted = args
break;
case 'splice':
inserted = args.slice(2)
break;
}
if(inserted) observerArray(inserted)
如果监听到数组被插入数据,即inserted有值,则对inserted进行循环,然后对每一项进行监听。
export function observerArray (inserted) {
console.log('inserted', inserted)
// 循环数组,对数组的每一项进行观测
for(let i = 0; i < inserted.length; i++) {
observer(inserted[i])
}
}
这里的observer见上一篇博文。
第一次获取array类型数据的时候,也需要对数组中的对象进行监听
我们再回头看一下这个方法
class Observer {
constructor(data) {
if (Array.isArray(data)) {
// 对编译方法进行处理
data.__proto__ = arrayMethods
// 对数组中的对象进行监听
observerArray(data)
} else {
...
}
}
}
写到这里,对数组进行劫持的原理就已经讲完了。
我们最后讨论一个问题,vue为什么对数组index的修改和lenght修改不做监听呢?
官网是这么解释的:
由于 JavaScript 的限制,Vue 不能检测以下变动的数组: 当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength
也就是说,当数组项非常大的时候,操作下标或length会有严重的性能问题。
「Tip」
** 为什么变异方法是七个?而不是其他方法?**
这是因为啊,只有这七个方法会改变数组嘛。