1. 程式人生 > >vue的原始碼學習之一——Snabbdom原始碼學習

vue的原始碼學習之一——Snabbdom原始碼學習

參考資料:

1、vue2原始碼學習開胃菜——snabbdom原始碼學習(二)

2、vue2原始碼學習開胃菜——snabbdom原始碼學習(一)

3、Vue 原理解讀系列(一) 之 Virtual DOM and Diff

1、Snabbdom 簡介

Snabbdom 非常地簡潔,其核心程式碼只有大約 200 行,並且支援可擴充套件性良好,它擁有一個模組化的結構,允許自定模組,而為了保持核心部分的簡潔,所有非必要的功能都統一代理給了模組來實現,與此同時 Snabbdom 還擁有很不錯的效能

程式碼結構

├── h.ts 建立 VNode
├── helpers
│   └── attachto.ts
├── hooks.ts 生命週期/鉤子
├── htmldomapi.ts DOM API 對映
├── is.ts
├── modules 模組程式碼
│   ├── attributes.ts
│   ├── class.ts
│   ├── dataset.ts
│   ├── eventlisteners.ts
│   ├── hero.ts
│   ├── module.ts
│   ├── props.ts
│   └── style.ts
├── snabbdom.bundle.ts
├── snabbdom.ts 核心程式碼
├── thunk.ts
├── tovnode.ts 轉換真實 DOM 節點至 VNode
└── vnode.ts VNode 定義

2、模組 Module

模組其實就是特殊的鉤子函式的集合,Snabbdom 中的模組都是在某些特定的鉤子裡觸發模組所對應的操作。還記得先前的目錄結構嗎?Snabbdom 提供了一些預設的模組,足夠我們操作 DOM 節點的各方面

├── modules 模組程式碼
│   ├── attributes.ts 操作屬性的模組
│   ├── class.ts 操作 class 的模組
│   ├── dataset.ts  操作 dataset 的模組
│   ├── eventlisteners.ts 操作事件的模組
│   ├── hero.ts 某個展示特定動效的模組(用於 example 展示,可不用理會)
│   ├── module.ts 模組的定義
│   ├── props.ts 操作 props 的模組
│   └── style.ts 操作 style 的模組

2.1 attributes.ts
      主要功能如下:
     從elm的屬性中刪除vnode中不存在的屬性(包括那些boolean類屬性,如果新vnode設定為false,同樣刪除)
     如果oldvnode與vnode用同名屬性,則在elm上更新對應屬性值
     如果vnode有新屬性,則新增到elm中
    如果存在名稱空間,則用setAttributeNS設定

import {VNode, VNodeData} from '../vnode';
import {Module} from './module';

// 全域性型別聲明裡的名稱將被引入整個 TypeScript 全域性名稱空間中,從引用這個 宣告檔案起就可以自由使用。
declare global {
  interface Element {
    setAttribute(name: string, value: string | number | boolean): void;
    setAttributeNS(namespaceURI: string, qualifiedName: string, value: string | number | boolean): void;
  }
}

export type Attrs = Record<string, string | number | boolean>

const xlinkNS = 'http://www.w3.org/1999/xlink';
const xmlNS = 'http://www.w3.org/XML/1998/namespace';
const colonChar = 58;
const xChar = 120;

function updateAttrs(oldVnode: VNode, vnode: VNode): void {
  var key: string, elm: Element = vnode.elm as Element,
      oldAttrs = (oldVnode.data as VNodeData).attrs,
      attrs = (vnode.data as VNodeData).attrs;
    //如果舊節點和新節點都不包含屬性,立刻返回
  if (!oldAttrs && !attrs) return;
  if (oldAttrs === attrs) return;
  oldAttrs = oldAttrs || {};
  attrs = attrs || {};

  // update modified attributes, add new attributes
  // 更新改變了的屬性,新增新的屬性
  for (key in attrs) {
    const cur = attrs[key];
    const old = oldAttrs[key];
    //如果舊的屬性和新的屬性不同
    if (old !== cur) {
        //如果是boolean類屬性,當vnode設定為falsy value時,直接刪除,而不是更新值
      if (cur === true) {
        elm.setAttribute(key, "");
      } else if (cur === false) {
        elm.removeAttribute(key);
      } else {
        if (key.charCodeAt(0) !== xChar) {
          elm.setAttribute(key, cur);
        } else if (key.charCodeAt(3) === colonChar) {
          // Assume xml namespace
          elm.setAttributeNS(xmlNS, key, cur);
        } else if (key.charCodeAt(5) === colonChar) {
          // Assume xlink namespace
          elm.setAttributeNS(xlinkNS, key, cur);
        } else {
          elm.setAttribute(key, cur);
        }
      }
    }
  }
  // remove removed attributes
  // use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
  // the other option is to remove all attributes with value == undefined
    //刪除不在新節點屬性中的舊節點的屬性
  for (key in oldAttrs) {
    if (!(key in attrs)) {
      elm.removeAttribute(key);
    }
  }
}

