javascript --- Vue初始化 模板渲染

不帶響應式的Vue縮減實現

模板

現有模板如下:

<div id ="app"><div class="c1"><div title='tt1' id="id">{{ name }}</div><div title='tt2' >{{age}}</div><div>hello3</div></div><ul><li>1</li><li>2</li><li>3</li></ul>
</div>
<script>let app = new Vue({el: '#app',data:{name: '張三',age: 19}})
</script>

Vue初始化流程

Vue的初始化,是從new Vue開始的,以下的圖中可以知道在new Vue后,會執行init,再$mount實現掛載,再到編譯compile,生成render函數,接下來是響應式依賴收集,通過pach實現異步更新。render function會被轉化為Vnode節點,Virtual DOM是一棵以JavaScript對象(Vnode節點)為基礎的樹。是對真實DOM的描述。通過patch()轉化為真實DOM。在數據有變化時,會通過setter -> Watcher -> update來更新視圖。整個Vue的運行機制大致就是這樣

img

實現

  • 在這里實現new Vue -> $mount -> compile -> render function -> Virtual DOM Tree -> patch() -> DOM,即除了響應式的部分.

  • 簡略版

【流程梳理】:

  • 首先要明確目的,我們需要將現有的HTML模板與數據結合,生成一個新的HTML結構,并渲染到頁面上.考慮到性能問題,我們首先將模板讀取到內存中(源代碼是進行HTML解析,生成一棵抽象AST).在這里使用帶mustcache語法的HTML模板代替.

  • 首先是執行new Vue,在Vue函數中會將傳入的數據和模板保存起來,為了后續的方便,會將模板及其父元素也保存起來,然后執行mount

function Vue(options){let elm = document.querySelector(options.el)this._data = options.datathis._template = elmthis._parent = elm.parentNodethis.mount()
}
  • 然后是mount函數,在里面做了2件事:
    • 第一件事是將HTML讀取為AST保存在內存中,并返回一個根據AST 和 data 生成 虛擬DOM的render函數
    • 第二件事是調用mountComponent: 將render函數生成的VNode(虛擬DOM)轉換成真實的HTML節點渲染到頁面上

【先看第一件事】

Vue.prototype.mount = function(){this.render = this.createRenderFn()
}
Vue.prototype.createRenderFn = function(){let AST = getVNode(this._template)return function render(){let _tmp = combine(AST, this._data)return _tmp}
}

上面在mount中調用了createRenderFn,生成了一個render函數(AST + DATA -> VNode). 之所以寫出那種形式,

是因為AST僅在一開始讀取DOM結構時候就固定不變了,采用上面的寫法可以提高性能.

getVNode函數根據模板,返回帶mustache語法的虛擬DOM.更多參考

