前言
在我的上一篇文章vue2响应式原理之Object.defineProperty()方法中介绍了Object.defineProperty()方法使单个对象属性进行数据劫持,通过getter和setter方法来更新对象的数据实现响应式。但是对象里面嵌套对象或者数组方法就不适用了,所以接下来是如何使对象的全部属性进行侦测实现每一层数据都是响应式。
一、Observer类实现侦测全部对象属性?
Observer类将一个正常的object转换为每个层级的属性都是响应式(可被侦测的) object
// src/Observer.js
import observe from "./observe"
export default class Observer {
constructor(value) {
// 给实例添加_ob_属性,值是这次new的实例
Object.defineProperty(value, "__ob__", {
// 值指代的就是Observer的实例
value: this,
// 不可枚举
enumerable: false,
writable: true,
configurable: true,
});
// 侦测值
this.walk(value)
}
// 遍历
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
}
function defineReactive(data, key, val) {
if (arguments.length == 2) {
val = data[key]
}
// 递归关键
// 子元素进行observe , 至此形成递归.这个递归不是函数自己调用自己,而是多个函数/类循环调用
let childOb = observe(val);
// 如果val还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止
Object.defineProperty(data , key , {
enumerable:true,
configurable:true,
get(){
console.log('访问obj属性触发')
return val
},
set(newValue) {
console.log('修改obj属性触发' , newValue)
if (val === newValue) return;
val = newValue
childOb = observe(newValue)
}
})
}
形成递归,多个函数/类循环调用
// src/observe.js
import Observer from './Observer'
// observe方法暴露
export default function(value) {
// 如果传过来的是对象或者数组 进行属性劫持
if(typeof value != 'object') return;
var ob;
if(typeof value.__ob__ !== 'undefined') {
ob = value.__ob__;
}else{
ob = new Observer(value);
}
return ob;
}
核心就是defineReactive()方法通过Object.defineProperty使对象属性进行数据劫持,通过getter和setter方法来更新对象的数据实现响应式。通过Observer类和observe方法递归侦测全部对象属性
思考:如果对象有数组数组有千千万万个通过下标来添加set和get是很消耗性能的,所以所以此方法只用来劫持对象,那么有什么方法使数组也变成响应式呢?
二、数组的响应式处理
下面这段代码意思是给每个响应式数据增加不可枚举__ob__属性并指向Observer实例,根据这个属性来防止已经被响应式侦测的数据反复被侦测
// src/Observer.js
import { arrayMethods } from './array'
import observe from './observe';
import {def} from './utils'
export default class Observer {
constructor(value) {
// 给实例添加_ob_属性,值是这次new的实例
def(value, '__ob__', this, false)
// 检查它是数组还是对象
if (Array.isArray(value)) {
// 如果是数组,强行将数组的原型,指向arrayMethods
// 通过重写数组原型方法来对数组的七种方法进行拦截
Object.setPrototypeOf(value, arrayMethods);
// 如果数组里面还包含数组 需要递归判断
this.observeArray(value)
} else {
// 侦测值
this.walk(value)
}
}
// 遍历
walk(value) {
for (let k in value) {
defineReactive(value, k)
}
}
}
重写数组的7个方法方法
// src/array.js
import {def} from './utils'
// 保留数组原型
const arrayPrototype = Array.prototype;
//以Array.prototype为原型创建arrayMethods对象
export const arrayMethods = Object.create(arrayPrototype);
console.log(arrayMethods);
//要被改写的7个数组方法
const methodsNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsNeedChange.forEach(methodName =>{
//备份原来的方法
const original = arrayPrototype[methodName];
//定义新的方法
def(arrayMethods, methodName, function(){
//恢复原来的功能
const result = original.apply(this, arguments);
//把类数组变为数组
const args = [...arguments]
//把数组身上这个__ob__取出来,__ob__已经被添加了,我为什么已经被添加了?因为数组肯定不是最高层,
//比如obj.cba属性是数组,obj不能是数组,第一次遍历obj对象第一层的时候,已经给cba属性添加了__ob__
// this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)
//this就是a ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例
const ob = this.__ob__;
let inserted = [];
switch (methodName) {
case 'push':
case 'unshift':
inserted = args
case 'splice':
inserted = args.slice[2];
break;
}
//判断没有要插入的新项,让新项也变为响应式
if(inserted) {
ob.observeArray(inserted)
}
console.log('dadadadadad');
return result;
},false)
})
工具
// src/utils.js
export const def = function(obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
// 是否可枚举
enumerable,
writableL:true,
configurable:true
})
}
总结
vue2响应式原理之递归对象全部属性二篇完结,但是我们怎么通过数据修改后通知视图更新呢?
响应式原理最后一篇来描述通过getter和sertter进行依赖收集通知Watcher进行视图更新