德阳市网站建设_网站建设公司_服务器维护_seo优化
2026/1/12 23:35:49 网站建设 项目流程


前端老铁都在用的可观察对象:这玩意到底能干啥?(附实战套路)

  • 前端老铁都在用的可观察对象:这玩意到底能干啥?(附实战套路)
    • 先别急着翻篇,我给你讲个真事儿
    • 为什么前端越来越离不开 Observable?
    • 可观察对象不是 RxJS 的专利!
      • 1. Vue 3 的 `reactive` + `watchEffect`
      • 2. Svelte 的 `store`
      • 3. 原生 JS 也能搓简易版
    • 这些场景不用 Observable 真的会哭
      • 1. 搜索框防抖 + 请求去重
      • 2. 多源数据“谁先回来用谁”
      • 3. WebSocket 断线重连 + 心跳
    • 内存泄漏:凌晨三点的幽灵
      • 故事续集
    • 冷热 Observable:别在雪地里等公交
      • 冷 Observable(Cold)
      • 热 Observable(Hot)
    • 调试 Observable 的野路子
    • 几个让你少加班的骚操作
      • 1. shareReplay 缓存 HTTP
      • 2. switchMap 自动取消旧请求
      • 3. 表单验证 = valueChanges + 异步校验
      • 4. localStorage 当数据源,持久化状态流
    • 当你发现代码里全是 `.pipe(...)` 别慌
    • 彩蛋:我把文章写成 Observable 了

前端老铁都在用的可观察对象:这玩意到底能干啥?(附实战套路)

友情提示:本文超长,代码量巨大,建议先收藏再蹲坑慢慢看。
如果你边看边骂“这孙子写得真啰嗦”,恭喜你,你已经掌握了人类技术博客的真谛。


先别急着翻篇,我给你讲个真事儿

去年双十一,我们组负责的商品筛选页在 0 点前 5 分钟直接白屏。
运营在群里疯狂 @ 全员,老板把键盘敲得跟打鼓似的。
我蹲在地上 Debug,发现罪魁祸首是——搜索框防抖写劈叉了
对,就是setTimeout+clearTimeout那种“土味防抖”,在疯狂点击下直接内存飙红。
那一刻我深刻体会到:不会 Observable,连购物车都欺负你


为什么前端越来越离不开 Observable?

说人话:
以前页面是“点一下 -> 发请求 -> 渲染”,现在呢?
用户一边输入,一边联想,一边语音,一边拖拽,一边还切后台。
数据流像早高峰的地铁,不找根管子疏导,那就等着爆炸
Observable 就是这根管子:

  • 想什么时候推数据就推,想什么时候停就停;
  • 能把多个来源的“脏水”拧成一股“纯净水”;
  • 还能在管子中途加滤网(filter)、加延时(debounce)、加开关(takeUntil)。

用一句话总结:它让异步数据从“回调地狱”变成“乐高积木”


可观察对象不是 RxJS 的专利!

一提 Observable 就想到 RxJS,就像一提泡面就想到康师傅——其实老坛酸菜也挺好吃的

1. Vue 3 的reactive+watchEffect

// Store 文件import{reactive,watchEffect}from'vue'exportconstcartStore=reactive({list:[],gettotalPrice(){returnthis.list.reduce((s,it)=>s+it.price*it.count,0)}})// 组件里直接用watchEffect(()=>{console.log('当前总价',cartStore.totalPrice)})

没有 RxJS 的影子,但思想一模一样:数据变 -> 副作用自动跑

2. Svelte 的store

// store.jsimport{writable}from'svelte/store'exportconstcount=writable(0)// 组件里count.subscribe(v=>console.log('count 现在是',v))

语法糖甜到齁,编译期帮你把订阅/取消订阅全搞定,内存泄漏?不存在的。

3. 原生 JS 也能搓简易版

// 用 AbortController + async generator 实现“点按钮 -> 间隔发数 -> 随时停”functioncounter$(step=1000){constac=newAbortController()conststream=(asyncfunction*(){leti=0while(!ac.signal.aborted){yieldi++awaitnewPromise(r=>setTimeout(r,step))}})()return{stream,cancel:()=>ac.abort()}}const{stream,cancel}=counter$();(async()=>{forawait(constnofstream){console.log('原生流',n)if(n===5)cancel()// 5 秒后自爆}})()

一行 RxJS 都没引,但思想还是“数据流+取消”那一套


