理解vue實(shí)現(xiàn)原理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue框架_第1頁(yè)
理解vue實(shí)現(xiàn)原理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue框架_第2頁(yè)
理解vue實(shí)現(xiàn)原理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue框架_第3頁(yè)
理解vue實(shí)現(xiàn)原理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue框架_第4頁(yè)
理解vue實(shí)現(xiàn)原理實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue框架_第5頁(yè)
已閱讀5頁(yè),還剩3頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

理解vue實(shí)現(xiàn)原理,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Vue框架其實(shí)對(duì)JS我研究不是太深,用過(guò)很多次,但只是實(shí)現(xiàn)功能就算了。最近JS實(shí)在是太火,從前端到后端,應(yīng)用越來(lái)越廣泛,各種框架層出不窮,忍不住也想趕一下潮流。Vue是近年出的一個(gè)前端構(gòu)建數(shù)據(jù)驅(qū)動(dòng)的web界面的庫(kù),主要的特色是響應(yīng)式的數(shù)據(jù)綁定,區(qū)別于以往的命令式用法。也就是在vara=1;的過(guò)程中,攔截’=’的過(guò)程,從而實(shí)現(xiàn)更新數(shù)據(jù),web視圖也自動(dòng)同步更新的功能。而不需要顯式的使用數(shù)據(jù)更新視圖(命令式)。這種用法我最早是在VCMFC中見過(guò)的,控件綁定變量,修改變量的值,輸入框也同步改變。Vue的官方文檔,網(wǎng)上的解析文章都很詳細(xì),不過(guò)出于學(xué)習(xí)的目的,還是了解原理后,自己實(shí)現(xiàn)一下記憶深刻,同時(shí)也可以學(xué)習(xí)下Js的一些知識(shí)。搞這行的,一定要多WTFC(WriteTheFuckingCode)。一、思考設(shè)計(jì)其實(shí)這里的思考是在看過(guò)幾篇文章、看過(guò)一些源碼后補(bǔ)上的,所以有的地方會(huì)有上帝視角的意思。但是這個(gè)過(guò)程是必須的,以后碰到問(wèn)題就會(huì)有思考的方向。先看看我們想要實(shí)現(xiàn)什么功能,以及現(xiàn)在所具有的條件:效果圖如下:這里寫圖片描述使用Vue框架代碼如下:<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>MVVM</title></head><body><scriptsrc="src/vue.js"></script><divid="msg">{{b.c}}這是普通文本{{b.c+1+message}}這是普通文本<p>{{message}}</p><p><inputtype="text"v-model="message"/></p><p>{{message}}</p><p><buttontype="button"v-on:click="clickBtn(message)">clickme</button></p></div><script>varvm=newVue({el:"#msg",data:{b:{c:1},message:"helloworld"},methods:{clickBtn:function(message){vm.message="clicked";}}});</script></body></html>然后我們還知道一個(gè)條件,Vue的官方文檔所說(shuō)的:把一個(gè)普通對(duì)象傳給Vue實(shí)例作為它的data選項(xiàng),Vue.js將遍歷它的屬性,用Object.defineProperty將它們轉(zhuǎn)為getter/setter。這是ES5特性,不能打補(bǔ)丁實(shí)現(xiàn),這便是為什么Vue.js不支持IE8及更低版本。用這個(gè)特性實(shí)現(xiàn)這樣的功能,我們需要做什么呢?首先,需要利用Object.defineProperty,將要觀察的對(duì)象,轉(zhuǎn)化成getter/setter,以便攔截對(duì)象賦值與取值操作,稱之為Observer;需要將DOM解析,提取其中的指令與占位符,并賦與不同的操作,稱之為Compiler;需要將Compile的解析結(jié)果,與Observer所觀察的對(duì)象連接起來(lái),建立關(guān)系,在Observer觀察到對(duì)象數(shù)據(jù)變化時(shí),接收通知,同時(shí)更新DOM,稱之為Watcher;最后,需要一個(gè)公共入口對(duì)象,接收配置,協(xié)調(diào)上述三者,稱為Vue;二、實(shí)現(xiàn)Observer1.轉(zhuǎn)化getter/setter本來(lái)以為實(shí)現(xiàn)起來(lái)很簡(jiǎn)單,結(jié)果只是轉(zhuǎn)換為getter和setter就碰到了很多問(wèn)題。原來(lái)對(duì)JS真得是只知道點(diǎn)皮毛啊……開始Observer.js代碼如下:/**Observer是將輸入的PlainObject進(jìn)行處理,利用Object.defineProperty轉(zhuǎn)化為getter與setter,從而在賦值與取值時(shí)進(jìn)行攔截這是Vue響應(yīng)式框架的基礎(chǔ)*/functionisObject(obj){returnobj!=null&&typeof(obj)=='object';}functionisPlainObject(obj){returnOtotype.toString(obj)=='[objectObject]';}functionobserver(data){if(!isObject(data)||!isPlainObject(data)){return;}returnnewObserver(data);}varObserver=function(data){this.data=data;this.transform(data);};Ototype.transform=function(data){for(varkeyindata){varvalue=data[key];Object.defineProperty(data,key,{enumerable:true,configurable:true,get:function(){console.log("interceptget:"+key);returnvalue;},set:function(newVal){console.log("interceptset:"+key);if(newVal==value){return;}data[key]=newVal;}});//遞歸處理this.transform(value);}};index.html:<scriptsrc="src/Observer.js"></script><divid="msg"><p>{{message}}</p><p><inputtype="text"v-model="message"/></p><p>{{message}}</p><p><buttontype="button"v-on:click="clickBtn">clickme</button></p></div><script>vara={b:{c:1},d:2};observer(a);a.d=3;</script>瀏覽器執(zhí)行直接死循環(huán)棧溢出了,問(wèn)題出在set函數(shù)里,有兩個(gè)問(wèn)題:set:function(newVal){console.log("interceptset:"+key);if(newVal==value){return;}//這里,通過(guò)data[key]來(lái)賦值,因?yàn)槲覀儗?duì)data對(duì)象進(jìn)行了改造,set中又會(huì)調(diào)用set函數(shù),就會(huì)遞歸調(diào)用,死循環(huán)//而上面本來(lái)用來(lái)判斷相同賦值不進(jìn)行處理的邏輯,也因?yàn)関alue的值沒(méi)有改變,沒(méi)有用到。很低級(jí)的錯(cuò)誤!data[key]=newVal;}修改為value=newVal可以嗎?為什么可以這樣修改,因?yàn)镴S作用域鏈的存在,value對(duì)于這個(gè)匿名對(duì)象來(lái)說(shuō),是如同全局變量的存在,在set中修改后,在get中也可正常返回修改后的值。但是僅僅這樣是不夠的,因?yàn)橐粋€(gè)很常見的錯(cuò)誤,在循環(huán)中建立的匿名對(duì)象,使用外部變量用的是循環(huán)最終的值?。。∵€是作用域鏈的原因,匿名對(duì)象使用外部變量,不是保留這個(gè)變量的值,而是延長(zhǎng)外部變量的生命周期,在該銷毀時(shí)也不銷毀(所以容易形成內(nèi)存泄露),所以匿名對(duì)象被調(diào)用時(shí),用的外部變量的值,是取決于變量在這個(gè)時(shí)刻的值(一般是循環(huán)執(zhí)行完的最終值,因?yàn)檠h(huán)結(jié)束后才有匿名函數(shù)調(diào)用)。所以,打印a.b的值,將會(huì)是2所以,最終通過(guò)新建函數(shù)的形式,Observer.js如下:Ototype.transform=function(data){for(varkeyindata){this.defineReactive(data,key,data[key]);}};Ototype.defineReactive=function(data,key,value){vardep=newDep();Object.defineProperty(data,key,{enumerable:true,configurable:false,get:function(){console.log("interceptget:"+key);if(Dep.target){//JS的瀏覽器單線程特性,保證這個(gè)全局變量在同一時(shí)間內(nèi),只會(huì)有同一個(gè)監(jiān)聽器使用dep.addSub(Dep.target);}returnvalue;},set:function(newVal){console.log("interceptset:"+key);if(newVal==value){return;}//利用閉包的特性,修改value,get取值時(shí)也會(huì)變化//不能使用data[key]=newVal//因?yàn)樵趕et中繼續(xù)調(diào)用set賦值,引起遞歸調(diào)用value=newVal;//監(jiān)視新值observer(newVal);dep.notify(newVal);}});//遞歸處理observer(value);};2.監(jiān)聽隊(duì)列現(xiàn)在我們已經(jīng)可以攔截對(duì)象的getter/setter,也就是對(duì)象的賦值與取值時(shí)我們都會(huì)知道,知道后需要通知所有監(jiān)聽這個(gè)對(duì)象的Watcher,數(shù)據(jù)發(fā)生了改變,需要進(jìn)行更新DOM等操作,所以我們需要維護(hù)一個(gè)監(jiān)聽隊(duì)列,所有對(duì)該對(duì)象有興趣的Watcher注冊(cè)進(jìn)來(lái),接收通知。這一部分之前看了Vue的實(shí)現(xiàn),感覺(jué)也不會(huì)有更巧妙的實(shí)現(xiàn)方式了,所以直接說(shuō)一下實(shí)現(xiàn)原理。首先,我們攔截了getter;我們要為a.d添加Wacher監(jiān)聽者tmpWatcher;將一個(gè)全局變量賦值target=tmpWatcher;取值a.d,也就調(diào)用到了a.d的getter;在a.d的getter中,將target添加到監(jiān)聽隊(duì)列中;target=null;就是這么簡(jiǎn)單,至于為什么可以這樣做,是因?yàn)镴S在瀏覽器中是單線程執(zhí)行的!!所以在執(zhí)行這個(gè)監(jiān)聽器的添加過(guò)程時(shí),決不會(huì)有其他的監(jiān)聽器去修改全局變量target!!所以這也算是因地制宜嗎0_0詳細(xì)代碼可以去看github中源碼的實(shí)現(xiàn),在Observer.js中。當(dāng)然他還有比較復(fù)雜的依賴、剔重等邏輯,我這里只是簡(jiǎn)單實(shí)現(xiàn)一個(gè)。varDep=function(){this.subs={};};Dtotype.addSub=function(target){if(![target.uid]){//防止重復(fù)添加this.subs[target.uid]=target;}};Dtotype.notify=function(newVal){for(varuidinthis.subs){this.subs[uid].update(newVal);}};Dep.target=null;三.實(shí)現(xiàn)Compiler這里,是在看過(guò)DMQ的源碼后,自己實(shí)現(xiàn)的一份代碼,因?yàn)閷?duì)JS不太熟悉,犯了一些小錯(cuò)誤。果然學(xué)習(xí)語(yǔ)言的最好方式就是去寫~_~,之后,對(duì)JS的理解又加深了不少。又因?yàn)橄胍獙?shí)現(xiàn)的深入一點(diǎn),也就是不只是單純的變量占位符如{{a}},而是表達(dá)式如{{a+Math.PI+b+fn(a)}},想不出太好的辦法,又去翻閱了Vue的源碼實(shí)現(xiàn),發(fā)現(xiàn)Vue的實(shí)現(xiàn)其實(shí)也不怎么優(yōu)雅,但確實(shí)也沒(méi)有更好的辦法。有時(shí)候,不得不寫出這種代碼,如枚舉所有分支,是最簡(jiǎn)單、最直接,也往往是最好的方法。1.最簡(jiǎn)單的實(shí)現(xiàn)也就是純的變量占位,這個(gè)大家都想得到,用正則分析占位符,將這個(gè)變量添加監(jiān)聽,與前面建立的setter/getter建立關(guān)系即可。2.進(jìn)階的實(shí)現(xiàn)——Vue說(shuō)一下Vue的實(shí)現(xiàn)方法:原理:將表達(dá)式{{a+Math.PI+b+fn(a)}},變成函數(shù):functiongetter(scope){returnscope.a+Math.PI+scope.b+scope.fn(scope.a);調(diào)用時(shí),傳入Vue對(duì)象getter(vm),這樣,所有表達(dá)式中的變量、函數(shù),變成vm作用域內(nèi)的調(diào)用。Vue的實(shí)現(xiàn)varbody=exp.replace(saveRE,save).replace(wsRE,'');*利用了幾個(gè)正則,首先將所有的字符串提取出來(lái),進(jìn)行替換,因?yàn)楹竺嬉コ械目崭瘢?去除空格;body=(''+body).replace(identRE,rewrite).replace(restoreRE,restore);*將所有的變量前加scope(除了保留字如Math,Date,isNaN等,具體見代碼中的正則);*將所有字符串替換回去*生成上面提到過(guò)的函數(shù)可以看出這個(gè)操作還是稍微有點(diǎn)耗時(shí),所以Vue做了一些優(yōu)化,加了一個(gè)緩存。3.實(shí)現(xiàn)中碰到的問(wèn)題明白了一個(gè)概念,DOM中每一個(gè)文字塊,也是一個(gè)節(jié)點(diǎn):文字節(jié)點(diǎn),而且只要被其他節(jié)點(diǎn)分隔,就是不同的文字節(jié)點(diǎn);JS中,可以使用childNodes與attributes等來(lái)枚舉子節(jié)點(diǎn)與屬性列表等;[].forEach.call,可以用來(lái)遍歷非Array對(duì)象如childNodes;[].slice會(huì)生成數(shù)組的一個(gè)淺復(fù)制,因?yàn)閏hildNodes在修改DOM對(duì)象時(shí),會(huì)實(shí)時(shí)變動(dòng),所以不能直接在遍歷中修改DOM,此時(shí),可以生成淺復(fù)制數(shù)組,用來(lái)遍歷;具體代碼太長(zhǎng)就不展示,可以直接看Git上的源碼。四、實(shí)現(xiàn)WatcherWatcher的實(shí)現(xiàn),需要考慮幾個(gè)問(wèn)題:傳入的表達(dá)式如前面提到的{{a+Math.PI+b+fn(a)}},如何與每一個(gè)具體對(duì)象建立關(guān)系,添加監(jiān)聽;添加后的關(guān)系如何維護(hù),其中包括:上一層對(duì)象被直接賦值,如表達(dá)式是{{a.b.c}},進(jìn)行賦值a.b={c:4},此時(shí),c的getter沒(méi)有被觸發(fā),與c相關(guān)的Watcher如何被通知;還是上面的例子,新添加的c如何與老的c的Watcher建立關(guān)系;其實(shí),上面說(shuō)監(jiān)聽隊(duì)列時(shí),已經(jīng)稍微提過(guò),利用JS單線程的特性,在調(diào)用對(duì)象的getter前,將D

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論