1.接上文描述
上次我们说vue的双向绑定是通过object.defineProperty去重写属性的get,set来实现的,那么数组呢?我们看个demo
let obj = {
name: '张三',
hoppy: ['下棋','阅读']
}
let keys = Object.keys(obj);
for(let i=0; i<keys.length; i++){
let value = obj[keys[i]]
Object.defineProperty(obj, keys[i], {
get:function(){
console.log('get')
return value
},
set: function(newVal){
console.log('set')
if(value === newVal) return
value = newVal
}
})
}
console.log(obj.hoppy) //get 值 (说明获取数组时可以监听到)
obj.name = '李四' //打印set
obj.hoppy.push('爬山') //无打印,说明set监听不到数组的更改
2.实现对数组的监听的思路
因为object是通过拦截器的方式来进行监听,那么我们Array也这样实现。
首先我们应该知道的是push等方法都是原型对象上的方法,
经过整理Array原型上可以改变自身的方法有七个。分别是push,pop,shift,unshift,splice,sort,reverse
那么首先我们获取数组的原型对象,并对这七个方法进行监听
const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto) //获取原型对象
let methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methods.forEach(function(method){ //这里就是获取监听的地方
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args){
return original.apply(this, args)
}
})
这里我们已经完成一个数组原型对象的七个方法的监听
3.改写Observer类
经过1,2我们已经知道依赖的收集依旧可以放在getter函数中收集,并且已经知道如何监听更改。那么我们现在改写Observe控制依赖的收集。 主要的双向绑定原理就是在这里对原型对象进行赋值。更改将变成响应式对象的原型对象
const hasProto = '_proto_' in {}
export default class Observer{
constructor(value){
this.value = value;
def(value, '__ob__', this) //仅仅为了提供让拦截器访问
let dep = new Dep()
if(Array.isArray(value)){ //这次改写
if (hasProto) { //判断当前浏览器是否支持'_proto_'
protoAugment(value, arrayMethods) //原型赋值
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
}else{
this.walk(value)
}
},
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
function protoAugment (target, src) {
target.__proto__ = src
}
function copyAugment (target, src, keys) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key]) //object.defineProperty重写一遍
}
}
//该方法见上篇walk方法执行步骤
function defineReactive(data, key, val){
let childOb = observer(val) //新增
let dep = new Dep()
Object.defineProperty(data, key, {
get: function(){
console.log('get')
dep.depend()
if(childOb){
childOb.db.depend() //收集依赖
}
return val
},
set: function(newVal){
console.log('set')
if(val === newVal) return
val = newVal
dep.notify();
}
})
}
export function observe (value) {
if (!isObject(value)) { //判断是否是对象类型且不为Null
return
}
let ob
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { //主要是为了防止重复监听
ob = value.__ob__
} else {
ob = new Observer(value)
}
return ob
}
4.更改拦截器
methods.forEach(function(method){
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args){
console.log('触发事件')
const result = original.apply(this, args)
const ob = this.__ob__
ob.dep.notify() //通知依赖
return result
},
enumerable:false,
writable: true,
configurable: true
})
})
5.总结
1.push等方法是原型链上的方法。所以我们应该想办法拦截这个原型上方法的调用
2.重写一个原型对象,使用Object.defineProperty将push等方法进行拦截重写
3.将这个原型对象赋值给你想要转换为响应式的对象 的原型对象(已经能实现了数据监听)
4.使用object.defineProperty重写getter方法进行依赖监听