简单实现vue中的依赖收集与响应的方法

news/2024/7/10 1:45:13 标签: vue

开始

声明一个对象man,可以视为vue中的data

let man = {
 height: 180,
 weight: 70,
 wealth: 100000000
}

添加Observer

作用在于将参数对象的属性变为响应式,只要对象的属性被读取或者被修改都能观察到。然后新建一个Observer实例,将man作为参数扔进去。这里的proxyData是将man的属性代理到以man为参数的Observer实例上去。

class Observer {
 constructor(obj) {
  this.walk(obj)
 }
 walk(obj) {
  Object.keys(obj).forEach(prop => {
   this[prop] = obj[prop]
   this.proxyData(obj, prop)
   this.defineReactive(this, prop, obj[prop])   
  })
 }
 proxyData(obj, prop) {
  let _this = this
  Object.defineProperty(obj, prop, {
   get() {
    return _this[prop]
   },
   set(newVal) {
    _this[prop] = newVal
   }
  })
 }
 defineReactive(obj, prop, val) {
  Object.defineProperty(obj, prop, {
   get() {
    console.log(`${prop} - 被读取!`)
    return val
   },
   set(newVal) {
    if (newVal == val) return
    val = newVal
    console.log(`${prop} - 被修改!`)
   }
  })
 }
}
 
new Observer(man)

这时打印一下man

现在man的属性都是由Observer实例所对应的属性的getter来返回,只有在查看时会被触发

对man的属性进行修改也会触发实例对应属性的setter

添加Watcher

现在的Watcher有点像vue中的computed,实际上就是定义一个计算属性,这个计算属性依赖于前面man中的某些属性,由他们计算而得。

class Watcher {
 constructor(obj, prop, computed) {
  this.getVal(obj, prop, computed)
 }
 
 getVal(obj, prop, computed) {
  Object.defineProperty(obj, prop, {
   get() {
    console.log(`computed属性 - ${prop}被读取!`)
    return computed()
   },
   set() {
    console.error('计算属性不可被修改!')
   }
  })
 }
}
 
new Watcher(man, 'strength', () => {
 let {height, weight} = man
 if (height > 160 && weight > 70) return 'strong'
 return 'weak'
})

看起来没什么问题,所依赖的属性如果变了,计算属性只要再被查看(get方法)一次就可以更新了。但vue中的视图渲染是实时的,视图层依赖于数据层,数据变化了,视图层也会跟着变化,不需要手动更新。类比到这个例子就是计算属性如何才能在其所依赖的属性发生变化时被通知从而触发应有的事件?

这时我们先给Watcher加多一个callback,用于处理当依赖的数据被修改时,我这个计算属性该怎么响应

比如当依赖被修改时,我们就把这个计算属性的值打印出来

