摘要:寫在前面的東西自從在上開源以來就受到各方的極大關注,并在短暫的時間里立即火了起來,現在已成為最流行的前端框架之一我也使用有一段時間了,對的雙向綁定有一定的理解,在這和大家分享我的愚見,有錯誤的地方望大家給予指正。
寫在前面的東西
Vue.js自從在github上開源以來就受到各方的極大關注,并在短暫的時間里立即火了起來,現在已成為最流行的前端框架之一;我也使用vue有一段時間了,對vue的雙向綁定有一定的理解,在這和大家分享我的愚見,有錯誤的地方望大家給予指正。
1、概述讓我們先來看一下官網的這張數據綁定的說明圖:
原理圖告訴我們,a對象下面的b屬性定義了getter、setter對屬性進行劫持,當屬性值改變是就會notify通知watch對象,而watch對象則會notify到view上對應的位置進行更新(這個地方還沒講清下面再講),然后我們就看到了視圖的更新了,反過來當在視圖(如input)輸入數據時,也會觸發訂閱者watch,更新最新的數據到data里面(圖中的a.b),這樣model數據就能實時響應view上的數據變化了,這樣一個過程就是數據的雙向綁定了。
看到這里就會第一個疑問:那么setter、getter是怎樣實現的劫持的呢?答案就是vue運用了es5中Object.defineProperty()這個方法,所以要想理解雙向綁定就得先知道Object.defineProperty是怎么一回事了;
2.Object.defineProperty它是es5一個方法,可以直接在一個對象上定義一個新屬性,或者修改一個已經存在的屬性, 并返回這個對象,對象里目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。數據描述符是一個擁有可寫或不可寫值的屬性。存取描述符是由一對 getter-setter 函數功能來描述的屬性。描述符必須是兩種形式之一;不能同時是兩者。
屬性描述符包括:configurable(可配置性相當于屬性的總開關,只有為true時才能設置,而且不可逆)、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..
上面分別給出了對象屬性描述符的數據描述符和存取描述的例子,注意一點是這兩種不能同時擁有,也就是valuewritable不能和getset同時具備。在這里只是很粗淺的說了一下Object.defineProperty這個方法,要了解更多可以點擊這里
3.實現observer我們在上面一部分講到了es5的Object.defineProperty()這個方法,vue正式通過它來實現對一個對象屬性的劫持的,在創建實例的時候vue會對option中的data對象進行一次數據格式化或者說初始化,給每個data的屬性都設置上get/set進行對象劫持,代碼如下:
function Observer(data){ this.data = data; if(Array.isArray(data)){ protoAugment(data,arrayMethods); //arrayMethods實現對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);//在這里對新設置的屬性再一次進行get/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
到這一步我們只實現了對屬性的set/get監聽,但并沒實現變化后notify,那該怎樣去實現呢?在VUE里面使用了訂閱器Dep,讓其維持一個訂閱數組,但有訂閱者時就通知相應的訂閱者notify。
let _id = 0; /* Dep構造器用于維持$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的一個屬性,當watcher時Dep.targert=watcher實例對象
在這里構造器Dep,維持內部一個數組subs,當有訂閱時就addSub進去,通知訂閱者更新時就會調用notify方法通知到訂閱者;我們現在合并一下這兩段代碼
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中是實現對數組一些方法的重寫,但他并不是直接在Array.prototype.[xxx]直接進行重寫這樣會影響到所有的數組中的方法,顯然是不明智的,vue很巧妙的進行了處理,使其并不會影響到所有的Array上的方法,代碼可以點擊這里
到這里我們實現了數據的劫持,并定義了一個訂閱器來存放訂閱者,那么誰是訂閱者呢?那就是Watcher,下面讓我們看看怎樣實現watcher
4.實現一個Watcherwatcher是實現view視圖指令及數據和model層數據聯系的管道,當在執行編譯時候,他會把對應的屬性創建一個Watcher對象讓他和數據層model建立起聯系。但數據發生變化是會觸發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到現在還差一步就是將我們在容器中寫的指令和{{}}讓他和我們的model建立起連續并轉化成,我們平時熟悉的html文檔,這個過程也就是編譯;編譯簡單的實現就是將我們定義的容器里面所有的子節點都獲取到,然后通過對應的規則進行轉換編譯,為了提高性能,先創建一個文檔碎片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.實現一個簡易版的vue到目前為止我們可以實現一個簡單的數據雙向綁定了,接下來要做的就是對這一套流程進行整合了,不多說上碼
function Wue(option){ this.$option = option; var data = this._data = this.$option.data; var _this = this; //數據代理實現數據從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構造函數,當實例化的時候他會對option的data屬性進行格式化(劫持),然后再進行編譯,讓數據和視圖建立起聯系;在這里用_proxy進行數據代理是為了當訪問數據時可以直接vm.xx而不需要vm._data.xx;
源碼放在這里
后話在這里只是很初步的實現了一些vue的功能,而且還很殘缺,比如對象的深層綁定,以及計算屬性都還沒有加入,作為后續部分吧,最后得膜拜一下尤神,太牛叉了!
參考資料:
1.https://segmentfault.com/a/11...
2.https://segmentfault.com/a/11...
3.https://github.com/youngwind/...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81924.html
摘要:關于雙向數據綁定當我們在前端開發中采用的模式時,,指的是模型,也就是數據,,指的是視圖,也就是頁面展現的部分。參考沉思錄一數據綁定雙向數據綁定實現數據與視圖的綁定與同步,最終體現在對數據的讀寫處理過程中,也就是定義的數據函數中。 關于雙向數據綁定 當我們在前端開發中采用MV*的模式時,M - model,指的是模型,也就是數據,V - view,指的是視圖,也就是頁面展現的部分。通常,...
摘要:雙向數據綁定可算是前端領域經久不衰的熱詞,不管是前端開發還是面試都會有所涉及。因此,中的挺身而出,拯救了中對數組數據處理的不足。有興趣的朋友請期待筆者的下一篇博客,討論下用實現雙向數據綁定。 雙向數據綁定可算是前端領域經久不衰的熱詞,不管是前端開發還是面試都會有所涉及。而且不同的框架也想盡一切辦法去實現這一特性,比如:Knockout / Backbone --- 發布-訂閱模式Ang...
摘要:雙向數據綁定的核心和基礎是其內部真正參與數據雙向綁定流程的主要有和基于和發布者訂閱者模式,最終實現數據的雙向綁定。在這里把雙向數據綁定分為兩個流程收集依賴流程依賴收集會經過以上流程,最終數組中存放列表,數組中存放列表。 Vue雙向數據綁定的核心和基礎api是Object.defineProperty,其內部真正參與數據雙向綁定流程的主要有Obderver、Dep和Watcher,基于d...
摘要:在模式中一般把層算在層中,只有在理想的雙向綁定模式下,才會完全的消失。層將通過特定的展示出來,并在控件上綁定視圖交互事件,一般由框架自動生成在瀏覽器中。三大框架的異同三大框架都是數據驅動型的框架及是雙向數據綁定是單向數據綁定。 MVVM相關概念 1) MVVM典型特點是有四個概念:Model、View、ViewModel、綁定器。MVVM可以是單向綁定也可以是雙向綁定甚至是不綁...
摘要:兼容性更詳細的可以看一下實現思路系列的雙向綁定,關鍵步驟實現數據監聽器,用重寫數據的,值更新就在中通知訂閱者更新數據。 showImg(https://segmentfault.com/img/remote/1460000015375220?w=640&h=426); 前言 現在的前端面試不管你用的什么框架,總會問你這個框架的雙向綁定機制,有的甚至要求你現場實現一個雙向綁定出來,那對于...
摘要:儲存訂閱器因為屬性被監聽,這一步會執行監聽器里的方法這一步我們把也給弄了出來,到這一步我們已經實現了一個簡單的雙向綁定了,我們可以嘗試把兩者結合起來看下效果。總結本文主要是對雙向綁定原理的學習與實現。 當今前端天下以 Angular、React、vue 三足鼎立的局面,你不選擇一個陣營基本上無法立足于前端,甚至是兩個或者三個陣營都要選擇,大勢所趨。 所以我們要時刻保持好奇心,擁抱變化,...
閱讀 2106·2021-11-24 09:39
閱讀 1495·2019-08-30 15:44
閱讀 1946·2019-08-29 17:06
閱讀 3393·2019-08-29 16:32
閱讀 3543·2019-08-29 16:26
閱讀 2654·2019-08-29 15:35
閱讀 3026·2019-08-29 12:50
閱讀 1636·2019-08-29 11:15