第一天我们手写了 vue 里面 data 里面的属性进行响应式,但是我们并没有对 data 里面的数组属性做响应式,接下来我们就开始做数组的响应式
首先我们要知道 vue2 数组响应式只是用数组的一些原生方法才能实现响应式,而不能通过索引来实现响应式,因为考虑到用户很少通过数组索引操作数组,而且要循环数组观测数据很消耗性能,所以 vue2 只对数组常用的方法进行拦截观测,那么 vue2 是如果在原生数组方法上进行拦截呢?
第一步:先判断属性是不是数组,打开 observer/index.js
class Objserver {
constructor(data) {
// 判断
if(Array.isArray(data)) {
// 让数组的原型链改成我们重新方法过后的原型链
data.__proto__ = arrayMethods
}else {
this.walk(data)
}
}
}
第二步: 新建 observer/array.js 文件,我们在里面重写数组原生的方法,本质上不是重写,而是用户调用数组方法的时候,我们拦截做了一些操作之后在调用原生的数组方法
let oldArrayPrototype = Array.prototype
// 先复制数组的原型链
export let arrayMethods = Object.create(oldArrayPrototype)
let methods = ['push', 'shift', 'unshift', 'pop', 'reverse', 'sort', 'splice']
old.forEach((methods) => {
// 用户调用的如果是以上七个方法,会用我自己重写的方法,否则用原来数组的方法(复制了原型链数组的其它方法正常使用,只是对上面 7 个进行了拦截改写)
arrayMethods[methods] = function(...args) {
// .......这里进行改写
// 最后调用原生的数组方法
oldArrayPrototype.call(this,...args)
}
})
做到这里,我们想想好像还漏了什么,那就是对数组里面的对象进行观测,虽然我们不对数组里面的内容响应式,但是我们总得要让数组里面的对象实现响应式吧
第三步:对数组里面的对象进行响应式
class Objserver {
constructor(data) {
// 判断
if(Array.isArray(data)) {
// 让数组的原型链改成我们重新方法过后的原型链
data.__proto__ = arrayMethods
// 对数组里面的数据是对象类型,需要监控对象的变化
this.observeArray(data)
}else {
this.walk(data)
}
}
observeArray(data) {
// item 是数组的每一项,我们只需要拦截数组里面每一项的对象进行拦截观测,意思就是要把数组里面的对象变成响应式的,而不需要对数组进行拦截观测
data.forEach((item) => {
// 如果是对象我们就进行拦截观测,数组的话也一样要递归
observe(item)
})
}
}
接下来我们该做什么呢?虽然我们观测拦截了数组对象里面的数据,但是还没有对方法新增的数据做拦截观测,比如我 push 了一个对象,那么这个对象也要拦截观测变成响应式
第四步:对数组新增的数据对象做拦截观测
// 打开 observer/array.js 文件
methods.forEach((method) => {
// 用户调用的如果是以上七个方法,会用我自己重写的方法,否则用原来数组的方法
arrayMethods[method] = function (...args) {
// 调用原来数组的原生方法
oldArrayPrototype[method].call(this, ...args)
// 用于保存新增的数据
let inserted;
// 从数据里面拿到 Observer 实例
let ob = this.__ob__
switch(method) {
case 'push':
inserted = args
brack;
case 'unshift':
inserted = args
brack;
case 'splice':
// 因为 splice 可以新增或替换数据,我们切割拿到第二个以后的参数就是新增或替换的数据
args.slice(2)
inserted = args
brack;
default:
brack;
}
}
// 如果有新增或替换的数据,调用 Observer 实例的 observeArray 方法对新增或替换的对象变成响应式
// 还是那句话,数组里面每一项的值为对象才进行观测,数组本身不需要观测,因为本来数组就不需要响应式
if(inserted) ob.observeArray(inserted)
})
// 打开 observer/index.js 文件
class Observer {
// 对 data 中的所有属性进行数据劫持
constructor(data) {
为了放在栈溢出,我们把这个属性设为不可枚举的,然后有人读取这个属性的时候,我们就返回实例
Object.defineProperty(data,'__ob__',{
value: this,
// 不可枚举
enumerable: false
})
// 把实例保存到 data 上,构造函数的 this 都指向实例
// 但是会出现死循环,因为是对象的话会一直递归,所以这种方法不可行
// data.__ob__ = this
if (Array.isArray(data)) {
......
}
......
}
......
}
总结一下:
- 如果数据是对象,会将对象不停的递归,进行劫持
- 如果是数组,会劫持数组的方法,并对数组中不是基本数据类型的进行检测