欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

Vue內(nèi)部渲染視圖的方法

1.什么是虛擬DOM

我們提供的服務有:成都做網(wǎng)站、成都網(wǎng)站制作、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認證、鶴峰ssl等。為近1000家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務,是有科學管理、有技術(shù)的鶴峰網(wǎng)站制作公司

  •  以前M的命令式操作DOM即使用jQuery操作DOM節(jié)點,隨著狀態(tài)的增多,DOM的操作就會越來越頻繁,程序的狀態(tài)也越難維護,現(xiàn)在主流的框架都是采用聲明式操作DOM,將操作DOM的方法封裝起來,我們只要更改數(shù)據(jù)的狀態(tài),框架本身會幫我們操作DOM。
  • 虛擬DOM根據(jù)狀態(tài)建立一顆虛擬節(jié)點樹,新的虛擬節(jié)點樹會與舊的虛擬節(jié)點樹進行對比,只渲染發(fā)生改變的部分,如下圖:

Vue內(nèi)部渲染視圖的方法

2.引入虛擬DOM的目的

  •  把渲染過程抽象化,從而使得組件的抽象能力也得到提升,并且可以適配DOM以外的渲染目標;
  • 可以更好地支持SSR、同構(gòu)渲染等;
  • 不再依賴HTML解析器進行模板解析,可以進行更多的AOT(預編譯)工作提高運行時效率,還能將Vue運行時體積進一步壓縮。

VNode的定義 Vue中定義了VNode的構(gòu)造函數(shù),這樣我們可以實例化不同的vnode 實例如:文本節(jié)點、元素節(jié)點以及注釋節(jié)點等。

var VNode = function VNode (
 tag,
 data,
 children,
 text,
 elm,
 context,
 componentOptions,
 asyncFactory
 ) {
 this.tag = tag;
 this.data = data;
 this.children = children;
 this.text = text;
 this.elm = elm;
 this.ns = undefined;
 this.context = context;
 this.fnContext = undefined;
 this.fnOptions = undefined;
 this.fnScopeId = undefined;
 this.key = data && data.key;
 this.componentOptions = componentOptions;
 this.componentInstance = undefined;
 this.parent = undefined;
 this.raw = false;
 this.isStatic = false;
 this.isRootInsert = true;
 this.isComment = false;
 this.isCloned = false;
 this.isOnce = false;
 this.asyncFactory = asyncFactory;
 this.asyncMeta = undefined;
 this.isAsyncPlaceholder = false;
 };

vnode其實就是一個描述節(jié)點的對象,描述如何創(chuàng)建真實的DOM節(jié)點;vnode的作用就是新舊vnode進行對比,只更新發(fā)生變化的節(jié)點。 VNode有注釋節(jié)點、文本節(jié)點、元素節(jié)點、組件節(jié)點、函數(shù)式組件、克隆節(jié)點:

注釋節(jié)點

var createEmptyVNode = function (text) {
 if ( text === void 0 ) text = '';
 var node = new VNode();
 node.text = text;
 node.isComment = true;
 return node
 };

只有isComment和text屬性有效,其余的默認為false或者null

文本節(jié)點

function createTextVNode (val) {
 return new VNode(undefined, undefined, undefined, String(val))
 }

只有一個text屬性

克隆節(jié)點

function cloneVNode (vnode) {
 var cloned = new VNode(
  vnode.tag,
  vnode.data,
  // #7975
  // clone children array to avoid mutating original in case of cloning
  // a child.
  vnode.children && vnode.children.slice(),
  vnode.text,
  vnode.elm,
  vnode.context,
  vnode.componentOptions,
  vnode.asyncFactory
 );
 cloned.ns = vnode.ns;
 cloned.isStatic = vnode.isStatic;
 cloned.key = vnode.key;
 cloned.isComment = vnode.isComment;
 cloned.fnContext = vnode.fnContext;
 cloned.fnOptions = vnode.fnOptions;
 cloned.fnScopeId = vnode.fnScopeId;
 cloned.asyncMeta = vnode.asyncMeta;
 cloned.isCloned = true;
 return cloned
 }

