ele表格操作区根据数据_Vue数据绑定
這是一篇簡單的學(xué)習(xí)筆記。在學(xué)習(xí)一段時間Vue后,嘗試實現(xiàn)一下Vue的數(shù)據(jù)綁定。
相關(guān)源碼:https://github.com/buchuitoudegou/Data-Binding-demo
Vue的數(shù)據(jù)綁定機(jī)制利用了觀察者設(shè)計模式,利用偵聽器動態(tài)更新DOM元素中的值,以下是Vue在編譯時綁定數(shù)據(jù)的過程。
綁定setter和getter
index.mjs是編譯的入口。從SelfVue的構(gòu)造函數(shù)我們可以看到,對于options中的data,我們會先對它進(jìn)行一個observe的操作,即綁定getter和setter,這也是數(shù)據(jù)響應(yīng)模式構(gòu)造的開始。
// index.mjs export function SelfVue(options) {this.data = options.data;observe(this.data);Object.keys(this.data).forEach((key) => {Object.defineProperty(this, key, {enumerable: true,configurable: true,get: () => {return this.data[key];},set: (newVal) => {this.data[key] = newVal;// console.log('set index');}});});new Compile(options.el, this);return this; } // Observable.mjs export function observe(data) {if (!data || !(typeof(data) === 'object')) {return;}Object.keys(data).forEach((key) => {defineReactive(data, key, data[key]);}); }function defineReactive(data, key, val) {observe(val);const dep = new Dep;Object.defineProperty(data, key, {enumerable: true,configurable: true,get: () => {if (Dep.target) {dep.addSub(Dep.target);}return val;},set: (newVal) => {// console.log(newVal);val = newVal;dep.notify();}}); }defineReactive函數(shù)遞歸給data對象的每個屬性(如果該屬性也是對象,則遞歸執(zhí)行)綁定getter和setter。
getter是這樣定義的:如果這個屬性被訂閱了,則將它的偵聽器加入到訂閱數(shù)組中,最后返回這個屬性的值。
// Dep.mjs export class Dep {constructor() {this.subs = [];}static target = null;addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());} }這里提到了兩個概念偵聽器(Watcher)和訂閱(Dep)數(shù)組。偵聽器接下來會提到,先簡單介紹一下訂閱的概念。訂閱器(Dep)是一個實例,每個data中的對象(Object)都會有一個Dep實例(data自己也會有一個),它里面定義了訂閱數(shù)組,存儲了所有訂閱某個屬性的偵聽器。訂閱器有一個notify方法,用于通知訂閱數(shù)組里面所有的偵聽器,某個值發(fā)生了改變(并不會判斷該偵聽器是否偵聽了這個值)。另外,訂閱器類(Dep)有一個靜態(tài)屬性target,用于存儲當(dāng)前正在進(jìn)行訂閱操作的屬性(相當(dāng)于訂閱器的辦事窗口,而且只有一個窗口)。
介紹完訂閱器的概念之后,繼續(xù)之前的內(nèi)容,setter的定義應(yīng)該就可以猜到了:setter在調(diào)用時,除了更新這個屬性的值,還會調(diào)用訂閱器的notify。
上面的步驟是Vue的root實例在定義的時候做的第一步工作。由于這個時候還不知道哪些屬性會綁定到DOM上,因此,所有訂閱器的訂閱數(shù)組都是空的。
編譯DOM節(jié)點
// Compile.mjs export class Compile {constructor(el, vm) {this.vm = vm;this.el = document.querySelector(el);this.fragment = null;this.init();}init() {if (this.el) {this.fragment = document.createDocumentFragment();let child = this.el.firstChild;while (child) {this.fragment.appendChild(child);child = this.el.firstChild;}[].slice.call(this.fragment.childNodes).forEach(node => {this.compileElement(node);});this.el.appendChild(this.fragment);}}compileElement(ele) {const reg = /{{(.*)}}/;if (ele.nodeType === 3) {if (reg.test(ele.nodeValue)) {let temp = RegExp.$1;temp = temp.trim();ele.nodeValue = getValue(this.vm, temp.split('.'));new Watcher(this.vm, temp.split('.'), (value) => {ele.nodeValue = value;});}} else if (ele.nodeType === 1) {const attr = ele.getAttribute('vmodel');if (attr) {ele.value = getValue(this.vm, attr.split('.'));new Watcher(this.vm, attr.split('.'), (value) => {ele.value = value;});ele.addEventListener('input', (e) => {this.vm[attr] = e.target.value;});}}} }這個步驟里,我們會知道哪些節(jié)點(HTMLElement或者文本節(jié)點)會和Vue的data有互動。給這些有互動的節(jié)點設(shè)置偵聽器(Watcher)。接下來介紹一下Watcher。
Watcher設(shè)置
// Watcher.mjs export function getValue(data, exp) {if (exp.length === 0) {return null;}let temp = data;exp.forEach((key) => {temp = temp[key];});return temp; }export class Watcher {constructor(vm, exp, cb) {this.vm = vm;this.exp = exp;this.cb = cb;this.value = this.get();}update() {const value = getValue(this.vm.data, this.exp);const oldVal = this.value;if (value !== oldVal) {this.value = value;this.cb.call(this.vm, value, oldVal);}}get() {Dep.target = this;const value = getValue(this.vm.data, this.exp);Dep.target = null;return value;} }偵聽器,顧名思義用于偵聽某個屬性的值是否發(fā)生了變化。我們在給某個屬性構(gòu)造偵聽器的時候需要傳入一個回調(diào)函數(shù),這個回調(diào)函數(shù)用于更新對應(yīng)DOM節(jié)點的值。至于哪個節(jié)點對應(yīng)哪個屬性值我們在編譯的時候就可以得知。
Watcher在構(gòu)造的時候,會強(qiáng)行調(diào)用這個屬性值的getter。這是為什么呢?之前我們也提到過,getter中定義了,若某個值被訂閱,則它的偵聽器會加入到訂閱數(shù)組中。因此,給某個值構(gòu)造Watcher時,先讓Dep的訂閱窗口(Dep.target)指向這個Watcher,說明這個值需要被訂閱,然后再調(diào)用這個值的getter,這樣子Dep的訂閱數(shù)組中就會把這個Watcher放進(jìn)去。最后將訂閱窗口(Dep.target)指向null,表示訂閱完成,可以訂閱下一個屬性。
每個Watcher都會有一個update方法,用于調(diào)用之前提到過的回調(diào)函數(shù)。當(dāng)Dep調(diào)用notify方法的時候,就會觸發(fā)每個在訂閱數(shù)組中的Watcher的update方法。update方法中確認(rèn)自己的偵聽的值是否發(fā)生了變化,若發(fā)生了變化則調(diào)用構(gòu)造的時候傳入的回調(diào)函數(shù),更新DOM元素中的值。
當(dāng)一個值發(fā)生變化時
當(dāng)data中一個值發(fā)生了變化時,setter會調(diào)用對應(yīng)Dep實例的notify函數(shù),notify函數(shù)調(diào)用實例中存儲的訂閱數(shù)組中所有偵聽器的update方法。update方法中,偵聽器會判斷自己偵聽的值是否發(fā)生了變化。若發(fā)生了變化,則調(diào)用回調(diào)函數(shù),更新DOM節(jié)點中的值。
參考
雙向綁定
vue 的雙向綁定原理及實現(xiàn) - 前端 - 掘金
總結(jié)
以上是生活随笔為你收集整理的ele表格操作区根据数据_Vue数据绑定的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: g++ vscode 环境选择_Visu
- 下一篇: gc日志一般关注什么_Java架构师必懂