vue学习笔记一(源码解读:虚拟DOM和diff算法)

采用TypeScript结合JavaScript来记录
大致需要懂得的知识:es6的定义,typescript的基础语法。

虚拟DOM

很多地方都讲了虚拟DOM的概念,这里就不多讲了。虚拟DOM对应的概念就是真实DOM。就是我们在浏览器中看到的DOM,也是我刚开始学web的时候,写的标签渲染出来。虚拟DOM就是一个对象,存着数据。并没有开始渲染(如果有错请指出)。
看数据就很容易理解了

<div>								   
	<ul>									
		<li>真实DOMli>		
		<li>真实DOMli>		
		<li>真实DOMli>		
	ul>								
div>									

相对应的虚拟DOM是这样的

{"sel": "div",//虚拟DOM
  "data": {},
  "children":{
      "sel": "ul",
      "data": {},
      "children": [
        { "sel": "li", "data": {}, "text": "真实DOM" },
        { "sel": "li", "data": {}, "text": "真实DOM" },
        { "sel": "li", "data": {}, "text": "真实DOM" }
      ]}
]}

在刚开始接触前端,我用的是jQuery去操作DOM来实现页面跟新的。大致写法就是$(".className").css({})。那时候就偶尔会发现一些问题,当动画多了以后,有一些动画会卡屏。可以理解成做AE的时候的实时渲染,渲染一点看一点,稍微大一点的项目就容易卡。而用虚拟DOM就能解决这个问题,大致流程就像先把AE的视频导出(编译),导出后还卡就是播放设备的问题了。如果有修改的内容,也只需要相应部分修改即可(单独把修改的部分导出)。

diff算法(结合源码)

接下来讲一下Vue2.0的渲染过程和diff算法(TypeScript)
先来看一下diff算法有关的目录:
vue学习笔记一(源码解读:虚拟DOM和diff算法)_第1张图片
看着东西很多,但是核心其实就三四个文件,先对需要用到的讲解一下主要用法。

vnode.ts

ts写法
export function vnode(
  sel: string | undefined,
  data: any | undefined,
  children: Array<VNode | string> | undefined,
  text: string | undefined,
  elm: Element | Text | undefined//接受了sel、data、children、text、elm,封装成为一个对象返回。
): VNode {
  const key = data === undefined ? undefined : data.key;//这里的key很关键,后续也会用到,就是vue中标签内写的key
  return { sel, data, children, text, elm, key };
}

vnode.ts 主要作用是接受虚拟DOM有关的数据,并且统一格式返回。其中数据不做详解,反正vnode中的主要代码就是这个。用js来写看上去会简单很多,大致如下:

js写法
export function vnode(sel, data, children, text, elm) {//让大家看一下,其实不是很难,下面就不结合js记录了
    const key = data === undefined ? undefined : data.key;
    return { sel, data, children, text, elm, key };
}

这里的key来源就是我们写在标签内的key,如果是空就undefined,如果有数据就赋值传入的数据。由此可见,class,key等标签内的属性都存在data变量内

is.ts

写了俩函数,判断是不是数组或者String和Number类型

export const array = Array.isArray;//暴露了array这个方法,其实就是Array.isArray。
export function primitive(s: any): s is string | number {//primitive函数判断传进这个函数的数据类型
  return typeof s === "string" ||
    typeof s === "number" ||
    s instanceof String ||
    s instanceof Number;//如果是'string'或'number',就返回true,其他都是false
}

h.ts

h.ts 主要作用是h函数,依据存在不同类别的数据,判断完是啥数据后,把对应位置上的数据传入vnode函数。 这边因为ts可以重载(同名方法,参数不同)所以和js有一丢丢不同(js内不能重载,就可以用传入的数据数据来判断–arguments)。

