热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

normalize函数_关于实现一个函数把真实dom转换成虚拟dom原来是这么一回事

实现这个函数之前,首先要对js的一些官方api要有明确概念。document.querySelector()HTMLElementHTMLDOMnodeType属性H

实现这个函数之前,首先要对js的一些官方api要有明确概念。

  • document.querySelector()

  • HTMLElement

  • HTML DOM nodeType 属性

HTML DOM Element 对象

HTML DOM 节点

在 HTML DOM (文档对象模型)中,每个部分都是节点:

  • 文档本身是文档节点
  • 所有 HTML 元素是元素节点
  • 所有 HTML 属性是属性节点
  • HTML 元素内的文本是文本节点
  • 注释是注释节点

Element 对象

在 HTML DOM 中,Element 对象表示 HTML 元素。

Element 对象可以拥有类型为元素节点、文本节点、注释节点的子节点。

NodeList 对象表示节点列表,比如 HTML 元素的子节点集合。

元素也可以拥有属性。属性是属性节点(参见下一节)。

下面的属性和方法可用于所有 HTML 元素上:

属性 / 方法描述
element.accessKey设置或返回元素的快捷键。
element.appendChild()向元素添加新的子节点,作为最后一个子节点。
element.attributes返回元素属性的 NamedNodeMap。
element.childNodes返回元素子节点的 NodeList。
element.className设置或返回元素的 class 属性。
element.clientHeight返回元素的可见高度。
element.clientWidth返回元素的可见宽度。
element.cloneNode()克隆元素。
element.compareDocumentPosition()比较两个元素的文档位置。
element.contentEditable设置或返回元素的文本方向。
element.dir设置或返回元素的内容是否可编辑。
element.firstChild返回元素的首个孩子。
element.getAttribute()返回元素节点的指定属性值。
element.getAttributeNode()返回指定的属性节点。
element.getElementsByTagName()返回拥有指定标签名的所有子元素的集合。
element.getFeature()返回实现了指定特性的 API 的某个对象。
element.getUserData()返回关联元素上键的对象。
element.hasAttribute()如果元素拥有指定属性,则返回true,否则返回 false。
element.hasAttributes()如果元素拥有属性,则返回 true,否则返回 false。
element.hasChildNodes()如果元素拥有子节点,则返回 true,否则 false。
element.id设置或返回元素的 id。
element.innerHTML设置或返回元素的内容。
element.insertBefore()在指定的已有的子节点之前插入新节点。
element.isContentEditable设置或返回元素的内容。
element.isDefaultNamespace()如果指定的 namespaceURI 是默认的,则返回 true,否则返回 false。
element.isEqualNode()检查两个元素是否相等。
element.isSameNode()检查两个元素是否是相同的节点。
element.isSupported()如果元素支持指定特性,则返回 true。
element.lang设置或返回元素的语言代码。
element.lastChild返回元素的最后一个子元素。
element.namespaceURI返回元素的 namespace URI。
element.nextSibling返回位于相同节点树层级的下一个节点。
element.nodeName返回元素的名称。
element.nodeType返回元素的节点类型。
element.nodeValue设置或返回元素值。
element.normalize()合并元素中相邻的文本节点,并移除空的文本节点。
element.offsetHeight返回元素的高度。
element.offsetWidth返回元素的宽度。
element.offsetLeft返回元素的水平偏移位置。
element.offsetParent返回元素的偏移容器。
element.offsetTop返回元素的垂直偏移位置。
element.ownerDocument返回元素的根元素(文档对象)。
element.parentNode返回元素的父节点。
element.previousSibling返回位于相同节点树层级的前一个元素。
element.removeAttribute()从元素中移除指定属性。
element.removeAttributeNode()移除指定的属性节点,并返回被移除的节点。
element.removeChild()从元素中移除子节点。
element.replaceChild()替换元素中的子节点。
element.scrollHeight返回元素的整体高度。
element.scrollLeft返回元素左边缘与视图之间的距离。
element.scrollTop返回元素上边缘与视图之间的距离。
element.scrollWidth返回元素的整体宽度。
element.setAttribute()把指定属性设置或更改为指定值。
element.setAttributeNode()设置或更改指定属性节点。
element.setIdAttribute()
element.setIdAttributeNode()
element.setUserData()把对象关联到元素上的键。
element.style设置或返回元素的 style 属性。
element.tabIndex设置或返回元素的 tab 键控制次序。
element.tagName返回元素的标签名。
element.textContent设置或返回节点及其后代的文本内容。
element.title设置或返回元素的 title 属性。
element.toString()把元素转换为字符串。
nodelist.item()返回 NodeList 中位于指定下标的节点。
nodelist.length返回 NodeList 中的节点数。
Node Types

