news 2026/5/4 5:46:30

设计模式之-观察者模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式之-观察者模式

1.先来看一个简单的例子

// 观察者classObserver{update(data){// 观察者收到数据变化,自行处理要做的事情console.log('接收到了数据:--',data);}}// 目标classSubject{constructor(){// 维护所有的观察者列表this.observers=[];}add(ob){// 添加观察者this.observers.push(ob);}notify(data){// 通知观察者数据发生变化for(constobofthis.observers){ob.update(data);}}}// 应用// 创建目标对象constsubject=newSubject();// 创建观察者constob1=newObserver();constob2=newObserver();// 给目标对象提那家观察者ob1,ob2subject.add(ob1);subject.add(ob2);// 通知所有观察者数据有变化啦subject.notify('hello,world,我来了')

2.写一个ts版本的

interfaceIObserver{update(data:string):void;}// Log观察者classLogNotificationListenerimplementsIObserver{update(data:string):void{console.log('LogNotificationListener---',data);}}// Email观察者classEmailNotificationListenerimplementsIObserver{update(data:string):void{console.log('EmailNotificationListener---',data);}}classSubject{privateobservers:IObserver[]=[];// 添加观察者publicadd(ob:IObserver){this.observers.push(ob);}publicnotify(data:string):void{for(constobofthis.observers){// 调用观察者自身的更新方法ob.update(data);}}}// 创建一个发布者(商店)constsubject=newSubject();// 再创建两个观察者实例(有需求的顾客)constemailListener=newEmailNotificationListener();constlogListener=newLogNotificationListener();// 接下来发布者需要添加观察者// 这一步就相当于顾客订阅了商店的消息subject.add(emailListener);subject.add(logListener);// 商店发布消息,会向所有订阅了商店消息的顾客发送消息subject.notify("新来了华为Mate99 pro,欢迎大家前来订阅");

3.说一下他的前端的实际应用吧
3.1dom事件的注册,这其实就是一种观察者模式,一个dom元素(发布者)可以有多个事件监听器(观察者)

<body><buttonid="mybtn">按钮</button><script>// 获取 DOM 元素(发布者)constbutton=document.getElementById('mybtn');// 第一个事件处理器,充当观察者的身份functionfirstObserver(){console.log('First observer responded to button click');}// 第二个事件处理器,同样也是充当观察者的身份functionsecondObserver(){console.log('Second observer responded to button click');}// 注册事件实际上就可以看作是发布者对观察者进行登记,或者说添加观察者的行为button.addEventListener('click',firstObserver);button.addEventListener('click',secondObserver);// 后期当用户真实的触发点击事件的时候,对应类型的所有的事件处理器都会被触发// 相当于就是发布者通知所有的观察者,观察者进行自身的一些行为</script></body>

3.2MutationObserver,这是一个WebApi,它允许开发者监听DOM树的变化,包括元素的添加,删除,属性变化之类的,监听到变化之后,可以做出一些响应的行为

<body><ulid="myList"><li>Item1</li><li>Item2</li></ul><buttonid="addBtn">添加Item</button><buttonid="removeBtn">移除最后一个Item</button><buttonid="modifyBtn">修改最后一个Item</button><script>// 获取相应的 DOM 元素constmyList=document.getElementById("myList");constaddBtn=document.getElementById("addBtn");constremoveBtn=document.getElementById("removeBtn");constmodifyBtn=document.getElementById("modifyBtn");// 创建一个 MutationObserver 实例// mutationsList 是一个 MutationRecord 对象的数组// 每一个 MutationRecord 对象代表一个被观察到的 DOM 对象constobserver=newMutationObserver((mutationsList)=>{// 遍历这些被观察的 DOM 对象for(letmutationofmutationsList){if(mutation.type==="childList"){// 这里是 DOM 节点发生了改变console.log("A child node has been added or removed.");}elseif(mutation.type==="attributes"){// DOM 属性发生了改变console.log("The "+mutation.attributeName+" attribute was modified.");}}})// 接下来调用observer来进行观察// 该方法接收两个参数,第一个是要观察的DOM元素,第二个是一个配置对象observer.observe(myList,{attributes:true,// 会观察 DOM 元素的属性变化childList:true,// 会观察 DOM 元素的直接子节点变化subtree:true,// 会观察 DOM 元素的所有后代节点})// 后面就是对 DOM 元素进行操作addBtn.onclick=function(){constli=document.createElement("li");li.textContent="Item"+(myList.children.length+1);myList.appendChild(li);};removeBtn.onclick=function(){myList.removeChild(myList.lastElementChild);};modifyBtn.onclick=function(){myList.lastElementChild.setAttribute("style","color: red;");};</script></body>

