vue單向數(shù)據(jù)綁定和雙向數(shù)據(jù)綁定方式
一、總結(jié)
vue中有2種數(shù)據(jù)綁定的方式:
- 單向數(shù)據(jù)綁定(v-bind):數(shù)據(jù)只能從data流向頁面;
- 雙向數(shù)據(jù)綁定(v-model):數(shù)據(jù)不僅能從data流向頁面,還可以從頁面流向data;
備注:
- 雙向數(shù)據(jù)綁定一般都應(yīng)用在表單類(輸入類)元素上 (如:input、select等);
- v-model:value可以簡(jiǎn)寫為v-model,因?yàn)関-model默認(rèn)收集的就是value的值;
二、分析
- 單向數(shù)據(jù)綁定:就是把
Model
綁定到View
,當(dāng)我們用JavaScript
代碼更新Model
時(shí),View
就會(huì)自動(dòng)更新雙向綁定就很容易聯(lián)想到了; - 雙向數(shù)據(jù)綁定:在單向綁定的基礎(chǔ)上,用戶更新了
View
,Model
的數(shù)據(jù)也自動(dòng)被更新了
三、 雙向綁定的原理
Vue
是數(shù)據(jù)雙向綁定的框架,雙向綁定由三個(gè)重要部分構(gòu)成:
- 數(shù)據(jù)層(Model):應(yīng)用的數(shù)據(jù)及業(yè)務(wù)邏輯
- 視圖層(View):應(yīng)用的展示效果,各類UI組件
- 業(yè)務(wù)邏輯層(ViewModel):框架封裝的核心,它負(fù)責(zé)將數(shù)據(jù)與視圖關(guān)聯(lián)起來
理解ViewModel,它的主要職責(zé)就是:
- 數(shù)據(jù)變化后更新視圖
- 視圖變化后更新數(shù)據(jù)
它還有兩個(gè)主要部分組成
- 監(jiān)聽器(Observer):對(duì)所有數(shù)據(jù)的屬性進(jìn)行監(jiān)聽
- 解析器(Compiler):對(duì)每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描跟解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
四、實(shí)現(xiàn)雙向綁定
- new vue()首先執(zhí)行初始化,對(duì)data執(zhí)行響應(yīng)式處理,這個(gè)過程發(fā)生在observe中;
- 同時(shí)對(duì)模板執(zhí)行編譯,找到其中動(dòng)態(tài)綁定的數(shù)據(jù),從data中獲取并初始化視圖,這個(gè)過程發(fā)生在Compile中;
- 同時(shí)定義一個(gè)更新函數(shù)和Watcher,將來對(duì)應(yīng)數(shù)據(jù)變化時(shí)Watcher會(huì)調(diào)用更新函數(shù);
- 由于data中的某個(gè)key在一個(gè)視圖中可能出現(xiàn)多次,所以每個(gè)key都需要一個(gè)管家Dep來管理多個(gè)Watcher
- 將來data中數(shù)據(jù)一旦發(fā)生變化,會(huì)首先找到對(duì)應(yīng)的Dep,通知所有Watcher執(zhí)行更新函數(shù);
五、實(shí)現(xiàn)
先來一個(gè)構(gòu)造函數(shù):執(zhí)行初始化,對(duì)data
執(zhí)行響應(yīng)化處理
class Vue { constructor(options) { this.$options = options; this.$data = options.data; // 對(duì)data選項(xiàng)做響應(yīng)式處理 observe(this.$data); // 代理data到vm上 proxy(this); // 執(zhí)行編譯 new Compile(options.el, this); } }
對(duì)data
選項(xiàng)執(zhí)行響應(yīng)化具體操作
function observe(obj) { if (typeof obj !== "object" || obj == null) { return; } new Observer(obj); } class Observer { constructor(value) { this.value = value; this.walk(value); } walk(obj) { Object.keys(obj).forEach((key) => { defineReactive(obj, key, obj[key]); }); } }
編譯Compile
對(duì)每個(gè)元素節(jié)點(diǎn)的指令進(jìn)行掃描跟解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
class Compile { constructor(el, vm) { this.$vm = vm; this.$el = document.querySelector(el); // 獲取dom if (this.$el) { this.compile(this.$el); } } compile(el) { const childNodes = el.childNodes; Array.from(childNodes).forEach((node) => { // 遍歷子元素 if (this.isElement(node)) { // 判斷是否為節(jié)點(diǎn) console.log("編譯元素" + node.nodeName); } else if (this.isInterpolation(node)) { console.log("編譯插值?本" + node.textContent); // 判斷是否為插值文本 {{}} } if (node.childNodes && node.childNodes.length > 0) { // 判斷是否有子元素 this.compile(node); // 對(duì)子元素進(jìn)行遞歸遍歷 } }); } isElement(node) { return node.nodeType == 1; } isInterpolation(node) { return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent); } }
收集依賴
視圖中會(huì)用到data中某key,這稱為依賴。同一個(gè)key可能出現(xiàn)多次,每次都需要收集出來用一個(gè)Watcher來維護(hù)它們,此過程稱為依賴收集多個(gè)Watcher需要一個(gè)Dep來管理,需要更新時(shí)由Dep統(tǒng)一通知;
實(shí)現(xiàn)思路
- defineReactive是為每一個(gè)key創(chuàng)建一個(gè)Dep實(shí)例;
- 初始化視圖時(shí)讀取某個(gè)key,例如name1,創(chuàng)建一個(gè)Wathcher1;
- 由于觸發(fā)name1的getter方法,便將Wathcher1添加到name1的對(duì)應(yīng)的Dep中;
- 當(dāng)name1更新,setter觸發(fā)時(shí),便可通過對(duì)應(yīng)Dep通知其管理所有Watncer更新;
// 負(fù)責(zé)更新視圖 class Watcher { constructor(vm, key, updater) { this.vm = vm this.key = key this.updaterFn = updater // 創(chuàng)建實(shí)例時(shí),把當(dāng)前實(shí)例指定到Dep.target靜態(tài)屬性上 Dep.target = this // 讀一下key,觸發(fā)get vm[key] // 置空 Dep.target = null } // 未來執(zhí)行dom更新函數(shù),由dep調(diào)用的 update() { this.updaterFn.call(this.vm, this.vm[this.key]) } }
聲明Dep
class Dep { constructor() { this.deps = []; // 依賴管理 } addDep(dep) { this.deps.push(dep); } notify() { this.deps.forEach((dep) => dep.update()); } }
創(chuàng)建watcher
時(shí)觸發(fā)getter
class Watcher { constructor(vm, key, updateFn) { Dep.target = this; this.vm[this.key]; Dep.target = null; } }
依賴收集,創(chuàng)建Dep
實(shí)例
function defineReactive(obj, key, val) { this.observe(val); const dep = new Dep(); Object.defineProperty(obj, key, { get() { Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher實(shí)例 return val; }, set(newVal) { if (newVal === val) return; dep.notify(); // 通知dep執(zhí)行更新方法 }, }); }
最后
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
el-input限制輸入正整數(shù)的兩種實(shí)現(xiàn)方式
el-input框是Element UI庫中的一個(gè)輸入框組件,用于接收用戶的輸入,這篇文章主要介紹了el-input限制輸入正整數(shù),需要的朋友可以參考下2024-02-02vue3編譯報(bào)錯(cuò)ESLint:defineProps is not defined&nbs
這篇文章主要介紹了vue3編譯報(bào)錯(cuò)ESLint:defineProps is not defined no-undef的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03vue仿網(wǎng)易云音樂播放器界面的簡(jiǎn)單實(shí)現(xiàn)過程
興趣乃學(xué)習(xí)的動(dòng)力,想自己動(dòng)手寫個(gè)音樂播放器,查了網(wǎng)上一些博客寫了一個(gè),這篇文章主要給大家介紹了關(guān)于vue仿網(wǎng)易云音樂播放器界面的簡(jiǎn)單實(shí)現(xiàn)過程,需要的朋友可以參考下2021-11-11Vue裝飾器中的vue-property-decorator?和?vux-class使用詳解
這篇文章主要介紹了Vue裝飾器中的vue-property-decorator?和?vux-class使用詳解,通過示例代碼給大家介紹的非常詳細(xì),對(duì)vue-property-decorator?和?vux-class的使用感興趣的朋友一起看看吧2022-08-08Flutter部件內(nèi)部狀態(tài)管理小結(jié)之實(shí)現(xiàn)Vue的v-model功能
本文是 Flutter 部件內(nèi)部狀態(tài)管理的小結(jié),從部件的基礎(chǔ)開始,到部件的狀態(tài)管理,并且在過程中實(shí)現(xiàn)一個(gè)類似 Vue 的 v-model 的功能,感興趣的朋友跟隨小編一起看看吧2019-06-06vue3.0 CLI - 2.1 - component 組件入門教程
這篇文章主要介紹了vue3.0 CLI - 2.1 - component 組件入門教程,本文主要的關(guān)注點(diǎn)就是組件,本文通過實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09