export const attributesModule = {create: updateAttrs, update: updateAttrs} as Module;
export default attributesModule;

2.2 class.ts
      主要功能如下:
       從elm中刪除vnode中不存在的或者值為false的類 將vnode中新的class新增到elm上去

       

import {VNode, VNodeData} from '../vnode';
import {Module} from './module';

export type Classes = Record<string, boolean>


/*從elm中刪除vnode中不存在的或者值為false的類
將vnode中新的class新增到elm上去*/

function updateClass(oldVnode: VNode, vnode: VNode): void {
  var cur: any, name: string, elm: Element = vnode.elm as Element,
      oldClass = (oldVnode.data as VNodeData).class,
      klass = (vnode.data as VNodeData).class;
  //如果舊節點和新節點都沒有class,直接返回
  if (!oldClass && !klass) return;
    //如果舊節點和新節點都有class,直接返回
  if (oldClass === klass) return;
  oldClass = oldClass || {};
  klass = klass || {};

  //從舊節點中刪除新節點不存在的類
  for (name in oldClass) {
    if (!klass[name]) {
      elm.classList.remove(name);
    }
  }

  //如果新節點中對應舊節點的類設定為false,則刪除該類,如果新設定為true,則新增該類
  for (name in klass) {
    cur = klass[name];
    if (cur !== oldClass[name]) {
      (elm.classList as any)[cur ? 'add' : 'remove'](name);
    }
  }
}

export const classModule = {create: updateClass, update: updateClass} as Module;
export default classModule;

2.3 dataset.ts
      主要功能如下:
     從elm中刪除vnode不存在的屬性集中的屬性 更新屬性集中的屬性值

import {VNode, VNodeData} from '../vnode';
import {Module} from './module';

export type Dataset = Record<string, string>;

const CAPS_REGEX = /[A-Z]/g;

/*dataset
主要功能如下:從elm中刪除vnode不存在的屬性集中的屬性
更新屬性集中的屬性值
*/

function updateDataset(oldVnode: VNode, vnode: VNode): void {
  let elm: HTMLElement = vnode.elm as HTMLElement,
    oldDataset = (oldVnode.data as VNodeData).dataset,
    dataset = (vnode.data as VNodeData).dataset,
    key: string;
    //如果新舊節點都沒資料集,則直接返回
  if (!oldDataset && !dataset) return;
  if (oldDataset === dataset) return;
  oldDataset = oldDataset || {};
  dataset = dataset || {};
  const d = elm.dataset;
  //刪除舊節點中在新節點不存在的資料集
  for (key in oldDataset) {
    if (!dataset[key]) {
      if (d) {
        if (key in d) {
          delete d[key];
        }
      } else {
        elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase());
      }
    }
  }
  //更新資料集
  for (key in dataset) {
    if (oldDataset[key] !== dataset[key]) {
      if (d) {
        d[key] = dataset[key];
      } else {
        elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]);
      }
    }
  }
}

export const datasetModule = {create: updateDataset, update: updateDataset} as Module;
export default datasetModule;

2.4 eventlisteners.ts
     snabbdom中對事件處理做了一層包裝,真實DOM的事件觸發的是對vnode的操作 ,主要途徑是 createListner => 返回handler作事件監聽生成器 =>handler上繫結vnode =>將handler作真實DOM的事件處理器 ,真實DOM事件觸發後 => handler獲得真實DOM的事件物件 => 將真實DOM事件物件傳入handleEvent => handleEvent找到 //對應的vnode事件處理器,然後呼叫這個處理器從而修改vnode

import {VNode, VNodeData} from '../vnode';
import {Module} from './module';

export type On = {
  [N in keyof HTMLElementEventMap]?: (ev: HTMLElementEventMap[N]) => void
} & {
  [event: string]: EventListener
};

//snabbdom中對事件處理做了一層包裝,真實DOM的事件觸發的是對vnode的操作
//主要途徑是
// createListner => 返回handler作事件監聽生成器 =>handler上繫結vnode =>將handler作真實DOM的事件處理器
//真實DOM事件觸發後 => handler獲得真實DOM的事件物件 => 將真實DOM事件物件傳入handleEvent => handleEvent找到
//對應的vnode事件處理器,然後呼叫這個處理器從而修改vnode