export function h(sel: string): VNode;
export function h(sel: string, data: VNodeData | null): VNode;
export function h(sel: string, children: VNodeChildren): VNode;
export function h(
  sel: string,
  data: VNodeData | null,
  children: VNodeChildren
): VNode;//上面几种方法,就直接把数据传入vnode函数。
export function h(sel: any, b?: any, c?: any): VNode {
  let data: VNodeData = {};//记录传下去的数据
  let children: any;//记录子集
  let text: any;//记录标签内的文本/数据内容
  let i: number;
  if (c !== undefined) {//确定c存在
    if (b !== null) {
      data = b;
    }
    if (is.array(c)) {//如果传进来的是数组,说明已经有嵌套,直接赋值给children这个变量就可以了。不做处理
      children = c;
    } else if (is.primitive(c)) {//primitive前面提过,如果是number和string就返回true
      text = c.toString();// 如果是的话,就toString读出来即可。存在text内
    } else if (c && c.sel) {//c.sel存在,说明c是个对象
      children = [c];//把对象放进数组里面
    }
  } else if (b !== undefined && b !== null) {//操作和上面的差不多
    if (is.array(b)) {
      children = b;
    } else if (is.primitive(b)) {
      text = b.toString();
    } else if (b && b.sel) {
      children = [b];
    } else {
      data = b;
    }
  }
  if (children !== undefined) {//如果上面children 有被赋值,就会触发这个if
    for (i = 0; i < children.length; ++i) {//遍历循环
      if (is.primitive(children[i]))//判断这个循环体是不是string或number类型
        children[i] = vnode(//把格式统一成对象
          undefined,//sel
          undefined,//data
          undefined,//children
          children[i],//text
          undefined//elm
        );//可以看出来,上面用了primitive判断,是string或文本,就把数据放在text对应的位置。很合理
    }
  }
  if (//如果是svg,就走这个函数,
    sel[0] === "s" &&
    sel[1] === "v" &&
    sel[2] === "g" &&
    (sel.length === 3 || sel[3] === "." || sel[3] === "#")//判断,确保是svg标签
  ) {
    addNS(data, children, sel);//addNS是一个函数,大致作用就是和vnode差不多,不过是专门处理svg的
  }
  //结束后,把上面的所有数据都放入vnode函数中,有赋值过的数据就有数据,没有的就undefined
  return vnode(sel, data, children, text, undefined);
}
function addNS(
  data: any,
  children: VNodes | undefined,
  sel: string | undefined
): void {
  data.ns = "http://www.w3.org/2000/svg";
  if (sel !== "foreignObject" && children !== undefined) {
    for (let i = 0; i < children.length; ++i) {
      const childData = children[i].data;
      if (childData !== undefined) {
        addNS(childData, children[i].children as VNodes, children[i].sel);
      }
    }
  }
}
//光看函数可能不太还理解,直接结合处理好后的虚拟DOM格式就很容易理解了
{"sel": "div",
  "data": {},
  "children":{
      "sel": "ul",
      "data": {},
      "children": [
        { "sel": "li", "data": {}, "text": "真实DOM" },
      ]}
]}

代码看上去有点长,但是其实核心就是由于每个标签内容可能不一样,分别处理,获得想要的数据,下面1和2都是div,但是一个直接内容是文本,一个是包含子元素。然后判断是文本还是包含着标签来判断,然后分别把数据存在不同的变量里面,最后再传进vnode函数里面,得到一个标准的虚拟dom的对象。

<div>最基本的div>
<div>
	<a>这个a>
	<a>有嵌套a>
div>

init.ts

这个比较重要,是diff算法的核心。在开始前,先把前面的总结回顾一下,vnode函数只是把数据打包成统一格式的对象返回,is.ts内只是有俩个判断的函数,h.ts内只是把数据判断一下啥数据是啥数据,判断完毕后传入到vnode函数内,相当于过滤机,把蛋黄蛋清过滤好,放入属于自己的位置上而已。
下面开始介绍init.ts


function sameVnode(vnode1: VNode, vnode2: VNode): boolean {//判断两个虚拟dom/节点是否相同
  const isSameKey = vnode1.key === vnode2.key;//如果v1的key==v2的key
  const isSameIs = vnode1.data?.is === vnode2.data?.is;//如果v1的is ==v2的is
  const isSameSel = vnode1.sel === vnode2.sel;
  return isSameSel && isSameKey && isSameIs;
}//这里用到了“节点”,因为可能只是children用来比较,并不是一整个vnode返回的数据,所以我感觉用节点来说比较好
function createKeyToOldIdx(children: VNode[],beginIdx: number,endIdx: number): KeyToIndexMap {
  const map: KeyToIndexMap = {};//这个函数是为没有key的对象加上key
  for (let i = beginIdx; i <= endIdx; ++i) {
    const key = children[i]?.key;
    if (key !== undefined) {
      map[key as string] = i;
    }
  }
  return map;
}
//依据节点vnode去创建真实DOM
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
    let i: any;
    let data = vnode.data;
    if (data !== undefined) {
      const init = data.hook?.init;
      if (isDef(init)) {
        init(vnode);
        data = vnode.data;
      }
    }
    const children = vnode.children;
    const sel = vnode.sel;
    if (sel === "!") {
      if (isUndef(vnode.text)) {
        vnode.text = "";
      }
      vnode.elm = api.createComment(vnode.text!);
    } 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.ns))
          ? api.createElementNS(i, tag, data)
          : api.createElement(tag, data));
      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));
      }
      const hook = vnode.data!.hook;
      if (isDef(hook)) {
        hook.create?.(emptyNode, vnode);
        if (hook.insert) {
          insertedVnodeQueue.push(vnode);
        }
      }
    } else {
      vnode.elm = api.createTextNode(vnode.text!);
    }
    return vnode.elm;
  }

还没写完,

你可能感兴趣的:(vue,学习,vue.js,前端框架,typescript)