亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > WEB開發 > 正文

理解vue實現原理,實現一個簡單的Vue框架

2024-04-27 15:09:53
字體:
來源:轉載
供稿:網友

原文地址:http://blog.csdn.net/pur_e/article/details/53066275

      其實對JS我研究不是太深,用過很多次,但只是實現功能就算了。最近JS實在是太火,從前端到后端,應用越來越廣泛,各種框架層出不窮,忍不住也想趕一下潮流。        Vue是近年出的一個前端構建數據驅動的web界面的庫,主要的特色是響應式的數據綁定,區別于以往的命令式用法。也就是在var a=1;的過程中,攔截’=’的過程,從而實現更新數據,web視圖也自動同步更新的功能。而不需要顯式的使用數據更新視圖(命令式)。這種用法我最早是在VC MFC中見過的,控件綁定變量,修改變量的值,輸入框也同步改變。        Vue的官方文檔,網上的解析文章都很詳細,不過出于學習的目的,還是了解原理后,自己實現一下記憶深刻,同時也可以學習下Js的一些知識。搞這行的,一定要多WTFC(Write The Fucking Code)。

一、思考設計

       其實這里的思考是在看過幾篇文章、看過一些源碼后補上的,所以有的地方會有上帝視角的意思。但是這個過程是必須的,以后碰到問題就會有思考的方向。        先看看我們想要實現什么功能,以及現在所具有的條件: 效果圖如下: 這里寫圖片描述

使用Vue框架代碼如下:

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>MVVM</title></head><body><script src="src/vue.js"></script><div id="msg"> {{b.c}}這是普通文本{{b.c+1+message}}這是普通文本 <p>{{message}}</p> <p><input type="text" v-model="message"/></p> <p>{{message}}</p> <p><button type="button" v-on:click="clickBtn(message)">click me</button></p></div><script> var vm = new Vue({ el:"#msg", data:{ b:{ c:1 }, message:"hello world" }, methods:{ clickBtn:function(message){ vm.message = "clicked"; } } });</script></body></html>12345678910111213141516171819202122232425262728293031323334351234567891011121314151617181920212223242526272829303132333435

然后我們還知道一個條件,Vue的官方文檔所說的:

把一個普通對象傳給 Vue 實例作為它的 data 選項,Vue.js 將遍歷它的屬性,用 Object.definePRoperty 將它們轉為 getter/setter。這是 ES5 特性,不能打補丁實現,這便是為什么 Vue.js 不支持 IE8 及更低版本。

用這個特性實現這樣的功能,我們需要做什么呢?

首先,需要利用Object.defineProperty,將要觀察的對象,轉化成getter/setter,以便攔截對象賦值與取值操作,稱之為Observer;需要將DOM解析,提取其中的指令與占位符,并賦與不同的操作,稱之為Compiler;需要將Compile的解析結果,與Observer所觀察的對象連接起來,建立關系,在Observer觀察到對象數據變化時,接收通知,同時更新DOM,稱之為Watcher;最后,需要一個公共入口對象,接收配置,協調上述三者,稱為Vue;

二、實現Observer

1.轉化getter/setter

       本來以為實現起來很簡單,結果只是轉換為getter和setter就碰到了很多問題。原來對JS真得是只知道點皮毛啊……

開始Observer.js代碼如下:

