Vue双向数据绑定的简单实现-- Observer、Compiler、proxy

前言

双向数据绑定人人都会背了,已经没什么新奇了。
但是如果遇到XX喜欢问源码之类的,或者问你设计思路你又该如何应对呢,所以下面这篇文章主要是为了记录双向数据绑定的一个实现,采用了类的方式,积极向面向对象编程靠拢。

这里采用的是vue2的数据劫持方式,vue3可以参考: 此处。


难点

1. Dep跟Watcher分别对应什么呢

一个Dep对应一个数据劫持属性,一个Watcher对应模板一个双向绑定的变量或变量属性--> {{xxx.xxx}}或者v-model。

  • Dep是发布者,从Observer类中可以看出,Dep对应的劫持到的data或者data的某一个属性。即,如果该值发生变化,就会触发数据劫持set操作,从而执行通知操作dep.notify(),遍历执行watcher.update(),从而更新视图。
  • Watcher是观察者,从Compiler类中可以看出,解析对应的模板会读取数据,触发数据劫持get操作从而触发dep.addSub(watcher)

源码

<!--* @Author: Penk* @LastEditors: Penk* @LastEditTime: 2021-07-12 00:23:30* @FilePath: \temp\myVue.html
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>#app {text-align: center;margin: 100px auto auto auto;}</style></head><body><div id="app"><div v-html="msg"></div><input v-model="author.name" style="margin-bottom: 20px" /><br />姓名:{{author.name}}<br />计算属性变大写:{{toUpperCaseName}}<br /><br /><button v-on:click='change(author.name,"自定义参数")'>test</button></div><!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> --><!-- <script src="./script.js"></script> --><script>class Penk {constructor(options) {this.$el = options.el;this.$data = options.data;let methods = options.methods;let computed = options.computed;if (this.$el) {// 数据劫持,初次劫持并没有触发new Dep()!!!new Observer(this.$data);// 设置代理,过滤$data,可直接访问data() 中的数据=> this.xxxthis.proxyData(this.$data);// 设置methods,同上this.proxyMethods(methods);// 设置computed,同上this.proxyComputed(computed);// 将模板转化成对象,进行解析// 默认会执行数据的get操作,触发数据劫持,并设置发布订阅模式new Compiler(this.$el, this);// 执行挂载mounted,并且作用域指向dataoptions.mounted.call(this.$data);}}proxyData(data) {for (let key in data) {Object.defineProperty(this, key, {enumerable: true,get() {return this.$data[key];},set(newVal) {if (this.$data[key] != newVal) {this.$data[key] = newVal;}}});}}proxyMethods(methods) {for (let key in methods) {this.$data[key] = methods[key];}}proxyComputed(computed) {for (let key in computed) {// this.$data[key] = computed[key].call(this);Object.defineProperty(this.$data, key, {get: () => {return computed[key].call(this);}});}}}// 观察者class Watcher {constructor(vm, expr, cb) {this.vm = vm;this.expr = expr;this.cb = cb;this.oldValue = this.get();}get() {Dep.target = this;let val = CompileUtils.getVal(this.expr, this.vm);Dep.target = null;return val;}update() {let newVal = CompileUtils.getVal(this.expr, this.vm);if (this.oldValue !== newVal) {this.cb(newVal);}}}// 订阅者class Dep {constructor() {this.subs = [];}// 订阅addSub(watcher) {this.subs.push(watcher);}// 发布notify() {this.subs.forEach((watcher) => watcher.update());}}// 编译者class Compiler {constructor(el, vm) {this.el = this.getElementByEl(el);this.vm = vm;// 获取dom节点let fragment = this.node2fragment(this.el);// 编译模板 用数据编译this.compile(fragment);// 把内容塞到页面中this.el.appendChild(fragment);}// 核心编译方法compile(node) {let childNodes = node.childNodes;[...childNodes].forEach((e) => {if (e.nodeType == 1) {this.compileElement(e);} else if (e.nodeType == 3) {this.compileText(e);}});}// 编译文本compileText(node) {let text = node.textContent;if (/\{\{(.*)\}\}/.test(text)) CompileUtils.text(node, text, this.vm);}// 编译元素compileElement(node) {this.compile(node);let attributes = node.attributes;[...attributes].forEach((attr) => {let { name, value } = attr;if (this.isDirective(name)) {let [, directive] = name.split('-');let [directiveName, eventName] = directive.split(':');CompileUtils[directiveName](node, value, this.vm, eventName);}});}// 判断是否指令isDirective(attrName) {return attrName.startsWith('v-');}// 节点转片段node2fragment(el) {let fragment = document.createDocumentFragment();let node;while ((node = el.firstChild)) {fragment.appendChild(node);}return fragment;}// 获取元素getElementByEl(el) {if (el.nodeType === 1) return el;return document.querySelector(el);}}// 编译工具var CompileUtils = {getVal(expr, vm) {let data = vm.$data;expr.split('.').forEach((e) => {data = data[e];});return data;},setVal(expr, vm, val) {let data = vm.$data;expr.split('.').reduce((total, currentValue, index, arr) => {if (index == arr.length - 1) {total[currentValue] = val;return;}return total[currentValue];}, data);},getContentValue(expr, vm) {let value = expr.replace(/\{\{(.*)\}\}/g, (...args) => {return this.getVal(args[1], vm);});return value;},getMethodObj(expr, vm) {console.log(expr);let leftIndex = expr.indexOf('(');let method = expr.slice(0, leftIndex);let params = expr.slice(leftIndex + 1, expr.length - 1).split(',');vm.$data[method]().call(this, ...params);return {method};},// 指令model(node, expr, vm) {let value = this.getVal(expr, vm);let fn = this.update.modelUpdater;fn(node, value);new Watcher(vm, expr, (newVal) => {fn(node, newVal);});node.addEventListener('input', (e) => {let val = e.target.value;this.setVal(expr, vm, val);});},html(node, expr, vm) {let value = this.getVal(expr, vm);let fn = this.update.htmlUpdater;fn(node, value);new Watcher(vm, expr, (newVal) => {fn(node, newVal);});},// 事件绑定on(node, expr, vm, eventName) {node.addEventListener(eventName, () => {let leftIndex = expr.indexOf('(');let method = expr.slice(0, leftIndex);let params = expr.slice(leftIndex + 1, expr.length - 1).split(',');let temParams = [];params.forEach((param) => {if (param.indexOf("'") == 0 || param.indexOf('"') == 0) {param;temParams.push(param.slice(1, param.length - 1));} else {temParams.push(this.getVal(param, vm));}});vm.$data[method].call(this, ...temParams);});},text(node, expr, vm) {let fn = this.update.textUpdater;let value = expr.replace(/\{\{(.*)\}\}/g, (...args) => {new Watcher(vm, args[1], (newVal) => {fn(node, this.getContentValue(expr, vm));});return this.getVal(args[1], vm);});fn(node, value);},// 更新视图方法update: {modelUpdater(node, value) {node.value = value;},htmlUpdater(node, value) {node.innerHTML = value;},textUpdater(node, value) {node.textContent = value;}}};// 数据劫持class Observer {constructor(data) {//初始化时候劫持数据this.observer(data);}observer(data) {if (data && typeof data == 'object') {for (let key in data) {this.defineReactive(data, key, data[key]);}}}defineReactive(obj, key, val) {this.observer(val);let dep = new Dep();Object.defineProperty(obj, key, {enumerable: true,get() {Dep.target && dep.addSub(Dep.target);return val;},set: (newVal) => {if (val == newVal) return;val = newVal;// 重新赋值的时候劫持数据this.observer(newVal);dep.notify();}});}}</script><script>let vm = new Penk({el: '#app',data: {author: {name: 'penk',age: 18,a: { aa: 1 }},msg: '<h1>v-html</h1>'},methods: {change(...data) {alert('method,带参~' + data);}},mounted() {// this.change('mounted');},computed: {toUpperCaseName() {return this.author.name.toUpperCase();}}});</script></body>
</html>

