目录
vue源码系列之 响应式数据处理2 数组的重写
vue源码系列之 响应式数据处理1 链接: link.
array.js
- 对数组的方法进行重写
- oldArrayPrototype 原始的数组方法
- arrayMethos = Object.create(oldArrayPrototype) 创建一个新的对象,也就是拷贝原始数组的方法
- methods 中的方法,是会影响原始数组的,因此需要重写这些方法
- 遍历这些会改原始数组的方法,然后执行步骤是
- 先执行自己定义的arrayMethos 方法,之后再调用原始数组的方法
- 其中最主要的是:假如args传入的实参为 对象时候 则需要特殊处理
- inserted 为新增要插入的数据( [ [1,2,3] , {name:“ppp”} ] )类似这种需要特殊处理
- 然后假设有新增数据时,则需要继续劫持,观测数组里面的每一项数据
let oldArrayPrototype = Array.prototype; // 原始的数组方法
export let arrayMethos = Object.create(oldArrayPrototype); // 数组方法的重写
// 其中arrayMethos_proto_ = Array.prototype
let methods = [
'push',
'shift',
'unshift',
'pop',
'reverse',
'sort',
'splice'
]
methods.forEach(method => {
// 用户调用了以上几个方法 会用自己重写的,否者使用原来的数组方法
arrayMethos[method] = function (...args) { // 先调用自己写的方法,之后调用内部原有的方法
// console.log("改变了原始数组"); // arr.push(1,2) -> [1,2]
oldArrayPrototype[method].call(this, ...args);
// 假如args传入的实参为 对象时候 则需要特殊处理
let inserted;
let ob = this.__ob__; // 根据当前数组实例获取到observer实例对象
switch (method) {
case "push":
case "unshift":
args; // 就是新增的内容
case "splice":
args.slice(2)
default:
break;
}
// 如果有新增的内容 要进行继续劫持, 我需要观测的数组里的每一项,而不是数组
if (inserted) ob.observeArray(inserted);
// 如果数组中的数据是对象类型,需要监控对象的变化
}
})
observe / index.js
- 作用:观察者监听数据的变化,实现数据的响应式
- 执行流程:
- Observer观察者之内首先判断是对象还是数组
- 是对象的时候,则是执行 walk() 方法,对对象属性的劫持,转化为响应式属性
- 其中转化为响应式属性是采用
Object.defineProperty
方法 - 值得注意的是:若是设置新值时,为对象,则需要继续监听
- 因此对于
data.__ob__
属性,则需要设置为不可枚举,否者会陷入死循环
- 其中转化为响应式属性是采用
- 为数组的时候:
- 拿到重写的方法,赋值给当前对象的属性,也就是重写的方法
- 值得注意的是:数组内含有数组或者对象时候,又需要针对数组进行监听
this.observeArray(data);
- observeArray(data)遍历数组中的每一项,然后进行观测
observe(item)
- 引入 数组重写的方法 arrayMethos
- 是对象的时候,则是执行 walk() 方法,对对象属性的劫持,转化为响应式属性
import {
arrayMethos
} from "../array";
import {
isObject
} from "../utils";
// 检测数据的变化 类是有类型的,对象是无类型!
class Observer {
constructor(data) { // 对对象中的所有属性 进行劫持
Object.defineProperty(data, '__ob__', {
value: "this",
enumerable: false, //不可枚举 之后也就无法遍历到 walk()
})
if (Array.isArray(data)) { // 数组劫持 -> 对数组的原来方法进行改写,切片编程,高阶函数
data.__proto__ = arrayMethos;
// 如果数组中的数据是对象类型,需要监控对象的变化
this.observeArray(data);
} else {
this.walk(data); // 对每一个对象劫持后 调用一次
}
}
// 遍历数组
observeArray(data) { // 针对数组中的数组 和 数组中的对象再一次劫持 递归了
data.forEach(item => {
observe(item)
})
}
// 遍历对象
walk(data) {
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]); // 把对象中的属性 定义为响应式数据
})
}
}
// vue2 对对象进行遍历后 将每个属性 用defineProperty重新定义 -> 造成性能较差
function defineReactive(data, key, value) {
// 注意点:就是value为对象的时候,需要再劫持
observe(value); // 本身默认传入的数据为对象套对象时,则需要递归劫持
// console.log("defineReactive", data, key, value); // defineReactive {name: 'xpl'} name xpl
Object.defineProperty(data, key, {
get() { // 取值
return value;
},
set(newVal) { // 设置data新的值
observe(newVal) // 对数据进行修改时 是一个对象,又需要劫持,修改为响应式数据
value = newVal;
}
})
}
// 观察者 监听data数据
export function observe(data) {
// console.log("observe内的", data); // observe内的 {name: 'xpl'}
// 如果为对象的时候 才观测
if (!isObject(data)) {
return;
}
if (data.__ob__) { // 如果数组已经被劫持了观测过了
return;
}
// 默认而言 data是一个对象
return new Observer(data)
}