/** Observer是將輸入的Plain Object進行處理,利用Object.defineProperty轉化為getter與setter,從而在賦值與取值時進行攔截 這是Vue響應式框架的基礎 */function isObject(obj){ return obj != null && typeof(obj) == 'object';}function isPlainObject(obj){ return Object.prototype.toString(obj) == '[object Object]';}function observer(data){ if(!isObject(data) || !isPlainObject(data)){ return; } return new Observer(data);}var Observer = function(data){ this.data = data; this.transform(data);};Observer.prototype.transform = function(data){ for(var key in data){ var value = data[key]; Object.defineProperty(data,key,{ enumerable:true, configurable:true, get:function(){ console.log("intercept get:"+key); return value; }, set:function(newVal){ console.log("intercept set:"+key); if(newVal == value){ return; } data[key] = newVal; } }); //遞歸處理 this.transform(value); }};12345678910111213141516171819202122232425262728293031323334353637383940414243444546471234567891011121314151617181920212223242526272829303132333435363738394041424344454647

index.html:

<script src="src/Observer.js"></script><div id="msg"> <p>{{message}}</p> <p><input type="text" v-model="message"/></p> <p>{{message}}</p> <p><button type="button" v-on:click="clickBtn">click me</button></p></div><script> var a = { b:{c:1}, d:2 }; observer(a); a.d = 3;</script>1234567891011121314151612345678910111213141516

瀏覽器執行直接死循環棧溢出了,問題出在set函數里,有兩個問題:

set:function(newVal){ console.log("intercept set:"+key); if(newVal == value){ return; } //這里,通過data[key]來賦值,因為我們對data對象進行了改造,set中又會調用set函數,就會遞歸調用,死循環 //而上面本來用來判斷相同賦值不進行處理的邏輯,也因為value的值沒有改變,沒有用到。很低級的錯誤! data[key] = newVal;}123456789123456789

修改為value = newVal可以嗎?為什么可以這樣修改,因為JS作用域鏈的存在,value對于這個匿名對象來說,是如同全局變量的存在,在set中修改后,在get中也可正常返回修改后的值。

但是僅僅這樣是不夠的,因為一個很常見的錯誤,在循環中建立的匿名對象,使用外部變量用的是循環最終的值?。?!

還是作用域鏈的原因,匿名對象使用外部變量,不是保留這個變量的值,而是延長外部變量的生命周期,在該銷毀時也不銷毀(所以容易形成內存泄露),所以匿名對象被調用時,用的外部變量的值,是取決于變量在這個時刻的值(一般是循環執行完的最終值,因為循環結束后才有匿名函數調用)。

所以,打印a.d的值,將會是2

所以,最終通過新建函數的形式,Observer.js如下:

Observer.prototype.transform = function(data){ for(var key in data){ this.defineReactive(data,key,data[key]); }};Observer.prototype.defineReactive = function(data,key,value){ var dep = new Dep(); Object.defineProperty(data,key,{ enumerable:true, configurable:false, get:function(){ console.log("intercept get:"+key); if(Dep.target){ //JS的瀏覽器單線程特性,保證這個全局變量在同一時間內,只會有同一個監聽器使用 dep.addSub(Dep.target); } return value; }, set:function(newVal){ console.log("intercept set:"+key); if(newVal == value){ return; } //利用閉包的特性,修改value,get取值時也會變化 //不能使用data[key]=newVal //因為在set中繼續調用set賦值,引起遞歸調用 value = newVal; //監視新值 observer(newVal); dep.notify(newVal); } }); //遞歸處理 observer(value);};123456789101112131415161718192021222324252627282930313233343536373839123456789101112131415161718192021222324252627282930313233343536373839

2.監聽隊列

       現在我們已經可以攔截對象的getter/setter,也就是對象的賦值與取值時我們都會知道,知道后需要通知所有監聽這個對象的Watcher,數據發生了改變,需要進行更新DOM等操作,所以我們需要維護一個監聽隊列,所有對該對象有興趣的Watcher注冊進來,接收通知。這一部分之前看了Vue的實現,感覺也不會有更巧妙的實現方式了,所以直接說一下實現原理。

首先,我們攔截了getter;我們要為a.d添加Wacher監聽者tmpWatcher;將一個全局變量賦值target=tmpWatcher;取值a.d,也就調用到了a.d的getter;在a.d的getter中,將target添加到監聽隊列中;target = null;

       就是這么簡單,至于為什么可以這樣做,是因為JS在瀏覽器中是單線程執行的!!所以在執行這個監聽器的添加過程時,決不會有其他的監聽器去修改全局變量target!!所以這也算是因地制宜嗎0_0

       詳細代碼可以去看github中源碼的實現,在Observer.js中。當然他還有比較復雜的依賴、剔重等邏輯,我這里只是簡單實現一個。

var Dep = function(){ this.subs = {};};Dep.prototype.addSub = function(target){ if(!this.subs[target.uid]) { //防止重復添加 this.subs[target.uid] = target; }};Dep.prototype.notify = function(newVal){ for(var uid in this.subs){ this.subs[uid].update(newVal); }};Dep.target = null;123456789101112131415123456789101112131415

三.實現Compiler

       這里,是在看過DMQ的源碼后,自己實現的一份代碼,因為對JS不太熟悉,犯了一些小錯誤。果然學習語言的最好方式就是去寫~_~,之后,對JS的理解又加深了不少。        又因為想要實現的深入一點,也就是不只是單純的變量占位符如{{a}},而是表達式如{{a+Math.PI+b+fn(a)}},想不出太好的辦法,又去翻閱了Vue的源碼實現,發現Vue的實現其實也不怎么優雅,但確實也沒有更好的辦法。有時候,不得不寫出這種代碼,如枚舉所有分支,是最簡單、最直接,也往往是最好的方法。

1.最簡單的實現

       也就是純的變量占位,這個大家都想得到,用正則分析占位符,將這個變量添加監聽,與前面建立的setter/getter建立關系即可。

2.進階的實現——Vue

說一下Vue的實現方法:

原理:

將表達式{{a+Math.PI+b+fn(a)}},變成函數:function getter(scope) { return scope.a + Math.PI + scope.b + scope.fn(scope.a);}123123調用時,傳入Vue對象getter(vm),這樣,所有表達式中的變量、函數,變成vm作用域內的調用。

Vue的實現

var body = exp.replace(saveRE, save).replace(wsRE, ''); * 利用了幾個正則,首先將所有的字符串提取出來,進行替換,因為后面要去除所有的空格; * 去除空格; body = (' ' + body).replace(identRE, rewrite).replace(restoreRE, restore); * 將所有的變量前加scope(除了保留字如Math,Date,isNaN等,具體見代碼中的正則); * 將所有字符串替換回去 * 生成上面提到過的函數

可以看出這個操作還是稍微有點耗時,所以Vue做了一些優化,加了一個緩存。

3.實現中碰到的問題

明白了一個概念,DOM中每一個文字塊,也是一個節點:文字節點,而且只要被其他節點分隔,就是不同的文字節點;JS中,可以使用childNodes與attributes等來枚舉子節點與屬性列表等;[].forEach.call,可以用來遍歷非Array對象如childNodes;[].slice會生成數組的一個淺復制,因為childNodes在修改DOM對象時,會實時變動,所以不能直接在遍歷中修改DOM,此時,可以生成淺復制數組,用來遍歷;

具體代碼太長就不展示,可以直接看Git上的源碼。

四、實現Watcher

       Watcher的實現,需要考慮幾個問題:

傳入的表達式如前面提到的{{a+Math.PI+b+fn(a)}},如何與每一個具體對象建立關系,添加監聽;添加后的關系如何維護,其中包括: 上一層對象被直接賦值,如表達式是{{a.b.c}},進行賦值a.b={c:4},此時,c的getter沒有被觸發,與c相關的Watcher如何被通知;還是上面的例子,新添加的c如何與老的c的Watcher建立關系;

       其實,上面說監聽隊列時,已經稍微提過,利用JS單線程的特性,在調用對象的getter前,將Dep.target這個全局變量修改為Watcher,然后getter中將其添加到監聽隊列中。所以,Watcher中,只需要取一次表達式的值,就會實現這個功能,而且,Watcher在初始化時,本來就需要調用一次取值來初始化DOM!

       來看一下上面的問題:

首先,Watcher需要監聽的是一個表達式,所有表達式中的成員,都需要監聽,如{{a+Math.PI+b+fn(a)}}需要監聽a和b的變化,而取這個表達式值時,會調用a和b的getter,從而將自身添加到a和b的監聽隊列中!關于添加后關系的維護: 我們在取表達式值{{a.b.c}}時,a和b和c的getter都會被調用,從而都會將Watcher添加到自己的監聽隊列中,所以a.b={c:4}賦值時,Watcher同樣會被觸發!上面Watcher被觸發后,會重新獲取a.b.c的值,則新的c的getter會被調用,從而新的c會將Watcher添加到自己的監聽隊列中。

       可以發現,上面的問題都被圓滿解決,如果這是我自己想出來的方案,我會被自己感動哭的T_T 這才是優雅的解決方案!

五、實現Vue

       這就是一個公共入口,整個框架從這里創建。需要實現的目標:

進行流程的串接,observe對象,compile Dom;對自己的對象data,函數methods等進行代理,從而可以直接使用vm.a,vm.init等進行調用,同樣通過Object.defineProperty進行對象定義;

       具體實現比較簡單,可以直接參考源碼


上一篇:JavaScript arguments對象

下一篇:css選擇器

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
98午夜经典影视| 日韩av影视综合网| 国产亚洲精品久久久久动| www.久久久久| 亚洲欧洲日产国码av系列天堂| 国产精品video| 一区二区三区回区在观看免费视频| 欧美在线欧美在线| 成人中文字幕+乱码+中文字幕| 最新91在线视频| 国产成人精品电影久久久| 欧美性做爰毛片| 国产精品视频免费观看www| 国产精品一区二区性色av| 国产精品在线看| 亚洲第一中文字幕在线观看| 久久韩剧网电视剧| 日韩免费黄色av| xx视频.9999.com| 欧美又大粗又爽又黄大片视频| 亚洲人成五月天| 国产精品成av人在线视午夜片| 国产精品影院在线观看| 日韩精品视频在线播放| 中文字幕亚洲一区二区三区五十路| 国产成人久久精品| 日韩www在线| 成人精品aaaa网站| 国语自产精品视频在线看| 国产v综合v亚洲欧美久久| 亚洲免费电影在线观看| 5252色成人免费视频| 国产精品亚洲精品| 亚洲精品一区二区三区不| 57pao成人永久免费视频| 国产精品久久久久国产a级| 欧美亚洲激情视频| 中文字幕日韩精品有码视频| 欧美日韩电影在线观看| 亚洲最大福利网站| 日韩精品视频免费专区在线播放| 日韩动漫免费观看电视剧高清| 日韩的一区二区| 欧美激情在线视频二区| 中日韩美女免费视频网站在线观看| 日韩av手机在线| 国产视频精品xxxx| 久久露脸国产精品| 亚洲免费小视频| 91精品视频在线播放| 日韩亚洲欧美中文高清在线| 在线电影av不卡网址| 欧美另类老肥妇| 久久五月情影视| 国产日产久久高清欧美一区| 日韩av三级在线观看| 色悠久久久久综合先锋影音下载| 亚洲午夜精品视频| 91精品国产高清久久久久久久久| 97精品视频在线播放| 欧美激情综合色| 国产精品亚洲欧美导航| 日韩久久免费电影| 亚洲欧美激情精品一区二区| 亚洲综合在线做性| 国产精品电影网| 91精品久久久久久久久中文字幕| 亚洲国产欧美精品| 日本aⅴ大伊香蕉精品视频| 青青久久av北条麻妃黑人| 精品久久香蕉国产线看观看gif| 亚洲一区精品电影| 欧美日韩国产精品一区二区不卡中文| 夜夜嗨av色一区二区不卡| 精品久久久久久久久久久久久| 欧美特黄级在线| 日韩欧美亚洲范冰冰与中字| 性欧美激情精品| 一本色道久久综合狠狠躁篇怎么玩| 国产69精品久久久久99| 国产成人精品网站| 亚洲欧洲一区二区三区久久| 久久99久久久久久久噜噜| 国产啪精品视频网站| 国外成人在线播放| 国产精品爱啪在线线免费观看| 国产91亚洲精品| 在线视频欧美日韩精品| 国产精品美女无圣光视频| 国产精品毛片a∨一区二区三区|国| 91av在线不卡| 91麻豆桃色免费看| 色播久久人人爽人人爽人人片视av| 国产香蕉精品视频一区二区三区| 久久久欧美一区二区| 国产精品色婷婷视频| 成人美女免费网站视频| 亚洲第一免费网站| 欧美成人国产va精品日本一级| 欧美激情二区三区| 久久伊人精品视频| 国产激情视频一区| 九九久久久久久久久激情| 日韩h在线观看| www.久久色.com| 91精品久久久久久久久| 成人国产精品免费视频| 亚洲色图综合网| 久久久久久久久综合| 国产丝袜高跟一区| 在线看日韩av| 色综合伊人色综合网| 精品亚洲一区二区三区| 精品久久久久久久久久久久久| 欧美亚洲一级片| 欧美日韩国产中字| 色妞久久福利网| 日韩av电影在线免费播放| 日韩精品在线观看一区| 日韩极品精品视频免费观看| 狠狠色香婷婷久久亚洲精品| 亚洲视频视频在线| 欧美精品在线免费播放| 日韩一级裸体免费视频| 国产亚洲欧美日韩一区二区| 在线精品视频视频中文字幕| 欧美电影在线免费观看网站| 欧美性xxxxxxxxx| 日韩在线观看av| zzjj国产精品一区二区| 国产精品成人一区| 国产精品高精视频免费| 亚洲色图校园春色| 精品国产91久久久| 国产欧美一区二区三区久久| 欧美猛男性生活免费| 欧美综合在线观看| 精品一区二区三区三区| 欧美洲成人男女午夜视频| 久久精品一偷一偷国产| 一区二区三区在线播放欧美| 91中文字幕在线| 日韩中文字幕网站| 亚洲第一级黄色片| 92看片淫黄大片欧美看国产片| 欧美日韩爱爱视频| 精品福利免费观看| 亚洲欧洲国产一区| 亚洲成人国产精品| 这里只有精品在线观看| 川上优av一区二区线观看| 热草久综合在线| 91精品免费久久久久久久久| 国内精品模特av私拍在线观看| 日韩一级黄色av| 欧美成人免费视频| 亚洲精品美女在线观看| 91欧美精品午夜性色福利在线| 菠萝蜜影院一区二区免费| 一二美女精品欧洲| 欧美一区三区三区高中清蜜桃| 91精品久久久久久久久久久久久久| 欧洲精品久久久| 亚洲图片欧洲图片av|