news 2026/2/17 12:37:34

设计模式之-观察者模式

作者头像

张小明

前端开发工程师

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/2/17 8:16:59

agent-zh.md

你是一个 AI 助手&#xff0c;帮助用户完成各种任务&#xff0c;包括编程、研究和分析。 核心角色 你的核心角色和行为可能会根据用户反馈和指示进行更新。当用户告诉你应该如何表现或你的角色应该是什么时&#xff0c;立即更新此记忆文件以反映该指导。 记忆优先协议 你可以访…

作者头像 李华
网站建设 2026/2/11 6:38:54

为什么过滤 rtmpt 而不是 rtmp?

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

作者头像 李华
网站建设 2026/2/14 6:47:19

Navicat x 达梦技术指引 | 启用和配置AI助手

近期&#xff0c;Navicat 宣布正式支持国产达梦数据库。Navicat 旗下全能工具 支持达梦用户的全方位管理开发需求&#xff0c;而轻量化免费的 则满足小型和独立开发者的基础需求。 Navicat Premium 自版本 17.3 开始支持达梦 DM8 或以上版本。它支持的系统有 Windows、Linux …

作者头像 李华
网站建设 2026/2/8 9:13:20

Transformer的注意力权重的理解

""" Transformer 注意力权重分析工具 详细解析注意力矩阵的含义和使用方法 """import torch import torch.nn as nn import numpy as np import math# # 简化的多头注意力&#xff08;用于演示&#xff09; # class SimpleMultiHeadAttention(…

作者头像 李华
网站建设 2026/2/12 2:23:01

解构 Codigger:从内核到无限生态的“进化阶梯”

当下开发工具市场繁杂又高度同质化&#xff0c;Codigger 却格外亮眼。它没有止步于单点工具的定位&#xff0c;而是成长为一个设计精巧、层层推进的技术有机体。从架构全景来看&#xff0c;它更像一套严谨的进化阶梯&#xff0c;六大核心层级彼此联动&#xff0c;共同构建出强悍…

作者头像 李华
网站建设 2026/2/6 12:33:56

基于Python的高考志愿报名推荐系统源码设计与文档

前言 在高考志愿填报精细化需求提升、传统填报模式存在 “数据维度单一、匹配精准度低、政策解读滞后、风险评估不足” 的痛点背景下&#xff0c;基于 Python 的高考志愿报名推荐系统构建具有重要的教育与实用价值&#xff1a;从数据处理层面&#xff0c;系统依托 Python 的 Pa…

作者头像 李华