摘要:上篇文章,我們講解了的屬性映射和方法的重定義,鏈接地址如下源碼解析一屬性映射和函數引用的重定義這篇文章給大家帶來的是的雙向綁定講解。這就是的雙向綁定。使用定時器定時檢查的值,發生變化就通知訂閱者。這個方法不好,定時器不能實時反應變化。
文章中的代碼時階段,可以下載源碼測試一下。
git項目地址:https://github.com/xubaodian/...
項目使用webpack構建,下載后先執行:
npm install
安裝依賴后使用指令:
npm run dev
可以運行項目。
上篇文章,我們講解了Vue的data屬性映射和方法的重定義,鏈接地址如下:
Vue源碼解析(一)data屬性映射和methods函數引用的重定義
這篇文章給大家帶來的是Vue的雙向綁定講解。
我們看一張圖:
可以看到,輸入框上方的內同和輸入框中的值是一致的。輸入框的之變化,上方的值跟著一起變化。
這就是Vue的雙向綁定。
我們先不著急了解Vue時如何實現這一功能的,如果我們自己要實現這樣的功能,如何實現呢?
我的思路是這樣:
可以分為幾個步驟,如下:
1、首先給輸入框添加input事件,監視輸入值,存放在變量value中。
2、監視value變量,確保value變化時,監視器可以發現。
3、若value發生變化,則重新渲染視圖。
上面三個步驟,1(addEventListener)和3(操作dom)都很好實現,對于2的實現,可能有一下兩個方案:
1、使用Object.defineProperty()重新定義對象set和get,在值發生變化時,通知訂閱者。
2、使用定時器定時檢查value的值,發生變化就通知訂閱者。(這個方法不好,定時器不能實時反應value變化)。
Vue源碼中采用了方案1,我們首先用方案1實現對對象值的監聽,代碼如下:
function defineReactive(obj, key, val, customSetter) { //獲取對象給定屬性的描述符 let property = Object.getOwnPropertyDescriptor(obj, key); //對象該屬性不可配置,直接返回 if (property && property.configurable === false) { return; } //獲取屬性get和set屬性,若此前該屬性已經進行監聽,則確保監聽屬性不會被覆蓋 let getter = property && property.get; let setter = property && property.set; if (arguments.length < 3) { val = obj[key]; } //監聽屬性 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val; console.log(`讀取了${key}屬性`); return value; }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val; //如果值沒有變化,則不做改動 if (newVal === value) { return; } //自定義響應函數 if (customSetter) { customSetter(newVal); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } console.log(`屬性${key}發生變化:${value} => ${newValue}`); } }) }
下面我們測試下,測試代碼如下:
let obj = { name: "xxx", age: 20 }; defineReactive(obj, "name"); let name = obj.name; obj.name = "1111";
控制臺輸出為:
讀取了name屬性 test.html:51 屬性name發生變化:xxx => 1111
可見,我們已經實現了對obj對象name屬性讀和寫的監聽。
實現了監聽,這沒問題,但是視圖怎么知道這些屬性發生了變化呢?可以使用發布訂閱模式實現。
發布訂閱模式什么是發布訂閱模式呢?
我畫了一個示意圖,如下:
發布訂閱模式有幾個部分構成:
1、訂閱中心,管理訂閱者列表,發布者發消息時,通知相應的訂閱者。
2、訂閱者,這個是訂閱消息的主體,就像關注微信公眾號一樣,有文章就會通知關注者。
3、發布者,類似微信公眾號的文章發布者。
訂閱中心的代碼如下:
export class Dep { constructor() { this.id = uid++; //訂閱列表 this.subs = []; } //添加訂閱 addSub(watcher) { this.subs.push(watcher); } //刪除訂閱者 remove(watcher) { let index = this.subs.findIndex(item => item.id === watcher.id); if (index > -1) { this.subs.splice(index, 1); } } depend () { if (Dep.target) { Dep.target.addDep(this); } } //通知訂閱者 notify() { this.subs.map(item => { item.update(); }); } } //訂閱中心 靜態變量,訂閱時使用 Dep.target = null; const targetStack = []; export function pushTarget (target) { targetStack.push(target); Dep.target = target; } export function popTarget () { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1]; }
訂閱中心已經實現,還有發布者和訂閱者,先看下發布者,這里誰是發布者呢?
沒錯,就是defineReactive函數,這個函數實現了對data屬性的監聽,它可以檢測到data屬性的修改,發生修改時,通知訂閱中心,所以defineReactive做一些修改,如下:
//屬性監聽 export function defineReactive(obj, key, val, customSetter) { //獲取對象給定屬性的描述符 let property = Object.getOwnPropertyDescriptor(obj, key); //對象該屬性不可配置,直接返回 if (property && property.configurable === false) { return; } //訂閱中心 const dep = new Dep(); //獲取屬性get和set屬性,若此前該屬性已經進行監聽,則確保監聽屬性不會被覆蓋 let getter = property && property.get; let setter = property && property.set; if (arguments.length < 3) { val = obj[key]; } //如果監聽的是一個對象,繼續深入監聽 let childOb = observe(val); //監聽屬性 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val; //這段代碼時添加訂閱時使用的 if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); } } return value; }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val; //如果值沒有變化,則不做改動 if (newVal === value) { return; } //自定義響應函數 if (customSetter) { customSetter(newVal); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } //如果新的值為對象,重新監聽 childOb = observe(newVal); /** * 訂閱中心通知所有訂閱者 **/ dep.notify(); } }) }
這里設計到閉包的概念,我們在函數里定義了:
const dep = new Dep();
由于set和get函數一直都存在的,所有dep會一直存在,不會被回收。
當值發生變化后,利用下面的代碼通知訂閱者:
dep.notify();
訂閱中心和發布者都有了,我們何時訂閱呢?或者什么時間訂閱合適呢?
我們是希望實現當讀取data屬性時候,實現訂閱。所以在defineReactive函數的get監聽中添加了如下代碼:
if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); } } return value;
Dep.target是一個靜態變量,用來存儲訂閱者的,每次訂閱前指向訂閱者,訂閱者置為null。
訂閱者代碼如下:
let uid = 0; //訂閱者類 export class Watcher{ //構造器,vm是vue實例 constructor(vm, expOrFn, cb) { this.vm = vm; this.cb = cb; this.id = uid++; this.deps = []; if (typeof expOrFn === "function") { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.value = this.get(); } //將訂閱這添加到訂閱中心 get() { //訂閱前,設置Dep.target變量,指向自身 pushTarget(this) let value; const vm = this.vm; /** * 這個地方讀取data屬性,觸發下面的訂閱代碼, * if (Dep.target) { * dep.depend(); * if (childOb) { * childOb.dep.depend(); * } * } * return value; **/ value = this.getter.call(vm, vm); //訂閱后,置Dep.target為null popTarget(); return value } //值變化,調用回調函數 update() { this.cb(this.value); } //添加依賴 addDep(dep) { this.deps.push(dep); dep.addSub(this); } } //解析類屬性的路徑,例如obj.sub.name,返回實際的值 export function parsePath (path){ const segments = path.split("."); return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return; obj = obj[segments[i]]; } return obj; } }
除了發布訂閱以外,雙向綁定還需要編譯dom。
主要實現兩個功能:
1、將dom中的{{key}}元素替換為Vue中的屬性。
2、檢測帶有v-model屬性的input元素,添加input事件,有修改時,修改Vue實例的屬性。
檢測v-model,綁定事件的代碼如下:
export function initModelMixin(Vue) { Vue.prototype._initModel = function () { if (this._dom == undefined) { if (this.$options.el) { let el = this.$options.el; let dom = document.querySelector(el); if (dom) { this._dom = dom; } else { console.error(`未發現dom: ${el}`); } } else { console.error("vue實例未綁定dom"); } } bindModel(this._dom, this); } } //input輸入框有V-model屬性,則綁定input事件 function bindModel(dom, vm) { if (dom) { if (dom.tagName === "INPUT") { let attrs = Array.from(dom.attributes); attrs.map(item => { if (item.name === "v-model") { let value = item.value; dom.value = getValue(vm, value); //綁定事件,暫不考慮清除綁定,因此刪除dom造成的內存泄露我們暫不考慮,這些問題后續解決 dom.addEventListener("input", (event) => { setValue(vm, value, event.target.value); }); } }) } let children = Array.from(dom.children); if (children) { children.map(item => { bindModel(item, vm); }); } } }
替換dom中{{key}}類似的屬性代碼:
export function renderMixin(Vue) { Vue.prototype._render = function () { if (this._dom == undefined) { if (this.$options.el) { let el = this.$options.el; let dom = document.querySelector(el); if (dom) { this._dom = dom; } else { console.error(`未發現dom: ${el}`); } } else { console.error("vue實例未綁定dom"); } } replaceText(this._dom, this); } } //替換dom的innerText function replaceText(dom, vm) { if (dom) { let children = Array.from(dom.childNodes); children.map(item => { if (item.nodeType === 3) { if (item.originStr === undefined) { item.originStr = item.nodeValue; } let str = replaceValue(item.originStr, function(key){ return getValue(vm, key); }); item.nodeValue = str; } else if (item.nodeType === 1) { replaceText(item, vm); } }); } }
到此位置,就實現了雙向綁定。
測試代碼如下,因為我用webpack構建的前端項目,html模板如下:
test {{name}}
main.js代碼:
import { Vue } from "../src/index"; let options = { el: "#app", data: { name: "xxx", age: 18 }, methods: { sayName() { console.log(this.name); } } } let vm = new Vue(options);
效果如下:
可以下載源碼嘗試,git項目地址:https://github.com/xubaodian/...
項目使用webpack構建,下載后先執行:
npm install
安裝依賴后使用指令:
npm run dev
可以運行項目。
如有疑問,歡迎留言或發送郵件至472784995@qq.com。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100593.html
摘要:執行的時候,會綁定上下文對象為組件實例于是中的就能取到組件實例本身,的代碼塊頂層作用域就綁定為了組件實例于是內部變量的訪問,就會首先訪問到組件實例上。其中的獲取,就會先從組件實例上獲取,相當于。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得...
摘要:所以無需太過介懷是實現的單向或雙向綁定。監聽數據綁定更新函數的處理是在這個方法中,通過添加回調來接收數據變化的通知至此,一個簡單的就完成了,完整代碼。 本文能幫你做什么?1、了解vue的雙向數據綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現雙向綁定為了便于說明原理與實現,本文相關代碼主要摘自vue源碼, 并進行了簡化改造,相對較簡陋,并未考慮到數組的處理、數據的循環依賴等,也...
摘要:接下來要看看這個訂閱者的具體實現了實現訂閱者作為和之間通信的橋梁,主要做的事情是在自身實例化時往屬性訂閱器里面添加自己自身必須有一個方法待屬性變動通知時,能調用自身的方法,并觸發中綁定的回調,則功成身退。 本文能幫你做什么?1、了解vue的雙向數據綁定原理以及核心代碼模塊2、緩解好奇心的同時了解如何實現雙向綁定為了便于說明原理與實現,本文相關代碼主要摘自vue源碼, 并進行了簡化改造,...
摘要:首先,兄弟,容我先說幾句涉及源碼很多,篇幅很長,我都已經分了上下三篇了,依然這么長,但是其實內容都差不多一樣,但是我還是毫無保留地給你了。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也...
閱讀 2336·2021-11-23 09:51
閱讀 1137·2021-11-22 13:52
閱讀 3611·2021-11-10 11:35
閱讀 1187·2021-10-25 09:47
閱讀 2994·2021-09-07 09:58
閱讀 1059·2019-08-30 15:54
閱讀 2817·2019-08-29 14:21
閱讀 3025·2019-08-29 12:20