4.实现vue的迷你简单版的响应式系统

// 定义options的类型接口interfaceVueOptions{el:string;data:Record<string,any>;}// 观察者classWatcher{vm:Vue;// 表示Vue的实例对象el:Node// 代表一个DOM节点vmKey:string;// 存储data中的keyconstructor(vm:Vue,el:Node,vmKey:string){this.vm=vm;this.el=el;this.vmKey=vmKey;// 在第一次进行Watcher初始化的时候,将当前的Watcher对象保存到Dep.target上// 之所以要存储,是为了依赖收集Dep.target=this;// 先初始化更新一遍this.update();// 避免重复依赖收集,收集完依赖后,将Dep.target置空Dep.target=null;}// 更新方法update():void{//根据节点的类型来进行更新//这个例子是做了简化,只有两种类型if(this.el.nodeType===Node.TEXT_NODE){// 说明是一个文本类型节点,直接更新该节点的nodeValue// this.vm[this.vmKey]相当于是访问Vue实例对象的data中的属性// 后面我们会对data中的属性进行劫持,将data里面的所有数据存储到Vue实例对象上this.el.nodeValue=this.vm[this.vmKey];}elseif(this.el.nodeType==Node.ELEMENT_NODE){// 说明是一个元素节点,这里简化了,直接更新innerhtml(this.elasHTMLElement).innerHTML=this.vm[this.vmKey]}}}classDep{// 该静态属性用于暂时保存当前的Watcher对象,主要用于进行依赖的收集statictarget:Watcher|null=null;// 维护一个观察者的列表subs:Watcher[];constructor(){this.subs=[];}// 添加观察者到观察者列表addSub(sub:Watcher):void{this.subs.push(sub);}//通知所有观察者更新notify():void{this.subs.forEach(sub=>{sub.update();})}}// 该方法主要就是做数据劫持,将传递过来的data数据绑定到VUe实例对象上面,并且添加getter/setterfunctionobserver(vm:Vue,obj:Record<string,any>):void{constdep=newDep();// 实例化一个发布者// 遍历数据属性Object.keys(obj).forEach(key=>{// 首先,将原来的值先保存下来letinternalVal=obj[key];Object.defineProperty(vm,key,{get():any{// 如果有观察者,应该将观察者添加到发布者的观察者列表里面if(Dep.target){dep.addSub(Dep.target);}returninternalVal;},set(newVal:any):void{internalVal=newVal;// 数据发生了变化以后,我们就需要通知所有夫人观察者// 告诉观察者,数据发生变化,你们需要更新一下dep.notify();}});})}functioncompile(vm:Vue):void{// 首先拿到Vue实例对象上面的el属性,这个属性是一个选择器// 这一步其实就是拿到最外层的DOM节点 <div id="app"></div>constel:HTMLElement|null=document.querySelector(vm.$el);if(!el){thrownewError("Element with selector can not be found.");}// 接下来创建一个文档碎片constdocumentFragment:DocumentFragment=document.createDocumentFragment();//对节点进行处理,使用正则匹配{{ }},因为猫须字符串会成为一个观察者constreg:RegExp=/\{\{(.*)\}\}/;while(el.firstChild){//拿到第一个子节点,然后我们会进行各种分析处理constchild:ChildNode=el.firstChild;// 接下来对子节点进行分析操作if(child.nodeType===Node.ELEMENT_NODE){// 说明是一个元素节点constelement=childasHTMLElement;if(reg.test(element.innerHTML)){// 说明里面是带猫须的,需要将其变成一个观察者constvmKey:string=RegExp.$1.trim();// $1 是正则表达式匹配到的第一个值,这里其实就是 msgnewWatcher(vm,child,vmKey);}else{//说明里面没有猫须,我们还需要判断这个元素节点的属性是否有v-model// 如果有v-model 也需要进行处理Array.from(element.attributes).forEach(attr=>{if(attr.name==='v-model'){//说明如果进入此分支,说明该元素节点的属性包含v-modelconstvmKey:string=attr.value;// 这里其实就是msgelement.addEventListener('input',(ev:Event)=>{consttarget=ev.targetasHTMLInputElement;// 这里其实就是将文本框所输入的值赋值给vm实例对象上面的msg属性vm[vmKey]=target.value;})}})}}elseif(child.nodeType===Node.TEXT_NODE&&reg.test(child.nodeValue||'')){// 说明这是一个文本节点,并且这个文本节点也是带猫须的// 那么我们需要将这个文本节点转换为一个观察者constvmKey:string=RegExp.$1.trim();// $1 是正则表达式匹配到的第一个值,这里其实就是 msgnewWatcher(vm,child,vmKey);}//处理完成之后,我们就会将其添加到文档碎片中//当我们讲一个已有节点添加到另一个节点下面的时候做的是一个移动的操作documentFragment.appendChild(child);}// 因此当退出上面的循环时,el应该是一个空节点// 所有子节点都放进了文档碎片中// 我们再见文档中的所有子节点重新添加回到el中el.appendChild(documentFragment);}classVue{$el:string;[key:string]:any;constructor(options:VueOptions){this.$el=options.el;observer(this,options.data);// 做数据劫持,将data上面的数据存储到vue的实例对象上面compile(this);// 对模版进行编译}}// 实例化Vue时,传入一个配置对象constoptions={el:'#app',data:{msg:'hello Vue!'}}newVue(options);