//對vnode進行事件處理

function invokeHandler(handler: any, vnode?: VNode, event?: Event): void {
  if (typeof handler === "function") {
    // call function handler
    //將事件處理器在vnode上呼叫
    handler.call(vnode, event, vnode);
  } //存在事件繫結資料或者存在多事件處理器
  else if (typeof handler === "object") {
    //說明只有一個事件處理器
    if (typeof handler[0] === "function") {
     //如果繫結資料只有一個,則直接將資料用call的方式呼叫,提高效能
     //形如on:{click:[handler,1]}
      if (handler.length === 2) {
        handler[0].call(vnode, handler[1], event, vnode);
      }
      //如果存在多個繫結資料,則要轉化為陣列,用apply的方式呼叫,而apply效能比call差
      //形如:on:{click:[handler,1,2,3]}
      else {
        //如果存在多個相同事件的不同處理器,則遞迴呼叫
        // 如on:{click:[[handeler1,1],[handler,2]]}
        var args = handler.slice(1);
        args.push(event);
        args.push(vnode);
        handler[0].apply(vnode, args);
      }
    } else {
      // call multiple handlers
      for (var i = 0; i < handler.length; i++) {
        invokeHandler(handler[i]);
      }
    }
  }
}
/**
 *
 * @param event 真實dom的事件物件
 * @param vnode
 */
function handleEvent(event: Event, vnode: VNode) {
  var name = event.type,
      on = (vnode.data as VNodeData).on;

  // 如果找到對應的vnode事件處理器,則呼叫
  if (on && on[name]) {
    invokeHandler(on[name], vnode, event);
  }
}
//事件監聽器生成器,用於處理真實DOM事件
function createListener() {
  return function handler(event: Event) {
    handleEvent(event, (handler as any).vnode);
  }
}
//更新事件監聽
function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
  var oldOn = (oldVnode.data as VNodeData).on,
      oldListener = (oldVnode as any).listener,
      oldElm: Element = oldVnode.elm as Element,
      on = vnode && (vnode.data as VNodeData).on,
      elm: Element = (vnode && vnode.elm) as Element,
      name: string;

    // optimization for reused immutable handlers
    //如果新舊事件監聽器一樣,則直接返回
  if (oldOn === on) {
    return;
  }

  // remove existing listeners which no longer used
    //如果新節點上沒有事件監聽,則將舊節點上的事件監聽都刪除
  if (oldOn && oldListener) {
    // if element changed or deleted we remove all existing listeners unconditionally
    if (!on) {
      for (name in oldOn) {
        // remove listener if element was changed or existing listeners removed
       //刪除舊節點中新節點不存在的事件監聽
        oldElm.removeEventListener(name, oldListener, false);
      }
    } else {
      for (name in oldOn) {
        // remove listener if existing listener removed
        if (!on[name]) {
          oldElm.removeEventListener(name, oldListener, false);
        }
      }
    }
  }

  // add new listeners which has not already attached
  if (on) {
    // reuse existing listener or create new
      // 如果oldvnode上已經有listener,則vnode直接複用,否則則新建事件處理器
    var listener = (vnode as any).listener = (oldVnode as any).listener || createListener();
    // update vnode for listener
    //在事件處理器上繫結vnode
    listener.vnode = vnode;

    // if element changed or added we add all needed listeners unconditionally
   //如果oldvnode上沒有事件處理器
    if (!oldOn) {
      for (name in on) {
        // add listener if element was changed or new listeners added
       //直接將vnode上的事件處理器新增到elm上
        elm.addEventListener(name, listener, false);
      }
    } else {
      for (name in on) {
        // add listener if new listener added
        //否則新增oldvnode上沒有的事件處理器
        if (!oldOn[name]) {
          elm.addEventListener(name, listener, false);
        }
      }
    }
  }
}

export const eventListenersModule = {
  create: updateEventListeners,
  update: updateEventListeners,
  destroy: updateEventListeners
} as Module;
export default eventListenersModule;

2.5 props.ts
    從elm上刪除vnode中不存在的屬性 更新elm上的屬性

import {VNode, VNodeData} from '../vnode';
import {Module} from './module';

