摘要:前言模式即模式簡稱單項雙向數(shù)據(jù)綁定的實現(xiàn),讓前端開發(fā)者們從繁雜的事件中解脫出來,很方便的處理數(shù)據(jù)和之間的聯(lián)動。本文將從的雙向數(shù)據(jù)綁定入手剖析庫設計的核心代碼與思路。
前言
mvvm模式即model-view-viewmodel模式簡稱,單項/雙向數(shù)據(jù)綁定的實現(xiàn),讓前端開發(fā)者們從繁雜的dom事件中解脫出來,很方便的處理數(shù)據(jù)和ui之間的聯(lián)動。
本文將從vue的雙向數(shù)據(jù)綁定入手,剖析mvvm庫設計的核心代碼與思路。
數(shù)據(jù)一旦改變則更新數(shù)據(jù)對應的ui
ui改變則觸發(fā)事件改變ui對應的數(shù)據(jù)
分析通過dom節(jié)點的指令獲取刷新函數(shù),用來刷新指定的ui。
實現(xiàn)一個橋接的方法,讓刷新函數(shù)和需要的數(shù)據(jù)關聯(lián)起來
監(jiān)聽數(shù)據(jù)變化,數(shù)據(jù)改變后通過橋接方法調用刷新函數(shù)
ui改變觸發(fā)對應的dom事件在改變特定的數(shù)據(jù)
實現(xiàn)思路實現(xiàn)observer,重新定義data,為data上每個屬性增加setter,getter以監(jiān)聽數(shù)據(jù)的變化
實現(xiàn)compile,掃描模版template,提取每個dom節(jié)點中的指令信息
實現(xiàn)directive,通過指令信息是實例化對應的directive實例,不同類型的directive擁有不同的刷新函數(shù)update
實現(xiàn)watcher,讓observer的屬性監(jiān)聽函數(shù)與directive的update函數(shù)做一一對應,以實現(xiàn)數(shù)據(jù)變化后更新視圖
模塊劃分MVVM目前劃分為observer,compile,directive,watcher四個模塊
數(shù)據(jù)監(jiān)聽模塊observer通過es5規(guī)范中的object.defineProperty方式實現(xiàn)對數(shù)據(jù)的監(jiān)聽
實現(xiàn)思路:
遞歸遍歷data,將data下面所有屬性都加上set,get方法,以實現(xiàn)對所有屬性的攔截.
注意:對象可能含有數(shù)組屬性,數(shù)組的內置有push,pop,splice等方法改變內部數(shù)據(jù).
此時做法是改變數(shù)組的原型鏈,在原型鏈中增加一層自定義的push,pop,splice方法做攔截,這些方法里面加上我們自己的回調函數(shù),然后在調用原生的push,pop,splice等方法.
具體可以看我上一篇文章js對象監(jiān)聽實現(xiàn)
observer.js代碼
export function Observer(obj) { this.$observe = function(_obj) { var type = Object.prototype.toString.call(_obj); if (type == "[object Object]") { this.$observeObj(_obj); } else if (type == "[object Array]") { this.$cloneArray(_obj); } }; this.$observeObj = function(obj) { var t = this; Object.keys(obj).forEach(function(prop) { var val = obj[prop]; defineProperty(obj, prop, val); if (prop != "__observe__") { t.$observe(val); } }); }; this.$cloneArray = function(a_array) { var ORP = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"]; var arrayProto = Array.prototype; var newProto = Object.create(arrayProto); ORP.forEach(function(prop) { Object.defineProperty(newProto, prop, { value: function(newVal) { var dep = a_array.__observe__; var re=arrayProto[prop].apply(a_array, arguments); dep.notify(); return re; }, enumerable: false, configurable: true, writable: true }); }); a_array.__proto__ = newProto; }; this.$observe(obj, []); } var addObserve = function(val) { if (!val || typeof val != "object") { return; } var dep = new Dep(); if (isArray(val)) { val.__observe__ = dep; return dep; } } export function defineProperty(obj, prop, val) { if (prop == "__observe__") { return; } val = val || obj[prop]; var dep = new Dep(); obj.__observe__ = dep; var childDep = addObserve(val); Object.defineProperty(obj, prop, { get: function() { var target = Dep.target; if (target) { dep.addSub(target); if (childDep) { childDep.addSub(target); } } return val; }, set: function(newVal) { if(newVal!=val){ val = newVal; dep.notify(); } } }); }編譯模塊compiler
實現(xiàn)思路:
1.將模版template上的dom遍歷一遍,將其存入文檔碎片frag
2.遍歷frag,通過attributes獲取節(jié)點的屬性信息,在通過正則表達式過濾屬性信息,進而拿到元素節(jié)點和文檔節(jié)點的指令信息
var complieTemplate = function (nodes, model) { if ((nodes.nodeType == 1 || nodes.nodeType == 11) && !isScript(nodes)) { paserNode(model, nodes); if (nodes.hasChildNodes()) { nodes.childNodes.forEach(node=> { complieTemplate(node, model); }) } } }; var paserNode = function (model, node) { var attributes = node.attributes || []; var direct_array = []; var scope = { parentNode: node.parentNode, nextNode: node.nextElementSibling, el: node, model: model, direct_array: direct_array }; attributes = toArray(attributes); var textContent = node.textContent; var attrs = []; var vfor; attributes.forEach(attr => { var name = attr.name; if (isDirective(name)) { if (name == "v-for") { vfor = attr; } else { attrs.push(attr); } removeAttribute(node, name); } }); //bug nodeType=3 var textValue = stringParse(textContent); if (textValue) { attrs.push({ name: "v-text", value: textValue }); node.textContent = ""; } if (vfor) { scope.attrs = attrs; attrs = [vfor]; } attrs.forEach(function (attr) { var name = attr.name; var val = attr.value; var directiveType = "v" + /v-(w+)/.exec(name)[1]; var Directive = directives[directiveType]; if (Directive) { direct_array.push(new Directive(val, scope)); } }); }; var isDirective = function (attr) { return /v-(w+)/.test(attr) }; var isScript = function isScript(el) { return el.tagName === "SCRIPT" && ( !el.hasAttribute("type") || el.getAttribute("type") === "text/javascript" ) }指令模塊directive
指令信息如:v-text,v-for,v-model等。
每種指令信息需要的初始化動作以及指令的刷新函數(shù)update都可能不一樣,所以我們把它抽象出來多帶帶做一個模塊。當然也有公用的如公共屬性,統(tǒng)一的watcher實例化,unbind.
update函數(shù)則具體定義所屬指令如何渲染ui
如簡單的vtext指令的update函數(shù)如下:
vt.update = function (textContent) { this.el.textContent = textContent; };結構圖 數(shù)據(jù)訂閱模塊watcher
watcher的功能是讓directive和observer模塊關聯(lián)起來。
初始化的時候做兩件事:
將directive模塊的update函數(shù)當參數(shù)傳入,并將其存入自身update屬性中
調用getValue,從而獲取對象data的特定屬性值,進而觸發(fā)一次之前在observer定義的屬性函數(shù)的getter方法。
由于在defineProperty函數(shù)中定義的dep變量在setter和getter函數(shù)里有引用,使dep變量處于閉包狀態(tài)沒有釋放,此時在getter方法中通過判斷Depend.target的存在,來獲取訂閱者watcher,通過發(fā)布者dep儲存起來。
數(shù)據(jù)的每個屬性都有一個唯一的的dep變量,記錄著所有訂閱者watcher的信息,一旦屬性有變化,調用setter函數(shù)的時候觸發(fā)dep.notify(),通知所有已訂閱的watcher,進而執(zhí)行所有與該屬性關聯(lián)的刷新函數(shù),最后更新指定的ui。
watcher 初始化部分代碼:
Depend.target = this; this.value = this.getValue(); Depend.target = null;
observer.js 屬性定義代碼:
export function defineProperty(obj, prop, val) { if (prop == "__observe__") { return; } val = val || obj[prop]; var dep = new Dep(); obj.__observe__ = dep; var childDep = addObserve(val); Object.defineProperty(obj, prop, { get: function() { var target = Dep.target; if (target) { dep.addSub(target); if (childDep) { childDep.addSub(target); } } return val; }, set: function(newVal) { if(newVal!=val){ val = newVal; dep.notify(); } } }); }流程圖
簡單的流程圖如下:
本文基本對mvvm庫的需求整理,拆分,以及對拆分模塊的逐一實現(xiàn)來達到整體雙向綁定功能的實現(xiàn),當然目前市場上的mvvm庫功能絕不止于此,本文只是略舉個人認為的核心代碼。
如果思路和實現(xiàn)上的問題,也請各位斧正,謝謝閱讀!
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/86525.html
摘要:如函數(shù)通過名,找到對應的數(shù)組,并觸發(fā)所有數(shù)組內回調函數(shù)。核心代碼如下效果圖源碼前端實現(xiàn)小節(jié)整篇文章基本是圍繞著如下點,的觀察者模式的實現(xiàn)展開,期間的銷毀則取消與之有關聯(lián)對象的關系,如銷毀時,注銷掉與之關聯(lián)的的回調函數(shù)。 web前端mvc庫實現(xiàn) 前言 隨著前端應用日趨復雜,如今如angular,vue的mvvm框架,基于virtual dom的react等前端庫基本成為了各個公司的首選。...
摘要:為的內置一個方法,用法和原生的事件機制一毛一樣。 前言 上兩篇Mvvm教程的熱度超出我的預期,很多碼友留言表揚同時希望我繼續(xù)出下一篇教程,當時我也半開玩笑說只要點贊超10就兌現(xiàn)承諾,沒想到還真破了10,所以就有了今天的文章。 準備工作 熟讀 【教學向】150行代碼教你實現(xiàn)一個低配版的MVVM庫(1)- 原理篇【教學向】150行代碼教你實現(xiàn)一個低配版的MVVM庫(2)- 代碼篇 本篇是在...
摘要:也放出地址,上面有完整工程以及在線演示地址相關閱讀教學向行代碼教你實現(xiàn)一個低配版的庫原理篇教學向行代碼教你實現(xiàn)一個低配版的庫代碼篇教學向再加行代碼教你實現(xiàn)一個低配版的庫設計篇教學向再加行代碼教你實現(xiàn)一個低配版的庫原理篇 書接上一篇: 150行代碼教你實現(xiàn)一個低配版的MVVM庫(1)- 原理篇 寫在前面 為了便于分模塊,和閱讀,我使用了Typescript來進行coding,總行數(shù)是正好...
閱讀 2510·2021-11-25 09:43
閱讀 2622·2021-11-16 11:50
閱讀 3305·2021-10-09 09:44
閱讀 3227·2021-09-26 09:55
閱讀 2853·2019-08-30 13:50
閱讀 1036·2019-08-29 13:24
閱讀 2100·2019-08-26 11:44
閱讀 2810·2019-08-26 11:37