在上一篇中,咱們保證了代碼不報錯,而且參數也成功傳進去了,但未實現任何的邏輯,這一章咱們的工做就是完成dom的解析,在vue中,經過構建虛擬dom結構實現dom的高效更新和渲染,模仿這個思路,咱們構建一個超級簡單虛擬的dom結構,本着讓全部人都能看得懂的目的,此次構建的dom結構不考慮性能,不考慮設計,以能實現功能爲目的。javascript
基本的思路:html
(function (global, factory) { global.Vue = factory })(this, function (options) { //1. 解析dom結構 domParser(); function domParser() { let el = options.el; //配置的el以'#'開頭 if (el.startsWith("#")) { el = el.substr(1); } let app = document.getElementById(el); let virtualDom = document.createDocumentFragment(); for (let i = 0; i < app.childNodes.length; ++i) { let node = compile(app.childNodes[i]); virtualDom.appendChild(node); } app.innerHTML = ''; app.appendChild(virtualDom); } })
createDocumentFragment
建立虛擬dom的頂層節點compile
的實現,在compile中咱們將實現變量的解析,事件掛載等,但這裏先無論app.innerHTML = '';
暴力的將原始內容所有清除app.appendChild(virtualDom);
插入虛擬dom結構在dom結構中,每個節點都是一個Node,Node有不一樣的類型,雖然標準中類型較多,但經常使用的就兩種,元素類型(Node.ELEMENT_NODE)和文本類型(Node.TEXT_NODE),好比<p>hello</p>
包含兩種類型,<p>
爲元素類型,hello
爲文本類型。ELEMENT_NODE值爲1,TEXT_NODE爲3,在compile中,這兩種類型的解析方式不同,TEXT只需解析文本便可,ELEMENT類型須要解析屬性。vue
function compile(node) { let element = node.cloneNode(false); for (let i = 0; i < node.childNodes.length; ++i) { element.appendChild(compile(node.childNodes[i])); } if (element.nodeType == 3) { //文本類型解析 let vars = parseVariable(element.textContent); for (let i = 0; i < vars.length; ++i) { let directive = Directive(element); addSubscriber(vars[i], directive); } } else if (element.nodeType == 1 && element.attributes) { //元素類型解析 let attrs = element.attributes; for (let i = 0; i < attrs.length; ++i) { let name = attrs[i].name; if (name.startsWith("v-bind") || name.startsWith(":")) { let directive = Directive(element, name, attr[i].value); addSubscriber(vars[i], directive); } } } return element; }
cloneNode(false)
克隆當前節點,false表示不復制子節點,由於咱們須要對子節點進行逐一的解析parseVariable
解析文本中變量,也就是相似{{param}}
字符串v-bind
或者:
開頭就認爲綁定了變量function parseVariable(content) { var variables = {}; let m; let variableRegExp = new RegExp("\{\{([^\}]+\)}\}", "g"); while (m = variableRegExp.exec(content)) { if (!variables[m[1]]) { variables[m[1]] = true; } } var items = []; for (k in variables) { items.push(k); } return items; }
這裏直接用正則對變量進行解析。java
這裏是借鑑了vue的概念,在vue中指令能夠完成一系列特定的功能,好比指令v-model
能夠將值綁定到變量,v-for
能夠對變量進行循環。咱們此次只完成頁面上使用到指令,咱們在這個系列中將實現如下指令node
function Directive(node, attr, expression) { var directive = { node: node } if (node.nodeType == 3) { directive.change = function (value) { this.node.textContent = value; } } else if (node.nodeType == 1) { if (attr === 'title') { directive.change = function (value) { this.node.title = value; } } else if (attr === 'v-model') { directive.change = function (value) { this.node.value = value; } node.addEventListener(('input', function (e) { valueTrigger(expression, e.target.value); })) } else if (attr === 'style') { directive.change = function(name, value) { this.node.style = this.origin.replace("\{\{" + name + "\}\}", value); } } } return directive; }
定義了一個directive的對象,該對象包含如下屬性vuex
對於文本類型節點(node.nodeType == 3),直接將變量的內容複製給節點,固然這個確定是不對的,會將其餘內容覆蓋,但請放心,咱們下一節會解決這個問題,我只不過不想給這一篇引入太多內容,致使你們消化不良。對於元素類型(node.nodeType == 1),若是是雙向綁定,會給節點加上一個input的事件,監聽節點值的變化,並執行valueTrigger的邏輯,該函數邏輯下一篇再講express
若是一個節點綁定了變量,那麼這個節點就是一個Subscriber(訂閱者),變量值發生變化會調用節點相關指令(Directive),咱們聲明一個subscriber的變量用於存放訂閱者信息,以變量的名稱做爲key,指令做爲值,好比有A和B兩個節點綁定了變量name
,那麼subscriber的結構以下json
{ name:[ { node:A, change:function(){...} },{ node:B, change:function(){...} }] }
addSubscriber
代碼以下:app
var subscriber = Object.create(null); function addSubscriber(variableName, directive) { let item = subscriber[variableName]; if (!item) { item = []; } item.push(directive); subscriber[variableName] = item; }
解析節點---->解析變量---->根據綁定的類型關聯指令--->新增訂閱者 | |--->雙向綁定監聽值變化--->值變化觸發事件
點擊這裏查看代碼和效果框架
點擊餘下連接,查看該系列其餘文章