您好,登錄后才能下訂單哦!
如何在vue2.0中實現雙向數據綁定?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
//發布訂閱模式 function Dep() { this.subs = []//收集依賴(也就是手機watcher實例), } Dep.prototype.addSub = function (sub) { //添加訂閱者 this.subs.push(sub); //實際上添加的是watcher這個實例 } Dep.prototype.notify = function (sub) { //發布,這個方法的作用是遍歷數組,讓每個訂閱者的update方法去執行 this.subs.forEach((sub) => sub.update()) } function Watcher(fn) { this.fn = fn; } Watcher.prototype.update = function () { //添加一個update屬性讓每一個實例都可以繼承這個方法 this.fn(); } let watcher = new Watcher(function () { alert(1) });//訂閱 let dep = new Dep(); dep.addSub(watcher);//添加依賴,添加訂閱者 dep.notify();//發布,讓每個訂閱者的update方法執行
只是針對雙向數據綁定做說明
<template> <div id="app"> <div>obj.text的值:{{obj.text}}</div> <p>word的值:{{word}}</p> <input type="text" v-model="word"> </div> </template> <script> new Vue({ el: "#app", data: { obj: { text: "向上", }, word: "學習" }, methods:{ // ... } }) </script>
Vue構造函數都干什么了?
function Vue(options = {}) { this.$options = options;//接收參數 var data = this._data = this.$options.data; observer(data);//對data中的數據進型循環遞歸綁定 for (let key in data) { let val = data[key]; observer(val); Object.defineProperty(this, key, { enumerable: true, get() { return this._data[key]; }, set(newVal) { this._data[key] = newVal; } }) } new Compile(options.el, this) };
在new Vue({…})構造函數時,首先獲取參數options,然后把參數中的data數據賦值給當前實例的_data屬性上(this._data = this.$options.data),重點來了,那下面的遍歷是為什么呢?首先我們在操作數據的時候是this.word獲取,而不是this._data.word,所以是做了一個映射,在獲取數據的時候this.word,其實是獲取的this._data.word的值,大家可以在自己項目中輸出this查看一下
1.接下來看看observer方法干了什么
function observer(data) { if (typeof data !== "object") return; return new Observer(data);//返回一個實例 } function Observer(data) { let dep = new Dep();//創建一個dep實例 for (let key in data) {//對數據進行循環遞歸綁定 let val = data[key]; observer(val); Object.defineProperty(data, key, { enumerable: true, get() { Dep.target && dep.depend(Dep.target);//Dep.target就是Watcher的一個實例 return val; }, set(newVal) { if (newVal === val) { return; } val = newVal; observer(newVal); dep.notify() //讓所有方法執行 } }) } }
Observer構造函數,首先let dep=new Dep(),作為之后的觸發數據劫持的get方法和set方法時,去收集依賴和發布時調用,主要的操作就是通過Object.defineProperty對data數據進行循環遞歸綁定,使用getter/setter修改其默認讀寫,用于收集依賴和發布更新。
2.再來看看Compile具體干了那些事情
function Compile(el, vm) { vm.$el = document.querySelector(el); let fragment = document.createDocumentFragment(); //創建文檔碎片,是object類型 while (child = vm.$el.firstChild) { fragment.appendChild(child); };//用while循環把所有節點都添加到文檔碎片中,之后都是對文檔碎片的操作,最后再把文檔碎片添加到頁面中,這里有一個很重要的特性是,如果使用appendChid方法將原dom樹中的節點添加到fragment中時,會刪除原來的節點。 replace(fragment); function replace(fragment) { Array.from(fragment.childNodes).forEach((node) => {//循環所有的節點 let text = node.textContent; let reg = /\{\{(.*)\}\}/; if (node.nodeType === 3 && reg.test(text)) {//判斷當前節點是不是文本節點且符不符合{{obj.text}}的輸出方式,如果滿足條件說明它是雙向的數據綁定,要添加訂閱者(watcher) console.log(RegExp.$1); //obj.text let arr = RegExp.$1.split("."); //轉換成數組的方式[obj,text],方便取值 let val = vm; arr.forEach((key) => { //實現取值this.obj.text val = val[key]; }); new Watcher(vm, RegExp.$1, function (newVal) { node.textContent = text.replace(/\{\{(.*)\}\}/, newVal) }); node.textContent = text.replace(/\{\{(.*)\}\}/, val); //對節點內容進行初始化的賦值 } if (node.nodeType === 1) { //說明是元素節點 let nodeAttrs = node.attributes; Array.from(nodeAttrs).forEach((item) => { if (item.name.indexOf("v-") >= 0) {//判斷是不是v-model這種指令 node.value = vm[item.value]//對節點賦值操作 } //添加訂閱者 new Watcher(vm, item.value, function (newVal) { node.value = vm[item.value] }); node.addEventListener("input", function (e) { let newVal = e.target.value; vm[item.value] = newVal; }) }) } if (node.childNodes) { //這個節點里還有子元素,再遞歸 replace(node); } }) } //這是頁面中的文檔已經沒有了,所以還要把文檔碎片放到頁面中 vm.$el.appendChild(fragment); }
Compile(編譯方法)
首先解釋一下DocuemntFragment(文檔碎片)它是一個dom節點收容器,當你創造了多個節點,當每個節點都插入到文檔當中都會引發一次回流,也就是說瀏覽器要回流多次,十分耗性能,而使用文檔碎片就是把多個節點都先放入到一個容器中,最后再把整個容器直接插入就可以了,瀏覽器只回流了1次。
Compile方法首先遍歷文檔碎片的所有節點,1.判斷是否是文本節點且符不符合{{obj.text}}的雙大括號的輸出方式,如果滿足條件說明它是雙向的數據綁定,要添加訂閱者(watcher),new Watcher(vm,動態綁定的變量,回調函數fn) 2.判斷是否是元素節點且屬性中是否含有v-model這種指令,如果滿足條件說明它是雙向的數據綁定,要添加訂閱者(watcher),new Watcher(vm,動態綁定的變量,回調函數fn) ,直至遍歷完成。
最后別忘了把文檔碎片放到頁面中
var uid=0; //發布訂閱 function Dep() { this.id=uid++; this.subs = []; } Dep.prototype.addSub = function (sub) { //訂閱 this.subs.push(sub); //實際上添加的是watcher這個實例 } Dep.prototype.depend = function () { // 訂閱管理器 if(Dep.target){//只有Dep.target存在時采取添加 Dep.target.addDep(this); } } Dep.prototype.notify = function (sub) { //發布,遍歷數組讓每個訂閱者的update方法去執行 this.subs.forEach((sub) => sub.update()) }
Dep構造函數內部有一個id和一個subs,id=uid++ ,id用于作為dep對象的唯一標識,subs就是保存watcher的數組。depend方法就是一個訂閱的管理器,會調用當前watcher的addDep方法添加訂閱者,當觸發數據劫持(Object.defineProperty)的get方法時會調用Dep.target && dep.depend(Dep.target)添加訂閱者,當數據改變時觸發數據劫持(Object.defineProperty)的set方法時會調用dep.notify方法更新操作。
function Watcher(vm, exp, fn) { this.fn = fn; this.vm = vm; this.exp = exp // this.newDeps = []; this.depIds = new Set(); this.newDepIds = new Set(); Dep.target = this; //this是指向當前(Watcher)的一個實例 let val = vm; let arr = exp.split("."); arr.forEach((k) => { //取值this.obj.text val = val[k] //取值this.obj.text,就會觸發數據劫持的get方法,把當前的訂閱者(watcher實例)添加到依賴中 }); Dep.target = null; } Watcher.prototype.addDep = function (dep) { var id=dep.id; if(!this.newDepIds.has(id)){ this.newDepIds.add(id); this.newDeps.push(dep); if(!this.depIds.has(id)){ dep.addSub(this); } } } Watcher.prototype.update = function () { //這就是每個綁定的方法都添加一個update屬性 let val = this.vm; let arr = this.exp.split("."); arr.forEach((k) => { val = val[k] //取值this.obj.text,傳給fn更新操作 }); this.fn(val); //傳一個新值 }
Watcher構造函數干了什么
1 接收參數,定義了幾個私有屬性( this.newDep ,this.depIds
,this.newDepIds)
2. Dep.target = this,通過參數進行data取值操作,這就會觸發Object.defineProperty的get方法,它會通過訂閱者管理器(dep.depend())添加訂閱者,添加完之后再將Dep.target=null置為空;
3.原型上的addDep是通過id這個唯一標識,和幾個私有屬性的判斷防止訂閱者被多次重復添加
4.update方法就是當數據更新時,dep.notify()執行,觸發訂閱者的update這個方法, 執行發布更新操作。
vue2.0中雙向數據綁定,簡單來說就是Observer、Watcher、Dep三大部分;
1.首先用Object.defineProperty()循環遞歸實現數據劫持,為每個屬性分配一個訂閱者集合的管理數組dep;
2.在編譯的時候,創建文檔碎片,把所有節點添加到文檔碎片中,遍歷文檔碎片的所有結點,如果是{{}},v-model這種,new Watcher()實例并向dep的subs數組中添加該實例
3.最后修改值就會觸發Object.defineProperty()的set方法,在set方法中會執行dep.notify(),然后循環調用所有訂閱者的update方法更新視圖。
關于如何在vue2.0中實現雙向數據綁定問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。