export type Props = Record<string, any>;
/*props
主要功能:
  從elm上刪除vnode中不存在的屬性
  更新elm上的屬性
*/
function updateProps(oldVnode: VNode, vnode: VNode): void {
  var key: string, cur: any, old: any, elm = vnode.elm,
      oldProps = (oldVnode.data as VNodeData).props,
      props = (vnode.data as VNodeData).props;

    //如果新舊節點都不存在屬性,則直接返回
  if (!oldProps && !props) return;
  if (oldProps === props) return;
  oldProps = oldProps || {};
  props = props || {};
  //刪除舊節點中新節點沒有的屬性
  for (key in oldProps) {
    if (!props[key]) {
      delete (elm as any)[key];
    }
  }
  //更新屬性
  for (key in props) {
    cur = props[key];
    old = oldProps[key];
      //如果新舊節點屬性不同,且對比的屬性不是value或者elm上對應屬性和新屬性也不同,那麼就需要更新
    if (old !== cur && (key !== 'value' || (elm as any)[key] !== cur)) {
      (elm as any)[key] = cur;
    }
  }
}

export const propsModule = {create: updateProps, update: updateProps} as Module;
export default propsModule;

2.6 style.ts
    將elm上存在於oldvnode中但不存在於vnode中不存在的style置空 如果vnode.style中的delayed與oldvnode的不同,則更新delayed的屬性值,並在下一幀將elm的style設定為該值,從而實現動畫過渡效果 非delayed和remove的style直接更新 vnode被destroy時,直接將對應style更新為vnode.data.style.destory的值 vnode被reomve時,如果style.remove不存在,直接呼叫全域性remove鉤子進入下一個remove過程 如果style.remove存在,那麼我們就需要設定remove動畫過渡效果,等到過渡效果結束之後,才呼叫 下一個remove過程

import {VNode, VNodeData} from '../vnode';
import {Module} from './module';

export type VNodeStyle = Record<string, string> & {
  delayed?: Record<string, string>
  remove?: Record<string, string>
}


/*主要功能如下:
將elm上存在於oldvnode中但不存在於vnode中不存在的style置空
如果vnode.style中的delayed與oldvnode的不同,則更新delayed的屬性值,並在下一幀將elm的style設定為該值,從而實現動畫過渡效果
非delayed和remove的style直接更新
vnode被destroy時,直接將對應style更新為vnode.data.style.destory的值
vnode被reomve時,如果style.remove不存在,直接呼叫全域性remove鉤子進入下一個remove過程
如果style.remove存在,那麼我們就需要設定remove動畫過渡效果,等到過渡效果結束之後,才呼叫
下一個remove過程*/


//如果存在requestAnimationFrame,則直接使用,以優化效能,否則用setTimeout
var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout;
var nextFrame = function(fn: any) { raf(function() { raf(fn); }); };
var reflowForced = false;


//通過nextFrame來實現動畫效果
function setNextFrame(obj: any, prop: string, val: any): void {
  nextFrame(function() { obj[prop] = val; });
}

function updateStyle(oldVnode: VNode, vnode: VNode): void {
  var cur: any, name: string, elm = vnode.elm,
      oldStyle = (oldVnode.data as VNodeData).style,
      style = (vnode.data as VNodeData).style;
  //如果oldvnode和vnode都沒有style,直接返回
  if (!oldStyle && !style) return;
  if (oldStyle === style) return;
  oldStyle = oldStyle || {} as VNodeStyle;
  style = style || {} as VNodeStyle;
  var oldHasDel = 'delayed' in oldStyle;
    //遍歷oldvnode的style
  for (name in oldStyle) {
    if (!style[name]) {
      if (name[0] === '-' && name[1] === '-') {
        (elm as any).style.removeProperty(name);
      } else {
          //如果vnode中無該style,則置空
        (elm as any).style[name] = '';
      }
    }
  }
  //如果vnode的style中有delayed且與oldvnode中的不同,則在下一幀設定delayed的引數
  for (name in style) {
    cur = style[name];
    if (name === 'delayed' && style.delayed) {
      for (let name2 in style.delayed) {
        cur = style.delayed[name2];
        if (!oldHasDel || cur !== (oldStyle.delayed as any)[name2]) {
          setNextFrame((elm as any).style, name2, cur);
        }
      }
        //如果不是delayed和remove的style,且不同於oldvnode的值,則直接設定新值
    } else if (name !== 'remove' && cur !== oldStyle[name]) {
      if (name[0] === '-' && name[1] === '-') {
        (elm as any).style.setProperty(name, cur);
      } else {

        (elm as any).style[name] = cur;
      }
    }
  }
}
//設定節點被destory時的style
function applyDestroyStyle(vnode: VNode): void {
  var style: any, name: string, elm = vnode.elm, s = (vnode.data as VNodeData).style;
  if (!s || !(style = s.destroy)) return;
  for (name in style) {
    (elm as any).style[name] = style[name];
  }
}
//刪除效果,當我們刪除一個元素時,先回呼叫刪除過度效果,過渡完才會將節點remove
function applyRemoveStyle(vnode: VNode, rm: () => void): void {
  var s = (vnode.data as VNodeData).style;
    //如果沒有style或沒有style.remove
  if (!s || !s.remove) {
      //直接呼叫rm,即實際上是呼叫全域性的remove鉤子
    rm();
    return;
  }
  if(!reflowForced) {
    getComputedStyle(document.body).transform;
    reflowForced = true;
  }
  var name: string, elm = vnode.elm, i = 0, compStyle: CSSStyleDeclaration,
      style = s.remove, amount = 0, applied: Array<string> = [];
    //設定並記錄remove動作後刪除節點前的樣式
  for (name in style) {
    applied.push(name);
    (elm as any).style[name] = style[name];
  }
  compStyle = getComputedStyle(elm as Element);
    //拿到所有需要過渡的屬性
  var props = (compStyle as any)['transition-property'].split(', ');
    //對過渡屬性計數,這裡applied.length >=amount,因為有些屬性是不需要過渡的
  for (; i < props.length; ++i) {
    if(applied.indexOf(props[i]) !== -1) amount++;
  }
    //當過渡效果的完成後,才remove節點,呼叫下一個remove過程
  (elm as Element).addEventListener('transitionend', function (ev: TransitionEvent) {
    if (ev.target === elm) --amount;
    if (amount === 0) rm();
  });
}

