摘要:寫在前面的東西自從在上開源以來就受到各方的極大關(guān)注,并在短暫的時間里立即火了起來,現(xiàn)在已成為最流行的前端框架之一我也使用有一段時間了,對的雙向綁定有一定的理解,在這和大家分享我的愚見,有錯誤的地方望大家給予指正。
寫在前面的東西
Vue.js自從在github上開源以來就受到各方的極大關(guān)注,并在短暫的時間里立即火了起來,現(xiàn)在已成為最流行的前端框架之一;我也使用vue有一段時間了,對vue的雙向綁定有一定的理解,在這和大家分享我的愚見,有錯誤的地方望大家給予指正。
1、概述讓我們先來看一下官網(wǎng)的這張數(shù)據(jù)綁定的說明圖:
原理圖告訴我們,a對象下面的b屬性定義了getter、setter對屬性進行劫持,當(dāng)屬性值改變是就會notify通知watch對象,而watch對象則會notify到view上對應(yīng)的位置進行更新(這個地方還沒講清下面再講),然后我們就看到了視圖的更新了,反過來當(dāng)在視圖(如input)輸入數(shù)據(jù)時,也會觸發(fā)訂閱者watch,更新最新的數(shù)據(jù)到data里面(圖中的a.b),這樣model數(shù)據(jù)就能實時響應(yīng)view上的數(shù)據(jù)變化了,這樣一個過程就是數(shù)據(jù)的雙向綁定了。
看到這里就會第一個疑問:那么setter、getter是怎樣實現(xiàn)的劫持的呢?答案就是vue運用了es5中Object.defineProperty()這個方法,所以要想理解雙向綁定就得先知道Object.defineProperty是怎么一回事了;
2.Object.defineProperty它是es5一個方法,可以直接在一個對象上定義一個新屬性,或者修改一個已經(jīng)存在的屬性, 并返回這個對象,對象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符。數(shù)據(jù)描述符是一個擁有可寫或不可寫值的屬性。存取描述符是由一對 getter-setter 函數(shù)功能來描述的屬性。描述符必須是兩種形式之一;不能同時是兩者。
屬性描述符包括:configurable(可配置性相當(dāng)于屬性的總開關(guān),只有為true時才能設(shè)置,而且不可逆)、Writable(是否可寫,為false時將不能夠修改屬性的值)、Enumerable(是否可枚舉,為false時for..in以及Object.keys()將不能枚舉出該屬性)、get(一個給屬性提供 getter 的方法)、set(一個給屬性提供 setter 的方法)
var o = {name:"vue"}; Object.defineProperty(o, "age",{ value : 3, writable : true,//可以修改屬性a的值 enumerable : true,//能夠在for..in或者Object.keys()中枚舉 configurable : true//可以配置 }); Object.keys(o)//["name","age"] o.age = 4; console.log(o.age) //4 var bValue; Object.defineProperty(o, "b", { get : function(){ return bValue; }, set : function(newValue){ console.log("haha..") bValue = newValue; }, enumerable : true,//默認值是false 及不能被枚舉 configurable : true//默認也是false }); o.b = "something"; //haha..
上面分別給出了對象屬性描述符的數(shù)據(jù)描述符和存取描述的例子,注意一點是這兩種不能同時擁有,也就是valuewritable不能和getset同時具備。在這里只是很粗淺的說了一下Object.defineProperty這個方法,要了解更多可以點擊這里
3.實現(xiàn)observer我們在上面一部分講到了es5的Object.defineProperty()這個方法,vue正式通過它來實現(xiàn)對一個對象屬性的劫持的,在創(chuàng)建實例的時候vue會對option中的data對象進行一次數(shù)據(jù)格式化或者說初始化,給每個data的屬性都設(shè)置上get/set進行對象劫持,代碼如下:
function Observer(data){ this.data = data; if(Array.isArray(data)){ protoAugment(data,arrayMethods); //arrayMethods實現(xiàn)對Array.prototype原型方法的拷貝; this.observeArray(data); }else{ this.walk(data); } } Observer.prototype = { walk:function walk(data){ var _this = this; Object.keys(data).forEach(function(key){ _this.convert(key,data[key]); }) }, convert:function convert(key,val){ this.defineReactive(this.data,key,val); }, defineReactive:function defineReactive(data,key,val){ var ochildOb = observer(val); var _this = this; Object.defineProperty(data,key,{ configurable:false, enumerable:true, get:function(){ console.log(`i get the ${key}-->${val}`) return val; }, set:function(newVal){ if(newVal == val)return; console.log(`haha.. ${key} changed oldVal-->${val} newVal-->${newVal}`); val = newVal; observer(newVal);//在這里對新設(shè)置的屬性再一次進行g(shù)et/set } }) }, observeArray:function observeArray(items){ for (var i = 0, l = items.length; i < l; i++) { observer(items[i]); } } } function observer(data){ if(!data || typeof data !=="object")return; return new Observer(data); } //讓我們來試一下 var obj = {name:"jasonCloud"}; var ob = observer(obj); obj.name = "wu"; //haha.. name changed oldVal-->jasonCloud newVal-->wu obj.name; //i get the name-->wu
到這一步我們只實現(xiàn)了對屬性的set/get監(jiān)聽,但并沒實現(xiàn)變化后notify,那該怎樣去實現(xiàn)呢?在VUE里面使用了訂閱器Dep,讓其維持一個訂閱數(shù)組,但有訂閱者時就通知相應(yīng)的訂閱者notify。
let _id = 0; /* Dep構(gòu)造器用于維持$watcher檢測隊列; */ function Dep(){ this.id = _id++; this.subs = []; } Dep.prototype = { constructor:Dep, addSub:function(sub){ this.subs.push(sub); }, notify:function(){ this.subs.forEach(function(sub){ if(typeof sub.update == "function") sub.update(); }) }, removeSub:function(sub){ var index = this.subs.indexOf(sub); if(index >-1) this.subs.splice(index,1); }, depend:function(){ Dep.target.addDep(this); } } Dep.target = null; //定義Dep的一個屬性,當(dāng)watcher時Dep.targert=watcher實例對象
在這里構(gòu)造器Dep,維持內(nèi)部一個數(shù)組subs,當(dāng)有訂閱時就addSub進去,通知訂閱者更新時就會調(diào)用notify方法通知到訂閱者;我們現(xiàn)在合并一下這兩段代碼
function Observer(data){ //省略的代碼.. this.dep = new Dep(); //省略的代碼.. } Observer.prototype = { //省略的代碼.. defineReactive:function defineReactive(data,key,val){ //省略的代碼.. var dep = new Dep(); Object.defineProperty(data,key,{ configurable:false, enumerable:true, get:function(){ if(Dep.target){ dep.depend(); //省略的代碼.. } return val; }, set:function(newVal){ //省略的代碼.. dep.notify(); } }) }, observeArray:function observeArray(items){ for (var i = 0, l = items.length; i < l; i++) { observer(items[i]); } } } function observer(data){ if(!data || typeof data !=="object")return; return new Observer(data); }
上面代碼中有一個protoAugment方法,在vue中是實現(xiàn)對數(shù)組一些方法的重寫,但他并不是直接在Array.prototype.[xxx]直接進行重寫這樣會影響到所有的數(shù)組中的方法,顯然是不明智的,vue很巧妙的進行了處理,使其并不會影響到所有的Array上的方法,代碼可以點擊這里
到這里我們實現(xiàn)了數(shù)據(jù)的劫持,并定義了一個訂閱器來存放訂閱者,那么誰是訂閱者呢?那就是Watcher,下面讓我們看看怎樣實現(xiàn)watcher
4.實現(xiàn)一個Watcherwatcher是實現(xiàn)view視圖指令及數(shù)據(jù)和model層數(shù)據(jù)聯(lián)系的管道,當(dāng)在執(zhí)行編譯時候,他會把對應(yīng)的屬性創(chuàng)建一個Watcher對象讓他和數(shù)據(jù)層model建立起聯(lián)系。但數(shù)據(jù)發(fā)生變化是會觸發(fā)update方法更新到視圖上view中,反過來亦然。
function Watcher(vm,expOrFn,cb){ this.vm = vm; this.cb = cb; this.expOrFn = expOrFn; this.depIds = {}; var value = this.get(),valuetemp; if(typeof value === "object" && value !== null){ if(Array.isArray(value)){ valuetemp = []; for(var i = 0,len = value.length;i到現(xiàn)在還差一步就是將我們在容器中寫的指令和{{}}讓他和我們的model建立起連續(xù)并轉(zhuǎn)化成,我們平時熟悉的html文檔,這個過程也就是編譯;編譯簡單的實現(xiàn)就是將我們定義的容器里面所有的子節(jié)點都獲取到,然后通過對應(yīng)的規(guī)則進行轉(zhuǎn)換編譯,為了提高性能,先創(chuàng)建一個文檔碎片createDocumentFragment(),然后操作都在碎片中進行,等操作成功后一次性appendChild進去;
function Compile(el,vm){ this.$vm = vm; this.$el = this.isElementNode(el) ? el : document.querySelector(el); if(this.$el){ this.$fragment = this.nodeToFragment(this.$el); this.init(); this.$el.appendChild(this.$fragment); this.$vm.$option["mount"] && this.$vm.$option["mount"].call(this.$vm); } }5.實現(xiàn)一個簡易版的vue到目前為止我們可以實現(xiàn)一個簡單的數(shù)據(jù)雙向綁定了,接下來要做的就是對這一套流程進行整合了,不多說上碼
function Wue(option){ this.$option = option; var data = this._data = this.$option.data; var _this = this; //數(shù)據(jù)代理實現(xiàn)數(shù)據(jù)從vm.xx == vm.$data.xx; Object.keys(data).forEach(function(val){ _this._proxy(val) }); observer(data) this.$compile = new Compile(this.$option.el , this); } Wue.prototype = { $watch:function(expOrFn,cb){ return new Watcher(this,expOrFn,cb); }, _proxy:function(key){ var _this = this; Object.defineProperty(_this,key,{ configurable: false, enumerable: true, get:function(){ return _this._data[key]; }, set:function(newVal){ _this._data[key] = newVal; } }) } }在這里定義了一個Wue構(gòu)造函數(shù),當(dāng)實例化的時候他會對option的data屬性進行格式化(劫持),然后再進行編譯,讓數(shù)據(jù)和視圖建立起聯(lián)系;在這里用_proxy進行數(shù)據(jù)代理是為了當(dāng)訪問數(shù)據(jù)時可以直接vm.xx而不需要vm._data.xx;
源碼放在這里
后話在這里只是很初步的實現(xiàn)了一些vue的功能,而且還很殘缺,比如對象的深層綁定,以及計算屬性都還沒有加入,作為后續(xù)部分吧,最后得膜拜一下尤神,太牛叉了!
參考資料:
1.https://segmentfault.com/a/11...
2.https://segmentfault.com/a/11...
3.https://github.com/youngwind/...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/81924.html
摘要:關(guān)于雙向數(shù)據(jù)綁定當(dāng)我們在前端開發(fā)中采用的模式時,,指的是模型,也就是數(shù)據(jù),,指的是視圖,也就是頁面展現(xiàn)的部分。參考沉思錄一數(shù)據(jù)綁定雙向數(shù)據(jù)綁定實現(xiàn)數(shù)據(jù)與視圖的綁定與同步,最終體現(xiàn)在對數(shù)據(jù)的讀寫處理過程中,也就是定義的數(shù)據(jù)函數(shù)中。 關(guān)于雙向數(shù)據(jù)綁定 當(dāng)我們在前端開發(fā)中采用MV*的模式時,M - model,指的是模型,也就是數(shù)據(jù),V - view,指的是視圖,也就是頁面展現(xiàn)的部分。通常,...
摘要:雙向數(shù)據(jù)綁定可算是前端領(lǐng)域經(jīng)久不衰的熱詞,不管是前端開發(fā)還是面試都會有所涉及。因此,中的挺身而出,拯救了中對數(shù)組數(shù)據(jù)處理的不足。有興趣的朋友請期待筆者的下一篇博客,討論下用實現(xiàn)雙向數(shù)據(jù)綁定。 雙向數(shù)據(jù)綁定可算是前端領(lǐng)域經(jīng)久不衰的熱詞,不管是前端開發(fā)還是面試都會有所涉及。而且不同的框架也想盡一切辦法去實現(xiàn)這一特性,比如:Knockout / Backbone --- 發(fā)布-訂閱模式Ang...
摘要:雙向數(shù)據(jù)綁定的核心和基礎(chǔ)是其內(nèi)部真正參與數(shù)據(jù)雙向綁定流程的主要有和基于和發(fā)布者訂閱者模式,最終實現(xiàn)數(shù)據(jù)的雙向綁定。在這里把雙向數(shù)據(jù)綁定分為兩個流程收集依賴流程依賴收集會經(jīng)過以上流程,最終數(shù)組中存放列表,數(shù)組中存放列表。 Vue雙向數(shù)據(jù)綁定的核心和基礎(chǔ)api是Object.defineProperty,其內(nèi)部真正參與數(shù)據(jù)雙向綁定流程的主要有Obderver、Dep和Watcher,基于d...
摘要:在模式中一般把層算在層中,只有在理想的雙向綁定模式下,才會完全的消失。層將通過特定的展示出來,并在控件上綁定視圖交互事件,一般由框架自動生成在瀏覽器中。三大框架的異同三大框架都是數(shù)據(jù)驅(qū)動型的框架及是雙向數(shù)據(jù)綁定是單向數(shù)據(jù)綁定。 MVVM相關(guān)概念 1) MVVM典型特點是有四個概念:Model、View、ViewModel、綁定器。MVVM可以是單向綁定也可以是雙向綁定甚至是不綁...
摘要:兼容性更詳細的可以看一下實現(xiàn)思路系列的雙向綁定,關(guān)鍵步驟實現(xiàn)數(shù)據(jù)監(jiān)聽器,用重寫數(shù)據(jù)的,值更新就在中通知訂閱者更新數(shù)據(jù)。 showImg(https://segmentfault.com/img/remote/1460000015375220?w=640&h=426); 前言 現(xiàn)在的前端面試不管你用的什么框架,總會問你這個框架的雙向綁定機制,有的甚至要求你現(xiàn)場實現(xiàn)一個雙向綁定出來,那對于...
摘要:儲存訂閱器因為屬性被監(jiān)聽,這一步會執(zhí)行監(jiān)聽器里的方法這一步我們把也給弄了出來,到這一步我們已經(jīng)實現(xiàn)了一個簡單的雙向綁定了,我們可以嘗試把兩者結(jié)合起來看下效果??偨Y(jié)本文主要是對雙向綁定原理的學(xué)習(xí)與實現(xiàn)。 當(dāng)今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足于前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。 所以我們要時刻保持好奇心,擁抱變化,...
閱讀 2278·2021-11-24 09:39
閱讀 1586·2019-08-30 15:44
閱讀 2034·2019-08-29 17:06
閱讀 3472·2019-08-29 16:32
閱讀 3633·2019-08-29 16:26
閱讀 2727·2019-08-29 15:35
閱讀 3090·2019-08-29 12:50
閱讀 1720·2019-08-29 11:15