文档、元素、属性以及 HTML 或 XML 文档的其他方面拥有不同的节点类型。

存在 12 种不同的节点类型,其中可能会有不同节点类型的子节点:

节点类型描述子节点
1Element代表元素Element, Text, Comment, ProcessingInstruction, CDATASection, EntityReference
2Attr代表属性Text, EntityReference
3Text代表元素或属性中的文本内容。None
4CDATASection代表文档中的 CDATA 部分(不会由解析器解析的文本)。None
5EntityReference代表实体引用。Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference
6Entity代表实体。Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference
7ProcessingInstruction代表处理指令。None
8Comment代表注释。None
9Document代表整个文档(DOM 树的根节点)。Element, ProcessingInstruction, Comment, DocumentType
10DocumentType向为文档定义的实体提供接口None
11DocumentFragment代表轻量级的 Document 对象,能够容纳文档的某个部分Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference
12Notation代表 DTD 中声明的符号。None

节点类型 - 返回值

对于每种节点类型,nodeName 和 nodeValue 属性的返回值:

节点类型nodeName 返回nodeValue 返回
1Element元素名null
2Attr属性名称属性值
3Text#text节点的内容
4CDATASection#cdata-section节点的内容
5EntityReference实体引用名称null
6Entity实体名称null
7ProcessingInstructiontarget节点的内容
8Comment#comment注释文本
9Document#documentnull
10DocumentType文档类型名称null
11DocumentFragment#document 片段null
12Notation符号名称null
HTML DOM querySelector() 方法

语法

document.querySelector(CSS selectors)

参数值

参数类型描述
CSS 选择器String必须。指定一个或多个匹配元素的 CSS 选择器。可以使用它们的 id, 类, 类型, 属性, 属性值等来选取元素。 对于多个选择器,使用逗号隔开,返回一个匹配的元素。 提示: 更多 CSS 选择器,请参阅 CSS 选择器参考手册。
真实dom转换成虚拟dom

思路

首先要明确,虚拟dom树上的一个节点VNode,需要哪些必要的数据。

  • 标签名
  • 元素类型
  • 元素属性值(id、class、name等,字典形式)
  • 子节点(数组形式)
  • 文本

所以,我们的一个VNode节点,可以设置成一个这样的数据结构,这里用es6的class语法来实现。

class VNode {
    constructor(tag, attrs, value, type) {
        this.tag = tag && tag.toLowerCase(); // 标签名
        this.attrs = attrs;  // 属性值
        this.value = value; // 文本
        this.type = type;  // 元素类型
        this.children = []  // 子节点
    }

    // 追加子节点
    appendChild(vnode) {
        this.children.push(vnode)
    }

}

然后,上文中的node type告诉我们:

文档、元素、属性以及 HTML 或 XML 文档的其他方面拥有不同的节点类型。

存在 12 种不同的节点类型,其中可能会有不同节点类型的子节点

所以判断一个node是文本节点还是元素结点时,可以用node.nodeType == 1node.nodeType == 3

那么接下来就很简单了,无非就是遍历一棵n叉树,使用递归就很容易实现,只是在递归的时候,加入node.nodeType == 1node.nodeType == 3这两个判断,然后遇到文本情况跳出递归栈就可以了。

再细化到每个节点的拷贝,就是针对上面提到的几个必要属性,值得注意的是,属性也是一个节点。从官方api中拿数据填到vnode节点中,这个就是一个简单的属性深拷贝,这里就不再赘述了,直接上代码。

function getVNode(node) {
    let nodeType = node.nodeType;
    let _vnode = null;

    // 对节点进行判断
    if (nodeType === 1) {
        // 元素节点
        let nodeName = node.nodeName;

        // 属性,返回属性组成的数组,我们就是把这个伪数组转换为对象
        let attrs = node.attributes

        let _attrObj = {};

        // 循环attrs
        for (let i = 0; i             // attrs[i]是一个属性节点,我们要的是nodeName这个属性
            _attrObj[attrs[i].nodeName] = attrs[i].nodeValue
        }

        _vnode = new VNode(nodeName, _attrObj, undefined, nodeType);


        // 考虑node的子元素
        let childNodes = node.childNodes;
        for (let i = 0; i             //递归
            _vnode.appendChild(getVNode(childNodes[i]));
        }
    } else if (nodeType === 3) {
        // 文本节点
        _vnode = new VNode(undefined, undefined, node.nodeValue, nodeType);
    }
    return _vnode;
}