4.1上面是一个index.ts文件,把它使用tsc index.ts编译成index.js,然后在html文件中引入该文件,自行验证

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>Document</title></head><body><divid="app"><div>{{msg}}</div><inputtype="text"v-model="msg"/><p>this is a test</p>{{msg}}</div><scripttype="module"src="./index.js"></script></body></html>

浏览器预览效果如下


非原创,来源渡一谢杰老师的设计模式讲解,简单记录分享

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/4 5:45:53

单北斗GNSS在桥梁形变监测与维护中的应用与优势分析

本文旨在深度分析单北斗GNSS在桥梁形变监测与维护中的应用与优势。首先&#xff0c;单北斗GNSS厂家提供的各类产品&#xff0c;如变形监测一体机和传感器&#xff0c;具备独特的技术特性&#xff0c;能够满足不同桥梁监测需求。其次&#xff0c;监测系统的定制与实施方法&#…

作者头像 李华
网站建设 2026/5/3 20:56:41

Arbess从基础到实践(16) - 集成GitHub实现Java项目构建并自动化Docker部署

Arbess 是一款国产开源免费的 CI/CD 工具&#xff0c;支持免费自动化部署&#xff0c;一键安装零配置。本文将详细介绍如何安装并使用ArbessGitHub实现Docker项目自动化构建部署 1、GitHub 配置 本章节将介绍如何创建GitHub个人访问令牌&#xff0c;提供给Arbess克隆源码。 …

作者头像 李华
网站建设 2026/5/2 8:11:09

基于Python的健身房管理系统源码设计与文档

前言在健身房精细化运营需求提升、传统管理模式存在 “会员管理混乱、课程预约低效、数据统计滞后、私教跟进缺位” 的痛点背景下&#xff0c;基于 Python 的健身房管理系统构建具有重要的商业与实用价值&#xff1a;从会员管理层面&#xff0c;系统依托 Python 的数据库交互能…

作者头像 李华
网站建设 2026/4/30 11:16:42

NVIDIA HGX™ B300 GPU Droplet 服务器,即将上线DigitalOcean 云平台!

人工智能正以史无前例的速度演进&#xff0c;新的模型和繁重的负载不断突破可能的边界。从复杂的大型语言模型&#xff08;LLM&#xff09;到精密的科学模拟&#xff0c;开发者与企业都需要获得最强大、最高效的算力基础设施。在 DigitalOcean&#xff0c;我们致力于提供顶级的…

作者头像 李华
网站建设 2026/5/2 18:02:25

基于SpringBoot旅游包车管理系统毕业设计项目源码

题目简介 在旅游包车行业存在 “供需对接碎片化、车辆调度效率低、行程监管缺失、费用结算不透明” 的行业痛点背景下&#xff0c;基于 SpringBoot 的旅游包车管理系统的构建具有重要现实意义与产业价值&#xff1a;从游客 / 旅行社端来看&#xff0c;系统打破传统线下找车、议…

作者头像 李华
网站建设 2026/5/2 3:22:46

硬件升级全攻略:从评估到优化

硬件升级前的准备工作评估当前硬件配置&#xff0c;包括处理器、内存、存储、显卡等关键部件的型号和性能。使用系统信息工具或第三方软件如CPU-Z、GPU-Z获取详细数据。明确升级目标和预算&#xff0c;确定是提升游戏性能、多任务处理能力还是存储速度。不同需求对应不同的硬件…

作者头像 李华