摘要:前言經常看到講解的虛擬原理的,但很多都是在原代碼的基礎上添加些注釋等等,這里從行代碼開始實現一個的虛擬實現標簽名孩子文本節點對應的真實對象為什么這里默認把置為,不直接根據用把賦值而要等后面時候再賦值呢定義一個類,創建節點分為兩類,一類為節點
前言
經常看到講解Vue2的虛擬Dom diff原理的,但很多都是在原代碼的基礎上添加些注釋等等,這里從0行代碼開始實現一個
Vue2的虛擬DOM
src/core/vdom/Vnode.js export class VNode{ constructor ( tag, //標簽名 children,//孩子[VNode,VNode], text, //文本節點 elm //對應的真實dom對象 ){ this.tag = tag; this.children = children this.text = text; this.elm = elm; } } export function createTextNode(val){ //為什么這里默認把elm置為undefined,不直接根據tag 用document.createElement(tagName)把elm賦值?而要等后面createElm時候再賦值呢? return new VNode(undefined,undefined,String(val),undefined) } export function createCommentNode(tag,children){ if(children){ for(var i=0;i先實現不用diff把Vnode渲染到頁面中來定義一個Vnode類, 創建節點分為兩類,一類為text節點,一類非text節點
src/main.js import {VNode,createCommentNode} from "./core/vdom/vnode" var newVonde = createCommentNode("ul",[createCommentNode("li",["item 1"]),createCommentNode("li",["item 2"]),createCommentNode("li",["item 3"])])在main.js就可以根據Vnode 生成對應的Vnode對象,上述代碼對應的dom表示
item1 item2 item3
為什么先來實現不用diff渲染Vnode的部分,這里也是為了統計渲染的時間,來表明一個道理
并不是diff就比非diff要開,虛擬DOM并不是任何時候性能都比非虛擬DOM 要快
先來實現一個工具函數,不熟悉的人可以手工敲下代碼 熟悉下
// 真實的dom操作 src/core/vdom/node-ops.js export function createElement (tagName) { return document.createElement(tagName) } export function createTextNode (text) { return document.createTextNode(text) } export function createComment (text) { return document.createComment(text) } export function insertBefore (parentNode, newNode, referenceNode) { parentNode.insertBefore(newNode, referenceNode) } export function removeChild (node, child) { node.removeChild(child) } export function appendChild (node, child) { node.appendChild(child) } export function parentNode (node) { return node.parentNode } export function nextSibling (node) { return node.nextSibling } export function tagName (node) { return node.tagName } export function setTextContent (node, text) { node.textContent = text } export function setAttribute (node, key, val) { node.setAttribute(key, val) }
src/main.js import {VNode,createCommentNode} from "./core/vdom/vnode" import patch from "./core/vdom/patch" var container = document.getElementById("app"); var oldVnode = new VNode(container.tagName,[],undefined,container); var newVonde = createCommentNode("ul",[createCommentNode("li",["item 1"]),createCommentNode("li",["item 2"]),createCommentNode("li",["item 3"])]) console.time("start"); patch(oldVnode,newVonde); //渲染頁面 console.timeEnd("start");
這里我們要實現一個patch方法,把Vnode渲染到頁面中
src/core/vdom/patch.js import * as nodeOps from "./node-ops" import VNode from "./vnode" export default function patch(oldVnode,vnode){ let isInitialPatch = false; if(sameVnode(oldVnode,vnode)){ //如果兩個Vnode節點的根一致 開始diff patchVnode(oldVnode,vnode) }else{ //這里就是不借助diff的實現 const oldElm = oldVnode.elm; const parentElm = nodeOps.parentNode(oldElm); createElm( vnode, parentElm, nodeOps.nextSibling(oldElm) ) if(parentElm != null){ removeVnodes(parentElm,[oldVnode],0,0) } } return vnode.elm; } function patchVnode(oldVnode,vnode,removeOnly){ if(oldVnode === vnode){ return } const elm = vnode.elm = oldVnode.elm const oldCh = oldVnode.children; const ch = vnode.children if(isUndef(vnode.text)){ //非文本節點 if(isDef(oldCh) && isDef(ch)){ //都有字節點 if(oldCh !== ch){ //更新children updateChildren(elm,oldCh,ch,removeOnly); } }else if(isDef(ch)){ //新的有子節點,老的沒有 if(isDef(oldVnode.text)){ nodeOps.setTextContent(elm,""); } //添加子節點 addVnodes(elm,null,ch,0,ch.length-1) }else if(isDef(oldCh)){ //老的有子節點,新的沒有 removeVnodes(elm,oldCh,0,oldCh.length-1) }else if(isDef(oldVnode.text)){ //否則老的有文本內容 直接置空就行 nodeOps.setTextContent(elm,""); } }else if(oldVnode.text !== vnode.text){ //直接修改文本 nodeOps.setTextContent(elm,vnode.text); } } function updateChildren(parentElm,oldCh,newCh,removeOnly){ //這里認真讀下,沒什么難度的,不行的話 也可以搜索下圖文描述這段過程的 let oldStartIdx = 0; let newStartIdx =0; let oldEndIdx = oldCh.length -1; let oldStartVnode = oldCh[0]; let oldEndVnode = oldCh[oldEndIdx]; let newEndIdx = newCh.length-1; let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let refElm; const canMove = !removeOnly while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){ if(isUndef(oldStartVnode)){ oldStartVnode = oldCh[++oldStartIdx] }else if(isUndef(oldEndVnode)){ oldEndVnode = oldCh[--oldEndIdx] }else if(sameVnode(oldStartVnode,newStartVnode)){ patchVnode(oldStartVnode,newStartVnode) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] }else if(sameVnode(oldEndVnode,newEndVnode)){ patchVnode(oldEndVnode,newEndVnode) oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; }else if(sameVnode(oldStartVnode,newEndVnode)){ patchVnode(oldStartVnode,newEndVnode); //更換順序 canMove && nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] }else if(sameVnode(oldEndVnode,newStartVnode)){ patchVnode(oldEndVnode,newStartVnode) canMove && nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] }else{ createElm(newStartVnode,parentElm,oldStartVnode.elm) newStartVnode = newCh[++newStartIdx]; } } if(oldStartIdx > oldEndIdx){ //老的提前相遇,添加新節點中沒有比較的節點 refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx+1].elm addVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx) }else{ //新的提前相遇 刪除多余的節點 removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx) } } function removeVnodes(parentElm,vnodes,startIdx,endIdx){ for(;startIdx<=endIdx;++startIdx){ const ch = vnodes[startIdx]; if(isDef(ch)){ removeNode(ch.elm) } } } function addVnodes(parentElm,refElm,vnodes,startIdx,endIdx){ for(;startIdx <=endIdx;++startIdx ){ createElm(vnodes[startIdx],parentElm,refElm) } } function sameVnode(vnode1,vnode2){ return vnode1.tag === vnode2.tag } function removeNode(el){ const parent = nodeOps.parentNode(el) if(parent){ nodeOps.removeChild(parent,el) } } function removeVnodes(parentElm,vnodes,startIdx,endIdx){ for(;startIdx<=endIdx;++startIdx){ const ch = vnodes[startIdx] if(isDef(ch)){ removeNode(ch.elm) } } } function isDef (s){ return s != null } function isUndef(s){ return s == null } function createChildren(vnode,children){ if(Array.isArray(children)){ for(let i=0;i這就是完整實現了
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/88602.html
摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運,我不曉得。我只曉得,不認命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
摘要:本系列文章將重點分析類似于的這類框架是如何實現的,歡迎大家關注和討論。作為一個極度精簡的庫,函數是屬于本身的。 前言 首先歡迎大家關注我的掘金賬號和Github博客,也算是對我的一點鼓勵,畢竟寫東西沒法獲得變現,能堅持下去也是靠的是自己的熱情和大家的鼓勵。 之前分享過幾篇關于React的文章: React技術內幕: key帶來了什么 React技術內幕: setState的秘密...
摘要:前端日報精選未來布局之星更快地構建使用預解析以及深入的虛擬原理原來與是這樣阻塞解析和渲染的怎樣把網站升級到中文視頻從談函數式與響應式編程葉俊星系列三之煙花效果實現掘金的故事解剖表情動圖的構成設計系列傳統遞歸和尾調用的實現前端架構經 2017-09-24 前端日報 精選 未來布局之星Grid更快地構建DOM: 使用預解析, async, defer 以及 preload_JavaScri...
摘要:虛擬原理流程簡單概括有三點用模擬樹,并渲染這個樹比較新老樹,得到比較的差異對象把差異對象應用到渲染的樹。下面是流程圖下面我們用代碼一步步去實現一個流程圖用模擬樹并渲染到頁面上其實虛擬,就是用對象結構的一種映射,下面我們一步步實現這個過程。 背景 大家都知道,在網頁中瀏覽器資源開銷最大便是DOM節點了,DOM很慢并且非常龐大,網頁性能問題大多數都是有JavaScript修改DOM所引起的...
閱讀 2753·2021-10-11 10:57
閱讀 1586·2021-09-26 09:55
閱讀 1322·2021-09-06 15:11
閱讀 3465·2021-08-26 14:16
閱讀 680·2019-08-30 15:54
閱讀 548·2019-08-30 12:43
閱讀 3307·2019-08-29 16:18
閱讀 2586·2019-08-23 16:14