注意点:文本节点的判断和节点文本的获取

由于文本也是一个节点。

**元素本身的nodeValue本来就是null,nodeValue是针对#text的。而且最重要的是nodeValue属性是用来获取文本节点的值的。**这也就意味着,nodeValue拿不到非文本节点的值。

一般情况下,打印文本节点,会出现#text字样,这就是在console里辨别出文本节点的其中一个方法。

ae68e7df7d173b4ccded8dd57c6231ed.png
img

完整代码

html>


    
    Titletitle>
head>


    hello1div>
    hello2div>
    hello3div>
    

    


        1span>2span>3span>4span>5span>6span>
    p>
    


            
  • 1li>
            
  • 2li>
            
  • 3li>
            
  • 4li>
            
  • 5li>
        ul>
    div>
    // 用内存去表述DOM// 将真实DOM转化为虚拟DOM// // 文本节点 => {tag:undefined,value:'文本节点'}   文本节点转化// // // 用构造函数来 进行以上转换class VNode {constructor(tag, attrs, value, type) {this.tag = tag && tag.toLowerCase(); // 标签名this.attrs = attrs;  // 属性值this.value = value; // 文本this.type = type;  // 元素类型this.children = []  // 子节点
            }// 追加子节点
            appendChild(vnode) {this.children.push(vnode)
            }
        }//兼容浏览器获取节点文本的方法function getTextFromNode(e) {let t = "";//如果传入的是元素,则继续遍历其子元素//否则假定它是一个数组
            e = e.childNodes || e;//遍历所有子节点for (let j = 0; j             //如果不是元素,追加其文本值//否则,递归遍历所有元素的子节点
                t += e[j].nodeType !== 1 ?
                    e[j].nodeValue : getTextFromNode(e[j].childNodes);
            }//返回区配的文本return t;
        }function getVirtualDom(node) {let nodeType = node.nodeType;let _vnode = null;if (nodeType === 1) {let nodeName = node.nodeName;let property = node.attributes;let _propertyObj = {};for (let i = 0; i                 _propertyObj[property[i].nodeName] = property[i].nodeValue;
                }
                _vnode = new VNode(nodeName, _propertyObj, undefined, nodeType)let childNodes = node.childNodes;for (let i = 0; i                 _vnode.appendChild(getVirtualDom(childNodes[i]))
                }
            } else if (nodeType === 3) {// 尤其要注意,文本也是一个节点// 可以回忆一下,原生js生成一个节点的步骤,就理解了let text = node.nodeValue
                _vnode = new VNode(undefined, undefined, text, nodeType)
            }return _vnode;
        }let root = document.querySelector("#root");console.log(root);console.log(root.nodeType);console.log(root.attributes)// let vroot = getVNode(root);let vRoot = getVirtualDom(root)// console.log(vroot);console.log(vRoot)script>
    body>
    html>

    部分输出如下:

    7388f582136abf34b2d210d7a5175fdd.png
    img



推荐阅读
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • node.jsurlsearchparamsAPI哎哎哎 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • PHP反射API的功能和用途详解
    本文详细介绍了PHP反射API的功能和用途,包括动态获取信息和调用对象方法的功能,以及自动加载插件、生成文档、扩充PHP语言等用途。通过反射API,可以获取类的元数据,创建类的实例,调用方法,传递参数,动态调用类的静态方法等。PHP反射API是一种内建的OOP技术扩展,通过使用Reflection、ReflectionClass和ReflectionMethod等类,可以帮助我们分析其他类、接口、方法、属性和扩展。 ... [详细]
  • 微信小程序导航跟随的实现方法
    本文介绍了在微信小程序中实现导航跟随的方法。通过设置导航的position属性和绑定滚动事件,可以实现页面向下滚动到导航位置时,导航固定在页面最上方;页面向上滚动到导航位置时,导航恢复到原始位置;点击导航可以平滑跳转到相应位置。代码示例也给出了具体实现方法。 ... [详细]
author-avatar
手机用户2502884057
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有