克隆節(jié)點將vnode的所有屬性賦值到clone節(jié)點,并且設置isCloned = true,它的作用是優(yōu)化靜態(tài)節(jié)點和插槽節(jié)點。以靜態(tài)節(jié)點為例,因為靜態(tài)節(jié)點的內(nèi)容是不會改變的,當它首次生成虛擬DOM節(jié)點后,再次更新時是不需要再次生成vnode,而是將原vnode克隆一份進行渲染,這樣在一定程度上提升了性能。

元素節(jié)點 元素節(jié)點一般會存在tag、data、children、context四種有效屬性,形如:

{
 children: [VNode, VNode],
 context: {...},
 tag: 'div',
 data: {attr: {id: app}}
}

組件節(jié)點 組件節(jié)點有兩個特有屬性 (1) componentOptions,組件節(jié)點的選項參數(shù),包含如下內(nèi)容:

{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }

(2) componentInstance: 組件的實例,也是Vue的實例 對應的vnode

new VNode(
  ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
  data, undefined, undefined, undefined, context,
  { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
  asyncFactory
 )

{
 componentOptions: {},
 componentInstance: {},
 tag: 'vue-component-1-child',
 data: {...},
 ...
}

函數(shù)式組件 函數(shù)組件通過createFunctionalComponent函數(shù)創(chuàng)建, 跟組件節(jié)點類似,暫時沒看到特殊屬性,有的話后續(xù)再補上。

patch

虛擬DOM最重要的功能是patch,將VNode渲染為真實的DOM。

patch簡介

patch中文意思是打補丁,也就是在原有的基礎上修改DOM節(jié)點,也可以說是渲染視圖。DOM節(jié)點的修改有三種:

  • 創(chuàng)建新增節(jié)點
  • 刪除廢棄的節(jié)點
  • 修改需要更新的節(jié)點。

當緩存上一次的oldvnode與最新的vnode不一致的時候,渲染視圖以vnode為準。

初次渲染過程

當oldvnode中不存在,而vnode中存在時,就需要使用vnode新生成真實的DOM節(jié)點并插入到視圖中。首先如果vnode具有tag屬性,則認為它是元素屬性,再根據(jù)當前環(huán)境創(chuàng)建真實的元素節(jié)點,元素創(chuàng)建后將它插入到指定的父節(jié)點。以上節(jié)生成的VNode為例,首次執(zhí)行

vm._update(vm._render(), hydrating);

vm._render()為上篇生成的VNode,_update函數(shù)具體為

Vue.prototype._update = function (vnode, hydrating) {
  var vm = this;
  var prevEl = vm.$el;
  var prevVnode = vm._vnode;
  var restoreActiveInstance = setActiveInstance(vm);
  // 緩存vnode
  vm._vnode = vnode;
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  // 第一次渲染,preVnode是不存在的
  if (!prevVnode) {
  // initial render
  vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
  } else {
  // updates
  vm.$el = vm.__patch__(prevVnode, vnode);
  }
  restoreActiveInstance();
  // update __vue__ reference
  if (prevEl) {
  prevEl.__vue__ = null;
  }
  if (vm.$el) {
  vm.$el.__vue__ = vm;
  }
  // if parent is an HOC, update its $el as well
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
  vm.$parent.$el = vm.$el;
  }
  // updated hook is called by the scheduler to ensure that children are
  // updated in a parent's updated hook.
 };

因第一次渲染,執(zhí)行 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); ,注意第一個參數(shù)是oldVnode為 vm.$el 為元素節(jié)點,__patch__函數(shù)具體過程為:

(1) 先判斷oldVnode是否存在,不存在就創(chuàng)建vnode

if (isUndef(oldVnode)) {
 // empty mount (likely as component), create new root element
 isInitialPatch = true;
 createElm(vnode, insertedVnodeQueue);
}

(2) 存在進入else,判斷oldVnode是否是元素節(jié)點,如果oldVnode是元素節(jié)點,則

if (isRealElement) {
 ...
 // either not server-rendered, or hydration failed.
 // create an empty node and replace it
 oldVnode = emptyNodeAt(oldVnode);
}

創(chuàng)建一個oldVnode節(jié)點,其形式為

{
 asyncFactory: undefined,
 asyncMeta: undefined,
 children: [],
 componentInstance: undefined,
 componentOptions: undefined,
 context: undefined,
 data: {},
 elm: div#app,
 fnContext: undefined,
 fnOptions: undefined,
 fnScopeId: undefined,
 isAsyncPlaceholder: false,
 isCloned: false,
 isComment: false,
 isOnce: false,
 isRootInsert: true,
 isStatic: false,
 key: undefined,
 ns: undefined,
 parent: undefined,
 raw: false,
 tag: "div",
 text: undefined,
 child: undefined
}

然后獲取oldVnode的元素節(jié)點以及其父節(jié)點,并創(chuàng)建新的節(jié)點

// replacing existing element
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);

// create new node
createElm(
 vnode,
 insertedVnodeQueue,
 // extremely rare edge case: do not insert if old element is in a
 // leaving transition. Only happens when combining transition +
 // keep-alive + HOCs. (#4590)
 oldElm._leaveCb ? null : parentElm,
 nodeOps.nextSibling(oldElm)
);

創(chuàng)建新節(jié)點的過程

// 標記是否是根節(jié)點
 vnode.isRootInsert = !nested; // for transition enter check
 // 這個函數(shù)如果vnode有componentInstance屬性,會創(chuàng)建子組件,后續(xù)具體介紹,否則不做處理
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
 return
}

接著在對子節(jié)點處理

var data = vnode.data;
 var children = vnode.children;
 var tag = vnode.tag;
 if (isDef(tag)) {
 ...
 vnode.elm = vnode.ns
  ? nodeOps.createElementNS(vnode.ns, tag)
  : nodeOps.createElement(tag, vnode);
 setScope(vnode);

 /* istanbul ignore if */
 {
  createChildren(vnode, children, insertedVnodeQueue);
  if (isDef(data)) {
   invokeCreateHooks(vnode, insertedVnodeQueue);
  }
  insert(parentElm, vnode.elm, refElm);
 }

 if (data && data.pre) {
  creatingElmInVPre--;
 }
 }
}

將vnode的屬性設置為創(chuàng)建元素節(jié)點elem,創(chuàng)建子節(jié)點 createChildren(vnode, children, insertedVnodeQueue); 該函數(shù)遍歷子節(jié)點children數(shù)組

function createChildren (vnode, children, insertedVnodeQueue) {
 if (Array.isArray(children)) {
  for (var i = 0; i < children.length; ++i) {
   createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
  }
 } else if (isPrimitive(vnode.text)) {
  // 如果vnode是文本直接掛載
  nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
 }
}

遍歷children,遞歸createElm方法創(chuàng)建子元素節(jié)點

else if (isTrue(vnode.isComment)) {
 vnode.elm = nodeOps.createComment(vnode.text);
 insert(parentElm, vnode.elm, refElm);
} else {
 vnode.elm = nodeOps.createTextNode(vnode.text);
 insert(parentElm, vnode.elm, refElm);
}

如果是評論節(jié)點,直接創(chuàng)建評論節(jié)點,并將其插入到父節(jié)點上,其他的創(chuàng)建文本節(jié)點,并將其插入到父節(jié)點parentElm(剛創(chuàng)建的div)上去。 觸發(fā)鉤子,更新節(jié)點屬性,將其插入到parentElm('#app'元素節(jié)點)上

{
 createChildren(vnode, children, insertedVnodeQueue);
 if (isDef(data)) {
  invokeCreateHooks(vnode, insertedVnodeQueue);
 }
 insert(parentElm, vnode.elm, refElm);
}

最后將老的節(jié)點刪掉

if (isDef(parentElm)) {
 removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
 invokeDestroyHook(oldVnode);
}
function removeAndInvokeRemoveHook (vnode, rm) {
 if (isDef(rm) || isDef(vnode.data)) {
  var i;
  var listeners = cbs.remove.length + 1;
  ...
  // recursively invoke hooks on child component root node
  if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
   removeAndInvokeRemoveHook(i, rm);
  }
  for (i = 0; i < cbs.remove.length; ++i) {
   cbs.remove[i](vnode, rm);
  }
  if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
   i(vnode, rm);
  } else {
   // 刪除id為app的老節(jié)點
   rm();
  }
 } else {
  removeNode(vnode.elm);
 }
}

