采用TypeScript结合JavaScript来记录
大致需要懂得的知识:es6的定义,typescript的基础语法。
很多地方都讲了虚拟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的视频导出(编译),导出后还卡就是播放设备的问题了。如果有修改的内容,也只需要相应部分修改即可(单独把修改的部分导出)。
接下来讲一下Vue2.0的渲染过程和diff算法(TypeScript)
先来看一下diff算法有关的目录:
看着东西很多,但是核心其实就三四个文件,先对需要用到的讲解一下主要用法。
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变量内
写了俩函数,判断是不是数组或者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函数,依据存在不同类别的数据,判断完是啥数据后,把对应位置上的数据传入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>
这个比较重要,是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;
}
还没写完,