Fork me on GitHub

VUE响应式原理(数据双向绑定)

 

1. 什么是Vue双向绑定?

理解MVVM:

  • 数据层(Model):应用数据及业务逻辑
  • 视图层(View):应用的展示效果,各类UI组件
  • 业务逻辑层:(ViewModel):负责将数据和视图关联起来

ViewModel的职责:

  • 数据变化后更新视图
  • 视图变化后更新数据

ViewModel主要由两部分组成:

  • 监听器(Observe):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

2. Vue的响应式流程

 

  1. new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observer
  2. 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile
  3. 同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
  4. 由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher
  5. 将来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统⼀通知。

 

 

 

实现思路

  1. defineReactive时为每⼀个key创建⼀个Dep实例
  2. 初始化视图时读取某个key,例如name1,创建⼀个watcher1
  3. 由于触发name1getter方法,便将watcher1添加到name1对应的Dep
  4. 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老师课程

 

posted @   zerozhupan  阅读(626)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
目 录 X
1. 什么是Vue双向绑定?
理解MVVM:
ViewModel的职责:
ViewModel主要由两部分组成:
2. Vue的响应式流程
3. Vue响应式的实现
参考: