濡備綍瀹炵幇涓€涓?Virtual DOM 鍙婃簮鐮佸垎鏋?/p>
Virtual DOM绠楁硶
web椤甸潰鏈変竴涓搴旂殑DOM鏍戯紝鍦ㄤ紶缁熷紑鍙戦〉闈㈡椂锛屾瘡娆¢〉闈㈤渶瑕佽鏇存柊鏃讹紝閮介渶瑕佹墜鍔ㄦ搷浣淒OM鏉ヨ繘琛屾洿鏂帮紝浣嗘槸鎴戜滑鐭ラ亾DOM鎿嶄綔瀵规€ц兘鏉ヨ鏄潪甯镐笉鍙嬪ソ鐨勶紝浼氬奖鍝嶉〉闈㈢殑閲嶆帓锛屼粠鑰屽奖鍝嶉〉闈㈢殑鎬ц兘銆傚洜姝ゅ湪React鍜孷UE2.0+寮曞叆浜嗚櫄鎷烡OM鐨勬蹇碉紝浠栦滑鐨勫師鐞嗘槸锛氭妸鐪熷疄鐨凞OM鏍戣浆鎹㈡垚javascript瀵硅薄鏍戯紝涔熷氨鏄櫄鎷烡OM锛屾瘡娆℃暟鎹渶瑕佽鏇存柊鐨勬椂鍊欙紝瀹冧細鐢熸垚涓€涓柊鐨勮櫄鎷烡OM锛屽苟涓斿拰涓婃鐢熸垚鐨勮櫄鎷烡OM杩涜瀵规瘮锛屽鍙戠敓鍙樺寲鐨勬暟鎹仛鎵归噺鏇存柊銆?--锛堝洜涓烘搷浣淛S瀵硅薄浼氭洿蹇紝鏇寸畝鍗曪紝姣旀搷浣淒OM鏉ヨ)銆?br /> 鎴戜滑鐭ラ亾web椤甸潰鏄敱涓€涓釜HTML鍏冪礌宓屽缁勫悎鑰屾垚鐨勶紝褰撴垜浠娇鐢╦avascript鏉ユ弿杩拌繖浜涘厓绱犵殑鏃跺€欙紝杩欎簺鍏冪礌鍙互绠€鍗曠殑琚〃绀烘垚绾补鐨凧SON瀵硅薄銆?/p>
姣斿濡備笅HTML浠g爜锛?/p>
<div id="container" class="container"> <ul id="list"> <li class="item">111</li> <li class="item">222</li> <li class="item">333</li> </ul> <button class="btn btn-blue"><em>鎻愪氦</em></button> </div>
涓婇潰鏄湡瀹炵殑DOM鏍戠粨鏋勶紝鎴戜滑鍙互浣跨敤javascript涓殑json瀵硅薄鏉ヨ〃绀虹殑璇濓紝鍙樻垚濡備笅锛?/p>
var element = { tagName: 'div', props: { // DOM鐨勫睘鎬?/span> id: 'container', class: 'container' }, children: [ { tagName: 'ul', props: { id: 'list' }, children: [ {tagName: 'li', props: {class: 'item'}, children: ['111']}, {tagName: 'li', props: {class: 'item'}, children: ['222']}, {tagName: 'li', props: {class: 'item'}, children: ['333']} ] }, { tagName: 'button', props: { class: 'btn btn-blue' }, children: [ { tagName: 'em', children: ['鎻愪氦'] } ] } ] };
鍥犳鎴戜滑鍙互浣跨敤javascript瀵硅薄琛ㄧずDOM鐨勪俊鎭拰缁撴瀯锛屽綋鐘舵€佸彉鏇寸殑鏃跺€欙紝閲嶆柊娓叉煋杩欎釜javascript瀵硅薄鐨勭粨鏋勶紝鐒跺悗鍙互浣跨敤鏂版覆鏌撶殑瀵硅薄鏍戝幓鍜屾棫鐨勬爲鍘诲姣旓紝璁板綍涓ら鏍戠殑宸紓锛屼袱棰楁爲鐨勫樊寮傚氨鏄垜浠渶瑕佸椤甸潰鐪熸鐨凞OM鎿嶄綔锛岀劧鍚庢妸浠栦滑搴旂敤鍒扮湡姝g殑DOM鏍戜笂锛岄〉闈㈠氨寰楀埌鏇存柊銆傝鍥剧殑鏁翠釜缁撴瀯纭疄鍏ㄦ覆鏌撲簡锛屼絾鏄渶鍚庢搷浣淒OM鐨勬椂鍊欙紝鍙彉鏇翠笉鍚岀殑鍦版柟銆?br />鍥犳鎴戜滑鍙互鎬荤粨涓€涓?strong> Virtual DOM绠楁硶锛?/strong>
1. 鐢╦avascript瀵硅薄缁撴瀯鏉ヨ〃绀篋OM鏍戠殑缁撴瀯锛岀劧鍚庣敤杩欎釜鏍戞瀯寤轰竴涓湡姝g殑DOM鏍戯紝鎻掑叆鍒版枃妗d腑銆?br />2. 褰撶姸鎬佸彉鏇寸殑鏃跺€欙紝閲嶆柊鏋勯€犱竴棰楁柊鐨勫璞℃爲锛岀劧鍚庝娇鐢ㄦ柊鐨勫璞℃爲涓庢棫鐨勫璞℃爲杩涜瀵规瘮锛岃褰曚袱棰楁爲鐨勫樊寮傘€?br />3. 鎶婅褰曚笅鏉ョ殑宸紓鐢ㄥ埌姝ラ1鎵€鏋勫缓鐨勭湡姝g殑DOM鏍戜笂銆傝鍥惧氨鏇存柊浜嗐€?/p>
绠楁硶瀹炵幇锛?/strong>
2-1 浣跨敤javascript瀵硅薄妯℃嫙DOM鏍戙€?/strong>
浣跨敤javascript鏉ヨ〃绀轰竴涓狣OM鑺傜偣锛屾湁濡備笂JSON鐨勬暟鎹紝鎴戜滑鍙渶瑕佽褰曞畠鐨勮妭鐐圭被鍨嬶紝灞炴€у拰瀛愯妭鐐瑰嵆鍙€?/p>
element.js 浠g爜濡備笅锛?/p>
function Element(tagName, props, children) { this.tagName = tagName; this.props = props; this.children = children; } Element.prototype.render = function() { var el = document.createElement(this.tagName); var props = this.props; // 閬嶅巻瀛愯妭鐐癸紝渚濇璁剧疆瀛愯妭鐐圭殑灞炴€?/span> for (var propName in props) { var propValue = props[propName]; el.setAttribute(propName, propValue); } // 淇濆瓨瀛愯妭鐐?/span> var childrens = this.children || []; // 閬嶅巻瀛愯妭鐐癸紝浣跨敤閫掑綊鐨勬柟寮?娓叉煋 childrens.forEach(function(child) { var childEl = (child instanceof Element) ? child.render() // 濡傛灉瀛愯妭鐐逛篃鏄櫄鎷烡OM锛岄€掑綊鏋勫缓DOM鑺傜偣 : document.createTextNode(child); // 濡傛灉鏄瓧绗︿覆鐨勮瘽锛屽彧鏋勫缓鏂囨湰鑺傜偣 el.appendChild(childEl); }); return el; }; module.exports = function(tagName, props, children) { return new Element(tagName, props, children); }
鍏ュ彛index.js浠g爜濡備笅锛?/p>
var el = require('./element'); var element = el('div', {id: 'container', class: 'container'}, [ el('ul', {id: 'list'},[ el('li', {class: 'item'}, ['111']), el('li', {class: 'item'}, ['222']), el('li', {class: 'item'}, ['333']), ]), el('button', {class: 'btn btn-blue'}, [ el('em', {class: ''}, ['鎻愪氦']) ]) ]); var elemRoot = element.render(); document.body.appendChild(elemRoot);
鎵撳紑椤甸潰鍗冲彲鐪嬪埌鏁堟灉銆?/p>
2-2 姣旇緝涓ら铏氭嫙DOM鏍戠殑宸紓鍙婂樊寮傜殑鍦版柟杩涜dom鎿嶄綔
涓婇潰鐨刣iv鍙細鍜屽悓涓€灞傜骇鐨刣iv瀵规瘮锛岀浜屽眰绾х殑鍙細鍜岀浜屽眰绾х殑瀵规瘮锛岃繖鏍风殑绠楁硶鐨勫鏉傚害鍙互杈惧埌O(n).
浣嗘槸鍦ㄥ疄闄呬唬鐮佷腑锛屼細瀵规柊鏃т袱棰楁爲杩涜涓€涓繁搴︿紭鍏堢殑閬嶅巻锛屽洜姝ゆ瘡涓妭鐐归兘浼氭湁涓€涓爣璁般€傚涓嬪浘鎵€绀猴細
鍦ㄩ亶鍘嗙殑杩囩▼涓紝姣忔閬嶅巻鍒颁竴涓妭鐐瑰氨鎶婅鑺傜偣鍜屾柊鐨勬爲杩涜瀵规瘮锛屽鏋滄湁宸紓鐨勮瘽灏辫褰曞埌涓€涓璞¢噷闈€?/p>
鐜板湪鎴戜滑鏉ョ湅涓嬫垜鐨勭洰褰曚笅 鏈夊摢浜涙枃浠讹紱鐒跺悗鍒嗗埆瀵规瘡涓枃浠朵唬鐮佽繘琛岃В璇伙紝鐪嬬湅鍋氫簡鍝簺浜嬫儏锛屾棫鐨勮櫄鎷焏om鍜屾柊鐨勮櫄鎷焏om鏄浣曟瘮杈冪殑锛屼笖鏄浣曟洿鏂伴〉闈㈢殑 濡備笅鐩綍锛?br />鐩綍缁撴瀯濡備笅锛?/strong>
vdom ---- 宸ョ▼鍚? | | ---- index.html html椤甸潰 | | ---- element.js 瀹炰緥鍖栧厓绱犵粍鎴恓son鏁版嵁 涓?鎻愪緵render鏂规硶 娓叉煋椤甸潰 | | ---- util.js 鎻愪緵涓€浜涘叕鐢ㄧ殑鏂规硶 | | ---- diff.js 姣旇緝鏂版棫鑺傜偣鏁版嵁 濡傛灉鏈夊樊寮備繚瀛樺埌涓€涓璞¢噷闈㈠幓 | | ---- patch.js 瀵瑰綋鍓嶅樊寮傜殑鑺傜偣鏁版嵁 杩涜DOM鎿嶄綔 | | ---- index.js 椤甸潰浠g爜鍒濆鍖栬皟鐢?/pre>
棣栧厛鏄?index.js鏂囦欢 椤甸潰娓叉煋瀹屾垚鍚?鍙樻垚濡備笅html缁撴瀯
<div id="container"> <h1 style="color: red;">simple virtal dom</h1> <p>the count is :1</p> <ul> <li>Item #0</li> </ul> </div>
鍋囧鍙戠敓鏀瑰彉鍚庯紝鍙樻垚濡備笅缁撴瀯
<div id="container"> <h1 style="color: blue;">simple virtal dom</h1> <p>the count is :2</p> <ul> <li>Item #0</li> <li>Item #1</li> </ul> </div>
鍙互鐪嬪埌 鏂版棫鑺傜偣椤甸潰鏁版嵁鐨勬敼鍙橈紝h1鏍囩浠庡睘鎬?棰滆壊浠庣孩鑹?鍙樹负钃濊壊锛宲鏍囩鐨勬枃鏈彂鐢熸敼鍙橈紝ul鏂板浜嗕竴椤瑰厓绱爈i銆?br />鍩烘湰鐨勫師鐞嗘槸锛氬厛娓叉煋鍑洪〉闈㈡暟鎹嚭鏉ワ紝鐢熸垚绗竴涓ā鏉块〉闈紝鐒跺悗浣跨敤瀹氭椂鍣ㄤ細鐢熸垚涓€涓柊鐨勯〉闈㈡暟鎹嚭鏉ワ紝瀵规柊鏃т袱棰楁爲杩涜涓€涓繁搴︿紭鍏堢殑閬嶅巻锛屽洜姝ゆ瘡涓妭鐐归兘浼氭湁涓€涓爣璁般€?br />鐒跺悗璋冪敤diff鏂规硶瀵规瘮瀵硅薄鏂版棫鑺傜偣閬嶅巻杩涜瀵规瘮锛屾壘鍑轰袱鑰呯殑涓嶅悓鐨勫湴鏂瑰瓨鍏ュ埌涓€涓璞¢噷闈㈠幓锛屾渶鍚庨€氳繃patch.js鎵惧嚭瀵硅薄涓嶅悓鐨勫湴鏂癸紝鍒嗗埆杩涜dom鎿嶄綔銆?/p>
index.js浠g爜濡備笅锛?/p>
var el = require('./element'); var diff = require('./diff'); var patch = require('./patch'); var count = 0; function renderTree() { count++; var items = []; var color = (count % 2 === 0) ? 'blue' : 'red'; for (var i = 0; i < count; i++) { items.push(el('li', ['Item #' + i])); } return el('div', {'id': 'container'}, [ el('h1', {style: 'color: ' + color}, ['simple virtal dom']), el('p', ['the count is :' + count]), el('ul', items) ]); } var tree = renderTree() var root = tree.render() document.body.appendChild(root) setInterval(function () { var newTree = renderTree() var patches = diff(tree, newTree) console.log(patches) patch(root, patches) tree = newTree }, 1000);
鎵ц var tree = renderTree()鏂规硶鍚庯紝浼氳皟鐢╡lement.js锛?br />1. 渚濇閬嶅巻瀛愯妭鐐?浠庡唴鍒板璋冪敤)渚濇涓?li, h1, p, ul, li鍜宧1鍜宲鏈変竴涓枃鏈瓙鑺傜偣锛屽洜姝ら亶鍘嗗畬鎴愬悗锛宑ount灏辩瓑浜?锛?br />浣嗘槸閬嶅巻ul鐨勬椂鍊欙紝鍥犱负鏈変竴涓瓙鑺傜偣li锛屽洜姝?count += 1; 鎵€浠ヨ皟鐢ㄥ畬鎴愬悗锛寀l鐨刢ount绛変簬2. 鍥犳浼氬姣忎釜element灞炴€ф坊鍔燾ount灞炴€с€傚浜庢渶澶栧眰鐨刢ontainer瀹瑰櫒灏辨槸瀵规瘡涓瓙鑺傜偣鐨勪緷娆″鍔狅紝h1瀛愯妭鐐归粯璁や负1锛屽惊鐜畬鎴愬悗 +1锛涘洜姝ゅ彉涓?锛?p鑺傜偣榛樿涓?锛屽惊鐜畬鎴愬悗 +1锛屽洜姝や篃鍙樹负2锛寀l涓?锛屽惊鐜畬鎴愬悗 +1锛屽洜姝ゅ彉涓?锛屽洜姝ontainer鑺傜偣鐨刢ount=2+2+3 = 7;
element.js閮ㄥ垎浠g爜濡備笅锛?/p>
function Element(tagName, props, children) { if (!(this instanceof Element)) { // 鍒ゆ柇瀛愯妭鐐?children 鏄惁涓?undefined if (!utils.isArray(children) && children !== null) { children = utils.slice(arguments, 2).filter(utils.truthy); } return new Element(tagName, props, children); } // 濡傛灉娌℃湁灞炴€х殑璇濓紝绗簩涓弬鏁版槸涓€涓暟缁勶紝璇存槑绗簩涓弬鏁颁紶鐨勬槸瀛愯妭鐐?/span> if (utils.isArray(props)) { children = props; props = {}; } this.tagName = tagName; this.props = props || {}; this.children = children || []; // 淇濆瓨key閿?濡傛灉鏈夊睘鎬?淇濆瓨key锛屽惁鍒欒繑鍥瀠ndefined this.key = props ? props.key : void 0; var count = 0; utils.each(this.children, function(child, i) { // 濡傛灉鏄厓绱犵殑瀹炲垪鐨勮瘽 if (child instanceof Element) { count += child.count; } else { // 濡傛灉鏄枃鏈妭鐐圭殑璇濓紝鐩存帴璧嬪€?/span> children[i] = '' + child; } count++; }); this.count = count; }
oldTree鏁版嵁鏈€缁堝彉鎴愬涓嬶細
var oldTree = { tagName: 'div', key: undefined, count: 7, props: {id: 'container'}, children: [ { tagName: 'h1', key: undefined count: 1 props: {style: 'colod: red'}, children: ['simple virtal dom'] }, { tagName: 'p', key: undefined count: 1 props: {}, children: ['the count is :1'] }, { tagName: 'ul', key: undefined count: 2 props: {}, children: [ { tagName: 'li', key: undefined, count: 1, props: {}, children: ['Item #0'] } ] }, ] };
瀹氭椂鍣?鎵ц var newTree = renderTree()鍚庯紝璋冪敤鏂规硶姝ラ杩樻槸鍜岀涓€姝ヤ竴鏍凤細
2. 渚濇閬嶅巻瀛愯妭鐐?浠庡唴鍒板璋冪敤)渚濇涓?li, h1, p, ul, li鍜宧1鍜宲鏈変竴涓枃鏈瓙鑺傜偣锛屽洜姝ら亶鍘嗗畬鎴愬悗锛宑ount灏辩瓑浜?锛屽洜涓烘湁2涓瓙鍏冪礌li锛宑ount閮戒负1锛屽洜姝l姣忔閬嶅巻渚濇鍦ㄥ師鏉ョ殑鍩虹涓婂姞1锛屽洜姝ら亶鍘嗗畬鎴愮涓€涓猯i鏃跺€欙紝ul涓殑count涓?锛屽綋閬嶅巻瀹屾垚绗簩涓猯i鐨勬椂鍊欙紝ul鐨刢ount灏变负4浜嗐€傚洜姝l涓殑count涓?. 瀵逛簬鏈€澶栧眰鐨刢ontainer瀹瑰櫒灏辨槸瀵规瘡涓瓙鍏冪礌渚濇澧炲姞銆?br />鎵€浠?container鑺傜偣鐨刢ount = 2 + 2 + 5 = 9;
newTree鏁版嵁鏈€缁堝彉鎴愬涓嬫暟鎹細
var newTree = { tagName: 'div', key: undefined, count: 9, props: {id: 'container'}, children: [ { tagName: 'h1', key: undefined count: 1 props: {style: 'colod: red'}, children: ['simple virtal dom'] }, { tagName: 'p', key: undefined count: 1 props: {}, children: ['the count is :1'] }, { tagName: 'ul', key: undefined count: 4 props: {}, children: [ { tagName: 'li', key: undefined, count: 1, props: {}, children: ['Item #0'] }, { tagName: 'li', key: undefined, count: 1, props: {}, children: ['Item #1'] } ] }, ] }
var patches = diff(oldTree, newTree);
璋冪敤diff鏂规硶鍙互姣旇緝鏂版棫涓ゆ5鏍戣妭鐐圭殑鏁版嵁锛屾妸涓ら鏍戠殑涓嶅悓鑺傜偣鎵惧嚭鏉ャ€?娉ㄦ剰锛屾煡鐪媎iff瀵规瘮鏁版嵁鐨勬柟娉曪紝鎵惧埌涓嶅悓鐨勮妭鐐癸紝鍙互鏌ョ湅杩欑瘒鏂囩珷diff绠楁硶)濡備笅璋冪敤浠g爜锛?/p>
function diff (oldTree, newTree) { var index = 0; var patches = {}; deepWalk(oldTree, newTree, index, patches); return patches; }
鎵цdeepWalk濡備笅浠g爜锛?/p>
function deepWalk(oldNode, newNode, index, patches) { var currentPatch = []; // 鑺傜偣琚垹闄ゆ帀 if (newNode === null) { // 鐪熸鐨凞OM鑺傜偣鏃?灏嗗垹闄ゆ墽琛岄噸鏂版帓搴?鎵€浠ヤ笉闇€瑕佸仛浠讳綍浜?/span> } else if(utils.isString(oldNode) && utils.isString(newNode)) { // 鏇挎崲鏂囨湰鑺傜偣 if (newNode !== oldNode) { currentPatch.push({type: patch.TEXT, content: newNode}); } } else if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) { // 鐩稿悓鐨勮妭鐐癸紝浣嗘槸鏂版棫鑺傜偣鐨勫睘鎬т笉鍚岀殑鎯呭喌涓?姣旇緝灞炴€?/span> // diff props var propsPatches = diffProps(oldNode, newNode); if (propsPatches) { currentPatch.push({type: patch.PROPS, props: propsPatches}); } // 涓嶅悓鐨勫瓙鑺傜偣 if (!isIgnoreChildren(newNode)) { diffChildren( oldNode.children, newNode.children, index, patches, currentPatch ) } } else { // 涓嶅悓鐨勮妭鐐癸紝閭d箞鏂拌妭鐐规浛鎹㈡棫鑺傜偣 currentPatch.push({type: patch.REPLACE, node: newNode}); } if (currentPatch.length) { patches[index] = currentPatch; } }
1. 鍒ゆ柇鏂拌妭鐐规槸鍚︿负null锛屽鏋滀负null锛岃鏄庤妭鐐硅鍒犻櫎鎺夈€?br />2. 鍒ゆ柇鏂版棫鑺傜偣鏄惁涓哄瓧绗︿覆锛屽鏋滀负瀛楃涓茶鏄庢槸鏂囨湰鑺傜偣锛屽苟涓旀柊鏃т袱涓枃鏈妭鐐逛笉鍚岀殑璇濓紝瀛樺叆鏁扮粍閲岄潰鍘伙紝濡備笅浠g爜锛?/p>
currentPatch.push({type: patch.TEXT, content: newNode});
patch.TEXT 涓?patch.js閲岄潰鐨?TEXT = 3锛沜ontent灞炴€т负鏂拌妭鐐广€?/p>
3. 濡傛灉鏂版棫tagName鐩稿悓鐨勮瘽锛屽苟涓旀柊鏃ц妭鐐圭殑key鐩稿悓鐨勮瘽锛岀户缁瘮杈冩柊鏃ц妭鐐圭殑灞炴€э紝濡備笅浠g爜锛?/p>
var propsPatches = diffProps(oldNode, newNode);
diffProps鏂规硶鐨勪唬鐮佸涓嬶細
function diffProps(oldNode, newNode) { var count = 0; var oldProps = oldNode.props; var newProps = newNode.props; var key, value; var propsPatches = {}; // 鎵惧嚭涓嶅悓鐨勫睘鎬у€?/span> for (key in oldProps) { value = oldProps[key]; if (newProps[key] !== value) { count++; propsPatches[key] = newProps[key]; } } // 鎵惧嚭鏂板灞炴€?/span> for (key in newProps) { value = newProps[key]; if (!oldProps.hasOwnProperty(key)) { count++; propsPatches[key] = newProps[key]; } } // 濡傛灉鎵€鏈夌殑灞炴€ч兘鏄浉鍚岀殑璇?/span> if (count === 0) { return null; } return propsPatches; }
diffProps浠g爜瑙f瀽濡備笅锛?/p>
for (key in oldProps) { value = oldProps[key]; if (newProps[key] !== value) { count++; propsPatches[key] = newProps[key]; } }
濡備笂浠g爜鏄?鍒ゆ柇鏃ц妭鐐圭殑灞炴€у€兼槸鍚﹀湪鏂拌妭鐐逛腑鎵惧埌锛屽鏋滄壘涓嶅埌鐨勮瘽锛宑ount++; 鎶婃柊鑺傜偣鐨勫睘鎬у€艰祴鍊肩粰 propsPatches 瀛樺偍璧锋潵銆?/p>
for (key in newProps) { value = newProps[key]; if (!oldProps.hasOwnProperty(key)) { count++; propsPatches[key] = newProps[key]; } }
濡備笂浠g爜鏄?鍒ゆ柇鏂拌妭鐐圭殑灞炴€ф槸鍚﹁兘鍦ㄦ棫鑺傜偣涓壘鍒帮紝濡傛灉鎵句笉鍒扮殑璇濓紝count++; 鎶婃柊鑺傜偣鐨勫睘鎬у€艰祴鍊肩粰 propsPatches 瀛樺偍璧锋潵銆?/p>
if (count === 0) { return null; } return propsPatches;
鏈€鍚庡鏋渃ount 绛変簬0鐨勮瘽锛岃鏄庢墍鏈夊睘鎬ч兘鏄浉鍚岀殑璇濓紝鎵€浠ヤ笉闇€瑕佸仛浠讳綍鍙樺寲銆傚惁鍒欑殑璇濓紝杩斿洖鏂板鐨勫睘鎬с€?/p>
濡傛灉鏈?propsPatches 鐨勮瘽锛屾墽琛屽涓嬩唬鐮侊細
if (propsPatches) { currentPatch.push({type: patch.PROPS, props: propsPatches}); }
鍥犳currentPatch鏁扮粍閲岄潰涔熸湁瀵瑰簲鐨勬洿鏂扮殑灞炴€э紝props灏辨槸闇€瑕佹洿鏂扮殑灞炴€у璞°€?/p>
缁х画浠g爜锛?/p>
// 涓嶅悓鐨勫瓙鑺傜偣 if (!isIgnoreChildren(newNode)) { diffChildren( oldNode.children, newNode.children, index, patches, currentPatch ) } function isIgnoreChildren(node) { return (node.props && node.props.hasOwnProperty('ignore')); }
濡備笂浠g爜鍒ゆ柇瀛愯妭鐐规槸鍚︾浉鍚岋紝diffChildren浠g爜濡備笅锛?/p>
function diffChildren(oldChildren, newChildren, index, patches, currentPatch) { var diffs = listDiff(oldChildren, newChildren, 'key'); newChildren = diffs.children; if (diffs.moves.length) { var recorderPatch = {type: patch.REORDER, moves: diffs.moves}; currentPatch.push(recorderPatch); } var leftNode = null; var currentNodeIndex = index; utils.each(oldChildren, function(child, i) { var newChild = newChildren[i]; currentNodeIndex = (leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1; // 閫掑綊 deepWalk(child, newChild, currentNodeIndex, patches); leftNode = child; }); }
濡備笂浠g爜锛歷ar diffs = listDiff(oldChildren, newChildren, 'key'); 鏂版棫鑺傜偣鎸夌収key鏉ユ瘮杈冿紝鐩墠key涓簎ndefined锛屾墍浠iffs 涓哄涓嬶細
diffs = { moves: [], children: [ { tagName: 'h1', key: undefined count: 1 props: {style: 'colod: blue'}, children: ['simple virtal dom'] }, { tagName: 'p', key: undefined count: 1 props: {}, children: ['the count is :2'] }, { tagName: 'ul', key: undefined count: 4 props: {}, children: [ { tagName: 'li', key: undefined, count: 1, props: {}, children: ['Item #0'] }, { tagName: 'li', key: undefined, count: 1, props: {}, children: ['Item #1'] } ] } ] };
newChildren = diffs.children;
oldChildren鏁版嵁濡備笅锛?/p>
oldChildren = [ { tagName: 'h1', key: undefined count: 1 props: {style: 'colod: red'}, children: ['simple virtal dom'] }, { tagName: 'p', key: undefined count: 1 props: {}, children: ['the count is :1'] }, { tagName: 'ul', key: undefined count: 2 props: {}, children: [ { tagName: 'li', key: undefined, count: 1, props: {}, children: ['Item #0'] } ] } ];
鎺ョ潃灏辨槸閬嶅巻 oldChildren, 绗竴娆¢亶鍘嗘椂 leftNode 涓簄ull锛屽洜姝?currentNodeIndex = currentNodeIndex + 1 = 0 + 1 = 1; 涓嶆槸绗竴娆¢亶鍘嗭紝閭d箞leftNode閮戒负涓婁竴娆¢亶鍘嗙殑瀛愯妭鐐癸紝鍥犳涓嶆槸绗竴娆¢亶鍘嗙殑璇濓紝閭d箞 currentNodeIndex = currentNodeIndex + leftNode.count + 1;
鐒跺悗閫掑綊璋冪敤 deepWalk(child, newChild, currentNodeIndex, patches); 鏂规硶锛屾帴鐫€鎶奵hild璧嬪€肩粰leftNode锛宭eftNode = child;
鎵€浠ヤ竴鐩撮€掑綊閬嶅巻锛屾渶缁堟妸涓嶇浉鍚岀殑鑺傜偣 浼氬瓨鍌ㄥ埌 currentPatch 鏁扮粍鍐呫€傛渶鍚庢墽琛?
if (currentPatch.length) { patches[index] = currentPatch; }
鎶婂搴旂殑currentPatch 瀛樺偍鍒?patches瀵硅薄鍐呬腑鐨勫搴旈」锛屾渶鍚庡氨杩斿洖 patches瀵硅薄銆?/p>
4. 杩斿洖鍒癷ndex.js 浠g爜鍐咃紝鎶婁袱棰椾笉鐩稿悓鐨勬爲鑺傜偣鐨勬彁鍙栧嚭鏉ュ悗锛岄渶瑕佽皟鐢╬atch.js鏂规硶浼犺繘锛涙妸涓嶇浉鍚岀殑鑺傜偣搴旂敤鍒扮湡姝g殑DOM涓?
涓嶇浉鍚岀殑鑺傜偣 patches鏁版嵁濡備笅锛?/p>
patches = { 1: [{type: 2, props: {style: 'color: blue'}}], 4: [{type: 3, content: 'the count is :2'}], 5: [ { type: 1, moves: [ { index: 1, item: { tagName: 'li', props: {}, count: 1, key: undefined, children: ['Item #1'] } } ] } ] }
濡備笅浠g爜璋冪敤锛?br /> patch(root, patches);
鎵цpatch鏂规硶锛屼唬鐮佸涓嬶細
function patch(node, patches) { var walker = {index: 0}; deepWalk(node, walker, patches); }
deepWalk 浠g爜濡備笅锛?/p>
function deepWalk(node, walker, patches) { var currentPatches = patches[walker.index]; // node.childNodes 杩斿洖鎸囧畾鍏冪礌鐨勫瓙鍏冪礌闆嗗悎锛屽寘鎷琀TML鑺傜偣锛屾墍鏈夊睘鎬э紝鏂囨湰鑺傜偣銆?/span> var len = node.childNodes ? node.childNodes.length : 0; for (var i = 0; i < len; i++) { var child = node.childNodes[i]; walker.index++; // 娣卞害澶嶅埗 閫掑綊閬嶅巻 deepWalk(child, walker, patches); } if (currentPatches) { applyPatches(node, currentPatches); } }
1. 棣栨璋冪敤patch鐨勬柟娉曪紝root灏辨槸container鐨勮妭鐐癸紝鍥犳璋冪敤deepWalk鏂规硶锛屽洜姝?var currentPatches = patches[0] = undefined,
var len = node.childNodes ? node.childNodes.length : 0; 鍥犳 len = 3; 寰堟槑鏄捐瀛愯妭鐐圭殑闀垮害涓?锛屽洜涓哄瓙鑺傜偣鏈?h1, p, 鍜寀l鍏冪礌锛?/p>
2. 鐒跺悗杩涜for寰幆锛岃幏鍙栬鐖惰妭鐐圭殑瀛愯妭鐐癸紝鍥犳绗竴涓瓙鑺傜偣涓?h1 鍏冪礌锛寃alker.index++; 鍥犳walker.index = 1; 鍐嶈繘琛岄€掑綊 deepWalk(child, walker, patches); 姝ゆ椂瀛愯妭鐐逛负h1, walker.index涓?锛?鍥犳鑾峰彇 currentPatches = patches[1]; 鑾峰彇鍊硷紝鍐嶈幏鍙?h1鐨勫瓙鑺傜偣鐨勯暱搴︼紝len = 1; 鐒跺悗鍐峟or寰幆锛岃幏鍙朿hild涓烘枃鏈妭鐐癸紝姝ゆ椂 walker.index++; 鎵€浠ユ鏃秝alker.index 涓?, 鍦ㄨ皟鐢╠eepwalk鏂规硶閫掑綊锛屽洜姝ゅ啀缁х画鑾峰彇 currentPatches = patches[2]; 鍊间负undefined锛屽啀鑾峰彇len = 0; 鍥犱负鏂囨湰鑺傜偣涔堟湁瀛愯妭鐐癸紝鎵€浠or寰幆璺冲嚭锛屾墍浠ュ垽鏂璫urrentPatches鏄惁鏈夊€硷紝鍥犱负姝ゆ椂 currentPatches 涓簎ndefined锛屾墍浠ラ€掑綊缁撴潫锛屽啀杩斿洖鍒?h1鍏冪礌涓婃潵锛屾墍浠urrentPatches = patches[1]; 鎵€浠ユ湁鍊硷紝鎵€浠ヨ皟鐢?applyPatches()鏂规硶鏉ユ洿鏂癲om鍏冪礌銆?/p>
3. 缁х画寰幆 i, 姝ゆ椂i = 1锛?鑾峰彇瀛愯妭鐐?child = p鍏冪礌锛寃alker.index++锛屾鏃秝alker.index = 3, 缁х画璋冪敤 deepWalk鏂规硶锛岃幏鍙?var currentPatches = patches[walker.index] = patches[3]鐨勫€硷紝var len = 1; 鍥犱负p鍏冪礌涓嬫湁涓€涓瓙鑺傜偣(鏂囨湰鑺傜偣)锛屽啀杩沠or寰幆锛屾鏃?walker.index++; 鍥犳walker.index = 4; child姝ゆ椂涓烘枃鏈妭鐐癸紝鍦ㄨ皟鐢?deepwalk鏂规硶鐨勬椂鍊欙紝鍐嶈幏鍙杤ar currentPatches = patches[walker.index] = patches[4]; 鍐嶆墽琛宭en 浠g爜鐨勬椂鍊?len = 0;鍥犳璺冲嚭for寰幆锛屽垽鏂?currentPatches鏄惁鏈夊€硷紝鏈夊€肩殑璇濓紝鏇存柊瀵瑰簲鐨凞OM鍏冪礌銆?br />
4. 缁х画寰幆i = 2; 鑾峰彇瀛愯妭鐐?child = ul鍏冪礌锛寃alker.index++; 姝ゆ椂walker.index = 5; 鍦ㄨ皟鐢╠eepWalk鏂规硶閫掑綊锛屽洜姝ゅ啀鑾峰彇 var currentPatches = patches[walker.index] = patches[5]; 鐒跺悗len = 1, 鍥犱负ul鍏冪礌涓嬫湁涓€涓猯i鍏冪礌锛屽湪缁х画for寰幆閬嶅巻锛岃幏鍙栧瓙鑺傜偣li锛屾鏃秝alker.index++; walker.index = 6; 鍐嶉€掑綊璋冪敤deepwalk鏂规硶锛屽啀鑾峰彇var currentPatches = patches[walker.index] = patches[6]; len = 1; 鍥犱负li鐨勫厓绱犱笅鏈変竴涓枃鏈妭鐐癸紝鍐嶈繘琛宖or寰幆锛屾鏃禼hild涓烘枃鏈妭鐐癸紝walker.index++锛涙鏃秝alker.index = 7; 鍐嶆墽琛?deepwalk鏂规硶锛屽啀鑾峰彇 var currentPatches = patches[walker.index] = patches[7]; 杩欐椂鍊?len = 0浜嗭紝鍥犳璺冲嚭for寰幆锛屽垽鏂?褰撳墠鐨刢urrentPatches鏄惁鏈夊€硷紝娌℃湁锛屽氨璺冲嚭锛岀劧鍚庡啀杩斿洖ul鍏冪礌锛岃幏鍙栬鑷繁li鐨勬椂鍊欙紝walker.index 绛変簬5锛屽洜姝ar currentPatches = patches[walker.index] = patches[5]; 鐒跺悗鍒ゆ柇 currentPatches鏄惁鏈夊€硷紝鏈夊€煎氨杩涜鏇存柊DOM鍏冪礌銆?/p>
鏈€鍚庡氨鏄?applyPatches 鏂规硶鏇存柊dom鍏冪礌浜嗭紝濡備笅浠g爜锛?/p>
function applyPatches(node, currentPatches) { utils.each(currentPatches, function(currentPatch) { switch (currentPatch.type) { case REPLACE: var newNode = (typeof currentPatch.node === 'string') ? document.createTextNode(currentPatch.node) : currentPatch.node.render(); node.parentNode.replaceChild(newNode, node); break; case REORDER: reorderChildren(node, currentPatch.moves); break; case PROPS: setProps(node, currentPatch.props); break; case TEXT: if(node.textContent) { node.textContent = currentPatch.content; } else { // ie bug node.nodeValue = currentPatch.content; } break; default: throw new Error('Unknow patch type' + currentPatch.type); } }); }
鍒ゆ柇绫诲瀷锛屾浛鎹㈠搴旂殑灞炴€у拰鑺傜偣銆?br />鏈€鍚庡氨鏄瀛愯妭鐐硅繘琛屾帓搴忕殑鎿嶄綔锛屼唬鐮佸涓嬶細
// 瀵瑰瓙鑺傜偣杩涜鎺掑簭 function reorderChildren(node, moves) { var staticNodeList = utils.toArray(node.childNodes); var maps = {}; utils.each(staticNodeList, function(node) { // 濡傛灉鏄厓绱犺妭鐐?/span> if (node.nodeType === 1) { var key = node.getAttribute('key'); if (key) { maps[key] = node; } } }) utils.each(moves, function(move) { var index = move.index; if (move.type === 0) { // remove Item if (staticNodeList[index] === node.childNodes[index]) { node.removeChild(node.childNodes[index]); } staticNodeList.splice(index, 1); } else if(move.type === 1) { // insert item var insertNode = maps[move.item.key] ? maps[move.item.key].cloneNode(true) : (typeof move.item === 'object') ? move.item.render() : document.createTextNode(move.item); staticNodeList.splice(index, 0, insertNode); node.insertBefore(insertNode, node.childNodes[index] || null); } }); }
閬嶅巻moves锛屽垽鏂璵oves.type 鏄瓑浜?杩樻槸绛変簬1锛岀瓑浜?鐨勮瘽鏄垹闄ゆ搷浣滐紝绛変簬1鐨勮瘽鏄柊澧炴搷浣溿€傛瘮濡傜幇鍦╩oves鍊煎彉鎴愬涓嬶細
moves = { index: 1, type: 1, item: { tagName: 'li', key: undefined, props: {}, count: 1, children: ['#Item 1'] } };
node鑺傜偣 灏辨槸 'ul'鍏冪礌锛寁ar staticNodeList = utils.toArray(node.childNodes); 鎶妘l鐨勬棫瀛愯妭鐐筶i杞垚Array褰㈠紡锛岀敱浜庢病鏈夊睘鎬ey锛屾墍浠ョ洿鎺ヨ烦鍒颁笅闈㈤亶鍘嗕唬鐮佹潵锛岄亶鍘唌oves,鑾峰彇鏌愪竴椤圭殑绱㈠紩index锛屽垽鏂璵ove.type 绛変簬0 杩樻槸绛変簬1锛?鐩墠绛変簬1锛屾槸鏂板涓€椤癸紝浣嗘槸娌℃湁key锛屽洜姝よ皟鐢╩ove.item.render(); 娓叉煋瀹屽悗锛屽staticNodeList鏁扮粍閲岄潰鐨勬棫鑺傜偣鐨刲i椤逛粠绗簩椤瑰紑濮嬫彃鍏ヨ妭鐐筶i锛岀劧鍚庢墽琛宯ode.insertBefore(insertNode, node.childNodes[index] || null); node灏辨槸ul鐖惰妭鐐癸紝insertNode鑺傜偣鎻掑叆鍒?node.childNodes[1]鐨勫墠闈€傚洜姝ゆ妸鍦ㄧ浜岄」鐨勫墠闈㈡彃鍏ョ涓€椤广€?br />鏌ョ湅github涓婃簮鐮?/a>