当前位置: 首页 > news >正文

Vue 响应式原理简易实现

Vue 响应式原理简易实现

在Vue框架中vue能够做到数据变化时视图自动更新,主要依靠其响应式方式,今天我分享便是它的简易实现。

一、Vue 实例的构造与初始化

基础结构:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script src="./index.js"></script><div id="app"><h1>标题是:{{ myTitle }} -- {{ myTitle }}</h1><p>内容是: {{ myContent }} </p></div><script>const vm = new Vue({el: '#app',data: {myTitle:'这是一个标题',myContent:'这是一段文本'}})</script>
</body>
</html>

预览:在这里插入图片描述

(一)Vue 类的构造函数

首先看 Vue 类的构造函数:

class Vue {constructor(options) {this.$options = options || {};this.$data = options.data || {};const el = options.el;this.$el = typeof el === 'string' ? document.querySelector(el) : el;// 1. 将 data 中的属性代理到 Vue 实例上proxy(this, this.$data);// 2. 对 data 进行响应式观测new Observer(this.$data);// 3. 解析模板,建立数据与视图的关联new Compile(this);}
}

当我们 new 一个 Vue 实例时,会依次做三件关键事:

  1. 属性代理:通过 proxy 函数,把 data 对象里的属性“代理”到 Vue 实例上,这样我们可以直接用 this.xxx 访问 data.xxx
  2. 响应式观测:创建 Observer 实例,对 data 进行递归的响应式处理,让 data 里的每个属性都具备“被监听”的能力。
  3. 模板解析:创建 Compile 实例,解析页面中的模板(比如包含 {{}} 插值的节点),建立数据和 DOM 之间的关联。

(二)proxy:让属性访问更便捷

proxy 函数的作用是属性代理:

function proxy(target, data) {Object.keys(data).forEach(key => {Object.defineProperty(target, key, {enumerable: true,configurable: true,get() {return data[key];},set(newValue) {data[key] = newValue;}})})
}

举个例子,如果 data 里有 name: 'Vue',原本要通过 this.$data.name 访问,经过代理后,直接 this.name 就能拿到值,赋值时 this.name = 'React' 也会同步修改 data.name。这一步是为了让开发者用起来更顺手,隐藏了 $data 这个中间层。

二、Observer:让数据变得“可观测”

(一)Observer 类的职责

Observer 类负责把普通的 data 对象变成“响应式”对象:

class Observer {constructor(data) {this.data = data;this.dep = new Dep();this.walk(data)}walk(data) {const dep = this.dep;Object.keys(data).forEach(key => defineReactive(data, key, data[key], dep))}
}

它的核心是 walk 方法,遍历 data 中的每个属性,然后调用 defineReactive 函数,对每个属性进行“响应式化”处理。

(二)defineReactive:给属性加“监听器”

defineReactive 是响应式的核心实现:

function defineReactive(data, key, value, dep) {if (typeof value === 'object' && value !== null) {return new Observer(value);}Object.defineProperty(data, key, {enumerable: true,configurable: true,get() {Dep.target && dep.addSub(Dep.target);return value;},set(newValue) {if (value === newValue) return;value = newValue;if (typeof value === 'object' && value !== null) {return new Observer(value);}dep.notify(); // 通知更新}})
}
  • get 方法:当属性被访问时(比如模板里用到 {{name}},读取 name 时),如果存在 Dep.target(后面会讲,这是 Watcher 实例),就把这个 Watcher 加入到当前属性的依赖集合 dep 中,这一步叫“依赖收集”。
  • set 方法:当属性被赋值时,先判断新值和旧值是否一样,不一样的话更新值。如果新值是对象,还需要递归地把新对象也变成响应式。最后,通过 dep.notify() 通知所有依赖这个属性的 Watcher,让它们去更新视图。
  • Dep 类Dep 是“依赖收集器”,每个响应式属性都有一个对应的 Dep 实例,用来存储依赖它的 Watcher
    class Dep {constructor() {this.subs = []}addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}
    }
    

三、Watcher:数据与视图的“纽带”

(一)Watcher 类的作用

Watcher 是连接数据和视图的桥梁:

class Watcher {constructor(vm, key, callback){this.vm = vm;this.key = key;this.callback = callback;Dep.target = this;this.oldValue = vm[key];Dep.target = null;}update(){const newValue = this.vm[this.key];if(this.oldValue === newValue) return;this.callback(newValue);this.oldValue = newValue;}
}

当创建 Watcher 实例时,会先把 Dep.target 指向自己,然后访问 vm[key](触发属性的 get 方法),这样 get 方法里的 dep.addSub(Dep.target) 就会把当前 Watcher 加入到属性的依赖集合中。之后,Dep.target 重置为 null,避免后续无关的依赖收集。

当数据变化时,Dep 会调用 Watcherupdate 方法,update 里会拿到新值,和旧值比较,如果不一样,就调用回调函数去更新视图。

四、Compile:解析模板,建立关联

(一)Compile 类的工作

Compile 类负责解析模板,找到数据和 DOM 的关联,并创建 Watcher 来监听数据变化:

class Compile {constructor(vm){this.vm = vm;this.el = vm.$el; this.compile(this.el);}compile(el) {const childNodes = el.childNodes;Array.from(childNodes).forEach(node=>{//1 表示元素节点,就是 HTML 里的各种标签,比如 div、p、span 这些。//2 表示属性节点,指的是元素的属性,比如 class、id//3 表示文本节点,就是元素里的文字内容,比如<p>里面的文字。if(node.nodeType === 3){this.compileText(node); // 文本节点}else if(node.nodeType === 1){// 元素节点(可扩展处理指令等,这里暂略)}if(node.childNodes && node.childNodes.length !== 0){this.compile(node);}})}compileText(node) {const reg = /\{\{(.+?)\}\}/g;const value = node.textContent.replace(/\s/g,'');const tokens = [];let result, index, lastIndex = 0;while(result = reg.exec(value)){index = result.index;if(index > lastIndex){tokens.push(value.slice(lastIndex, index));}const key = result[1].trim();tokens.push(this.vm[key]);      lastIndex = index + result[0].length;const pos = tokens.length - 1;// 创建 Watcher,数据变化时更新节点文本new Watcher(this.vm, key, newValue => {tokens[pos] = newValue;node.textContent = tokens.join('');});} if(lastIndex < value.length){tokens.push(value.slice(lastIndex));}if(tokens.length){node.textContent = tokens.join('');}}
}

nodeType的几种常见情况:
在这里插入图片描述
result = reg.exec(value)的输出结果:
在这里插入图片描述

compile 方法递归遍历 DOM 节点,遇到文本节点时,调用 compileText方法。compileText 会用正则匹配 {{}} 格式的插值表达式,提取出数据的 key,然后创建 Watcher 监听这个 key 对应的数据变化。当数据变化时,Watcher 的回调函数会更新节点的文本内容。

五、整体过程

  1. 初始化阶段

    • 构造 Vue 实例,进行属性代理、响应式观测、模板解析。
    • Observer 遍历 data,通过 defineReactive 给每个属性加上 get/set 拦截,同时为每个属性创建 Dep
    • Compile 解析模板,遇到插值表达式,提取数据 key,并为每个 key 创建 WatcherWatcher 触发 get 方法,完成“依赖收集”(把自己加入 Dep)。
  2. 数据更新阶段

    • 当我们修改数据(如 this.name = 'New Name'),会触发属性的 set 方法。
    • set 方法里调用 dep.notify(),通知所有依赖该属性的 Watcher
    • Watcherupdate 方法被调用,执行回调函数,更新对应的 DOM 节点,视图随之更新。

预览:

在这里插入图片描述
出处详见:vue2响应式系统

http://www.dtcms.com/a/488150.html

相关文章:

  • 安徽省建设业协会网站项目营销策划公司
  • 网站建设程序员招聘网站开速度几秒
  • 商城程序搭建B2B2C平台的物流轨迹信息展示之在途监控API
  • 临沂国际外贸网站建设网站建设哪个最好
  • 台州黄岩网站建设车床加工东莞网站建设
  • 做薆视频网站网络营销推广的主要特点
  • 上海网站建设高端定制网络服务公司高端 建站
  • 深度解析 OCR识别 技术:从原理到应用生态的全景视角
  • 【原】linux内核RCU锁
  • Java的Object类详解--hashcode
  • 怎么用php自己做网站网址导航网站建设
  • 垂直门户网站都有什么wordpress is sticky
  • ONNXRuntime(CUDA版本)源码编译安装与C++部署Pytorch模型教程
  • JavaScript 01 【基础语法学习】
  • 建设门户网站的目的和需求西安专业做淘宝网站的公司
  • init wordpressseo诊断晨阳
  • 网站运营成本预算好看响应式网站模板下载
  • 建网站软件工具建一个设计网站要多少钱
  • AI Coding实现X2SeaTunnel的设计、开发与落地
  • 给做网站建设的一些建议网站首页的导航栏
  • MySQL——联合查询数据表
  • 在Springboot中处理log4j2日志文件
  • 威海+网站建设一人开公司做网站创业
  • Cookie、Session、JWT、SSO,网站与 APP 登录持久化与缓存
  • 营销网站制作皆选ls15227负责wordpress+边框插件
  • 视频网站开发需求分析外包网站开发公司
  • 个人网站制作基本步骤淄博网站备案公司
  • python单元测试 unittest.mock.patch (二)
  • 手机网站后台编辑器有哪些贵州建筑网站
  • 如果使用自己电脑做网站com是什么网站