电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

瀏覽:5日期:2022-11-01 17:16:03

為什么實現(xiàn)數(shù)據(jù)響應(yīng)式

當(dāng)前vue、react等框架流行。無論是vue、還是react框架大家最初的設(shè)計思路都是類似的。都是以數(shù)據(jù)驅(qū)動視圖,數(shù)據(jù)優(yōu)先。希望能夠通過框架減少開發(fā)人員直接操作節(jié)點,讓開發(fā)人員能夠把更多的精力放在業(yè)務(wù)上而不是過多的放在操作節(jié)點上。另一方面,框架會通過虛擬dom及diff算法提高頁面性能。這其中需要數(shù)據(jù)優(yōu)先最根本的思路就是實現(xiàn)數(shù)據(jù)響應(yīng)式。so,本次來看下如何基于原生實現(xiàn)數(shù)據(jù)響應(yīng)式。

vue中的數(shù)據(jù)響應(yīng)

vue中會根據(jù)數(shù)據(jù)將數(shù)據(jù)通過大胡子語法及指令渲染到視圖上,這里我們以大胡子語法為例。如下:

<div id='app'> {{message}}</div>

let vm = new Vue({ el:'#app', data:{ message:'測試數(shù)據(jù)' }})setTimeout(()=>{ vm.message = '修改的數(shù)據(jù)';},1000)

如上代碼,很簡單 。vue做了兩件事情。一、把message數(shù)據(jù)初次渲染到視圖。二、當(dāng)message數(shù)據(jù)改變的時候視圖上渲染的message數(shù)據(jù)同時也會做出響應(yīng)。以最簡單的案例。帶著問題來看,通過原生js如何實現(xiàn)??這里為了簡化操作便于理解,這里就不去使用虛擬dom。直接操作dom結(jié)構(gòu)。

實現(xiàn)數(shù)據(jù)初次渲染

根據(jù)vue調(diào)用方式。定義Vue類來實現(xiàn)各種功能。將初次渲染過程定義成編譯compile函數(shù)渲染視圖。通過傳入的配置以及操作dom來實現(xiàn)渲染。大概思路是通過正則查找html 里 #app 作用域內(nèi)的表達(dá)式,然后查找數(shù)據(jù)做對應(yīng)的替換即可。具體實現(xiàn)如下:

class Vue { constructor(options) { this.opts = options; this.compile(); } compile() { let ele = document.querySelector(this.opts.el); // 獲取所有子節(jié)點 let childNodes = ele.childNodes; childNodes.forEach(node => { if (node.nodeType === 3) {// 找到所有的文本節(jié)點let nodeContent = node.textContent;// 匹配“{{}}”let reg = /{{s*([^{}s]+)s*}}/g;if (reg.test(nodeContent)) { let $1 = RegExp.$1; // 查找數(shù)據(jù)替換 “{{}}” node.textContent = node.textContent.replace(reg, this.opts.data[$1]);} } }) }}

如上完成了初次渲染,將message數(shù)據(jù)渲染到了視圖上。但是會返現(xiàn)并沒對深層次的dom結(jié)構(gòu)做處理也就是如下情況:

<div id='app'> 1{{ message }}2 <div> hello , {{ message }} </div> </div>

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

渲染結(jié)果如上

發(fā)現(xiàn)結(jié)果并沒有達(dá)到預(yù)期。so,需要改下代碼,讓節(jié)點可以深層次查找就可以了。代碼如下:

compile() { let ele = document.querySelector(this.opts.el); this.compileNodes(ele); } compileNodes(ele) { // 獲取所有子節(jié)點 let childNodes = ele.childNodes; childNodes.forEach(node => { if (node.nodeType === 3) {// 找到所有的文本節(jié)點let nodeContent = node.textContent;// 匹配“{{}}”let reg = /{{s*([^{}s]+)s*}}/g;if (reg.test(nodeContent)) { let $1 = RegExp.$1; // 查找數(shù)據(jù)替換 “{{}}” node.textContent = node.textContent.replace(reg, this.opts.data[$1]);} } else if (node.nodeType === 1) {if (node.childNodes.length > 0) { this.compileNodes(node);} } }) }

上述代碼通過遞歸查找節(jié)點 實現(xiàn)深層次節(jié)點的渲染工作。如此,就實現(xiàn)了視圖的初次渲染。

數(shù)據(jù)劫持

回過頭來看下上面說的第二個問題:當(dāng)message數(shù)據(jù)改變的時候視圖上渲染的message數(shù)據(jù)同時也會做出響應(yīng)。如何實現(xiàn)數(shù)據(jù)響應(yīng)式?簡而言之就是數(shù)據(jù)變動影響視圖變動?再將問題拆分下 1. 如何知道數(shù)據(jù)變動了? 2.如何根據(jù)數(shù)據(jù)變動來更改視圖?

如何知道數(shù)據(jù)變動了? 這里就需要用到數(shù)據(jù)攔截了,或者叫數(shù)據(jù)觀察。把會變動的data數(shù)據(jù)觀察起來。當(dāng)他變動的時候我們可以做后續(xù)的渲染事情。如何攔截數(shù)據(jù)呢 ?vue2里采取的是definePrototype。

let obj = { myname:'張三'}Object.defineProperty(obj,’myname’,{ configurable:true, enumerable:true, get(){ console.log('get.') return '張三'; }, set(newValue){ console.log('set') console.log(newValue); }})console.log(obj);

上述代碼會發(fā)現(xiàn),通過defineProperty劫持的對象屬性下都會有g(shù)et及set方法。那么當(dāng)我們獲取或者設(shè)置數(shù)據(jù)的時候就能出發(fā)對應(yīng)的get及set 。這樣就能攔截數(shù)據(jù)做后續(xù)操作。

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

還有沒有其他方式達(dá)到數(shù)據(jù)劫持的效果呢?ES6中出現(xiàn)了Proxy 代理對象同樣也可以達(dá)到類似劫持?jǐn)?shù)據(jù)的功能。如下代碼:

let obj = { myname:'張三'}let newObj = new Proxy(obj,{ get(target,key){ console.log('get...') return '張三' }, set(target,name,newValue){ console.log('set...'); return Reflect.set(target,name,newValue); }})

兩種方式都可以實現(xiàn)數(shù)據(jù)劫持。proxy功能更加強(qiáng)大,很多方法是defineProperty所不具備的。且proxy直接攔截的是對象而defineProperty攔截的是對象屬性。so,可以利用上述方式將data數(shù)據(jù)做劫持,代碼如下:

observe(data){ let keys = Object.keys(data); keys.forEach(key=>{ let value = data[key]; Object.defineProperty(data,key,{configurable:true,enumerable:true,get(){ return value;},set(newValue){ value = newValue;} }); }) }

觀察者模式實現(xiàn)數(shù)據(jù)響應(yīng)

有了劫持?jǐn)?shù)據(jù)方式后,接下來需要實現(xiàn)的就是當(dāng)修改數(shù)據(jù)的時候?qū)⑿聰?shù)據(jù)渲染到視圖。如何辦到呢?會發(fā)現(xiàn),需要在data設(shè)置的時候觸發(fā)視圖的compile編譯。二者之間互相影響,此時可以想到利用觀察者模式,通過觀察者模式讓二者產(chǎn)生關(guān)聯(lián),如下:

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

圖略小,代碼也貼上吧。

