文章目录

  • Vue2.x响应式,Observer,Dep,Watcher,理解
    • Observer 「响应式」
    • Dep 「依赖管理」
      • 什么是依赖?
      • 如何收集依赖
      • 什么是依赖
    • Watcher 「中介」
  • 最终代码实现
    • 总结

Vue2.x响应式,Observer,Dep,Watcher,理解

Observer 「响应式」

Vue中用Observer类来管理上述响应式化Object.defineProperty的过程。我们可以用如下代码来描述,将this.data也就是我们在Vue代码中定义的data属性全部进行「响应式」绑定。

class Observer {constructor() {// 响应式绑定数据通过方法observe(this.data);}
}export function observe (data) {const keys = Object.keys(data);for (let i = 0; i < keys.length; i++) {// 将data中我们定义的每个属性进行响应式绑定defineReactive(obj, keys[i]);}
}

Dep 「依赖管理」

什么是依赖?

相信没有看过源码或者刚接触Dep这个词的同学都会比较懵。那Dep究竟是用来做什么的呢? 我们通过defineReactive方法将data中的数据进行响应式后,虽然可以监听到数据的变化了,那我们怎么处理通知视图就更新呢?

Dep就是帮我们收集【究竟要通知到哪里的】。比如下面的代码案例,我们发现,虽然data中有textmessage属性,但是只有message被渲染到页面上,至于text无论怎么变化都影响不到视图的展示,因此我们仅仅对message进行收集即可,可以避免一些无用的工作。

那这个时候messageDep就收集到了一个依赖,这个依赖就是用来管理datamessage变化的。

<div><p>{{message}}</p>
</div>
data: {text: 'hello world',message: 'hello vue',
}

当使用watch属性时,也就是开发者自定义的监听某个data中属性的变化。比如监听message的变化,message变化时我们就要通知到watch这个钩子,让它去执行回调函数。

这个时候messageDep就收集到了两个依赖,第二个依赖就是用来管理watchmessage变化的。

watch: {message: function (val, oldVal) {console.log('new: %s, old: %s', val, oldVal)},
}

当开发者自定义computed计算属性时,如下messageT属性,是依赖message的变化的。因此message变化时我们也要通知到computed,让它去执行回调函数。 这个时候messageDep就收集到了三个依赖,这个依赖就是用来管理computedmessage变化的。

computed: {messageT() {return this.message + '!';}
}

图示如下:一个属性可能有多个依赖,每个响应式数据都有一个Dep来管理它的依赖。

如何收集依赖

我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法。可以将代码进行如下改造:

function defineReactive (obj, key, val) {let Dep; // 依赖Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: () => {console.log('我被读了,我要不要做点什么好?');// 被读取了,将这个依赖收集起来Dep.depend(); // 本次新增return val;},set: newVal => {if (val === newVal) {return;}val = newVal;// 被改变了,通知依赖去更新Dep.notify(); // 本次新增console.log("数据被改变了,我要把新的值渲染到页面上去!");}})
}

什么是依赖

那所谓的依赖究竟是什么呢?上面的图中已经暴露了答案,就是Watcher

Watcher 「中介」

Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。

Watcher能够控制自己属于哪个,是data中的属性的还是watch,或者是computedWatcher自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。

因此我们可以推测出,Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。

class Watcher {addDep() {// 我这个Watcher要被塞到Dep里去了~~},update() {// Dep通知我更新呢~~},
}

最终代码实现

// 手写vue相应源码
const Observer = function(data){// 循环修改为每个属性添加get setfor(let key in data){defineReactive(key,data)}
}
function defineReactive(key,obj){// 局部变量dep,用于get set内部调用const dep = new Dep()let val = obj[key]Object.defineProperty(obj,key,{enumerable:true,configurable:true,get(){console.log('初始化响应式,添加watcher');// 调用依赖收集器中的addSub,用于收集当前属性与Watcher中的依赖关系dep.depend()return val},set(newValue){if(newValue === val) returnconsole.log('当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher,');val = newValue// 通知依赖收集器更新需要更新的watcher// 这里每个需要更新通过什么断定?dep.subsdep.notify()}})
}
function Dep(){const self = thisthis.target = nullthis.subs = []//  如果有目标时,绑定目标与dep的关系this.depend = function(){if(Dep.target){Dep.target.addDep(self)}}// 为目标添加watcherthis.addSub = function(watcher){self.subs.push(watcher)}// 通知watcher更新依赖this.notify = function(){for(let i in self.subs){self.subs[i].update()}}
}
function Watcher(vm,fn){const self = this//向Dep.target 指向自身Dep.target = this//向dep添加自身依赖this.addDep = function(dep){dep.addSub(self)}// 更新方法,用于触发vm._renderthis.update = function(){console.log('in watcher update');fn()}// 这里会首次调用vm._render,从而触发text的get// 从而将当前的Wathcer与Dep关联起来this.value = fn()// 清空值// 这里清空了Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep,// 造成代码死循环Dep.target = null
}
function Vue(options){const self = this// 初始化状态// 将data赋值给this._data,源码这部分用的Proxy所以我们用最简单的方式临时实现if(options && typeof options.data === 'function'){this._data = options.data.apply(this)}//渲染函数this.render = function(){const that = selffor(let i in that._data){that._data[i]  // 这里渲染有使用的数据}}// 挂载函数this.mount = function(){new Watcher(self,self.render)}// 监听dataObserver(this._data)
}const vue = new Vue({data(){return {name:'孙悟空'}}
})
vue.mount()   // 初始化数据,收集依赖  初始化响应式,添加watcher
vue._data.name = 'sss'
//当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher
//in watcher update

总结

回顾一下,Vue响应式原理的核心就是ObserverDepWatcher

Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher

在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watchcomputed就执行开发者自定义的回调方法。

100行代码,10分钟,详解Vue2.x响应式原理——理解Observer,Dep,Watcher相关推荐

  1. 【Vuejs】952- 一文带你了解vue2之响应式原理

    在面试的过程中也会问到:请阐述vue2的响应式原理?,凡是出现阐述或者理解,一般都是知无不言言无不尽,知道多少说多少.接下来,我来谈谈自己的理解,切记不要去背,一定要理解之后,用自己的语言来描述出来. ...

  2. Vue2的响应式原理

    --------Vue2响应式原理---------- 原理:通过数据劫持 defineProperty + 发布订阅者模式,当 vue 实例初始化后 observer 会针对实例中的 data 中的 ...

  3. 实现vue2.0响应式原理

    很久之前为了面试,看了一些分析源码的博客,没有最近重新看一下vue的源码,匆匆浏览记住一些概念,因为懒惰,后面也没有再去深入探索:前段时间痛定思痛,觉得不能这样下去,过一下vue源码,此文章也是为了记 ...

  4. 100行的python作品详解_漫画喵的100行Python代码逆袭

    小喵的唠叨话:这次的博客,讲的是使用python编写一个爬虫工具.为什么要写这个爬虫呢?原因是小喵在看完<极黑的布伦希尔特>这个动画之后,又想看看漫画,结果发现各大APP都没有资源,最终好 ...

  5. 100行的python作品详解_不到 100 行 Python 代码徐峥变葛优

    给照片换脸大家应该都见过,本文我们来介绍一下如何通过 Python 实现换脸. 功能实现 实现换脸功能,我们大致可以分为两种:一种是所有功能都通过自己编码来实现,另一种是借助于第三方 API 来实现, ...

  6. 10分钟详解:算法面试5大必考排序方式

    来源:IT技术思维原创,转载请注明原出处 内容提供:苏勇, Google 资深软件工程师 算法学习其实是一种学习和提高思维能力的过程.无论是在面试还是实际的工作和生活中,都会碰见一些从没遇到过的问题 ...

  7. 100行代码 5分钟开发一个数字货币行情软件(升级版)

    鉴于之前 ccxt 的接口貌似被墙了. 国内访问不了. 所以我又重新用了 非小号 的数据 重新弄了一下,现在国内的也能访问了. 非小号的 api 禁止了浏览器跨域请求.所以中间我又用我的服务器做了一层 ...

  8. 60行代码 5分钟开发一个数字货币行情软件

    本文旨在研究数字货币量化交易.无意中发现,其实一款简易的行情软件可能5真的只要5分钟-就留下这篇教程,希望让更多的人了解到相关的开发.抛砖引玉~ 技术栈: html css js vue elemen ...

  9. vue2响应式原理解析并实现一个简单响应系统

    vue2响应式原理 Object.defineProperty() 要理解 vue2 数据响应式原理,我们首先要了解Object.defineProperty()方法.下面这些概念引自MDN. Obj ...

  10. Day 05- Vue3 Vue2响应式原理

    Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持  --> 给对象扩展属性 -->  属性设置 实现原理: ...

最新文章

  1. 读大话数据结构之二--------算法(上)
  2. html5学习笔记1
  3. 程序详细设计之代码编写规范_我在不编写任何代码的情况下建立了一个设计策划网站
  4. antd request 通过jsessionid传参数_Umi-request源码阅读
  5. SpringAop与AspectJ的联系与区别____比较分析 Spring AOP 和 AspectJ 之间的差别
  6. ExtJS4之helloworld
  7. 内存分区与栈帧使用分析
  8. clion导入mysql库_CLion如何添加依赖库 ? 需要把mysql/Connector c++放入 用cpp连接数据库...
  9. 酒店管理系统-需求分析报告
  10. python椭圆曲线加密信息_ECC椭圆曲线加密算法:ECDH 和 ECDSA
  11. 李佳琦、薇娅们的残酷生存物语
  12. 【Matlab三维路径规划】蚁群算法三维路径规划【含源码 179期】
  13. 【无人机】【2017.12】基于AGENT的防御群建模分析
  14. 精简指令集(RISC)和复杂指令集(CISC)的区别
  15. WPF TabControl 美化
  16. 【狂神说】Spring Cloud
  17. python中的sort排序加换行_python中sort()排序的方法
  18. msxml4.dll加载失败、动态链接库例程失败
  19. python输入多个整数 输入quit表示结束_Python Selenium 之关闭窗口close与quit的方法
  20. t420i升级固态硬盘提升_2100MB超高读速,雷克沙推出新品固态,笔记本硬盘升级新选择...

热门文章

  1. 7-128 大于m的最小素数
  2. 高通SDM439平台使能sensor hub的auto detect模式
  3. yarn : 无法加载文件 C:\Users\mosho\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁止运行脚本。
  4. 在Unity中接入Xbox360手柄
  5. 决策树注意事项和参数调节
  6. 浅谈对于机器学习的理解
  7. 华盛顿大学计算机专业gpa,以未决定专业进入大学再转计算机专业可行吗?
  8. wps中设置公式编辑器字体颜色
  9. [966]无需ROOT就能让你用上Xposed框架
  10. Lumion 11学会像真正的专业人士一样渲染