Proxy 实现简易 toy-vue

news/2024/11/22 21:47:03/

一、主要功能

  • 实现响应式
  • 处理 {{ }}
  • 实现 v-model
  • 实现 v-bind
  • 实现 v-on

二、实现思路

实现响应式

通过使用 Proxy 实现数据劫持 ,通过 effect 函数实现数据响应,从而更新页面内容.

  • get 阶段
    • get 中保存当前依赖到 effects 上,由于 effectsMap 结构,于是可以将当前被访问对象当成对应 key 进行保存,其对应的 vlaue 初始为 Array,并根据 get 触发时将 当前的 currentEffect 存存储在 effects 中与当前对应的 effects[key] 中.
  • set 阶段
    • 当对被劫持的数据对象进行赋值更新时,会触发 set, 在 set 中会取出 effects 中与当前对应的依赖数组遍历执行,这一步是为了将更新后的数据同步到视图进行更新.

处理 tempalte 模板

当进行 new ToyVue 时,会通过模板中的 el 获取到真实的 dom,这样是为了便于在 effect 中最直接的更新视图.

  • 获取到真实 dom 后,可以获取 dom 元素的文本节点,将文本节点中被 {{}} 包裹的内容替换成 data 中的值
  • 基于 Dom Api 可以获取到对应的 dom 属性,包括自定义指令 v-bind v-on v-model
    • 对于 v-bind 的处理,这里直接将 bind 的名称和数据内容,设置成为 dom 上的一个属性名和属性值
    • 对于 v-on 的处理,通过 addEventListenerdom 实现事件注册,同时要保证 methods 中所有方法的 this 指向问题,这里默认让 this 指向了 this.data
    • 对于 v-model 的处理,首先为 dom 设置 value 属性并指向 v-model 中对应 data 的值,同时为 dom 注册对应的事件,这里默认注册 input 事件,并在 input 事件中更新 data 中的值
  • 遍历处理所有子节点,进行上述处理

三、具体实现

html

<div id="app" style="text-align: center;"><h1>v-model</h1><p>{{ msg }}</p><input type="text" v-model="msg" /><hr /><h1>v-bind</h1><p v-bind:title="title">{{ title }}</p><hr /><h1>v-on</h1><p>{{ reverseMessage }}</p><p v-on:click="reverseMessage">反转</p>
</div>
<script type="module">javascript">import { ToyVue as Vue } from './toy-vue.js'var app = new Vue({el: '#app',data: {title: 'this is a title !!!',msg: 'this is a message !!!',reverseMessage:'this is reverseMessage !!!'},methods: {reverseMessage(){this.reverseMessage = this.reverseMessage.split('').reverse().join('');}}});
</script>

toy-vue