class Watcher {
 constructor(obj, prop, computed, callback) {
  this.getVal(obj, prop, computed, callback)
 }
 
new Watcher(man, 'strength', () => {
 let {height, weight} = man
 if (height > 160 && weight > 70) return 'strong'
 return 'weak'
}, () => {
 console.log(`i am so ${man.strength} !`)
})

一切都准备好了,接下来就是该如何实现?

我们先看下Watcher中getVal这个方法

getVal(obj, prop, computed, callback) {
 Object.defineProperty(obj, prop, {
  get() {
   console.log(`computed属性 - ${prop}被读取!`)
   return computed()
  },
  set() {
   console.error('计算属性不可被修改!')
  }
 })
}

当我们查看计算属性时,会调用computed这个方法,相当于查看了其所依赖的height和weight属性,而在上面我们已经让man的所有属性都拥有了get方法,即他们被查看时我们是不是可以把callback塞给他们?
这时候我们引进一个桥梁,来连接Watcher和Observer。

添加Dep

Dep的用处在于当某一个属性(以下称‘自己’)被依赖了,将依赖自己的粉丝(们)–也就是Watcher(s),收集起来,假如自己发生了变化,能够及时通知粉丝们。

class Dep {
 constructor() {
  this.deps = []
 }
 getDeps() {
  if (!Dep.target || this.deps.includes(Dep.target)) return
  console.log('依赖添加', Dep.target)
  this.deps.push(Dep.target)
 }
 notify() {
  this.deps.forEach(dep => {
   dep()
  })
 }
}

这里的Dep.target就是前面所说的callback方法了。这时我们改一下Watcher中的getVal

getVal(obj, prop, computed, callback) {
 Object.defineProperty(obj, prop, {
  get() {
   Dep.target = callback
   console.log(`computed属性 - ${prop}被读取!`)
   return computed()
  },
  set() {
   console.error('计算属性不可被修改!')
  }
 })
}

在计算属性被查看时,将callback赋值给Dep.target,接下来就会调用其所依赖属性的getter,我们只要在getter里把callback给收集起来就行了。接下来修改依赖属性的getter方法。

defineReactive(obj, prop, val) {
 let dep = new Dep()
 Object.defineProperty(obj, prop, {
  get() {
   console.log(`${prop} - 被读取!`)
   dep.getDeps() // 依赖收集
   return val
  },
  set(newVal) {
   if (newVal == val) return
   val = newVal
   console.log(`${prop} - 被修改!`)    
  }
 })
}

这时watcher的callback都被依赖属性给收集起来了,当依赖属性发生变化时只要去运行这些callback就可以了。接下来就是修改依赖属性的setter方法。

defineReactive(obj, prop, val) {
 let dep = new Dep()
 Object.defineProperty(obj, prop, {
  get() {
   console.log(`${prop} - 被读取!`)
   dep.getDeps()
   return val
  },
  set(newVal) {
   if (newVal == val) return
   val = newVal
   console.log(`${prop} - 被修改!`)
   dep.notify() // 运行所有callback
  }
 })

运行看看

我们加多一个Watcher试试

new Watcher(man, 'isGreat', () => {
 let {height, weight, wealth} = man
 if (height > 180 && weight > 70 && wealth > 100000) return 'Great!'
 return 'not good enough ...'
}, () => {
 console.log(`they say i am ${man.isGreat}`)
})


这就是vue中的一个依赖对应多个Watcher

最后

为了帮助大家让学习变得轻松、高效,给大家免费分享一大批资料,帮助大家在成为全栈工程师,乃至架构师的路上披荆斩棘。在这里给大家推荐一个前端全栈学习交流圈:866109386.欢迎大家进群交流讨论,学习交流,共同进步。

当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。

但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有有效资源还是很有必要的。

最后祝福所有遇到瓶疾且不知道怎么办的前端程序员们,祝福大家在往后的工作与面试中一切顺利。


http://www.niftyadmin.cn/n/1300768.html

相关文章

map hash_map unordered_map 性能测试

测试环境: 测试工具: Microsoft Visual Studio Enterprise 2015 版本 14.0.25431.01 Update 3 Microsoft .NET Framework 版本 4.6.01586 测试代码: 插入操作 #include <stdio.h>#include <windows.h> #include <Mmsystem.h> …

15个最好用的JavaScript代码压缩工具

JavaScript 代码压缩是指去除源代码里的所有不必要的字符&#xff0c;而不改变其功能的过程。这些不必要的字符通常包括空格字符&#xff0c;换行字符&#xff0c;注释以及块分隔符等用来增加可读性的代码&#xff0c;但并不需要它来执行。 在这篇文章中&#xff0c;我们选择了…

空类大小及指针转换(多继承)

先得出结论: 1. 空类大小为1(仅标识类的存在),如果空类中加了virtual关键字则是4(虚函数表指针)。 2. 子类指针转父类时, 强转、static_cast、dynamic_cast效果一样! 3. 强转时,A,B 指针值 与 C的指针值可能相同或不同! 4.虚函数原理,请查阅http://blog.csdn.n…

空类大小及指针转换(单继承)

先得出结论: 1. 空类大小为1(仅标识类的存在),如果空类中加了virtual关键字则是4(虚函数表指针)。 2. 子类指针转父类时, 强转、static_cast、dynamic_cast效果一样! 3. 强转时,大部分情况A,B与 C 指针值相同,但少数情况不同! 情形一: class A { public:void A…

总结4个方面优化Vue项目

运行时优化 1、使用v-if代替v-show 两者的区别是&#xff1a;v-if不渲染DOM&#xff0c;v-show会预渲染DOM 除以下情况使用v-show&#xff0c;其他情况尽量使用v-if 有预渲染需求 需要频繁切换显示状态 2、v-for必须加上key&#xff0c;并避免同时使用v-if 一般我们在两…

Vue动画事件详解及过渡动画实例

为了应用过渡效果&#xff0c;需要在目标元素上使用 transition 特性&#xff1a; <div v-if"show" transition"my-transition"></div>transition 特性可以与下面资源一起用&#xff1a; v-ifv-showv-for &#xff08;只在插入和删除时触发&…

用代码读懂C++类的内存布局

类的内存布局: 结论: 成员变量才占空间,普通函数不占空间! 如果有virtual 则类头部是一个虚函数表指针! #include <stdio.h>class A { private:int a;char b;short c; public: virtual void hello(){printf("Hello\n" );}virtual void world(){pri…

icePubDll.dll简介

今日偶尔发现一个DLL,特此记录 icePubDLL Ice Public DLL 冰雪公开发布的动态链接库 //vc版函数声明 #ifndef SOCKET #define SOCKET unsigned int #endif extern "C" { // __declspec(dllexport) void WINAPI icePub_getExeName(char *strReturn); __declspec(d…