提到了vue實(shí)現(xiàn)的基本實(shí)現(xiàn)原理:Object.defineProperty() -數(shù)據(jù)劫持 和 發(fā)布訂閱者模式(觀察者),下面講的就是數(shù)據(jù)劫持在代碼中的具體實(shí)現(xiàn)。
樂平網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)2013年開創(chuàng)至今到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
1.先看如何調(diào)用
new一個(gè)對(duì)象,傳入我們的參數(shù),這個(gè)Myvue ,做了啥?
上面看到了在實(shí)例化一個(gè)Myvue 對(duì)象的時(shí)候,會(huì)執(zhí)行init方法, init 方法做了兩個(gè)事,調(diào)用了observer 方法,和 實(shí)例化調(diào)用了 compile 方法。 到這里我們就明白了,實(shí)例化一個(gè)Myvue后,我們要做的就是監(jiān)聽數(shù)據(jù)變化和編譯模板 。
上面Object.key() 方法,實(shí)例化時(shí)傳入的data里面對(duì)應(yīng)的變量緩存到 Myvue 對(duì)象的 $prop上,這樣方便在后續(xù)處理數(shù)據(jù)。怎么個(gè)方便法呢!...
2.observer 的實(shí)現(xiàn)
observer ,模式里面的角色定位 他是一個(gè)發(fā)布者,也可以理解為是一個(gè)觀察者
function observer (data) { if(!data || typeof data !== 'object') { return; } Object.keys(data).forEach(key => { // 對(duì)每個(gè)屬性監(jiān)聽處理 defineReactive(data, key, data[key]); }) }
defineReactive
function defineReactive (data,key,value) { // 每次訪問/修改屬性的時(shí)候 實(shí)例化一個(gè)調(diào)度中心Dep var dep = new Dep(); Object.defineProperty(data,key,{ get: function() { // 添加到watcher 的Dep 調(diào)度中心 if (Dep.target) { // Dep.target 是個(gè)什么鬼? 轉(zhuǎn)到watcher.js 它是某個(gè)訂閱者 watcher dep.addSub(Dep.target); //這個(gè)代碼段的意思: 如果有訂閱者(訪問/修改屬性的時(shí)候) 就將這個(gè)訂閱者統(tǒng)一放進(jìn) Dep 調(diào)度中心中 } // console.log(`${key}屬性被訪問了`) return value }, set: function (newValue) { if (value != newValue) { // console.log(`${key}屬性被重置了`) value = newValue dep.notify(); //我這里有做改動(dòng)了,通知調(diào)度中心的notify方法 } } }) // 遞歸調(diào)用,observe 這個(gè)value observer(value) }
Dep: 這里是所有訂閱者的一個(gè)調(diào)度中心,它不是直接監(jiān)聽 發(fā)布者的信息,發(fā)布者將要發(fā)布的信息 發(fā)布到 一個(gè)中介、調(diào)度中心(Dep),由這個(gè)Dep 來調(diào)度信息給哪個(gè)訂閱者(Watcher)
// 統(tǒng)一管理watcher訂閱者的Dep (調(diào)度中心) Dispatch center function Dep () { // 所有的watcher 放進(jìn)這里統(tǒng)一管理 this.subs = [] } Dep.target = null; // 通知視圖更新dom的 notify的方法 Dep.prototype.notify = function () { // this.subs 是上面訂閱器watcher 的集合 this.subs.forEach(sub => { // sub 是某個(gè)Watcher 具體調(diào)用某個(gè)Watcher的update 方法 sub.update() }) } // 添加訂閱者的方法 Dep.prototype.addSub = function (sub) { this.subs.push(sub) }
3.訂閱器Watcher
// 具體的訂閱器Watcher // 傳入一個(gè)vue 的實(shí)例, 監(jiān)聽的屬性, 以及處理的回調(diào)函數(shù) function Watcher (vm,prop,callback) { this.vm = vm; this.$prop = prop; this.value = this.get(); this.callback = callback; // 具體watcher所具有的方法,不同的watcher 不同的回調(diào)函數(shù),處理不同的業(yè)務(wù)邏輯 } // 添加watcher 獲得屬性的get 方法,當(dāng)有屬性訪問/設(shè)置 的時(shí)候,就產(chǎn)生訂閱者 將這個(gè)訂閱者放進(jìn)調(diào)度中心 Watcher.prototype.get = function () { Dep.target = this; // 獲得屬性值 const value = this.vm.$data[this.$prop]; return value } // 添加watcher的更新視圖的方法 Watcher.prototype.update = function () { // 當(dāng)屬性值有變化的時(shí)候,執(zhí)行方法,更新試圖 const value = this.vm.$data[this.$prop]; const oldValue = this.value; // update 執(zhí)行的時(shí)候,先獲取 vm 中data實(shí)時(shí)更新的屬性值,this.value 是vm data中之前的老值 if (oldValue != value) { // console.log('人家通知了,我要改變了') // 把剛剛獲取的更新值賦給之前vm data 中的值 this.value = value // 執(zhí)行回調(diào)函數(shù) 具體怎么處理這個(gè),看實(shí)際調(diào)用時(shí)候 callback 的處理 this.callback(this.value) } }
4.模板編譯
(為了直接看到頁面數(shù)據(jù)變化的效果,在模板編譯的核心數(shù)據(jù)處理上做了dom 操作,下一篇將講模板編譯的一些細(xì)節(jié)處理)
// dom模板編譯 vm 就是我們最上面的Myvue 對(duì)象 function Compile (vm) { this.vm = vm; this.$el = vm.el; // this.data = vm.data; this.fragment = null; // 用作后面模板引擎 創(chuàng)建文檔片段 this.init() } Compile.prototype = { // init 方法簡(jiǎn)單處理,直接做dom 操作,后面會(huì)用詳細(xì)的模板引擎的學(xué)習(xí) init: function () { let value = this.vm.$data.name // 初始化獲取到的值 放進(jìn)dom節(jié)點(diǎn)中 document.querySelector('.form-control').value = value; document.querySelector('.template').textContent = value // 通知訂閱者更新dom new Watcher(this.vm,this.vm.$prop, (value) => { document.querySelector('.form-control').value = value; document.querySelector('.template').textContent = value }) document.querySelector('.form-control').addEventListener('input',(e) => { let targetValue = e.target.value if(value !== targetValue) { this.vm.$data.name = e.target.value // 將修改的值 更新到 vm的data中 document.querySelector('.form-control').value = targetValue; // 更新dom 節(jié)點(diǎn) document.querySelector('.template').textContent = targetValue } },false) } }
這樣就可以看到 在表單中,數(shù)據(jù)的雙向綁定了。
未完待續(xù),錯(cuò)誤之處,敬請(qǐng)指出,共同進(jìn)步!
下一篇 vue 雙向數(shù)據(jù)綁定的實(shí)現(xiàn)學(xué)習(xí)(三)- 模板編譯
附:演示代碼:
js:
function Myvue (options) { this.$options = options this.$el = document.querySelector(options.el); this.$data = options.data; Object.keys(this.$data).forEach(key => { this.$prop = key; }) this.init() } Myvue.prototype.init = function () { // 監(jiān)聽數(shù)據(jù)變化 observer(this.$data); // 獲得值 // let value = this.$data[this.$prop]; // 不經(jīng)過模板編譯直接 通知訂閱者更新dom // new Watcher(this,this.$prop,value => { // console.log(`watcher ${this.$prop}的改動(dòng),要有動(dòng)靜了`) // this.$el.textContent = value // }) //通知模板編譯來執(zhí)行頁面上模板變量替換 new Compile(this) } function observer (data) { if(!data || typeof data !== 'object') { return; } Object.keys(data).forEach(key => { // 對(duì)每個(gè)屬性監(jiān)聽處理 defineReactive(data, key, data[key]); }) } function defineReactive (data,key,value) { // 每次訪問/修改屬性的時(shí)候 實(shí)例化一個(gè)調(diào)度中心Dep var dep = new Dep(); Object.defineProperty(data,key,{ get: function() { // 添加到watcher 的Dep 調(diào)度中心 if (Dep.target) { // Dep.target 是個(gè)什么鬼? 轉(zhuǎn)到watcher.js 它是某個(gè)訂閱者 watcher dep.addSub(Dep.target); //這個(gè)代碼段的意思: 如果有訂閱者(訪問/修改屬性的時(shí)候) 就將這個(gè)訂閱者統(tǒng)一放進(jìn) Dep 調(diào)度中心中 } // console.log(`${key}屬性被訪問了`) return value }, set: function (newValue) { if (value != newValue) { // console.log(`${key}屬性被重置了`) value = newValue dep.notify(); //我這里有做改動(dòng)了,通知調(diào)度中心的notify方法 } } }) // 遞歸調(diào)用,observe 這個(gè)value observer(value) } // 統(tǒng)一管理watcher訂閱者的Dep (調(diào)度中心) Dispatch center function Dep () { // 所有的watcher 放進(jìn)這里統(tǒng)一管理 this.subs = [] } Dep.target = null; // 通知視圖更新dom的 notify的方法 Dep.prototype.notify = function () { // this.subs 是上面訂閱器watcher 的集合 this.subs.forEach(sub => { // sub 是某個(gè)Watcher 具體調(diào)用某個(gè)Watcher的update 方法 sub.update() }) } // 添加訂閱者的方法 Dep.prototype.addSub = function (sub) { this.subs.push(sub) } // 具體的訂閱器Watcher // 傳入一個(gè)vue 的示例, 監(jiān)聽的屬性, 以及處理的回調(diào)函數(shù) function Watcher (vm,prop,callback) { this.vm = vm; this.$prop = prop; this.value = this.get(); this.callback = callback; // 具體watcher所具有的方法,不同的watcher 不同的回調(diào)函數(shù),處理不同的業(yè)務(wù)邏輯 } // 添加watcher 獲得屬性的get 方法,當(dāng)有屬性訪問/設(shè)置 的時(shí)候,就產(chǎn)生訂閱者 將這個(gè)訂閱者放進(jìn)調(diào)度中心 Watcher.prototype.get = function () { Dep.target = this; // 獲得屬性值 const value = this.vm.$data[this.$prop]; return value } // 添加watcher的更新視圖的方法 Watcher.prototype.update = function () { // 當(dāng)屬性值有變化的時(shí)候,執(zhí)行方法,更新試圖 const value = this.vm.$data[this.$prop]; const oldValue = this.value; // update 執(zhí)行的時(shí)候,先獲取 vm 中data實(shí)時(shí)更新的屬性值,this.value 是vm data中之前的老值 if (oldValue != value) { // console.log('人家通知了,我要改變了') // 把剛剛獲取的更新值賦給之前vm data 中的值 this.value = value // 執(zhí)行回調(diào)函數(shù) 具體怎么處理這個(gè),看實(shí)際調(diào)用時(shí)候 callback 的處理 this.callback(this.value) } } // dom模板編譯 vm 就是我們最上面的Myvue 對(duì)象 function Compile (vm) { this.vm = vm; this.$el = vm.el; // this.data = vm.data; this.fragment = null; // 用作后面模板引擎 創(chuàng)建文檔片段 this.init() } Compile.prototype = { // init 方法簡(jiǎn)單處理,直接做dom 操作,后面會(huì)用詳細(xì)的模板引擎的學(xué)習(xí) init: function () { let value = this.vm.$data.name // 初始化獲取到的值 放進(jìn)dom節(jié)點(diǎn)中 document.querySelector('.form-control').value = value; document.querySelector('.template').textContent = value // 通知訂閱者更新dom new Watcher(this.vm,this.vm.$prop, (value) => { document.querySelector('.form-control').value = value; document.querySelector('.template').textContent = value }) document.querySelector('.form-control').addEventListener('input',(e) => { let targetValue = e.target.value if(value !== targetValue) { this.vm.$data.name = e.target.value // 將修改的值 更新到 vm的data中 document.querySelector('.form-control').value = targetValue; // 更新dom 節(jié)點(diǎn) document.querySelector('.template').textContent = targetValue } },false) } }
html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue雙向綁定原理及實(shí)現(xiàn)</title> <link rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh5u" crossorigin="anonymous"> <style> #app { margin: 20px auto; width: 400px; padding: 50px; text-align: center; border: 2px solid #ddd; } </style> </head> <body> <div id="app"> <input class="form-control" v-model="name" type="text"> <h2 class="template">{{name}}</h2> </div> <script src="./js/index1.js"></script> <script> const vm = new Myvue({ el: "#app", data: { name: "vue 雙向數(shù)據(jù)綁定test1" } }); </script> </body> </html>
總結(jié)
以上所述是小編給大家介紹的vue 雙向數(shù)據(jù)綁定的實(shí)現(xiàn)學(xué)習(xí)之監(jiān)聽器的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)創(chuàng)新互聯(lián)網(wǎng)站的支持!
網(wǎng)頁名稱:vue雙向數(shù)據(jù)綁定的實(shí)現(xiàn)學(xué)習(xí)之監(jiān)聽器的實(shí)現(xiàn)方法
文章路徑:http://www.rwnh.cn/article30/gpocpo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、關(guān)鍵詞優(yōu)化、小程序開發(fā)、商城網(wǎng)站、手機(jī)網(wǎng)站建設(shè)、App設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)