初次渲染結(jié)束。

更新節(jié)點過程

為了更好地測試,模板選用

<div id="app">{{ message }}<button @click="update">更新</button></div>

點擊按鈕,會更新message,重新渲染視圖,生成的VNode為

{
 asyncFactory: undefined,
 asyncMeta: undefined,
 children: [VNode, VNode],
 componentInstance: undefined,
 componentOptions: undefined,
 context: Vue實例,
 data: {attrs: {id: "app"}},
 elm: undefined,
 fnContext: undefined,
 fnOptions: undefined,
 fnScopeId: undefined,
 isAsyncPlaceholder: false,
 isCloned: false,
 isComment: false,
 isOnce: false,
 isRootInsert: true,
 isStatic: false,
 key: undefined,
 ns: undefined,
 parent: undefined,
 raw: false,
 tag: "div",
 text: undefined,
 child: undefined
}

在組件更新的時候,preVnode和vnode都是存在的,執(zhí)行

vm.$el = vm.__patch__(prevVnode, vnode);

實際上是運行以下函數(shù)

patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly);

該函數(shù)首先判斷oldVnode和vnode是否相等,相等則立即返回

if (oldVnode === vnode) {
 return
}

如果兩者均為靜態(tài)節(jié)點且key值相等,且vnode是被克隆或者具有isOnce屬性時,vnode的組件實例componentInstance直接賦值

if (isTrue(vnode.isStatic) &&
 isTrue(oldVnode.isStatic) &&
 vnode.key === oldVnode.key &&
 (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
 vnode.componentInstance = oldVnode.componentInstance;
 return
}

接著對兩者的屬性值作對比,并更新

var oldCh = oldVnode.children;
var ch = vnode.children;
if (isDef(data) && isPatchable(vnode)) {
 for (i = 0; i < cbs.update.length; ++i) {  // 以vnode為準更新oldVnode的不同屬性
  cbs.update[i](oldVnode, vnode); 
 }
 if (isDef(i = data.hook) && isDef(i = i.update)) { 
  i(oldVnode, vnode); 
 }
}

vnode和oldVnode的對比以及相應的DOM操作具體如下:

// vnode不存在text屬性的情況
if (isUndef(vnode.text)) {
 if (isDef(oldCh) && isDef(ch)) {
 // 子節(jié)點不相等時,更新
 if (oldCh !== ch) { 
  updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); }
 } else if (isDef(ch)) {
  {
  checkDuplicateKeys(ch);
  }
  // 只存在vnode的子節(jié)點,如果oldVnode存在text屬性,則將元素的文本內(nèi)容清空,并新增elm節(jié)點
  if (isDef(oldVnode.text)) {    nodeOps.setTextContent(elm, ''); 
  }
  addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
 } else if (isDef(oldCh)) {
  // 如果只存在oldVnode的子節(jié)點,則刪除DOM的子節(jié)點
  removeVnodes(elm, oldCh, 0, oldCh.length - 1);
 } else if (isDef(oldVnode.text)) {
  // 只存在oldVnode有text屬性,將元素的文本清空
  nodeOps.setTextContent(elm, '');
 }
} else if (oldVnode.text !== vnode.text) {
 // node和oldVnode的text屬性都存在且不一致時,元素節(jié)點內(nèi)容設置為vnode.text
 nodeOps.setTextContent(elm, vnode.text);
}

對于子節(jié)點的對比,先分別定義oldVnode和vnode兩數(shù)組的前后兩個指針索引

var oldStartIdx = 0;
var newStartIdx = 0;
var oldEndIdx = oldCh.length - 1;
var oldStartVnode = oldCh[0];
var oldEndVnode = oldCh[oldEndIdx];
var newEndIdx = newCh.length - 1;
var newStartVnode = newCh[0];
var newEndVnode = newCh[newEndIdx];
var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