效果

待发…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757728.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css实现自适应正方形

通过百分比&#xff08;%&#xff09;和宽高比通过vw或者vh通过百分比&#xff08;%&#xff09; padding-top 或者 padding-bottom以上列举三种方法&#xff0c;还有其他方法请各路大神评论区展示 展示 <style>* {margin: 0;padding: 0;}.box {width: 20%;aspect-rati…

软件测评中心:进行科技成果鉴定测试的注意事项和好处简析

软件产品科技成果鉴定是有效评价科技成果质量和水平的方法之一&#xff0c;也是鼓励科技成果通过市场竞争等方式得到有效的评价和认可&#xff0c;可以推动科技成果的进步和转化。 一、进行科技成果鉴定测试时的注意事项&#xff1a;   1、应由具备一定资质和能力的专业机构…

Android Studio实现内容丰富的安卓外卖平台

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 项目编号122 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看公告 3.查看外卖分类 4.购物车&#xff0c; 5.个人中…

深度学习_微调_7

目标 微调的原理利用微调模型来完成图像的分类任务 微调的原理 微调&#xff08;Fine-tuning&#xff09;是一种在深度学习中广泛应用的技术&#xff0c;特别是在预训练模型&#xff08;Pretrained-Models&#xff09;的基础上进行定制化训练的过程。微调的基本原理和步骤如下…

