摘要:原理如圖,實現(xiàn)一個,需要幾個輔助工具,分別是。我的模式中的功能有兩個。對將中的數(shù)據(jù)綁定到上下文環(huán)境上,對數(shù)據(jù)進行劫持,當數(shù)據(jù)變化的時候通知。到此就全部完成了模式。
前言
網(wǎng)上講 vue 原理,mvvm 模式的實現(xiàn),數(shù)據(jù)雙向綁定的文章一搜一大堆,不管寫的誰好誰壞,都是寫的自己的理解,我也發(fā)一篇文章記錄自己的理解,如果對看官有幫助,那也是我莫大的榮幸,不過看完之后,你們以后如果再被面試官問到 vue 的原理的時候,千萬不要只用一句【通過 javascrit 的 Object.defineProperty 將 data 進行劫持,發(fā)生改變的時候改變對應節(jié)點的值】這么籠統(tǒng)的話來應付了。如果有不懂的,可以問我。話不多說,上效果圖:
效果以及代碼
{{a}}
怎么樣,是不是跟vue的寫法很像,跟著我的思路,你們也可以的。
原理talk is cheap, show you the picture
如圖,實現(xiàn)一個mvvm,需要幾個輔助工具,分別是 Observer, Compile, Dep, Watcher。每個工具各司其職,再由 MVVM 統(tǒng)一掉配從而實現(xiàn)數(shù)據(jù)的雙向綁定,下面我分別介紹下接下來出場的幾位菇?jīng)?/p>
Compile 能夠?qū)㈨撁嬷械捻撁娉跏蓟?,對指令進行解析,把 data 對應的值渲染上去的同時,new 一個 Watcher,并告訴它,當渲染的這個數(shù)據(jù)發(fā)生改變時告訴我,我好更新視圖。
Observer 能夠?qū)崿F(xiàn)將 data 中的數(shù)據(jù)通過Object.defineProperty進行劫持,當獲取 data 中的值的時候,觸發(fā)get里方法,把 Compile 新建的 Watcher 抓過來,關到 Dep(發(fā)布訂閱者模式)的小黑屋里狂...,當值修改的時候,觸發(fā) set 里的方法,通知小黑屋(Dep)里所有 Watcher 菇?jīng)鰝?,你們解放啦?/p>
Dep 就是傳說中的小黑屋了,其內(nèi)在原理是發(fā)布訂閱者模式,不了解發(fā)布訂閱者模式的話可以看我 這篇文章
Watcher 們從小黑屋里逃出來之后就趕緊跑到對應的 Compile 那,告訴他開始更新視圖吧,看,我是愛你的。
哈哈,通過我很(lao)幽(si)默(ji)的講解。你們是不是都想下車了?
嗯,知道大概是怎么回事之后,我分別講他們的功能。不過話說前面,mvvm 模式之前有千絲萬縷的聯(lián)系,必須要全部看完,才能真正理解 mvvm 的原理。
Observe我的 mvvm 模式中 Observe 的功能有兩個。1.對將data中的數(shù)據(jù)綁定到上下文環(huán)境上,2.對數(shù)據(jù)進行劫持,當數(shù)據(jù)變化的時候通知 Dep。下面用一個 demo 來看看,如何將數(shù)據(jù)綁定到環(huán)境中,并劫持數(shù)據(jù)
Document
可以看到將 data 數(shù)據(jù)綁定到 window 上,當數(shù)據(jù)變化時候,會打印 "值更新啦",那么 data 變化 是如何通知 Dep 的呢?首先我們要明白,observe 只執(zhí)行一遍,將數(shù)據(jù)綁定到 mvvm 實例上,Dep也只有一個,之前說把所有的 Watcher 抓過來,全放在這個 Dep 里,還是看代碼說話把。
function observe (obj, vm) { if (!obj || typeof obj !== "object") return; return new Observer(obj, vm) } class Observer { constructor(obj, vm) { // vm 代表上下文環(huán)境,也是指向 mvvm 的實例 (調(diào)用的時候會傳入) this.walk(obj, vm); // 實例化一個 Dep; this.dep = new Dep(); } walk (obj, vm) { var self = this; Object.keys(obj).forEach(key => { Object.defineProperty(vm, key, { configurable: true, enumerable: true, get () { // 當獲取 vm 的值的時候,如果 Dep 有 target 時執(zhí)行,目的是將 Watcher 抓過來,后面還會說明 if (Dep.target) { self.dep.depend(); } return obj[key]; }, set (newVal) { var val = obj.key; if (val === newVal) return; obj[key] = newVal; // 當 劫持的值發(fā)生變化時候觸發(fā),通知 Dep self.dep.notify(); } }) }) } }Dep
接下來講講 Dep 的實現(xiàn),Dep 功能很簡單,難點是如何將 watcher 聯(lián)系起來,先看代碼吧。
class Dep { constructor (props) { this.subs = []; this.uid = 0; } addSub (sub) { this.subs.push(sub); this.uid++; } notify () { this.subs.forEach(sub => { sub.update(); }) } depend (sub) { Dep.target.addDep(this, sub); } } Dep.target = null;
subs 是一個數(shù)組,用來存儲 Watcher 的,當數(shù)據(jù)更新時候(由Observer告知),會觸發(fā) Dep 的 notify 方法,調(diào)用 subs 里所有 Watcher 的 update 方法。
接下來是不是迫不及待的想知道 Dep 是如何將 Watcher 抓過來的吧(污污污),別著急我們先看看 Watcher 是如何誕生的。
我覺得 Compile 是 mvvm 中最勞苦功高的一個了,它的任務是頁面過來時候,初始化視圖,將頁面中的{{.*}}解析成對應的值,還有指令解析,如綁定值的 v-text、v-html 還有綁定的事件 v-on,還有創(chuàng)造 Watcher 去監(jiān)聽值的變化,當值變化的時候又要更新節(jié)點的視圖。
我們先看看 Compile 是如何初始化視圖的
Document {{a}}
額,感覺還好理解吧,這里只是講了 Compile 是如何將data中的值渲染到視圖上,買了個關子,沒有說如何創(chuàng)建 Watcher 的,思考一下,如果要創(chuàng)建 Watcher ,應該在哪個位置創(chuàng)建比較好呢?
答案是渲染值的同時,同時創(chuàng)造一個 Watcher 來監(jiān)聽,上代碼:
class Compile { constructor (el, vm) { this.$el = this.isElementNode(el) ? el : document.querySelector(el); this.$vm = vm; if (this.$el) { this.$fragment = this.nodeFragment(this.$el); this.compileElement(this.$fragment); this.$el.appendChild(this.$fragment); } } nodeFragment (el) { let fragment = document.createDocumentFragment(); let child; while (child = el.firstChild) { fragment.appendChild(child); } return fragment; } compileElement (el) { var childNodes = Array.from(el.childNodes); if (childNodes.length > 0) { childNodes.forEach(child => { var childArr = Array.from(child.childNodes); // 匹配{{}}里面的內(nèi)容 var reg = /{{((?:.)+?)}}/; if (childArr.length > 0) { this.compileElement(child) } if (this.isTextNode(child)) { var text = child.textContent.trim(); var matchTextArr = reg.exec(text); var matchText; if (matchTextArr && matchTextArr.length > 1) { matchText = matchTextArr[1]; this.compileText(child, matchText); } } else if (this.isElementNode(child)) { this.compileNode(child); } }) } } compileText(node, exp) { this.bind(node, this.$vm, exp, "text"); } compileNode (node) { var attrs = Array.from(node.attributes); attrs.forEach(attr => { if (this.isDirective(attr.name)) { var directiveName = attr.name.substr(2); if (directiveName.includes("on")) { node.removeAttribute(attr.name); var eventName = directiveName.split(":")[1]; this.addEvent(node, eventName, attr.value); } else if (directiveName.includes("model")) { // v-model this.bind(node, this.$vm, attr.value, "value"); node.addEventListener("input", (e) => { this.$vm[attr.value] = e.target.value; }) }else{ // v-text v-html node.removeAttribute(attr.name); this.bind(node, this.$vm, attr.value, directiveName); } } }) } addEvent(node, eventName, exp) { node.addEventListener(eventName, this.$vm.$options.methods[exp].bind(this.$vm)); } bind (node, vm, exp, dir) { if (dir === "text") { node.textContent = vm[exp]; } else if (dir === "html") { node.innerHTML = vm[exp]; } else if (dir === "value") { node.value = vm[exp]; } new Watcher(exp, vm, function () { if (dir === "text") { node.textContent = vm[exp]; } else if (dir === "html") { node.innerHTML = vm[exp]; } }) } hasChildNode (node) { return node.children && node.children.length > 0; } // 是否是指令 isDirective (attr) { if (typeof attr !== "string") return; return attr.includes("v-"); } // 元素節(jié)點 isElementNode (node) { return node.nodeType === 1; } // 文本節(jié)點 isTextNode (node) { return node.nodeType === 3; } }
這里比上面演示的demo多創(chuàng)建一個文檔碎片,可以加快解析速度,另外在 80 行創(chuàng)建了 Watcher,當數(shù)據(jù)變化時,執(zhí)行回調(diào)函數(shù),從而更新視圖。
Watcher期待已久的 Watcher 終于出來了,我們先看看它長什么樣:
class Watcher { constructor (exp, vm, cb) { this.$vm = vm; this.$exp = exp; this.depIds = {}; this.getter = this.parseGetter(exp); this.value = this.get(); this.cb = cb; } update () { let newVal = this.get(); let oldVal = this.value; if (oldVal === newVal) return; this.cb.call(this.vm, newVal); this.value = newVal; } get () { Dep.target = this; var value = this.getter.call(this.$vm, this.$vm); Dep.target = null; return value; } parseGetter (exp) { if (/[^w.$]/.test(exp)) return; return function (obj) { if (!obj) return; obj = obj[exp]; return obj; } } addDep (dep) { if (!this.depIds.hasOwnProperty(dep.id)) { this.depIds[dep.id] = dep; dep.subs.push(this); } } }
也不怎么樣嘛,只有30多行代碼,接下來睜大眼睛啦,看看它是怎么被 Dep 抓過來的。
當 Compile 創(chuàng)建 Watcher 出來的時候,也將 Dep.target 指向了 Watcher。同時獲取了該節(jié)點要渲染的值,觸發(fā)了 Observer 中的 get 方法,Dep.target 有值了,就執(zhí)行 self.dep.depend();
depend 方法里執(zhí)行 Dep.target.addDep(this); 而現(xiàn)在 Dep.target 指向 Watcher,所以執(zhí)行的是 Watcher 里的 addDep 方法 同時把 Dep 實例傳過去。
Watcher 里的 addDep 方法是將 Watcher 放在的 Dep實例的 subs 數(shù)組里。
當vm里的值放生變化時,觸發(fā) Observer 的 set 方法,觸發(fā)所有 subs 里的 Watcher 執(zhí)行 Watcher 里的 update 方法。
update 方法里有 Compile 的回調(diào),從而更新視圖。
好吧,真想大白了,原來 Watcher 是引誘 Dep 把自己裝進小黑屋的。哈哈~
源碼已放在我自己的git庫里,點擊這里獲取源碼
講了半天,正主該出來了,mvvm 是如何將上面四個小伙伴給自己打工的呢,其實很簡單,上代碼
class MVVM { constructor (options) { this.$options = options; var data = this._data = this.$options.data; observe(data, this); new Compile(options.el || document.body, this); } }
就是實例 MVVM 的時候,調(diào)用數(shù)據(jù)劫持,和 Compile 初始化視圖。到此就全部完成了mvvm模式。
參考合格前端系列第三彈-實現(xiàn)一個屬于我們自己的簡易MVVM庫
vue.js 權(quán)威指南
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/95489.html
摘要:原理如圖,實現(xiàn)一個,需要幾個輔助工具,分別是。我的模式中的功能有兩個。對將中的數(shù)據(jù)綁定到上下文環(huán)境上,對數(shù)據(jù)進行劫持,當數(shù)據(jù)變化的時候通知。到此就全部完成了模式。 前言 網(wǎng)上講 vue 原理,mvvm 模式的實現(xiàn),數(shù)據(jù)雙向綁定的文章一搜一大堆,不管寫的誰好誰壞,都是寫的自己的理解,我也發(fā)一篇文章記錄自己的理解,如果對看官有幫助,那也是我莫大的榮幸,不過看完之后,你們以后如果再被面試官問...
摘要:在模式中一般把層算在層中,只有在理想的雙向綁定模式下,才會完全的消失。層將通過特定的展示出來,并在控件上綁定視圖交互事件,一般由框架自動生成在瀏覽器中。三大框架的異同三大框架都是數(shù)據(jù)驅(qū)動型的框架及是雙向數(shù)據(jù)綁定是單向數(shù)據(jù)綁定。 MVVM相關概念 1) MVVM典型特點是有四個概念:Model、View、ViewModel、綁定器。MVVM可以是單向綁定也可以是雙向綁定甚至是不綁...
摘要:具體代碼執(zhí)行方式進入到的目錄下,命令行運行即可。確保為一個對象如果對象下有則不需要再次生成函數(shù)返回該對象的實例,這里判斷了如果該對象下已經(jīng)有實例,則直接返回,不再去生產(chǎn)實例。這就確保了一個對象下的實例僅被實例化一次。 看這篇之前,如果沒有看過之前的文章,可拉到文章末尾查看之前的文章。 回顧 在 step4 中,我們大致實現(xiàn)了一個 MVVM 的框架,由3個部分組成: defineRe...
閱讀 1531·2021-11-24 09:38
閱讀 3379·2021-11-18 10:02
閱讀 3268·2021-09-22 15:29
閱讀 2956·2021-09-22 15:15
閱讀 1057·2021-09-13 10:25
閱讀 1875·2021-08-17 10:13
閱讀 2006·2021-08-04 11:13
閱讀 1986·2019-08-30 15:54