这些场景不用 Observable 真的会哭

1. 搜索框防抖 + 请求去重

需求:用户疯狂输入,300 ms 内不敲键盘再发请求;如果上一次请求没回来,直接 cancel。
土味写法:

lettimer=nullletlastController=nullfunctionsearch(keyword){clearTimeout(timer)lastController?.abort()timer=setTimeout(()=>{lastController=newAbortController()fetch(`/search?q=${keyword}`,{signal:lastController.signal}).then(r=>r.json()).then(render)},300)}

Observable 写法:

import{fromEvent}from'rxjs'import{ajax}from'rxjs/ajax'import{debounceTime,distinctUntilChanged,switchMap,map,catchError}from'rxjs/operators'constsearch$=fromEvent(inputDom,'input').pipe(map(e=>e.target.value.trim()),debounceTime(300),distinctUntilChanged(),// 输入值没变就跳过switchMap(keyword=>// 自动取消上一次未完成的 ajaxajax.getJSON(`/search?q=${keyword}`).pipe(catchError(err=>of({list:[],err})))))search$.subscribe(render)

switchMap 会自动帮你把旧请求扔掉,代码量砍半, bug 率砍 80%。

2. 多源数据“谁先回来用谁”

需求:商品详情页,优先走缓存,缓存 1 秒没回就请求网络,谁先到用谁

import{of,timer,race}from'rxjs'import{map,timeout,catchError}from'rxjs/operators'constcache$=of(JSON.parse(localStorage.getItem('detail_'+id))).pipe(filter(_=>!!_),// 缓存空就直接无视timeout(1000),// 1 秒没发值就报错catchError(()=>EMPTY)// 报错就流废掉,让 race 继续等网络)constnet$=ajax.getJSON(`/api/detail/${id}`).pipe(tap(res=>localStorage.setItem('detail_'+id,JSON.stringify(res))))race(cache$,net$).subscribe(render)

race 就是“谁先冲线谁赢”,缓存快就省流量,缓存慢也不卡用户。

3. WebSocket 断线重连 + 心跳

需求:聊天室,掉线后指数退避重连,连上后每 30 秒 ping 一次,服务端 5 秒不回 pong 就重连

import{webSocket}from'rxjs/webSocket'import{retryWhen,delay,takeUntil,repeat,share}from'rxjs/operators'import{timer,EMPTY}from'rxjs'functionmakeSocket(url){constws$=webSocket(url)constheartbeat$=timer(0,30_000).pipe(switchMap(()=>ws$.multiplex(()=>({type:'ping'}),()=>({type:'pong'}),msg=>msg.type==='pong')),takeUntil(ws$.pipe(filter(m=>m.type==='error'))),repeat()// 出错后重新订阅心跳)return{messages$:ws$.pipe(share()),// 业务消息heartbeat$// 心跳流,内部自己管理}}const{messages$,heartbeat$}=makeSocket('wss://chat.example')messages$.subscribe(renderMessage)heartbeat$.subscribe()

retryWhen + 指数退避把重连逻辑包得明明白白,share 保证多订阅只连一次,谁用谁知道。


内存泄漏:凌晨三点的幽灵

故事续集

上文搜索框防抖上线后,QA 妹子说:“页面切 10 次,Chrome 任务管理器里 JS 事件监听器 +2000。”
我头皮发麻,打开 Performance 一看,每次切路由都重新 subscribe,旧监听没清
根因:

// ❌ 组件里随手写created(){search$.subscribe(this.render)}

正确姿势:

// ✅ Vue 3import{watchEffect}from'vue'letstop=nullonMounted(()=>{stop=watchEffect(()=>search$.subscribe(render))})onUnmounted(()=>stop&&stop())// ✅ ReactuseEffect(()=>{constsub=search$.subscribe(render)return()=>sub.unsubscribe()// 清理函数必写},[])// ✅ Angularprivatedestroy$=newSubject<void>()ngOnInit(){search$.pipe(takeUntil(this.destroy$)).subscribe(render)}ngOnDestroy(){this.destroy$.next()this.destroy$.complete()}

一句话:只要你 subscribe,就必须想好怎么 unsubscribe
否则内存泄漏不是 bug,是定时炸弹


冷热 Observable:别在雪地里等公交

冷 Observable(Cold)

“每次订阅都从头开始放电影”

constcold$=newObservable(sub=>{console.log('开始放电影')sub.next(1)sub.next(2)sub.complete()})cold$.subscribe(v=>console.log('观众 A 看到',v))cold$.subscribe(v=>console.log('观众 B 看到',v))// 输出两遍“开始放电影”

热 Observable(Hot)

“现场直播,错过就错过”

constlive$=newSubject()live$.next('开场')// A 还没来,错过了live$.subscribe(v=>console.log('观众 A 看到',v))live$.next('高潮')live$.subscribe(v=>console.log('观众 B 看到',v))live$.next('彩蛋')

HTTP 请求默认是冷流,所以两次 subscribe 会打两次接口;
WebSocket、Subject、share 后是热流,早订阅早享受,晚订阅没回头。


调试 Observable 的野路子

  1. tap插针
source$.pipe(tap(v=>console.log('%c 原始值','color:green',v)),map(expensiveCalc),tap(v=>console.log('%c 计算后','color:red',v)))
  1. timestamp看时序
source$.pipe(timestamp(),tap(({value,timestamp})=>console.log(`${value}${newDate(timestamp).toLocaleTimeString()}到达`)))
  1. finalize捕获结束
ajax.post('/save',data).pipe(finalize(()=>console.log('请求结束,无论成功失败')))
  1. 断点调试
    tap里写debugger比打一堆 console 更精准
tap(v=>{if(v.id==='buggy')debugger})

几个让你少加班的骚操作

1. shareReplay 缓存 HTTP

constprofile$=ajax.getJSON('/me').pipe(shareReplay({bufferSize:1,refCount:true})// 1 秒内复用,无人订阅自动释放)// 组件 A、B、C 同时注入,只发一次请求

2. switchMap 自动取消旧请求

上文搜索框已演示,一句话:网络 race 条件杀手

3. 表单验证 = valueChanges + 异步校验

fromEvent(input,'input').pipe(map(e=>e.target.value),debounceTime(300),distinctUntilChanged(),switchMap(v=>ajax.post('/check-username',{username:v}).pipe(map(res=>res.available?null:'用户名被占用'),catchError(()=>of('网络错误'))))).subscribe(error=>{errorEl.textContent=error||''input.setCustomValidity(error||'')})

实时、防抖、异步、错误处理,全在 10 行里

4. localStorage 当数据源,持久化状态流

functionpersistent(key,init){constraw=localStorage.getItem(key)constsubject$=newBehaviorSubject(raw?JSON.parse(raw):init)subject$.pipe(debounceTime(500)).subscribe(v=>localStorage.setItem(key,JSON.stringify(v)))return{get:()=>subject$.value,set:v=>subject$.next(v),stream:()=>subject$.asObservable()}}// 使用consttheme$=persistent('theme','light')theme$.set('dark')theme$.stream().subscribe(v=>document.body.className=v)

刷新页面状态还在,堪称低配版 Vuex/Pinia


当你发现代码里全是.pipe(...)别慌

说明你已经从“命令式”进化到“响应式”
但记住三句话:

  1. 简单一次性请求,fetch + await更清爽;
  2. 业务流复杂、多源、需要组合/取消,再上 Observable;
  3. 不要为了炫技把 if else 全部换成流,否则三个月后你自己都看不懂。

彩蛋:我把文章写成 Observable 了

constarticle$=of('前端老铁都在用的可观察对象').pipe(concatMap(()=>from(['为啥离不开','不是 RxJS 专利','实战场景','内存泄漏','调试野路子','骚操作'])),map(section=>`---\n##${section}\n(此处省略 1000 字)\n`),reduce((a,b)=>a+b),finalize(()=>console.log('文章结束,点个赞再跑!')))article$.subscribe(console.log)

参考资料
Vue.observable 实战案例
Angular 路由守卫异步技巧
Redux-Observable 深度解析
Chrome 新 Observable API 提案
Vue 状态管理最佳实践
Vue.observable 轻量级状态管理方案

欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!


专栏系列(点击解锁)学习路线(点击解锁)知识定位
《微信小程序相关博客》持续更新中~结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等
《AIGC相关博客》持续更新中~AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结
《HTML网站开发相关》《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识
《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。
通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心
《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页
《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化
《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅
《python相关博客》持续更新中~Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具
《sql数据库相关博客》持续更新中~SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能
《算法系列相关博客》持续更新中~算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维
《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识
《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方
《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面
《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等
《photoshop相关博客》持续更新中~基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结
日常开发&办公&生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具

吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询