[vue2源码]深度理解Vue中Observer,Dep,Watcher以及解决监听Array数组变化

双向绑定

  1. 首先通过一次渲染操作触发Data的getter进行依赖收集
  2. 在data发生变化的时候会触发它的setter
  3. setter通知Watcher
  4. Watcher进行回调通知组件重新渲染的函数
  5. diff算法来决定是否发生视图的更新

Observe

  • 每个数据都有一个标记,防止重复绑定
  • Observer为数据加上响应式属性进行双向绑定,如果是对象,则进行深度遍历,为每一个子对象都绑定上方法,如果是数组,对每个成员进行遍历绑定方法

源码解析:

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number;

  constructor (value: any) {
    this.value = value
    this.dep = new Dep() //建立发布者
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      //是数组对每个成员进行遍历绑定方法
      if (hasProto) {
        // __proto__指向重写过后的原型
        protoAugment(value, arrayMethods)
      } else {
	    //遍历arrayMethods把它身上的这些方法直接给value 
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
	    //是对象,则进行深度遍历,为每一个子对象都绑定上方法
	    //defineReactive通过Object.defineProperty定义getter和setter收集依赖通知更新
	    const keys = Object.keys(obj)
	    for (let i = 0; i < keys.length; i++) {
	      defineReactive(obj, keys[i])
	    }
    }
  }

Watcher

观察者对象

  1. 依赖收集后保存在deps里
  2. 变动的时候deps作为发布者通知watcher
  3. watcher进行回调渲染

Dep

  • 发布者,可以订阅多个观察者
  • 收集依赖后会有一个或者多个watcher
  • 一旦有变动便通知所有watcher

Watch监听Array数组变化

//此为监听对象
data(){
      return {
        objVal: {
          name: 'obj',
          type: 'obj'
        }
      }
    },
    watch:{
      objVal:{
        handler(val,oldval){
 
        },
        deep: true,
        immediate:true
      }
    },
    methods:{
      changeObj(){
        this.objVal.name = 'newobj';
      }
    }

deep
当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,深入监听,即监听对象里面的值的变化

immediate
watch默认当值第一次绑定的时候,不会执行监听函数,immediate的作用就是首次获取值也执行函数

以上demo是监听对象,如果换成数组的话,会出现vue不会响应数据变化而重新去渲染页面,则监听失败

解决方法

// Vue.set
Vue.$set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

原理
Object.defineProperty对数组进行响应式化是有缺陷的
Vue使用了重写原型的方案代替

  1. 先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。
  2. 对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。
  3. 把需要被拦截的 Array 类型的数据原型指向改造后原型
const arrayProto = Array.prototype // 获取Array的原型
 
function def (obj, key) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    value: function(...args) {
      console.log(key); // 控制台输出 push
      console.log(args); // 控制台输出 [Array(2), 7, "hello!"]
       
      // 获取原生的方法
      let original = arrayProto[key];
      // 将开发者的参数传给原生的方法,保证数组按照开发者的想法被改变
      const result = original.apply(this, args);
 
      // do something 比如通知Vue视图进行更新
      console.log('我的数据被改变了,视图该更新啦');
      this.text = 'hello Vue';
      return result;
    }
  });
}
 
// 新的原型
let obj = {
  push() {}
}
 
// 重写赋值
def(obj, 'push');
 
let arr = [0];
 
// 原型的指向重写
arr.__proto__ = obj;
 
// 执行push
arr.push([1, 2], 7, 'hello!');
console.log(arr);

在这里插入图片描述
源码解析
array.js

Vue在array.js中重写了methodsToPatch中七个方法,并将重写后的原型暴露出去。

// Object.defineProperty的封装
import { def } from '../util/index'

// 获得原型上的方法
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

// Vue拦截的方法
const methodsToPatch = [
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
];

// 1.拦截方法
// 2.将开发者的参数传给原生的方法
// 3.重写
// 4.视图更新
methodsToPatch.forEach(function (method) {
  // 原型方法进行赋值,不会去重新改写Array.prototype
  const original = arrayProto[method]
  //ob为成员唯一标识
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    //判断方法
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    //判断后observeArray为每个成员绑定方法
    if (inserted) ob.observeArray(inserted)
    // 通知视图更新
    ob.dep.notify()
    return result
  })
})

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值