export class ToyVue {constructor(config) {// 1. 模板 -> 真实 domthis.tempalte = document.querySelector(config.el);// 2. data -> 响应式 datathis.data = reactive(config.data);// 3. 处理 methodsthis.handleMethods(config.methods);// 4. 处理 templatethis.traversal(this.tempalte);}handleMethods(methods) {for (let name in methods) {// 保证方法中的 this 指向为 this.datathis[name] = methods[name].bind(this.data);}}traversal(node) {// 1. 处理文本节点,处理 {{}}if (node.nodeType === Node.TEXT_NODE) {console.log("textContent = ", node.textContent);if (node.textContent.trim().match(/^{{([\s\S]+)}}$/)) {let name = RegExp.$1.trim();// 1.1 替换 {{ msg }} 为 data.msgeffect(() => {node.textContent = this.data[name];});}}// 2. 处理 dom 元素 attributes 属性, 处理指令if (node.nodeType === Node.ELEMENT_NODE) {let attributes = node.attributes;for (let attribute of attributes) {console.log("attribute = ", attribute);// 2.1 处理 v-modelif (attribute.name === "v-model") {let name = attribute.value;// 2.1.1 更新 dom 元素 valueeffect(() => {node.value = this.data[name];});// 2.1.2 给 dom 元素注册事件,根据不同表单类型注册不同事件node.addEventListener("input", () => {this.data[name] = node.value;});}// 2.2 处理 v-bindif (attribute.name.match(/^v\-bind:([\s\S]+)$/)) {let attrName = RegExp.$1;let name = attribute.value;console.log("v-bind = ", attrName, name);// 2.2.1 为 dom 元素设置对应属性和属性内容effect(() => {node.setAttribute(attrName, this.data[name]);});}// 2.3 处理 v-onif (attribute.name.match(/^v\-on:([\s\S]+)$/)) {let attrName = RegExp.$1;let name = attribute.value;console.log("v-on = ", attrName, name);// 2.2.1 为 dom 元素注册事件effect(() => {node.addEventListener(attrName, this[name]);});}}}// 3. 递归处理子节点if (node.childNodes && node.childNodes.length) {for (let child of node.childNodes) {this.traversal(child);}}}
}let effects = new Map();
let currentEffect = null;// 收集依赖
function effect(fn) {currentEffect = fn;fn(); // 初始化执行,目的是为了自动收集依赖 dependscurrentEffect = null;
}// 响应式
function reactive(traget) {let observe = new Proxy(traget, {get(object, prop) {console.log("get = ", object, prop);// 1. 当前 currentEffect 存在,证明当前 effect 中依赖了当前响应式数据中的属性,此时要收集依赖if (currentEffect) {// 1.1 当前 effects 不存在和 object 对应属性, 收集依赖if (!effects.has(object)) {effects.set(object, new Map());}// 1.2 从 effects 中获到对应依赖项,判断当前访问 key 是否已存在对应依赖中,不存在则给定初始值为 array ,方便之后添加和删除if (!effects.get(object).has(prop)) {effects.get(object).set(prop, new Array());}// 1.3 将当前 currentEffect 存储在对应的 effects[object][prop] 中effects.get(object).get(prop).push(currentEffect);}// 2. 返回当前访问属性对应值return object[prop];},set(object, prop, value) {console.log("set = ", object, prop, value);// 1. 更新当前属性对应值object[prop] = value;// 2. 根据收集的依赖,执行 effectif (effects.has(object) && effects.get(object).has(prop)) {for (let effect of effects.get(object).get(prop)) {effect();}}return true;},});return observe;
}

http://www.ppmy.cn/news/1549131.html

相关文章

使用Spring异步任务

在实际的应用开发中&#xff0c;异步任务能够显著提高系统的响应性能和并发处理能力。Spring 框架为异步任务的实现提供了强大且便捷的支持。 异步任务的使用与注意事项 一、核心注解与配置 Async需要和EnableAsync一起使用才有效果 Async注解 Async 注解用于标记需要异步…

985研一学习日记 - 2024.11.17

一个人内耗&#xff0c;说明他活在过去&#xff1b;一个人焦虑&#xff0c;说明他活在未来。只有当一个人平静时&#xff0c;他才活在现在。 日常 1、起床6:00 2、健身2h 3、LeetCode刷了1题 动态规划&#xff1a;01背包理论基础 掌握01背包和完全背包&#xff0c;就够用了…

初识Linux · 信号处理 · 续

目录 前言&#xff1a; 可重入函数 重谈进程等待和优化 前言&#xff1a; 在前文&#xff0c;我们已经介绍了信号产生&#xff0c;信号保存&#xff0c;信号处理的主题内容&#xff0c;本文作为信号处理的续篇&#xff0c;主要是介绍一些不那么重要的内容&#xff0c;第一个…

数据结构哈希表-(开放地址法+二次探测法解决哈希冲突)(创建+删除+插入)+(C语言代码)

#include<stdio.h> #include<stdlib.h> #include<stdbool.h> #define M 20 #define NULLDEL -1 #define DELDEY -2typedef struct {int key;int count; }HashTable;//创建和插入 void Insert(HashTable ha[], int m, int p, int key) {int i, HO, HI;HO key…

vim 一次注释多行 的几种方法

在 Vim 中一次注释多行是一个常见操作。可以使用以下方法根据你的具体需求选择合适的方式&#xff1a; 方法 1&#xff1a;手动插入注释符 进入正常模式&#xff1a; 按 Esc 确保进入正常模式。 选择需要注释的多行&#xff1a; 移动到第一行&#xff0c;按下 Ctrlv 进入可视块…

kafka中是如何快速定位到一个offset的

定位到具体的segment日志文件&#xff0c;采用二分法先定位到index索引文件计算查找的offset在日志文件的相对偏移量 1、分区和日志段&#xff1a; 每个主题的分区&#xff08;Partition&#xff09;被划分为多个日志段&#xff08;Log Segment&#xff09;。每个日志段是一个…

GPT promote 论文学术润色提示词

学术写作的润色 01 我正在为某知名[学科]学术期刊撰写一篇关于[主题]的论文。我在以下部分试图表达的是[具体观点]。请重新措辞&#xff0c;使之清晰、连贯、简洁&#xff0c;确保每段之间衔接流畅。去除口语化的内容&#xff0c;使用专业化语气。 Im writing a paper on [t…

MySQL:表的约束

目录 一. 表的约束和约束的目标 二. 空属性 三. 默认值default 四. 列描述 五. zerofill 六. 主键 6.1 建表时定义主键 6.2 去掉主键 6.3 建表后添加主键 6.4 复合主键 七. 自增长 八. 唯一键 九. 外键 一. 表的约束和约束的目标 表…