function forceReflow() {
  reflowForced = false;
}

export const styleModule = {
  pre: forceReflow,
  create: updateStyle,
  update: updateStyle,
  destroy: applyDestroyStyle,
  remove: applyRemoveStyle
} as Module;
export default styleModule;

3、snabbdom.ts

/* global module, document, Node */
import {Module} from './modules/module';
import {Hooks} from './hooks';
import vnode, {VNode, VNodeData, Key} from './vnode';
import * as is from './is';
import htmlDomApi, {DOMAPI} from './htmldomapi';

function isUndef(s: any): boolean { return s === undefined; }
function isDef(s: any): boolean { return s !== undefined; }

type VNodeQueue = Array<VNode>;

const emptyNode = vnode('', {}, [], undefined, undefined);

//這個函式主要用於比較oldvnode與vnode同層次節點的比較,如果同層次節點的key和sel都相同
// 我們就可以保留這個節點,否則直接替換節點
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
  return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}

function isVnode(vnode: any): vnode is VNode {
  return vnode.sel !== undefined;
}

type KeyToIndexMap = {[key: string]: number};

type ArraysOf<T> = {
  [K in keyof T]: (T[K])[];
}

type ModuleHooks = ArraysOf<Module>;
//這個函式的功能十分簡單,就是將oldvnode陣列中位置對oldvnode.key的對映轉換為oldvnode.key對位置的對映
function createKeyToOldIdx(children: Array<VNode>, beginIdx: number, endIdx: number): KeyToIndexMap {
  let i: number, map: KeyToIndexMap = {}, key: Key | undefined, ch;
  for (i = beginIdx; i <= endIdx; ++i) {
    ch = children[i];
    if (ch != null) {
      key = ch.key;
      if (key !== undefined) map[key] = i;
    }
  }
  return map;
}

/*
pre	patch開始時觸發
init	vnode被建立時觸發
create	vnode轉換為真實DOM節點時觸發
insert	插入到DOM樹時觸發
prepatch	元素準備patch前觸發
update	元素更新時觸發
postpatch	元素patch完觸發
destroy	元素被刪除時觸發
remove	元素從父節點刪除時觸發,和destory略有不同,remove隻影響到被移除節點中最頂層的節點
post	patch完成後觸發

create => style,class,dataset,eventlistener,props,hero
update => style,class,dataset,eventlistener,props,hero
remove => style
destory => eventlistener,style,hero
pre => hero
post => hero
*/
const hooks: (keyof Module)[] = ['create', 'update', 'remove', 'destroy', 'pre', 'post'];

export {h} from './h';
export {thunk} from './thunk';

/*
init函式有兩個引數modules和api,其中modules是init依賴的模組,如attribute、props
、eventlistener這些模組,api則是對封裝真實DOM操作的工具函式庫,如果我們沒有傳入,則預設
使用snabbdom提供的htmldomapi。init還包含了許多vnode和真實DOM之間的操作和註冊全域性鉤子,
還有patchVnode和updateChildren這兩個重要功能,然後返回一個patch函式
*/
export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
  let i: number, j: number, cbs = ({} as ModuleHooks);

  const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
  //註冊鉤子的回撥,在發生狀態變更時,觸發對應屬性變更
  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = [];
    for (j = 0; j < modules.length; ++j) {
      const hook = modules[j][hooks[i]];
      if (hook !== undefined) {
        (cbs[hooks[i]] as Array<any>).push(hook);
      }
    }
  }