AutoSAR配置与实践(深入篇)10.5 CANTP 层对意外到达的N-PDU处理策略

AutoSAR配置与实践(深入篇)10.5 CANTP 层对意外到达的N-PDU处理策略 CANTP 层对意外到达的N-PDU处理策略一、规范说明二、具体流程图解析2.1 发送端对意外到达的PDU的处理图解2.2 接收端对意外到达的PDU的处理图解CANTP 层对意外到达的N-PDU处理策略 ->返回总目录<- …

【项目】YOLOv5+PaddleOCR实现艺术字验证码识别

YOLOv5PaddleOCR实现艺术字类验证码识别 一、引言1.1 实现目标1.2 人手动点选验证码逻辑1.3 计算机点选逻辑 二、计算机验证方法2.1 PaddleOCR下方文字识别方法2.2 YOLOv5目标检测方法2.3 艺术字分类方法2.4 返回结果 三、代码获取 一、引言 1.1 实现目标 要识别的验证码类型…

c语言综合练习题

1.编写程序实现键盘输入一个学生的学分绩点 score&#xff08;合法的范围为:1.0—5.0&#xff09;&#xff0c;根据学生的学分绩点判定该学 生的奖学金的等级&#xff0c;判定规则如下表所示。 #include <stdio.h>int main() {float score;printf("请输入学生的学分…

Harbor-私有镜像仓库

目录 一、Harbor 原理说明 1.软件资源介绍 2.Harbor 特性 3.Harbor 认证过程 4.Harbor 认证流程 二、私有镜像仓库实验 1.环境准备 2.安装docker 3.配置镜像加速和私有仓库地址 4.搭建harbor仓库 5.本地windows浏览器访问配置 一、Harbor 原理说明 1.软件资源介绍 …

突破编程_C++_设计模式(访问者模式)

1 访问者模式的基本概念 C中的访问者模式是一种行为设计模式&#xff0c;它允许你在不修改类层次结构的情况下增加新的操作。这种模式将数据结构与数据操作解耦&#xff0c;使得操作可以独立于对象的类来定义。 访问者模式的主要组成部分包括&#xff1a; &#xff08;1&…

面试算法-62-盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。…

CycleGAN训练及测试过程细节记录

