VUE响应式原理(数据双向绑定)
1. 什么是Vue双向绑定?
理解MVVM:
- 数据层(Model):应用数据及业务逻辑
- 视图层(View):应用的展示效果,各类UI组件
- 业务逻辑层:(ViewModel):负责将数据和视图关联起来
ViewModel的职责:
- 数据变化后更新视图
- 视图变化后更新数据
ViewModel主要由两部分组成:
- 监听器(Observe):对所有数据的属性进行监听
- 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
2. Vue的响应式流程
new Vue()
首先执行初始化,对data
执行响应化处理,这个过程发生Observer
中- 同时对模板执行编译,找到其中动态绑定的数据,从
data
中获取并初始化视图,这个过程发生在Compile
中 - 同时定义⼀个更新函数和
Watcher
,将来对应数据变化时Watcher
会调用更新函数 - 由于
data
的某个key
在⼀个视图中可能出现多次,所以每个key
都需要⼀个管家Dep
来管理多个Watcher
- 将来data中数据⼀旦发生变化,会首先找到对应的
Dep
,通知所有Watcher
执行更新函数
3. Vue响应式的实现
步骤(通俗) | 对应于专业术语 |
1. 侦测数据变化 |
1. 数据劫持(数据代理) |
2. 收集视图依赖了哪些数据 |
2. 依赖收集 |
3. 数据变化时候,通知视图更新 |
3. 发布订阅者模式 |
首先,对data进行响应式处理:
1 class Vue { 2 constructor(options) { 3 this.$options = options; 4 this.$data = options.data; 5 6 // 对data选项做响应式处理 7 observe(this.$data); 8 9 // 代理data到vm上 10 proxy(this); 11 12 // 执行编译 13 new Compile(options.el, this); 14 } 15 }
其中:
- observe操作为:
1 function observe(obj) { 2 if (typeof obj !== "object" || obj == null) { 3 return; 4 } 5 new Observer(obj); 6 } 7 8 class Observer { 9 constructor(value) { 10 this.value = value; 11 this.walk(value); 12 } 13 walk(obj) { 14 Object.keys(obj).forEach((key) => { 15 defineReactive(obj, key, obj[key]); // defineReactive 将普通数据处理成响应式的 16 }); 17 } 18 }
- compile为:对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
1 class Compile { 2 constructor(el, vm) { 3 this.$vm = vm; 4 this.$el = document.querySelector(el); // 获取dom 5 if (this.$el) { 6 this.compile(this.$el); 7 } 8 } 9 compile(el) { 10 const childNodes = el.childNodes; 11 Array.from(childNodes).forEach((node) => { // 遍历子元素 12 if (this.isElement(node)) { // 判断是否为节点 13 console.log("编译元素" + node.nodeName); 14 } else if (this.isInterpolation(node)) { 15 console.log("编译插值⽂本" + node.textContent); // 判断是否为插值文本 {{}} 16 } 17 if (node.childNodes && node.childNodes.length > 0) { // 判断是否有子元素 18 this.compile(node); // 对子元素进行递归遍历 19 } 20 }); 21 } 22 isElement(node) { 23 return node.nodeType == 1; 24 } 25 isInterpolation(node) { 26 return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent); 27 } 28 }
其次,进行依赖收集:
视图中会用到data
中某key
,这称为依赖。同⼀个key
可能出现多次,每次都需要收集出来用⼀个Watcher
来维护它们,此过程称为依赖收集多个Watcher
需要⼀个Dep
来管理,需要更新时由Dep
统⼀通知。
实现思路
defineReactive
时为每⼀个key
创建⼀个Dep
实例- 初始化视图时读取某个
key
,例如name1
,创建⼀个watcher1
- 由于触发
name1
的getter
方法,便将watcher1
添加到name1
对应的Dep中 - 当
name1
更新,setter
触发时,便可通过对应Dep
通知其管理所有Watcher
更新
1 // 负责更新视图 2 class Watcher { 3 constructor(vm, key, updater) { 4 this.vm = vm 5 this.key = key 6 this.updaterFn = updater 7 8 // 创建实例时,把当前实例指定到Dep.target静态属性上 9 Dep.target = this 10 // 读一下key,触发get 11 vm[key] 12 // 置空 13 Dep.target = null 14 } 15 16 // 未来执行dom更新函数,由dep调用的 17 update() { 18 this.updaterFn.call(this.vm, this.vm[this.key]) 19 } 20 }
1 class Dep { 2 constructor() { 3 this.deps = []; // 依赖管理 4 } 5 addDep(dep) { 6 this.deps.push(dep); 7 } 8 notify() { 9 this.deps.forEach((dep) => dep.update()); 10 } 11 }
1 // 创建watcher时触发getter 2 class Watcher { 3 constructor(vm, key, updateFn) { 4 Dep.target = this; 5 this.vm[this.key]; 6 Dep.target = null; 7 } 8 }
1 // 依赖收集 2 function defineReactive(obj, key, val) { 3 this.observe(val); 4 const dep = new Dep(); 5 Object.defineProperty(obj, key, { 6 get() { 7 Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher实例 8 return val; 9 }, 10 set(newVal) { 11 if (newVal === val) return; 12 dep.notify(); // 通知dep执行更新方法 13 }, 14 }); 15 }
参考:
- https://www.liaoxuefeng.com/wiki/1022910821149312/1109527162256416
- coderwhy老师课程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话