/*
   這個函式主要的功能是將一個真實DOM節點轉化成vnode形式,
   如<div id='a' class='b c'></div>將轉換為{sel:'div#a.b.c',data:{},children:[],text:undefined,elm:<div id='a' class='b c'>}
*/
  function emptyNodeAt(elm: Element) {
    const id = elm.id ? '#' + elm.id : '';
    const c = elm.className ? '.' + elm.className.split(' ').join('.') : '';
    return vnode(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm);
  }
/*
   我們知道當我們需要remove一個vnode時,會觸發remove鉤子作攔截器,只有在所有remove鉤子
    回撥函式都觸發完才會將節點從父節點刪除,而這個函式提供的就是對remove鉤子回撥操作的計數功能
*/
  function createRmCb(childElm: Node, listeners: number) {
    return function rmCb() {
      if (--listeners === 0) {
        const parent = api.parentNode(childElm);
        api.removeChild(parent, childElm);
      }
    };
  }
/*
初始化vnode,呼叫init鉤子
建立對應tagname的DOM element節點,並將vnode.sel中的id名和class名掛載上去
如果有子vnode,遞迴建立DOM element節點,並新增到父vnode對應的element節點上去,
否則如果有text屬性,則建立text節點,並新增到父vnode對應的element節點上去
vnode轉換成dom節點操作完成後,呼叫create鉤子
如果vnode上有insert鉤子,那麼就將這個vnode放入insertedVnodeQueue中作記錄,到時
再在全域性批量呼叫insert鉤子回撥
*/
  function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
    let i: any, data = vnode.data;
    if (data !== undefined) {
        //當節點上存在hook而且hook中有init鉤子時,先呼叫init回撥,對剛建立的vnode進行處理
      if (isDef(i = data.hook) && isDef(i = i.init)) {
        i(vnode);
          //獲取init鉤子修改後的資料
        data = vnode.data;
      }
    }
    let children = vnode.children, sel = vnode.sel;
    if (sel === '!') {
      if (isUndef(vnode.text)) {
        vnode.text = '';
      }
      vnode.elm = api.createComment(vnode.text as string);
    } else if (sel !== undefined) {
      // Parse selector
      const hashIdx = sel.indexOf('#');
      const dotIdx = sel.indexOf('.', hashIdx);
      const hash = hashIdx > 0 ? hashIdx : sel.length;
      const dot = dotIdx > 0 ? dotIdx : sel.length;
      const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel;
      const elm = vnode.elm = isDef(data) && isDef(i = (data as VNodeData).ns) ? api.createElementNS(i, tag)
                                                                               : api.createElement(tag);
      if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot));
      if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' '));
      for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
      if (is.array(children)) {
        for (i = 0; i < children.length; ++i) {
          const ch = children[i];
          if (ch != null) {
            api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
          }
        }
      } else if (is.primitive(vnode.text)) {
        api.appendChild(elm, api.createTextNode(vnode.text));
      }
      i = (vnode.data as VNodeData).hook; // Reuse variable
      if (isDef(i)) {
        if (i.create) i.create(emptyNode, vnode);
        if (i.insert) insertedVnodeQueue.push(vnode);
      }
    } else {
      vnode.elm = api.createTextNode(vnode.text as string);
    }
    return vnode.elm;
  }
  //就是將vnode轉換後的dom節點插入到dom樹的指定位置中去
  function addVnodes(parentElm: Node,
                     before: Node | null,
                     vnodes: Array<VNode>,
                     startIdx: number,
                     endIdx: number,
                     insertedVnodeQueue: VNodeQueue) {
    for (; startIdx <= endIdx; ++startIdx) {
      const ch = vnodes[startIdx];
      if (ch != null) {
        api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
      }
    }
  }
/*
這個函式用於手動觸發destory鉤子回撥,主要步驟如下:
先呼叫vnode上的destory
再呼叫全域性下的destory
遞迴呼叫子vnode的destory
*/
  function invokeDestroyHook(vnode: VNode) {
    let i: any, j: number, data = vnode.data;
    if (data !== undefined) {
      //先觸發該節點上的destory回撥
      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode);
      //在觸發全域性下的destory回撥
      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
     //遞迴觸發子節點的destory回撥
      if (vnode.children !== undefined) {
        for (j = 0; j < vnode.children.length; ++j) {
          i = vnode.children[j];
          if (i != null && typeof i !== "string") {
            invokeDestroyHook(i);
          }
        }
      }
    }
  }