CycleGAN训练及测试过程细节记录 文章目录 关于训练关于测试 关于训练 1、训练前将数据配置好&#xff0c;并在Pycharm中写好配置信息 2、关于训练过程的参数配置在 options/train_options.py options/base_options.py batch_size&#xff1a;批大小 crop_size&#xff1a;…

python实现--拓扑排序

拓扑排序是对有向无环图&#xff08;DAG&#xff09;进行排序的一种算法&#xff0c;它可以将图中的顶点排成一个线性序列&#xff0c;使得图中的任意一条有向边都从序列中的较早顶点指向较晚顶点。换句话说&#xff0c;如果图中存在一条从顶点A到顶点B的有向边&#xff0c;那么…

Android分区存储到底该怎么做

文章目录 一、Android存储结构二、什么是分区存储&#xff1f;三、私有目录和公有目录三、存储权限和分区存储有什么关系&#xff1f;四、我们应该该怎么做适配&#xff1f;4.1、利用File进行操作4.2、使用MediaStore操作数据库 一、Android存储结构 Android存储分为内部存储和…

【逆向】使用 Frida 进行 Android 应用程序动态分析与加密算法逆向

不愿染是与非 怎料事与愿违 心中的花枯萎 时光它去不回 回忆辗转来回 痛不过这心扉 愿只愿余生无悔 随花香远飞 &#x1f3b5; 毛不易《不染》 在移动应用程序开发中&#xff0c;保护用户数据的安全至关重要。加密算法是保护数据安全的重要手段之一。然而…

【晴问算法】提高篇—动态规划专题—最长上升子序列

题目描述 现有一个整数序列a1,a2,...,an​​​​​​&#xff0c;求最长的子序列&#xff08;可以不连续&#xff09;&#xff0c;使得这个子序列中的元素是非递减的。输出该最大长度。 输入描述 第一行一个正整数n&#xff08;1≤n≤100​​​​&#xff09;&#xff0c;表示序…

【进阶版讲解深度学习如何入门?】

深度学习如何入门&#xff1f; 1. 前言2. 学习基础知识3. 了解机器学习4. 编程和工具5. 深度学习基础6. 实战项目7. 高级概念8. 持续学习9. 推荐资源 1. 前言 深度学习是机器学习的一个子领域&#xff0c;它受到了生物神经网络的启发&#xff0c;依赖于构建多层的神经网络来学…

Windows 11 安装 Scoop

[Windows 11 安装 Scoop](Windows 11 安装 Scoop) 0. 引言 Scoop 从命令行安装您熟悉和喜爱的程序&#xff0c;差异最小。 它的主要功能如下&#xff1a; 消除权限弹出窗口 隐藏 GUI 向导样式的安装程序 防止PATH污染安装大量程序 避免安装和卸载程序的意外副作用 自动查…

算法-背包问题

问题描述 假设我有一个背包&#xff0c;希望在装得下的情况下&#xff0c;尽量装进价值更多的物品。那么我该怎么做呢&#xff1f; 问题抽象 假设背包的容量是m&#xff0c;就假设是4吧 # 表示背包容量4KG m 4 可选装进背包的物品有n个&#xff0c;物品的价值存储在prices…

支付宝手机网站支付,微信扫描二维码支付

支付宝手机网站支付 支付宝文档 响应示例 <form name"punchout_form" method"post" action"https://openapi.alipay.com/gateway.do?charsetUTF-8&methodalipay.trade.wap.pay&formatjson&signERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE…

Maven打包时报错:Cannot allocate memory

使用Jenkins执行Maven打包任务时报错 Cannot allocate memory解决办法&#xff1a; 配置系统变量 MAVEN_OPTS-Xmx256m -XX:MaxPermSize512m或者 在项目目录下新建文件 .mvn/jvm.config -Xmx256m -Xms256m -XX:MaxPermSize512m -Djava.awt.headlesstrue参考 Jenkins Maven …