如下圖:

Vue內(nèi)部渲染視圖的方法

接下來是一個while循環(huán),在這過程中,oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 會逐漸向中間靠攏

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) 

當oldStartVnode或者oldEndVnode為空時,兩中間移動

if (isUndef(oldStartVnode)) {
 oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
 oldEndVnode = oldCh[--oldEndIdx];
} 

接下來這一塊,是將 oldStartIdx、newStartIdx、oldEndIdx 以及 newEndIdx 兩兩比對的過程,共四種:

else if (sameVnode(oldStartVnode, newStartVnode)) {
 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
 oldStartVnode = oldCh[++oldStartIdx];
 newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
 oldEndVnode = oldCh[--oldEndIdx];
 newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx);
 canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
 oldStartVnode = oldCh[++oldStartIdx];
 newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
 oldEndVnode = oldCh[--oldEndIdx];
 newStartVnode = newCh[++newStartIdx];
}

第一種: 前前相等比較

Vue內(nèi)部渲染視圖的方法

如果相等,則oldStartVnode.elm和newStartVnode.elm均向后移一位,繼續(xù)比較。 第二種: 后后相等比較

Vue內(nèi)部渲染視圖的方法

如果相等,則oldEndVnode.elmnewEndVnode.elm均向前移一位,繼續(xù)比較。 第三種: 前后相等比較

Vue內(nèi)部渲染視圖的方法

將oldStartVnode.elm節(jié)點直接移動到oldEndVnode.elm節(jié)點后面,然后將oldStartIdx向后移一位,newEndIdx向前移動一位。 第四種: 后前相等比較

Vue內(nèi)部渲染視圖的方法

將oldEndVnode.elm節(jié)點直接移動到oldStartVnode.elm節(jié)點后面,然后將oldEndIdx向前移一位,newStartIdx向后移動一位。 如果以上均不滿足,則

else {
 if (isUndef(oldKeyToIdx)) { 
   oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); 
 }
 idxInOld = isDef(newStartVnode.key)
  ? oldKeyToIdx[newStartVnode.key]
  : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
 if (isUndef(idxInOld)) { // New element
   createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
 } else {
   vnodeToMove = oldCh[idxInOld];
   if (sameVnode(vnodeToMove, newStartVnode)) {
      patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx);
      oldCh[idxInOld] = undefined;
      canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
   } else {
   // same key but different element. treat as new element
     createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
   }
  }
  newStartVnode = newCh[++newStartIdx];
}

createkeyToOldIdx函數(shù)的作用是建立key和index索引對應的map表,如果還是沒有找到節(jié)點,則新創(chuàng)建節(jié)點

createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);

插入到oldStartVnode.elm節(jié)點前面,否則,如果找到了節(jié)點,并符合sameVnode,將兩個節(jié)點patchVnode,并將該位置的老節(jié)點置為undefined,同時將vnodeToMove.elm移到oldStartVnode.elm的前面,以及newStartIdx往后移一位,示意圖如下:

 Vue內(nèi)部渲染視圖的方法

如果不符合sameVnode,只能創(chuàng)建一個新節(jié)點插入到 parentElm 的子節(jié)點中,newStartIdx 往后移動一位。 最后如果,oldStartIdx > oldEndIdx,說明老節(jié)點比對完了,但是新節(jié)點還有多的,需要將新節(jié)點插入到真實 DOM 中去,調(diào)用 addVnodes 將這些節(jié)點插入即可;如果滿足 newStartIdx > newEndIdx 條件,說明新節(jié)點比對完了,老節(jié)點還有多,將這些無用的老節(jié)點通過 removeVnodes 批量刪除即可。到這里這個過程基本結(jié)束。

總結(jié)

以上所述是小編給大家介紹的Vue內(nèi)部渲染視圖的方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對創(chuàng)新互聯(lián)網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!

文章題目:Vue內(nèi)部渲染視圖的方法
分享路徑:http://www.chinadenli.net/article20/jogeco.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供定制網(wǎng)站外貿(mào)建站網(wǎng)站收錄虛擬主機網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設計