vue双向绑定原理简化版Array---(二)

248 阅读2分钟

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方法进行依赖监听