class Vue extends EventTarget { constructor(options) { super(); this.opts = options; this.observe(this.opts.data); this.compile(); } observe(data){ let keys = Object.keys(data); let _this = this; keys.forEach(key=>{ let value = data[key]; Object.defineProperty(data,key,{configurable:true,enumerable:true,get(){ return value;},set(newValue){ _this.dispatchEvent(new CustomEvent(key,{ detail:newValue })); value = newValue;} }); }) } compile() { let ele = document.querySelector(this.opts.el); this.compileNodes(ele); } compileNodes(ele) { // 獲取所有子節(jié)點 let childNodes = ele.childNodes; childNodes.forEach(node => { if (node.nodeType === 3) {// 找到所有的文本節(jié)點let nodeContent = node.textContent;// 匹配“{{}}”let reg = /{{s*([^{}s]+)s*}}/g;if (reg.test(nodeContent)) { let $1 = RegExp.$1; // 查找數(shù)據(jù)替換 “{{}}” node.textContent = node.textContent.replace(reg, this.opts.data[$1]); this.addEventListener($1,e=>{ let oldValue = this.opts.data[$1]; let newValue = e.detail; let reg = new RegExp(oldValue); node.textContent = node.textContent.replace(reg,newValue); })} } else if (node.nodeType === 1) {if (node.childNodes.length > 0) { this.compileNodes(node);} } }) }}

如上,成功的通過觀察者模式實現(xiàn)了數(shù)據(jù)的響應(yīng)。但是會發(fā)現(xiàn)data與compile之間需要通過鍵名來進(jìn)行關(guān)聯(lián)。如果data數(shù)據(jù)結(jié)構(gòu)嵌套關(guān)系復(fù)雜后面會比較難處理。有沒有一種方式讓二者松解耦呢?這時候可以用發(fā)布訂閱模式來進(jìn)行改造。

發(fā)布訂閱模式改造響應(yīng)式

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

還是略小,也還是貼上代碼:

class Vue { constructor(options) { this.opts = options; this.observe(this.opts.data); this.compile(); } observe(data){ let keys = Object.keys(data); let _this = this; keys.forEach(key=>{ let value = data[key]; let dep = new Dep(); Object.defineProperty(data,key,{configurable:true,enumerable:true,get(){ if(Dep.target){ dep.addSub(Dep.target); } return value;},set(newValue){ dep.notify(newValue); value = newValue;} }); }) } compile() { let ele = document.querySelector(this.opts.el); this.compileNodes(ele); } compileNodes(ele) { // 獲取所有子節(jié)點 let childNodes = ele.childNodes; childNodes.forEach(node => { if (node.nodeType === 3) {// 找到所有的文本節(jié)點let nodeContent = node.textContent;// 匹配“{{}}”let reg = /{{s*([^{}s]+)s*}}/g;if (reg.test(nodeContent)) { let $1 = RegExp.$1; // 查找數(shù)據(jù)替換 “{{}}” node.textContent = node.textContent.replace(reg, this.opts.data[$1]); new Watcher(this.opts.data,$1,(newValue)=>{ let oldValue = this.opts.data[$1]; let reg = new RegExp(oldValue); node.textContent = node.textContent.replace(reg,newValue); })} } else if (node.nodeType === 1) {if (node.childNodes.length > 0) { this.compileNodes(node);} } }) }}class Dep{ constructor(){ this.subs = []; } addSub(sub){ this.subs.push(sub); } notify(newValue){ this.subs.forEach(sub=>{ sub.update(newValue); }) }}class Watcher{ constructor(data,key,cb){ Dep.target = this; data[key]; this.cb = cb; Dep.target = null; } update(newValue){ this.cb(newValue); }}

如上代碼思路是 針對每個數(shù)據(jù)會生成一個dep(依賴收集器)在數(shù)據(jù)get的時候收集watcher,將watcher 添加到dep里保存。數(shù)據(jù)一旦有改變觸發(fā)notify發(fā)布消息從而影響compile編譯更新視圖。這個流程也可以參看下圖:

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

如上就完成了視圖響應(yīng)。通過上述代碼,我們可以看出實現(xiàn)數(shù)據(jù)響應(yīng)兩個核心點1.數(shù)據(jù)劫持。2.觀察者和發(fā)布訂閱。在這我們可以思考一個問題,2個設(shè)計模式都是可以實現(xiàn)的但是有什么區(qū)別呢?

觀察者與發(fā)布訂閱

這里需要從概念來看

觀察者模式:定義一個對象與其他對象之間的一種依賴關(guān)系,當(dāng)對象發(fā)生某種變化的時候,依賴它的其它對象都會得到更新,一對多的關(guān)系。 發(fā)布訂閱模式:是一種消息范式,消息的發(fā)送者(稱為發(fā)布者)不會將消息直接發(fā)送給特定的接收者(稱為訂閱者)。而是將發(fā)布的消息分為不同的類別,無需了解哪些訂閱者(如果有的話)可能存在。同樣的,訂閱者可以表達(dá)對一個或多個類別的興趣,只接收感興趣的消息,無需了解哪些發(fā)布者(如果有的話)存在。

vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)

兩者之間關(guān)系,發(fā)布訂閱是三者之間關(guān)系。發(fā)布訂閱會多了一個關(guān)系器來組織主題和觀察者之間的關(guān)系。這樣做的好處就是松解耦。看上面響應(yīng)式例子可以看出觀察者需要通過事件名稱來進(jìn)行關(guān)聯(lián)。發(fā)布訂閱定義dep管理器之后data和compile徹底解耦,讓二者松散解耦。在處理多層數(shù)據(jù)結(jié)構(gòu)上發(fā)布訂閱會更清晰。松解耦能夠應(yīng)對更多變化,把模塊之間依賴降到最低。發(fā)布訂閱廣義上是觀察者模式。

好了 暫時先over 。 如果覺得有收獲的話可以點個贊,贈人玫瑰,手有余香?。。。?/p>

以上就是vue mvvm數(shù)據(jù)響應(yīng)實現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于vue mvvm數(shù)據(jù)響應(yīng)的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Vue
相關(guān)文章:
主站蜘蛛池模板: 对照品_中药对照品_标准品_对照药材_「格利普」高纯中药标准品厂家-成都格利普生物科技有限公司 澳门精准正版免费大全,2025新澳门全年免费,新澳天天开奖免费资料大全最新,新澳2025今晚开奖资料,新澳马今天最快最新图库 | 跨境物流_美国卡派_中大件运输_尾程派送_海外仓一件代发 - 广州环至美供应链平台 | 杭州画室_十大画室_白墙画室_杭州美术培训_国美附中培训_附中考前培训_升学率高的画室_美术中考集训美术高考集训基地 | 深圳律师咨询_深圳律师事务所_华荣【免费在线法律咨询】网 | 洗瓶机厂家-酒瓶玻璃瓶冲瓶机-瓶子烘干机-封口旋盖压盖打塞机_青州惠联灌装机械 | 短信通106短信接口验证码接口群发平台_国际短信接口验证码接口群发平台-速度网络有限公司 | 仓储笼_金属箱租赁_循环包装_铁网箱_蝴蝶笼租赁_酷龙仓储笼租赁 测试治具|过炉治具|过锡炉治具|工装夹具|测试夹具|允睿自动化设备 | 对夹式止回阀_对夹式蝶形止回阀_对夹式软密封止回阀_超薄型止回阀_不锈钢底阀-温州上炬阀门科技有限公司 | 安徽净化板_合肥岩棉板厂家_玻镁板厂家_安徽科艺美洁净科技有限公司 | 短信通106短信接口验证码接口群发平台_国际短信接口验证码接口群发平台-速度网络有限公司 | 包装机_厂家_价格-山东包装机有限公司 | 【同风运车官网】一站式汽车托运服务平台,验车满意再付款 | 无线遥控更衣吊篮_IC卡更衣吊篮_电动更衣吊篮配件_煤矿更衣吊篮-力得电子 | 浙江清风侠环保设备有限公司| 雨水收集系统厂家-雨水收集利用-模块雨水收集池-徐州博智环保科技有限公司 | 温控器生产厂家-提供温度开关/热保护器定制与批发-惠州市华恺威电子科技有限公司 | 氟塑料磁力泵-不锈钢离心泵-耐腐蚀化工泵厂家「皖金泵阀」 | 压装机-卧式轴承轮轴数控伺服压装机厂家[铭泽机械] | 冲击式破碎机-冲击式制砂机-移动碎石机厂家_青州市富康机械有限公司 | 广州活动策划公司-15+年专业大型公关活动策划执行管理经验-睿阳广告 | 油罐车_加油机_加油卷盘_加油机卷盘_罐车人孔盖_各类球阀_海底阀等车用配件厂家-湖北华特专用设备有限公司 | 搅拌磨|搅拌球磨机|循环磨|循环球磨机-无锡市少宏粉体科技有限公司 | 重庆钣金加工厂家首页-专业定做监控电视墙_操作台 | 好物生环保网、环保论坛 - 环保人的学习交流平台 | 山东聚盛新型材料有限公司-纳米防腐隔热彩铝板和纳米防腐隔热板以及钛锡板、PVDF氟膜板供应商 | 臭氧实验装置_实验室臭氧发生器-北京同林臭氧装置网 | 山东螺杆空压机,烟台空压机,烟台开山空压机-烟台开山机电设备有限公司 | 国际船舶网 - 船厂、船舶、造船、船舶设备、航运及海洋工程等相关行业综合信息平台 | 登车桥动力单元-非标液压泵站-非标液压系统-深圳市三好科技有限公司 | 【电子厂招聘_普工招工网_工厂招聘信息平台】-工立方打工网 | 数控专用机床,专用机床,自动线,组合机床,动力头,自动化加工生产线,江苏海鑫机床有限公司 | 西宁装修_西宁装修公司-西宁业之峰装饰-青海业之峰墅级装饰设计公司【官网】 | 医养体检包_公卫随访箱_慢病随访包_家签随访包_随访一体机-济南易享医疗科技有限公司 | 广西绿桂涂料--承接隔热涂料、隔音涂料、真石漆、多彩仿石漆等涂料工程双包施工 | 广州展览制作工厂—[优简]直营展台制作工厂_展会搭建资质齐全 | 热镀锌槽钢|角钢|工字钢|圆钢|H型钢|扁钢|花纹板-天津千百顺钢铁贸易有限公司 | 潍坊大集网-潍坊信息港-潍坊信息网| 德国EA可编程直流电源_电子负载,中国台湾固纬直流电源_交流电源-苏州展文电子科技有限公司 | 不锈钢/气体/液体玻璃转子流量计(防腐,选型,规格)-常州天晟热工仪表有限公司【官网】 | 槽钢冲孔机,槽钢三面冲,带钢冲孔机-山东兴田阳光智能装备股份有限公司 | PCB接线端子_栅板式端子_线路板连接器_端子排生产厂家-置恒电气 喷码机,激光喷码打码机,鸡蛋打码机,手持打码机,自动喷码机,一物一码防伪溯源-恒欣瑞达有限公司 假肢-假肢价格-假肢厂家-河南假肢-郑州市力康假肢矫形器有限公司 |