在上一篇中,咱們實現了vue對象的構建,而且已經初步實現了變量的綁定和事件綁定,如今咱們就剩下一個問題須要解決,就是v-for
指令的實現,這也是本系列中最難的部分。javascript
實現v-for有如下幾個難點html
item in items
和(item,index) in items
,第二種能夠獲取到序號,程序須要解析這兩種語法在compile中,若是遇到v-for會先將v-for內的節點所有生成好,再做爲子節點append到父節點上,所以第一步就是判斷是否包含v-for指令vue
function isLoop(element) { return element.attributes && element.attributes['v-for']; }
compile函數遞歸編譯子節點從java
for (let i = 0; i < node.childNodes.length; ++i) { element.appendChild(compile(node.childNodes[i])); }
修改成node
for (let i = 0; i < node.childNodes.length; ++i) { let child = node.childNodes[i]; if (isLoop(child)) { let ns = compileLoop(child, element); for (let j = 0; j < ns.length; ++j) { element.appendChild(ns[j]); } } else { element.appendChild(compile(child)); } }
compileLoop
會對v-for節點進行編譯,而且返回節點數組,父節點對返回的節點進行append。vuex
編譯的第一步就是解析,須要解析三部分的內容數組
let vfor = element.attributes['v-for'].value; let itemName; let indexName; let varName; let loopExp1 = /\(([^,]+),([^\)]+)\)\s+in\s+(.*)/g; let loopExp2 = /(\w+)\s+in\s+(.*)/g; let m; if (m = loopExp1.exec(vfor)) { itemName = m[1]; indexName = m[2] varName = m[3]; } else if (m = loopExp2.exec(vfor)) { itemName = m[1]; varName = m[2]; }
直接用正則進行解析,loopExp1和loopExp2分別對應兩種語法,varName:數組名,itemName:循環變量名,indexName:循環下標閉包
解析完成後就能夠開始生成元素app
var directive = { origin: element.cloneNode(true), attr: 'v-for', exp: { varName: varName, indexName: indexName, itemName: itemName } } element.attributes.removeNamedItem('v-for'); let arrays = vue[varName]; let elements = []; for (let i = 0; i < arrays.length; ++i) { vue[itemName] = arrays[i]; vue[indexName] = i; elements.push(compile(element.cloneNode(true), false)); } if (!loopElement[varName]) { let loop = {}; loop.elements = elements; loop.parent = parent; loopElement[varName] = loop; }
for (let i = 0; i < arrays.length; ++i) { vue[itemName] = arrays[i]; vue[indexName] = i; elements.push(compile(element.cloneNode(true), false)); }
directive.change = function (name, value) { let ele = loopElement[name]; for (let i = 0; i < ele.elements.length; ++i) { ele.elements[i].remove(); } let newEles = []; let arrays = vue[this.exp.varName]; for (let i = 0; i < arrays.length; ++i) { vue[this.exp.itemName] = arrays[i]; vue[this.exp.indexName] = i; let node = compile(this.origin.cloneNode(true)); newEles.push(node); } loopElement[name].elements = newEles; for (let j = 0; j < newEles.length; ++j) { ele.parent.appendChild(newEles[j]); } } addSubscriber(varName, directive);
完整的compileLoop代碼以下框架
function compileLoop(element, parent) { let vfor = element.attributes['v-for'].value; let itemName; let indexName; let varName; let loopExp1 = /\(([^,]+),([^\)]+)\)\s+in\s+(.*)/g; let loopExp2 = /(\w+)\s+in\s+(.*)/g; let m; if (m = loopExp1.exec(vfor)) { itemName = m[1]; indexName = m[2] varName = m[3]; } else if (m = loopExp2.exec(vfor)) { itemName = m[1]; varName = m[2]; } var directive = { origin: element.cloneNode(true), attr: 'v-for', exp: { varName: varName, indexName: indexName, itemName: itemName } } element.attributes.removeNamedItem('v-for'); let arrays = vue[varName]; let elements = []; for (let i = 0; i < arrays.length; ++i) { vue[itemName] = arrays[i]; vue[indexName] = i; elements.push(compile(element.cloneNode(true), false)); } if (!loopElement[varName]) { let loop = {}; loop.elements = elements; loop.parent = parent; loopElement[varName] = loop; } directive.change = function (name, value) { let ele = loopElement[name]; for (let i = 0; i < ele.elements.length; ++i) { ele.elements[i].remove(); } let newEles = []; let arrays = vue[this.exp.varName]; for (let i = 0; i < arrays.length; ++i) { vue[this.exp.itemName] = arrays[i]; vue[this.exp.indexName] = i; let node = compile(this.origin.cloneNode(true)); newEles.push(node); } loopElement[name].elements = newEles; for (let j = 0; j < newEles.length; ++j) { ele.parent.appendChild(newEles[j]); } } addSubscriber(varName, directive); return elements; }
在上一篇中咱們的事件響應是這麼寫的
function addEvent(element, event, method) { element.addEventListener(event, function(e) { let params = []; let paramNames = method.params; if (paramNames) { for (let i = 0; i < paramNames.length; ++i) { params.push(vue[paramNames[i]]); } } vue[method.name].apply(vue, params); }) }
這麼寫對於循環有個問題,由於每次循環都會重置下標和循環變量,下標和循環變量都是保存在vue對象中的,因此當事件觸發時,params.push(vue[paramNames[i]]);
這行代碼是取不到值的由於上下文已經發生變化。解決這個問題的辦法就是閉包,經過閉包保存當時環境信息,不至於運行時丟失,只需將獲取數據移到外面就行。
function addEvent(element, event, method) { let params = []; let paramNames = method.params; if (paramNames) { for (let i = 0; i < paramNames.length; ++i) { params.push(vue[paramNames[i]]); } } element.addEventListener(event, function (e) { vue[method.name].apply(vue, params); }) }
到這裏就能夠實現v-for指令,但以前的一些遺留還未修復,咱們在dom解析這篇中提到目前對於文本節點值發生變化只是簡單的文本替換,以下:
if (node.nodeType == 3) { directive.change = function(name, value) { this.node.textContent = this.origin.replace("\{\{" + name + "\}\}", value); } }
若是有多個變量或者相似todo.text
這種多級變量結果就會出錯,這裏寫了一個專門用來解析表達的函數
if (node.nodeType == 3) { directive.change = function (name, value) { this.node.textContent = evaluteExpression(this.origin); } }
function evaluteExpression(text) { let vars = parseVariable(text); for (let i = 0; i < vars.length; ++i) { let value = getVariableValue(vars[i]); text = text.replace("\{\{" + vars[i] + "\}\}", value); } return text; }
function getVariableValue(name) { let value; if (name.indexOf(".")) { let ss = name.split("."); value = vue[ss[0]]; if (value) { for (let i = 1; i < ss.length; ++i) { value = value[ss[i]]; if (value == undefined) { break; } } } } else { value = vue[name]; } if (value == undefined || value == null) { value = ""; } return value; }
item.text
的多級變量進行循環獲取值如下是實現的效果圖,也能夠點擊這裏進行查看
完整js代碼點擊這裏查看
點擊如下連接,查看該系列其餘文章