/*
   這個函式主要功能是批量刪除DOM節點,需要配合invokeDestoryHook和createRmCb服用,效果更佳
    主要步驟如下:
   呼叫invokeDestoryHook以觸發destory回撥
    呼叫createRmCb來開始對remove回撥進行計數
    刪除DOM節點
*/
  function removeVnodes(parentElm: Node,
                        vnodes: Array<VNode>,
                        startIdx: number,
                        endIdx: number): void {
    for (; startIdx <= endIdx; ++startIdx) {
      let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx];
      if (ch != null) {
        if (isDef(ch.sel)) {
          //呼叫destroy鉤子
          invokeDestroyHook(ch);
          //對全域性remove鉤子進行計數
          listeners = cbs.remove.length + 1;
          rm = createRmCb(ch.elm as Node, listeners);
          //呼叫全域性remove回撥函式,並每次減少一個remove鉤子計數
          for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
          if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
            i(ch, rm);
          } else {
              //如果沒有內部remove鉤子,需要呼叫rm,確保能夠remove節點
            rm();
          }
        } else { // Text node
          api.removeChild(parentElm, ch.elm as Node);
        }
      }
    }
  }

  function updateChildren(parentElm: Node,
                          oldCh: Array<VNode>,
                          newCh: Array<VNode>,
                          insertedVnodeQueue: VNodeQueue) {
    let oldStartIdx = 0, 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 oldKeyToIdx: any;
    let idxInOld: number;
    let elmToMove: VNode;
    let before: any;

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (oldStartVnode == null) {
        oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
      } else if (oldEndVnode == null) {
        oldEndVnode = oldCh[--oldEndIdx];
      } else if (newStartVnode == null) {
        newStartVnode = newCh[++newStartIdx];
      } else if (newEndVnode == null) {
        newEndVnode = newCh[--newEndIdx];
      }
      //如果舊頭索引節點和新頭索引節點相同,
      else if (sameVnode(oldStartVnode, newStartVnode)) {
          //對舊頭索引節點和新頭索引節點進行diff更新, 從而達到複用節點效果
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
        //舊頭索引向後
        oldStartVnode = oldCh[++oldStartIdx];
        //新頭索引向後
        newStartVnode = newCh[++newStartIdx];
      }
      //如果舊尾索引節點和新尾索引節點相似,可以複用
      else if (sameVnode(oldEndVnode, newEndVnode)) {
        //舊尾索引節點和新尾索引節點進行更新
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
         //舊尾索引向前
        oldEndVnode = oldCh[--oldEndIdx];
        //新尾索引向前
        newEndVnode = newCh[--newEndIdx];
      }
      //如果舊頭索引節點和新頭索引節點相似,可以通過移動來複用
      //如舊節點為【5,1,2,3,4】,新節點為【1,2,3,4,5】,如果缺乏這種判斷,意味著
      //那樣需要先將5->1,1->2,2->3,3->4,4->5五次刪除插入操作,即使是有了key-index來複用,
      // 也會出現【5,1,2,3,4】->【1,5,2,3,4】->【1,2,5,3,4】->【1,2,3,5,4】->【1,2,3,4,5】
      // 共4次操作,如果有了這種判斷,我們只需要將5插入到最後一次操作即可
      else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
        api.insertBefore(parentElm, oldStartVnode.elm as Node, api.nextSibling(oldEndVnode.elm as Node));
        oldStartVnode = oldCh[++oldStartIdx];
        newEndVnode = newCh[--newEndIdx];
      }
      // //原理與上面相同
      else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
        api.insertBefore(parentElm, oldEndVnode.elm as Node, oldStartVnode.elm as Node);
        oldEndVnode = oldCh[--oldEndIdx];
        newStartVnode = newCh[++newStartIdx];
      }
      //如果上面的判斷都不通過,我們就需要key-index表來達到最大程度複用了
      else {
          //如果不存在舊節點的key-index表,則建立
        if (oldKeyToIdx === undefined) {
          oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
        }
          //找到新節點在舊節點組中對應節點的位置
        idxInOld = oldKeyToIdx[newStartVnode.key as string];
        if (isUndef(idxInOld)) { // New element
          api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm as Node);
          newStartVnode = newCh[++newStartIdx];
        } else {
            //如果新節點在就舊節點組中存在,先找到對應的舊節點
          elmToMove = oldCh[idxInOld];
          if (elmToMove.sel !== newStartVnode.sel) {
            api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm as Node);
          } else {
              //先將新節點和對應舊節點作更新
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
              //然後將舊節點組中對應節點設定為undefined,代表已經遍歷過了,不在遍歷,否則可能存在重複插入的問題
            oldCh[idxInOld] = undefined as any;
              //插入到舊頭索引節點之前
            api.insertBefore(parentElm, (elmToMove.elm as Node), oldStartVnode.elm as Node);
          }
            //新頭索引向後
          newStartVnode = newCh[++newStartIdx];
        }
      }
    }
    if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
        //當舊頭索引大於舊尾索引時,代表舊節點組已經遍歷完,將剩餘的新Vnode新增到最後一個新節點的位置後
      if (oldStartIdx > oldEndIdx) {
        before = newCh[newEndIdx+1] == null ? null : newCh[newEndIdx+1].elm;
        addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
      } else {
          //如果新節點組先遍歷完,那麼代表舊節點組中剩餘節點都不需要,所以直接刪除
        removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
      }
    }
  }

  function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
    let i: any, hook: any;
      //在patch之前,先呼叫vnode.data的prepatch鉤子
    if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
      i(oldVnode, vnode);
    }
    const elm = vnode.elm = (oldVnode.elm as Node);
    let oldCh = oldVnode.children;
    let ch = vnode.children;
      //如果oldvnode和vnode的引用相同,說明沒發生任何變化直接返回,避免效能浪費
    if (oldVnode === vnode) return;
      //如果oldvnode和vnode不同,說明vnode有更新
      //如果vnode和oldvnode不相似則直接用vnode引用的DOM節點去替代oldvnode引用的舊節點
    if (vnode.data !== undefined) {
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
      i = vnode.data.hook;
        //如果vnode和oldvnode相似,那麼我們要對oldvnode本身進行更新
      if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode);
    }
      // 如果新的 vnode 節點不是一個文字節點
    if (isUndef(vnode.text)) {
        // 如果兩個 vnode 節點都有子節點
      if (isDef(oldCh) && isDef(ch)) {
          // @important 並且子節點不一樣,開始 diff
        if (oldCh !== ch) updateChildren(elm, oldCh as Array<VNode>, ch as Array<VNode>, insertedVnodeQueue);
      } else if (isDef(ch)) {
          // 如果只有新的 vnode 有子節點,設定舊的 vnode 的內容為空
        if (isDef(oldVnode.text)) api.setTextContent(elm, '');
          // 新增插入新的 DOM 節點
        addVnodes(elm, null, ch as Array<VNode>, 0, (ch as Array<VNode>).length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
          // 如果只有舊的 vnode 有子節點,則移除所有子節點
        removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
      } else if (isDef(oldVnode.text)) {
          // 如果舊 vnode 是個文字節點,並且新 vnode 也沒有子節點,則清空舊 vnode 的內容
        api.setTextContent(elm, '');
      }
    } else if (oldVnode.text !== vnode.text) {
      if (isDef(oldCh)) {
        removeVnodes(elm, oldCh as Array<VNode>, 0, (oldCh as Array<VNode>).length - 1);
      }
        // 如果新的 vnode 節點是文字節點,如果文字內容和舊 vnode 不一樣則設定新的值
      api.setTextContent(elm, vnode.text as string);
    }
      // 呼叫 postpatch 鉤子
    if (isDef(hook) && isDef(i = hook.postpatch)) {
      i(oldVnode, vnode);
    }
  }

  return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
    let i: number, elm: Node, parent: Node;
      //記錄被插入的vnode佇列,用於批觸發insert
    const insertedVnodeQueue: VNodeQueue = [];
      //呼叫全域性pre鉤子
    for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
      //如果oldvnode是dom節點,轉化為oldvnode
    if (!isVnode(oldVnode)) {
      oldVnode = emptyNodeAt(oldVnode);
    }
      //如果oldvnode與vnode相似,進行更新
    if (sameVnode(oldVnode, vnode)) {
      patchVnode(oldVnode, vnode, insertedVnodeQueue);
    } else {
        //否則,將vnode插入,並將oldvnode從其父節點上直接刪除
      elm = oldVnode.elm as Node;
      parent = api.parentNode(elm);

      createElm(vnode, insertedVnodeQueue);

      if (parent !== null) {
        api.insertBefore(parent, vnode.elm as Node, api.nextSibling(elm));
        removeVnodes(parent, [oldVnode], 0, 0);
      }
    }
      //插入完後,呼叫被插入的vnode的insert鉤子
    for (i = 0; i < insertedVnodeQueue.length; ++i) {
      (((insertedVnodeQueue[i].data as VNodeData).hook as Hooks).insert as any)(insertedVnodeQueue[i]);
    }
      //然後呼叫全域性下的post鉤子
    for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
      //返回vnode用作下次patch的oldvnode
    return vnode;
  };
}