vue深度学习之对象和数组的监听原理

对象的监听

定义一个defineReactive对Object.defineProperty进行封装,用于监听对象的调用和改变

// 传入的key要是计算属性格式哟
function defineReactive(obj, key, val) {
 const dep = new Dep() // 用于收集依赖的类,具体原理就不讲解了
 Object.defineProperty(obj, key) {
  enumerable: true,
  configurable: true,
  get: function () { // 每次调用到对象属性时都会出发这个函数
    dep.depend()// window.target是一个方法,用于存放调用的实例的依赖
    return val // 返回调用对象时获取到的值
  },
  set: function (newVal) {
   if (val === newVal) return
   for (i = 0; i < dep.length; i ++) {
    dep.notify()  // 更显依赖绑定的数据值,实现页面的重新渲染
   }
   val = newVal
  }
 }
}

数组的监听

数组的监听和对象略有不同。除了通过赋值操作改变数组外,还可以通过数组的一些自带方法改变数组,而通过后面那种方式该改变数组setter是监听不到的。

解决方法:拦截数组的原型,并赋予新的原型。

// 通过整理,数组中能够改变数组本身的有七个方法

// 数组能够改变自身的方法:
  const arrayProto = Array.prototype
  // Object.create(obj) 创建一个新对象,并且使用现有对象obj作为新对象的__proto__
  const arrayMethods = Object.create(arrayProto);
  
  [
    'push',  // 在数组最后追加一个元素
    'pop',  // 删除最后一个元素
    'unshift', // 在第一个元素位添加一个元素
    'shift',  // 删除第一个元素
    'splice', // 添加或者删除元素
    'sort',  // 排序
    'reverse' // 反向排序
  ].forEach( method => {
    // 缓存原始方法
    var original = arrayProto[method]
 
    Object.defineProperty(arrayMethods, method, {
      value: function mutator (...args) {  // args是调用原型方法时传入的参数
        console.log('this', this) // this指向属性所属对象
        ... // 在这里面执行监听变化的操作
        return original.apply(this, args)
      }
    })
  })
  module.exports = arrayMethods  

只对需要覆盖原型的数组实例进行原型拦截;避免直接改变Array.proptotype造成的全局污染

const  arrayMethods = require('./3-2拦截器')
// 让拦截器只覆盖那些响应式数组的原型,而非构造函数Array的原型

// 考虑到有些浏览器不支持原型的情况,要进行判断
const hasProto = '__ptoto__' in {} // 有酒返回true,没有就返回false

export class Observe {
  // 数组实例; value的实例数组
  constructor (value) {
    this.value = value
    if (Array.isArray(value)) {
      // 添加拦截器
      const augment = hasProto ? protoAugment : copyAugment
      augment(value, arrayMethods, arrayKeys)
          
      } else {
       // 如果浏览器不支持原型,就直接将方法添加到数组上
      }
     
    } else {
      this.walk(value) // 如果数据是其他类型时执行
    }
  }
}

// 用拦截器直接覆盖原型
function protoAugment (value, arrayMethods, arrayKeys) {
  value.__proto__ = arrayMethods  
}

// 将添加了拦截器的方法追加到数组的属性中
function copyAugment (value, arrayMethods, keys) {
  for(i = 0; i < keys.length; i ++) {
     const key = keys[i]
    def(value, key, src[key])
  }
}

收集依赖:将数组作为对象的属性传入defineReactive方法

function defineReactive (data, key, val) {
  if (typeof val === 'object') new Observer()
  let dep = new dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      dep.depend() // 收集array的依赖
      return value
    },
    set: function (newVal) {
      if (val === newVal) return
      dep.notify() // 监听array的变化
      val = newVal
    }
  })
}

缺陷:像通过直接改变数组项的值(比如:arr[2]=23)或者通过arr.length改变数组的长度是不能被监听到的。通过es6的元编程能力可以解决,后续再了解。

原文地址:https://www.cnblogs.com/tina-12138/p/13060629.html