手動觸發指令繫結是比較直接的實現方式,主要思路是通過在資料物件上定義get()方法和set()方法,呼叫時手動觸發get()或set()函式來獲取、修改資料,改變資料後會主動觸發get()和set()函式中view層的重新渲染功能。我們來看乙個栗子
q-value="value"
type="text"
id="input">
q-text="value"
id="el">
span>
let elems = [document.getelementbyid('el'), document.getelementbyid('input')];
let data = ;
//定義directive指令
let directive = ,
value: function
(value)
}//資料繫結監聽
if(document.addeventlistener), false);
} else , false);
}//開始掃瞄節點
scan();
//模擬一次使用者操作,設定頁面2秒後自動改變資料更新檢視
settimeout(function
(), 1000)
function
scan
(); for(let attr of elem.attributes)}}
}//設定資料改變之後掃瞄節點
function
viewmodelset
(key, value)
通過瀏覽器載入這個指令碼之後,viewmodel的變化會自動改變輸入框的內容,輸入的內容的變化也會驅動viewmodel的變化。我們通過viewmodelset()
方法改變viewmodel的資料後,需要主動呼叫scan()方法重新掃瞄html頁面上的節點。
髒檢測的基本原理是在viewmodel物件的某個屬性值發生變化時,找到這個屬性值相關的所有元素,然後再比較資料變化,如果變化則進行directive呼叫,舉個栗子
qg-event="value"
qg-bind="value"
type="text"
id="input">
qg-event="text"
qg-bind="value"
id="el">
span>
let elems = [document.getelementbyid('el'), document.getelementbyid('input')];
let data = ;
//定義directive指令
let directive = ,
value: function
(value)
};scan(elems);
$digest('value');
//資料繫結監聽
if(document.addeventlistener), false);
} else , false);
}//模擬一次使用者操作
settimeout(function
(), 1000)
function
scan
() }
//可以理解為資料劫持監聽
function
$digest
(value)
//髒資料迴圈檢測
function
digest
(elems)}}
}}
這裡和手動繫結不同的是,髒檢測只針對可能修改的元素進行掃瞄,這樣就提高了viewmodel內容變化後掃瞄檢視渲染的效率
資料劫持是目前使用比較廣泛的方式,其基本思路是使用object.defineproperty和object.difineproperties對viewmodel資料物件進行get和set的監聽,當有資料讀取和賦值操作的時候則掃瞄元素節點,執行指定對應節點的directive指令,這樣viewmodel使用通用的等號賦值就可以了。舉個栗子
q-value="value"
type="text"
id="input">
q-text="value"
id="el">
div>
let elems = [document.getelementbyid('el'), document.getelementbyid('input')];
let data = ;
//定義directive指令
let directive = ,
value: function
(value)
}let bvalue;
scan();
//可以理解為資料劫持監聽
definegetandset(data, 'value');
//資料繫結監聽
if(document.addeventlistener), false);
} else , false);
}settimeout(function
(), 2000);
function
scan
(); for(let attr of elem.attributes)}}
//定義物件屬性設定劫持
function
definegetandset
(obj, propname),
set: function
(newvalue),
enumerable: true,
configurable: true})}
}
需要注意的是,defineproperty只支援ie8以上和chrome瀏覽器,且ie8瀏覽器中需要使用es5-shim來提供支援。firefox瀏覽器不支援該方法,需要使用_ difinegetter_和_ difinesetter_來代替。關於物件劫持可以參考我另一篇部落格vue 雙向資料繫結原理
mvvm的前端互動模式大大提高了程式設計效率,自動雙向資料繫結讓我們可以頁面邏輯實現的核心轉移到資料層的修改操作上,而不是在頁面中直接操作dom。但是mvvm最終資料層反應到頁面上view層的渲染和改變仍是通過對應的指令進行dom操作來完成的,而且通常一次viewmodel的變化可能會觸發頁面上多個指令操作dom的變化,帶來大量的頁面結構層dom操作或渲染,先來看下面這個應用場景
id="root">
q-repeat="list">
q-text="value">
span>
固定文字span>
li>
ul>
let viewmodel = new vm(, , ]
}});
使用mvvm框架時就生成了乙個數字列表,此時如果需要顯示的內容變成了list: [, , ]
在沒有做優化的mvvm框架中一般會重新渲染整個列表。那麼該怎樣將這個增加的資料反映到view層上呢?我們其實可以把新的model data和舊的model data進行對比,然後記錄viewmodel的改變方式和位置,就知道這次view層怎樣去更新。
如果用js物件的屬性層級結構來描述html dom物件樹的結構,在渲染前面對比前後兩個js物件,就能找出檢視改變的最小操作描述的物件。這裡的html dom物件樹可以理解為virtual dom。
先總結一下使用virtual dom模式來控制頁面dom結構更新的過程:建立原始頁面或元件的virtual dom結構,使用者操作進行dom更新時,生成使用者操作後頁面或元件的virtual dom結構並與之前的結構進行對比,找到最小變化的virtual dom的差異化描述物件,最後把差異化的virtual dom根據特定規則渲染到頁面上。
建立virtual dom即把一段html字串文字解析成乙個能夠描述它的js物件。我們很自然地想,通過瀏覽器提供的dom api掃瞄這段dom的節點,遍歷它的屬性,然後新增到js物件上即可。這似乎很合理,但其實這樣是錯的,這樣建立virtual dom會直接失去virtual dom的優勢,它是為了避免直接進行dom操作而設計的,因為掃瞄過程本身使用到dom的讀取操作,這個過程很慢。所以一種更可選的方式是,自己實現上述這段html字串文字的解析方式,根據標籤之間的關係,讀取生成virtual dom的結構。
let htmlstring = ';'
let ulelement = createvdom(htmlstring);
那麼createvdom就可以如下實現:逐個分析字串中的字元,根據詞法分析內容,將標籤名存為tagname,屬性存入attributes,子標籤內容存入chidren。根據html字串解析建立virtual dom的過程相當於實現了乙個html文字解析器。那麼在對比virtual dom的演算法實際上是對於多叉樹結構的遍歷演算法,對多叉樹遍歷就有廣度優先演算法和深度優先演算法。我們通過virtual dom的方式,有效減少了dom操作。 現代前端技術解析 Web 前端技術基礎
對 dom 操作進行封裝 dom 文件物件模型 指 html 內容通過瀏覽器解析後建立的具有節點父子關係的樹形物件 模組化 元件化 非同步載入 保證盡快展示頁面 webp 格式的 更高壓縮比 瀏覽器快取檔案 304 狀態碼 客戶端傳送了乙個帶條件的 get 請求且該請求已被允許,而文件的內容並沒有改...
33 深入解析spring技術 3
通過beandefinitionparserdelegate完成對資源檔案的解析與載入,資源檔案在ioc容器中建立了相應資料,但是這些資料還不能直接供ioc使用,需要在defaultlistablebeanfactory中進行註冊 在31章圖2中,這個類實現了beandefinitionregist...
33 深入解析spring技術 3
通過beandefinitionparserdelegate完成對資源檔案的解析與載入,資源檔案在ioc容器中建立了相應資料,但是這些資料還不能直接供ioc使用,需要在defaultlistablebeanfactory中進行註冊 在31章圖2中,這個類實現了beandefinitionregist...