class VNode {constructor(tag ,data, value, type){this.tag = tag && tag.toLowerCase()this.data = datathis.value = valuethis.type = typethis.children = []}appendChild(vnode){this.children.push(vnode)}
}
function getVNode(node){let nodeType = node.nodeTypelet _vnode = nullif(nodeType == 1){// 元素節點let tag = node.nodeName,attrs = node.attributes,_data = {}for(let i = 0, len = attrs.length; i < len; i++){_data[attrs[i].nodeName] = attrs[i].nodeValue}_vnode = new VNode(tag, _data, undefined, nodeType)// 考慮子元素let childNodes = node.childNodes;for(let i = 0, len = childNodes.length; i< len; i++){_vnode.appendChild(getVNode(childNodes[i]))}} else if(nodeType == 3){// 文本節點_vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)}return _vnode
}

此時得到的是一個對象,這個對象中的值類似{{name}}(模擬了AST),下面使用combine將該對象模板與數據結合生成一個新的對象(在Vue中是虛擬的DOM)。即將mustache語法用真實的數據替換

function combine(vnode ,data){let _type = vnode.type, _data = vnode.data, _tag = vnode.tag, _value = vnode.value, _children = vnode.children, _vnode = nullif(_type == 3){// 文本節點_value = _value.replace(/\{\{(.+?)\}\}/g, function(_, g){return getValueByPath(data, g.trim())})_vnode = new VNode(_tag, _data, _value, _type)} else if(_type == 1){// 元素節點_vnode = new VNode(_tag, _data, _value, _type)_children.forEach(_subVNode => _vnode.appendChild(combine(_subVNode, data)))}return _vnode
}
// getValueByPath,深層次獲取對象的數據. 栗子: 獲取 a.name.age.salary
function getValueByPath(obj, path){let res=obj, currProp, props = path.join('.')while(currProp = props.shift()){res = res[props]}return res
}

【再看第二件事】

mountComponent中會使用第一件事中的render函數將AST和Data結合起來生成虛擬DOM,然后調用this.update方法將虛擬DOM渲染到頁面上

Vue.prototype.mountComponent = function(){let mount = () => {this.update(this.render())}mount.call(this)
}
// 之所以采用this.update,是因為update后面會交付給watcher來調用的
Vue.prototype.update = function (vnode){let realDOM = parseVNode(vnode)this._parent.replaceChild(realDOM, this._template)
}
function parseVNode(vnode){let type = vnode.type, _node = nullif(type ==3){return document.createTextNode(vnode.value)} else if (type == 1){_node = document.createElement(vnode.tag)let data = vnode.dataObject.keys(data).forEach(key => {_node.setAttribute(key, data[key])})let children = vnode.childrenchildren.forEach(subvnode =>{_node.appendChild(parseNode(subvnode))})}return _node
}

整體代碼

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"><div class="c1"><div title="tt1" id="id">{{ name }}</div><div title="tt2">{{age}}</div><div>hello3</div><ul><li>1</li><li>2</li><li>3</li></ul></div></div><script>/* 虛擬DOM 構造函數 */class VNode {constructor(tag, data, value, type) {this.tag = tag && tag.toLowerCase()this.data = datathis.value = valuethis.type = typethis.children = []}appendChild(vnode) {this.children.push(vnode)}}/* HTML DOM -> VNode(帶坑的Vnode): 將這個函數當做 compiler 函數  *//*Vue中會將真實的DOM結構當作字符串去解析得到一棵 AST此處使用帶有mustache語法的虛擬DOM來代替 AST*/function getVNode(node) {let nodeType = node.nodeTypelet _vnode = nullif (nodeType == 1) {// 元素let nodeName = node.nodeNamelet attrs = node.attributeslet _attrObj = {}for (let i = 0; i < attrs.length; i++) {_attrObj[attrs[i].nodeName] = attrs[i].nodeValue}_vnode = new VNode(nodeName, _attrObj, undefined, nodeType)// 考慮node的子元素let childNodes = node.childNodesfor (let i = 0; i < childNodes.length; i++) {_vnode.appendChild(getVNode(childNodes[i]))}} else if (nodeType == 3) {_vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)}return _vnode}/* 將虛擬DOM轉換成真正的DOM */function parseVNode(vnode){// 創建真實的DOMlet type = vnode.type;let _node = null;if( type == 3){return document.createTextNode(vnode.value)} else if(type == 1){_node = document.createElement(vnode.tag)// 屬性let data = vnode.data  // 現在這個data是鍵值對Object.keys(data).forEach((key)=>{let attrName = keylet attrValue = data[key]_node.setAttribute(attrName, attrValue)})// 子元素let children = vnode.children;children.forEach(subvnode =>{_node.appendChild(parseVNode(subvnode))})return _node}}const mustache = /\{\{(.+?)\}\}/g // 匹配{{}}的正則表達式// 根據路徑訪問對象成員function getValueByPath(obj, path) {let res = obj,currProp,props = path.split('.')while ((currProp = props.shift())) {res = res[currProp]}return res}/*模擬 AST -> VNode 的過程將帶有坑(mustache語法)的VNode與數據data結合,得到填充數據的VNode:*/function combine(vnode, data) {let _type = vnode.typelet _data = vnode.datalet _tag = vnode.taglet _value = vnode.valuelet _children = vnode.childrenlet _vnode = nullif (_type == 3) {// 文本節點// 對文本處理_value = _value.replace(mustache, function(_, g) {return getValueByPath(data, g.trim())})_vnode = new VNode(_tag, _data, _value, _type)} else if (_type == 1) {// 元素節點_vnode = new VNode(_tag, _data, _value, _type)_children.forEach(_subVNode => _vnode.appendChild(combine(_subVNode, data)))}return _vnode}function JGVue(options) {// this._options = options;this._data = options.datalet elm = document.querySelector(options.el)this._template = elmthis._parent = elm.parentNodethis.mount() // 掛載}JGVue.prototype.mount = function() {// 需要提供一個render方法: 生成虛擬DOM// if(typeof this._options.render !== 'function'){// }this.render = this.createRenderFn() // 帶有緩存this.mountComponent()}JGVue.prototype.mountComponent = function() {// 執行mountComponent()let mount = () => {// update將虛擬DOM渲染到頁面上this.update(this.render())}mount.call(this) // 本質上應該交給 watcher 來調用// 為什么// this.update(this.render())  // 使用發布訂閱模式,渲染和計算的行為應該交給watcher來完成}/*在真正的Vue中,使用了二次提交的設計結構第一次提交是在內存中,在內存中確定沒有問題了在修改硬盤中的數據1. 在頁面中的DOM和虛擬DOM是一一對應的關系*/// 這里是生成render函數,目的是緩存抽象語法樹(我們使用虛擬DOM來模擬)JGVue.prototype.createRenderFn = function() {let AST = getVNode(this._template)// 將 AST + data => VNode// 我們: 帶坑的VNode + data => 含有數據的 VNodereturn function render() {// 將帶坑的VNode轉換為真正帶數據的VNodelet _tmp = combine(AST, this._data)return _tmp}}// 將虛擬DOM熏染到頁面中: diff算法就在這里JGVue.prototype.update = function(vnode) {// 簡化,直接生成HTML DOM replaceChild 到頁面中// 父元素.replaceChild(新元素,舊元素)let realDOM = parseVNode(vnode)// debuggerthis._parent.replaceChild(realDOM, document.querySelector('#app'))// 這個算法是不負責任的// 每次都會將頁面中的DOM全部替換}let app = new  ({el: '#app',data: {name: '張三',age: 19}})</script></body>
</html>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/250269.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/250269.shtml
英文地址,請注明出處:http://en.pswp.cn/news/250269.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

#RANK_1 極其簡單的遞歸——騎士與金幣

2000:金幣 總時間限制: 1000ms內存限制: 65536kB描述國王將金幣作為工資&#xff0c;發放給忠誠的騎士。第一天&#xff0c;騎士收到一枚金幣&#xff1b;之后兩天&#xff08;第二天和第三天&#xff09;里&#xff0c;每天收到兩枚金幣&#xff1b;之后三天&#xff08;第四、…

動手動腦4

import java.io.*; public class ThrowMultiExceptionsDemo { public static void main(String[] args) { try { throwsTest(); } catch(IOException e) { System.out.println("捕捉異常"); }}private static void throwsTest() throws ArithmeticException,IOExcep…

javascript --- 對象原型

對象原型 參考 - MDN Javascript中的原型 在Javascript中,每一個函數都有一個特殊的屬性,叫做原型 下面獲取函數的原型fn.prototype function f1(){} console.log(f1.prototype) /*{constructor: f f1()__proto__:{constructor: f Object()__defineGetter__: f __defineGe…

從零認識單片機(9)

keil軟件&#xff1a; IDE:IDE是集成開發環境&#xff0c;就是用來開發的完整的軟件系統。 keil和mdk: keil:只能用來開發單片機 mdk:基于keil 拓展ARM的開發&#xff0c;主要用來開發ARM-cortex-m系列單片機的程序。 使用keil打開已有的工程項目&#xff1a; 1、IDE開發軟件&a…

javascript --- vue2.x中原型的使用(攔截數組方法) 響應式原理(部分)

說明 在Vue2.x中,利用了對原型鏈的理解,巧妙的利用JavaScript中的原型鏈,實現了數組的pop、push、shift、unshift、reverse、sort、splice等的攔截. 你可能需要的知識 參考 - MDN 原型鏈 JavaScript常被描述為一種基于原型的語言(prototype-based language),每個對象擁有一…

dubbo-admin構建報錯

dubbo-admin構建報錯 意思是maven庫里沒有dubbo2.5.4-SNAPSHOT.jar這個版本的dubbo的jar包&#xff0c;把dubbo-admin項目的pom.xml的   <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${proje…

javascript --- 手寫Promise、快排、冒泡、單例模式+觀察者模式

手寫promise 一種異步的解決方案, 參考 Promise代碼基本結構 function Promise(executor){this.state pending;this.value undefined;this.reason undefined;function resolve(){}function reject(){} } module.exports Promisestate保存的是當前的狀態,在Promise狀態發…

PyCharm 通過Github和Git上管理代碼

1.Pycharm中設置如圖: 2.配置Git,通過網頁 https://www.git-scm.com/download/win 下載 3. 轉載于:https://www.cnblogs.com/0909/p/9956406.html

【BZOJ】2395: [Balkan 2011]Timeismoney

題解 最小乘積生成樹&#xff01; 我們把&#xff0c;x的總和和y的總和作為x坐標和y左邊&#xff0c;畫在坐標系上 我們選擇兩個初始點&#xff0c;一個是最靠近y軸的A&#xff0c;也就是x總和最小&#xff0c;一個是最靠近x軸的B&#xff0c;也就是y總和最小 連接兩條直線&…

http --- http與https相關概念小結

網絡協議 參考 HTTP的特性 HTTP協議構建于TCP/IP協議之上,是一個應用層協議,默認端口是80HTTP是無連接無狀態的 HTTP報文 請求報文 HTTP協議是以ASCII碼傳輸,建立在 TCP/IP 協議之上的應用層規范。規范把HTTP請求分為三個部分:狀態行、請求頭、消息主體。 <method>…

Spring AOP注解方式實現

簡介 上文已經提到了Spring AOP的概念以及簡單的靜態代理、動態代理簡單示例&#xff0c;鏈接地址&#xff1a;https://www.cnblogs.com/chenzhaoren/p/9959596.html 本文將介紹Spring AOP的常用注解以及注解形式實現動態代理的簡單示例。 常用注解 aspect&#xff1a;定義切面…

享元模式-Flyweight(Java實現)

享元模式-Flyweight 享元模式的主要目的是實現對象的共享,即共享池,當系統中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。 本文中的例子如下: 使用享元模式: 小明想看編程技術的書, 就到家里的書架上拿, 如果有就直接看, 沒有就去買一本, 回家看. 看完了就放到家里…

算法 --- 回溯法

回溯法 參考 - 劍指Offer 回溯法可以看成蠻力法的升級版,它從解決問題每一步的所有可能選項里系統地選擇出一個可行的解決方案. 回溯法解決的問題的特性: 可以形象地用樹狀結構表示: 節點: 算法中的每一個步驟節點之間的連接線: 每個步驟中的選項,通過每一天連接線,可以到達…

013.Zabbix的Items(監控項)

一 Items簡介 Items是從主機里面獲取的所有數據&#xff0c;可以配置獲取監控數據的方式、取值的數據類型、獲取數值的間隔、歷史數據保存時間、趨勢數據保存時間、監控key的分組等。通常情況下item由key參數組成&#xff0c;如監控項中需要獲取cpu信息&#xff0c;則需要一個對…

Cookie 和 Session的區別

pass 下次再寫轉載于:https://www.cnblogs.com/nieliangcai/p/9073520.html

算法 --- 記一道面試dp算法題

題目: 給定一個數組(長度大于1),如下 let a [1,4,3,4,5] // 長度不確定,數值為整數要求寫一個函數,返回該數組中,除本身數字之外其他元素的成積.即返回如下: // 過程[4*3*4*5, 1*3*4*5, 1*4*4*5, 1*4*3*5, 1*4*3*4] // 結果[240, 60, 80, 60, 48]題目要求不使用除法,且時間…

編碼

一、什么是編碼&#xff1f;首先&#xff0c;我們從一段信息即消息說起&#xff0c;消息以人類可以理解、易懂的表示存在。我打算將這種表示稱為“明文”&#xff08;plain text&#xff09;。對于說英語的人&#xff0c;紙張上打印的或屏幕上顯示的英文單詞都算作明文。其次&a…

ASP.NET MVC 實現頁落網資源分享網站+充值管理+后臺管理(10)之素材管理

源碼下載地址&#xff1a;http://www.yealuo.com/Sccnn/Detail?KeyValuec891ffae-7441-4afb-9a75-c5fe000e3d1c 素材管理模塊也是我們這個項目的核心模塊&#xff0c;里面的增刪查改都跟文章管理模塊相同或者相似&#xff0c;唯一不同點可能是對附件的上傳處理&#xff0c;但…

javascript --- [express+ vue2.x + elementUI]登陸的流程梳理

說明 涉及到以下知識點: 登陸的具體流程express、vue2.x、elementUI、axios、jwt、assert 登陸方面的API使用中間件的使用前后端通過http狀態碼,進行響應的操作(這里主要是401)密碼驗證(bcrypt的hashSync方法對明文密碼進行加密,compareSync方法對加密的密碼進行驗證)token的…

設計模式---裝飾模式

今天學習了裝飾模式&#xff0c;做個筆記。。裝飾模式的基礎概念可以參考&#xff1a;https://blog.csdn.net/cjjky/article/details/7478788 這里&#xff0c;就舉個簡單例子 孫悟空有72變&#xff0c;但是它平時是猴子&#xff0c;遇到情況下&#xff0c;它可以變成蝴蝶等等 …