vue2.x带你手写响应式代码(响应式原理)

先上结论

  • 在new Vue()实例里面的data里的数据会通过Obsever重新定义一遍
  • Observer其实就是Object.defineProperty()
  • Vue实例里面的el模板会通过compile解析之后再页面上初始化
  • 因为初始化的时候调用了Obeject.defineProperty()里的get方法,所以会通过Watcher添加到了订阅者里
  • 此时,当Vue实例里的data数据发生改变的时候,先是调用了Object.definePeroperty()里的set方法
  • 然后通过Dependence里的notify去通知每一个订阅者去执行自己的updata方法,然后更改页面显示

直接上代码

<div id='app'>
    <h1>{
   
   {message}}</h1>
    <h1>{
   
   {message}}</h1>
    <h2>{
   
   {name}}</h2>
</div>
const app = new Vue({
    
    
  el:'#app',
  data:{
    
    
    message:'哈哈哈',
    name:'liu'
  }
})

数据响应的步骤

data的数据传到Vue实例里面

Vue内部会做出

  • 先拿到data里面的数据
  message:'哈哈哈',
  name:'liu'
  • 拿到之后,取出对应的键值对
Object.keys(obj).forEach(key => {
    
    
  let value = obj[key]
})

开始监听对象值的改变

  • 原来的属性不好监听,把原来的属性全部重新通过Object.definePorperty重新定义一遍
Object.keys(obj).forEach(key => {
    
    
  let value = obj[key]
  Object.defineProperty(obj,key,{
    
    
    set(newValue){
    
    
      console.log(`监听${
      
      key}的改变`)
    },
    get(){
    
    
      console.log(`获取${
      
      key}对应的值`)
      return value
    }
  })
})

被监听的数据改变之后应该告诉谁?

  • 谁用告诉谁
  • 对其进行解析
<div id='app'>
  //.....
</div>
  • 根据解析html代码,获取到那些再用这个属性
  • 在用这个属性的时候肯定会调用一次message的 get 方法
  • 从而获取那些地方在用这些属性
  • 当属性发生改变的时候,就去通知这些地方

当数据发生改变,Vue是如何知道通知那些数据的

  • 发布->订阅者模式

创建 dependency

  • constructor里的数组就是用来记录谁订阅了这个属性的
  • 通过addSub方法添加订阅者
class Dependency {
    
    
  constructor() {
    
    
    this.subscribe = []
  }
  addSub(watcher) {
    
    
   this.subscribe.push(watcher)
 }
}

创建 Watcher

class Watcher{
    
    
  constructor(name) {
    
    
    this.name = name
  }
}

一旦有人调用属性(message)的get方法,就赶紧创建一个

const watcher1 = new Watcher('watcher1再用属性')
  • 相当于放到了
Object.keys(obj).forEach(key => {
    
    
   //.....
    get(){
    
    
      const watcher1 = new Watcher('watcher1再用属性')
      return value
    }
  })
})
  • 还要通过dep的addSub方法把watcher1加到dep里面
dep.addSub(watcher1)
  • 此时就知道了watcher1在用这个属性

给 dependency 添加 notify 方法

  • notify 会遍历所有订阅者(subscribe)们,提醒他们去执行update方法刷新数据
class Dependency {
    
    
  constructor() {
    
    
    this.subscribe = []
  }
  addSub(watcher) {
    
    
    this.subscribe.push(watcher)
  }
  notify() {
    
    
    this.subscribe.forEach(item => {
    
    
      item.update()
    })
  }
}

给 wather 添加update方法

class Watcher{
    
    
  constructor(name) {
    
    
    this.name = name
  }
  update(){
    
    
    console.log(`${
      
      this.name}发生更新了`)
  }
}

一旦属性(message)发生改变,就回去调用class Dependenc里的notify方法

Object.keys(obj).forEach(key => {
    
    
  let value = obj[key]
  Object.defineProperty(obj,key,{
    
    
    set(newValue){
    
    
      console.log(`监听${
      
      key}的改变`)
      value = newValue
      dependency.notify() // 调用notify,谁用谁改变
    },
    get(){
    
    
      console.log(`获取${
      
      key}对应的值`)
      return value
    }
  })
})

Vue如何检测数组的变化

  • 数组没有使用Object.defineProperty去重新定义
  • 函数劫持:在函数里去通知数据更新
  • Vue把的数组的原型方法进行了重写,重写一个新的原型,让数组类型通过这个原型链,找到新写的原型,如果用户调用数组上的方法,那走的就是我们自己的方法,就可以去设置响应式了
    数组里的每一项有可能还是一个对象,把数组遍历一下,如果数组里有对象的话,会继续深度遍历,
  • 对数组里的每一项进行观测,如果是对象的也会进行数据更新数组里的对象会再次进行Observer
  • 重写后的数组原型上拥有的方法是有限的,都是一些能够改变数组的方法
  • 如何处理循环依赖:在Vue进行检测的时候如果发现这个数据已经被检测过的了,就不会观测,直接把观测后的数据返回了,不会重复检测
  • 源码
data:{
    
    
  arr:[]
}
  • 在Vue内部
data.arr.__proto__ == arrayMethods
  • arrayMethods 就是Vue内部重写后的数组原型

猜你喜欢

转载自blog.csdn.net/m0_47883103/article/details/108302053