Appearance
数组处理
本节目标:理解“数组处理”的核心思路,并能把它用于实际开发或面试表达。
说明:原 OneNote 中有图片引用,但图片未包含在导出目录中;本页保留了可用的文字与代码内容。
注意:Object.keys、Ojbect.values、Ojbect.entries都可以遍历数组;此外,Object.defineProperty方法可以对数组的访问进行拦截,如下代码:var a = [1, 2, 3, 4]
Object.entries(a).forEach(([k, v]) => {
Object.defineProperty(a, k, {
get() {
// 闭包,此vaule会向上层的value进行查找
return v + 1;
},
set(newValue) {
v = newValue;
}
})
})
console.log(a[1]);// 输出为3所以,之前的代码会对数组的每一项进行劫持;这不合适;因为我们很少使用属性名,即键名的方式去对数组进行取值或赋值操作;
此时还对数据按索引进行截持时,如果数组长度大时,将会非常耗性能;
所以这种当成对象处理的方式不合适;所以修改数组索引和修改数组长度不会引发更新;但vue3中为了兼容proxy,内部对数组就是defineProperty所以数组需要特殊处理: 正常用户修改数组,无非采用数组的变异方法, push、pop、splice、shift、unsfhit、reverse、sort
修改observe/index.js中逻辑当new Observe(value)传入的值是数组时,我们进行特殊处理;处理的方式是对数组的变异方法进行重写;然后再调用observeArray方法遍历数组,对数组每一项调用observe进行数据劫持,这样的话:
如果数组成员是基本数据类型,不进行劫持。如果是objcet类型时,再进行劫持;如果是数组,我们递归进行处理;
这样的话,对于数组中的成员,如果是数组,它的原型上的七个变异方法都是被重写过的;
class Observer {
constructor(value) {
// 核心是循环对象
==if== ==(====isArray====(====value====)) {==
==//== ==更改数组原型方法====,== ==如果是数组== ==我就改写数组的原型链==
==value====.====__proto__== ===== ==arrayMethods====;== ==//== ==重写数组的方法==
==this====.====observeArray====(====value====);==
==}== ==else== =={==
==this====.====walk====(====value====);== ==//== ==核心就是循环对象==
==}==
}
==observeArray====(====data====) {== ==//== ==递归遍历数组,对数组内部的对象再次重写== ==[[]] [{}]==
==// vm.arr[0].a = 100;==
==// vm.arr[0] = 100;==
==//== ==数组里面如果是引用类型那么是响应式的==
==data====.====forEach====(====item== ===>== ==observe====(====item====));==
}
walk(data) {
// 使用defineProperty重新定义
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
}新建observe/array.js文件:
主要逻辑:如果变异方法是对数组元素进行新增,我们要对新增的元素调用数组的observeArray方法进行新值的劫持;
如果变异方法是对数组元素进行删除,我们不用处理;这里注意使用…args将参数转化为数组;要让这arrayMethods方法可以获取到Observe实例的observeArray方法;class Observer {
constructor(value) {
// 不让__ob__ 被遍历到
// value.__ob__ = this; // 我给对象和数组添加一个自定义属性
==Object====.====defineProperty====(====value====,== =='__ob__'====,== =={==
==value:== ==this====,==
==enumerable:== ==false== ==//== ==标识这个属性不能被列举出来,不能被循环到==
==})==
// 核心是循环对象
if (isArray(value)) {
// 更改数组原型方法, 如果是数组 我就改写数组的原型链
value.__proto__ = arrayMethods; // 重写数组的方法
this.observeArray(value);
} else {
this.walk(value); // 核心就是循环对象
}
}
observeArray(data) { // 递归遍历数组,对数组内部的对象再次重写 [[]] [{}]
// vm.arr[0].a = 100;
// vm.arr[0] = 100;
data.forEach(item => observe(item)); // 数组里面如果是引用类型那么是响应式的
}
walk(data) {
// 使用defineProperty重新定义
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
}
为了observe方法调用时的死循环,我们在上面的代码中定义==enumerable:== ==false==
此外,添加以下代码,防止属性被重复劫持;export function observe(value) {
if (!isObject(value)) return;
==if== ==(====value====.====__ob__====) {==
==return====;== ==//== ==一个对象不需要重新被观测==
==}==
// 利用new Class方式的特点
// 调用函数
// 返回一个实例
// 这个实例我们加上了很多实例属性和方法
return new Observer(value)
}// 获取数组的老的原型方法
let oldArrayPrototype = Array.prototype;
// 让arrayMethods 通过__proto__ 能获取到数组的方法
// 相当于 arrayMethods.__proto__ == oldArrayPrototype
export let arrayMethods = Object.create(oldArrayPrototype);// 只有这七个方法 可以导致数组发生变化
let methods = [
'push',
'shift',
'pop',
'unshift',
'reverse',
'sort',
'splice'
]
methods.forEach(method => {
arrayMethods[method] = function (...args) {
// 数组新增的属性 要看一下是不是对象,如果是对象 继续进行劫持
// 需要调用数组原生逻辑
oldArrayPrototype[method].call(this, ...args)
// todo... 可以添加自己逻辑 函数劫持 切片
let inserted = null;
let ob = this.__ob__;// this指向数组实例,即调用此方法的数组
switch (method) {
// 修改 删除 添加 arr.splice(0,0,100,200,300)
case 'splice':
// splice方法从第三个参数起 是增添的新数据
inserted = args.slice(2);
break;
case 'push':
case 'unshift':
// 调用push 和 unshift 传递的参数就是新增的逻辑
inserted = args;
break;
}
// inserted[] 遍历数组 看一下它是否需要进行劫持
if (inserted) ob.observeArray(inserted)
}
});所以,如果数组使用变异方法新增了元素,这些新增的元素转换成一个数组,然后会偏历其中的每个元素,并对其调用observe方法,也就是如果是对象,就进行截持,基本类型不做处理;
总结:
New Observer中的重要逻辑:给每个传入的值,也就是对象类型的值,加上__ob__属性,表示已经被观测;同时,通过每个值的__ob__属性,就能获取Observer对象,也就能获取Observer对象的所有属性和方法;
再判断传入的值是对象还是数组,如果是对象,遍历对象的属性值,然后分别添加get和set方法拦截对属性的访问和修改;
如果是数组,修改这个数组的原型,这样当调用这个数组的变异方法时,会调用我们改写过的变异方法;其中这些变异方法如果是给数组新增元素,我们把新增的元素用数组进行包装,然后调用obsever对象的observerArray方法,也就是对每个数组元素进行调用observer方法进行观测,如果其中的元素是数组,我们再递归调用observerArray方法;
