以前经过深刻学习DOM的相关知识,看了慕课网DOM探索之基础详解篇这个视频(在最近看第三遍的时候,准备记录一点东西,算是对本身学习的一点总结),对DOM的理解又具体了一步,由于DOM原本就是一个抽象和概念性的东西,每深刻一步了解,在脑中就会稍微具体一点,经过此次的对DOM的系统学习,对DOM有一个比较深入的理解,明白了DOM在JavaScript这门语言中举足轻重的地位,了解了DOm的发展历史,也让我明白了存在浏览器浏览器兼容性的历史缘由,对DOM的结构有了进一步的认知,对DOM的一些API也更加熟悉,对比较抽象和概念性的DOM认知稍微具体了一些。下面就是本身深刻学习DOM这门课程整理的一些笔记,大部分来自学习中查阅的资料以及视频中老师讲的一些关键性知识点,固然也不可或缺的有本身的一些记录和理解。javascript
原文收录在个人 GitHub博客 (https://github.com/jawil/blog) ,喜欢的能够关注最新动态,你们一块儿多交流学习,共同进步,以学习者的身份写博客,记录点滴。css
文章稍长,本文只论述DOM基础概念,不涉及DOM的一些事件原理机制,页面元素的操做和经常使用API的讲解以及兼容性事项,因此概念性东西比较多,稍微有点抽象,其中有笔记来大部分来自老师的口述,还有一部分是查阅的文档,最后有一部分是本身的记录和理解。html
经过document.createElement("p")建立一个p元素一共溯寻了7层原型链,你知道吗?
学习视频地址:DOM探索之基础详解篇,老师讲的很好,有兴趣的能够结合视频学习一下,建议看完视频再看笔记,加深印象,你会受益不浅。
vue
DOM,文档对象模型(Document Object Model)。DOM是 W3C(万维网联盟)的标准,DOM定义了访问HTML和XML文档的标准。在W3C的标准中,DOM是独于平台和语言的接口,它容许程序和脚本动态地访问和更新文档的内容、结构和样式。html5
W3C DOM由如下三部分组成:java
核心DOM - 针对任何结构化文档的标准模型node
XML DOM - 针对 XML 文档的标准模型react
HTML DOM - 针对 HTML 文档的标准模型jquery
DOM(文档对象模型)是针对xml通过扩展用于html的应用程序编程接口,咱们又叫API。DOM把整个页面映射为一个多层的节点结构,html或xml页面中的每一个组成部分都是某种类型的节点,这些节点又包含着不一样类型的数据。git
<img width="50%" src="http://ww1.sinaimg.cn/large/a...
咱们知道,一个网页是由html来搭建结构的,经过css来定义网页的样式,而JavaScript赋予了页面的行为,经过它咱们能够与页面进行交互,实现页面的动画效果等等。那javascript究竟经过什么来实现的呢?经过ECMAScript这个标准,咱们能够编写程序让浏览器来解析,利用ECMAScript,咱们能够经过BOM对象(即browser object model)来操做浏览器窗口、浏览器导航对象(navigator)、屏幕分辨率(screen)、浏览器历史(history)、cookie等等。但这个经过BOM来实现的交互远远不够。要实现页面的动态交互和效果,操做html才是核心。那如何操做html呢?对,就是DOM,简单的说,DOM给咱们提供了用程序来动态控制html的接口,也就是早期的DHTMl的概念。所以,DOM处在javascript赋予html具有动态交互和效果的能力的核心地位上。
<img width="50%" src="http://ww1.sinaimg.cn/large/a...
JavaScript在早期版本中提供了查询和操做Web文档的内容API(如:图像和表单),在JavaScript中定义了定义了'images'、'forms'等,所以咱们能够像下这样访问第一张图片或名为“user”的表单:
document.images[0]document.forms['user']
这其实是未造成标准的试验性质的初级阶段的DOM,如今习惯上被称为DOM0,即:第0级DOM。因为DOM0在W3C进行标准备化以前出现,还处于未造成标准的初期阶段,这时Netscape和Microsoft各自推出本身的第四代浏览器,自此DOM遍开始出各类问题。
Netscape Navigator 4和IE4分别发布于1997年的6月和10月,这两种浏览器都大幅扩展了DOM,使JavaScript的功能大大增长,而此时也开始出现一个新名词:DHTML。
DHTML是Dynamic HTML(动态HTML)的简称。DHTML并非一项新技术,而是将HTML、CSS、JavaScript技术组合的一种描述。即:
利用HTML把网页标记为各类元素
利用CSS设置元素样式及其显示位置
利用JavaScript操控页面元素和样式
利用DHTML,看起来能够很容易的控制页面元素,并实现一此本来很复杂的效果(如:经过改变元素位置实现动画)。但事实并不是如此,由于没有规范和标准,两种浏览器对相同功能的实现确彻底不同。为了保持程序的兼容性,程序员必须写一些探查代码以检测JavaScript是运行于哪一种浏览器之下,并提供与之对应的脚本。JavaScript陷入了史无前例的混乱,DHTML也所以在人们心中留下了不好的印象。
咱们在阅读DOM标准的时候,常常会看到DOM0级这样的字眼,实际上DOM0级这个标准是不存在的。所谓DOM0级只是DOM 历史坐标系中的一个参照点而已,具体地说DOM0级就是指IE4.0和Netscape navigator4.0最初支持的那个DHTML。
在浏览器厂商进行浏览器大站的同时,W3C结合你们的优势推出了一个标准化的DOM,并于1998年10月完成了第一级 DOM,即:DOM1。W3C将DOM定义为一个与平台和编程语言无关的接口,经过这个接口程序和脚本能够动态的访问和修改文档的内容、结构和样式。
DOM1级主要定义了HTML和XML文档的底层结构。在DOM1中,DOM由两个模块组成:DOM Core(DOM核心)和DOM HTML。其中,DOM Core规定了基于XML的文档结构标准,经过这个标准简化了对文档中任意部分的访问和操做。DOM HTML则在DOM核心的基础上加以扩展,添加了针对HTML的对象和方法,如:JavaScript中的Document对象.
在DOM1的基础上DOM2引入了更多的交互能力,也支持了更高级的XML特性。DOM2将DOM分为更多具备联系的模块。DOM2级在原来DOM的基础上又扩充了鼠标、用户界面事件、范围、遍历等细分模块,并且经过对象接口增长了对CSS的支持。DOM1级中的DOM核心模块也通过扩展开始支持XML命名空间。在DOM2中引入了下列模块,在模块包含了众多新类型和新接口:
DOM视图(DOM Views):定义了跟踪不一样文档视图的接口
DOM事件(DOM Events):定义了事件和事件处理的接口
DOM样式(DOM Style):定义了基于CSS为元素应用样式的接口
DOM遍历和范围(DOM Traversal and Range):定义了遍历和操做文档树的接口
完整的DOM2标准(图片来自百度百科):
DOM3级:进一步扩展了DOM,引入了以统一方式加载和保存文档的方法,它在DOM Load And Save这个模块中定义;同时新增了验证文档的方法,是在DOM Validation这个模块中定义的。
DOM3进一步扩展了DOM,在DOM3中引入了如下模块:
DOM加载和保存模块(DOM Load and Save):引入了以统一方式加载和保存文档的方法
DOM验证模块(DOM Validation):定义了验证文档的方法
DOM核心的扩展(DOM Style):支持XML 1.0规范,涉及XML Infoset、XPath和XML Base
DOM能够将任何HTML描绘成一个由多层节点构成的结构。节点分为12种不一样类型,每种类型分别表示文档中不一样的信息及标记。每一个节点都拥有各自的特色、数据和方法,也与其余节点存在某种关系。节点之间的关系构成了层次,而全部页面标记则表现为一个以特定节点为根节点的树形结构。
先看一张w3school上面的一张图:
先来看看下面代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>DOM</title> </head> <body> <h2><a href="http://www.baidu.com">javascript DOM</a></h2> <p>对HTML元素进行操做,可添加、改变或移除css样式等</p> <ul> <li>Javascript</li> <li>DOM</li> <li>CSS</li> </ul> </body> </html>
将HTML代码分解为DOM节点层次图:
HTML文档能够说由节点构成的集合,DOM节点有:
元素节点:上图中<html>、<body>、<p>等都是元素节点,即标签。
文本节点:向用户展现的内容,如<li>...</li>中的JavaScript、DOM、CSS等文本。
属性节点:元素属性,如<a>标签的连接属性href="http://www.baidu.com"。
咱们说DOM文档对象模型是从文档中抽象出来的,DOM操做的对象也是文档,所以咱们有必要了解一下文档的类型。文档随着历史的发展演变为多种类型,以下:
GML(Generalized Markup Language, 通用标记语言)是1960年代的一种IBM文档格式化语言,用于描述文档的组织结构、各部件及其相互关系。GML在文档具体格式方面,为文档员提供了一些方便,他们没必要再为IBM的打印机格式化语言SCRIPT要求的字体规范、行距以及页面设计等浪费精力。这个IBM的GML包括1960年代的GML和1980年代的ISIL。
SGML(Standard Generalized Markup Language, 标准通用标记语言)是1986年基于IBM的GML制定ISO标准(ISO 8879)。SGML是现时经常使用的超文本格式的最高层次标准,是能够定义标记语言的元语言,甚至能够定义没必要采用"<>"的常规方式。因为SGML的复杂,于是难以普及。HTML和XML一样衍生于SGML,XML能够被认为是SGML的一个子集,而HTML是SGML的一个应用。
HTML(HyperText Markup Language, 超文本标记语言)是为“网页建立和其它可在网页浏览器中看到的信息”设计的一种标记语言。HTML被用来结构化信息——例如标题、段落和列表等等,也可用来在必定程度上描述文档的外观和语义。1982年,蒂姆·伯纳斯-李为使世界各地的物理学家可以方便的进行合做研究,建立了使用于其系统的HTML。以后HTML又不断地扩充和发展,成为国际标准,由万维网联盟(W3C)维护。第一个正式标准是1995年发布的RFC 1866(HTML 2.0)。
XML(eXtensible Markup Language, 可扩展标记语言)是专家们使用SGML精简制做,并依照HTML的发展经验,产生出一套使用上规则严谨,可是简单的描述数据语言。XML在1995年开始有雏形,在1998二月发布为W3C的标准(XML1.0)
XHTML(eXtensible HyperText Markup Language, 可扩展超文本标记语言)的表现方式与超文本标记语言(HTML)相似,不过语法上更加严格。从继承关系上讲,HTML是一种基于标准通用标记语言(SGML)的应用,是一种很是灵活的置标语言,而XHTML则基于可扩展标记语言(XML),XML是SGML的一个子集。XHTML 1.0在2000年1月26日成为W3C的推荐标准。
DOM1级定义了一个Node接口,这个Node接口在javascript中是做为Node类型来实现的。除了IE之外,其余全部浏览器均可以访问这个类型。每一个节点都有一个nodeType属性,用于代表节点的类型。节点类型经过定义数值常量和字符常量两种方式来表示,IE只支持数值常量。节点类型一共有12种,这里介绍经常使用的7种类型。以下图:
看下面这个例子:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>DocumentFragment文档片断节点</title> </head> <body> <!-- tip区域 --> <div id="tip">test1</div> <ul class="list-node"> <li>test2<li> </ul> <script> var frag = document.createDocumentFragment(); for (var i = 0; i < 10; i++) { var li = document.createElement("li"); li.innerHTML = "List item" + i; frag.appendChild(li); } document.getElementById("list-node").appendChild(frag); </script> </body> </html>
如下引用均来自老师说的话,感受每句话都很重要,因此就写下来了。
是组成文档树的重要部分,它表示了html、xml文档中的元素。一般元素由于有子元素、文本节点或者二者的结合,元素节点是惟一可以拥有属性的节点类型。
例子中的:html
、heade
、meta
、title
、body
、div
、ul
、li
、script
都属于Element(元素节点);
表明了元素中的属性,由于属性其实是附属于元素的,所以属性节点不能被看作是元素的子节点。于是在DOM中属性没有被认为是文档树的一部分。换句话说,属性节点其实被看作是包含它的元素节点的一部分,它并不做为单独的一个节点在文档树中出现。
例子中的:lang
、charset
、id
、class
都属于Attr(属性节点);
是只包含文本内容的节点,在xml中称为字符数据,它能够由更多的信息组成,也能够只包含空白。在文档树中元素的文本内容和属性的文本内容都是由文本节点来表示的。
例子中的:DocumentFragment文档片断节点
、test1
、test2
、元素节点以后的空白区域
都属于Text(文本节点);
表示注释的内容
例子中的:<!-- tip区域 -->
都属于Comment(注释节点);
是文档树的根节点,它是文档中其余全部节点的父节点。要注意的是,文档节点并非html、xml文档的根元素,由于在xml文档中,处理指令、注释等内容能够出如今根元素以外,因此咱们在构造DOM树的时候,根元素并不适合做为根节点,所以就有了文档节点,而根元素是做为文档节点的子节点出现的。
例子中的:<!DOCTYPE html>
、html
做为Document(文档节点)的子节点出现;
每个Document都有一个DocumentType属性,它的值或者是null,或者是DocumentType对象。好比声明文档类型时<!doctype html>就是文档类型节点。
例子中的:<!DOCTYPE html>
就属于DocumentType(文档类型节点);
是轻量级的或最小的Document对象,它表示文档的一部分或者是一段,不属于文档树。不过它有一种特殊的行为,该行为使得它很是有用。好比:当请求把一个DocumentFragment节点插入到文档的时候,插入的不是DocumentFragment自身,而是它的全部的子孙节点。这使得DocumentFragment成了有用的占位符,暂时存放那些一次插入文档的节点,同时它还有利于实现文档的剪切、复制和粘贴等操做。
例子中的:var frag = document.createDocumentFragment();
就属于DocumentFragment(文档片断节点);
经过DOM节点类型,咱们可知,能够经过某个节点的nodeType属性来得到节点的类型,节点的类型能够是数值常量或者字符常量。示例代码以下:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>nodeType</title> </head> <body> <div id="container">这是一个元素节点</div> <script> var divNode = document.getElementById('container'); /* IE中只支持数值常量,由于低版本IE浏览器没有内置Node对象,其余浏览器数值常量和字符常量都支持,所以可 以直接用数值常量判断,这里为了比较两种写法,便都写在了这里 */ if (divNode.nodeType == Node.ELEMENT_NODE || divNode.nodeType === 1) { alert("Node is an element."); } </script> </body> </html>
先看示例代码:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>nodeName,nodeValue</title> </head> <body> <!--nodeName,nodeValue实验--> <div id="container">这是一个元素节点</div> <script> var divNode = document.getElementById('container'); console.log(divNode.nodeName + "/" + divNode.nodeValue); //结果: DIV/null var attrNode = divNode.attributes[0]; console.log(attrNode.nodeName + "/" + attrNode.nodeValue); //结果: id/container var textNode = divNode.childNodes[0]; console.log(textNode.nodeName + "/" + textNode.nodeValue); //结果: #text/这是一个元素节点 var commentNode = document.body.childNodes[1]; //表示取第二个注释节点,由于body下面的第一个注释节点为空白符。 console.log(commentNode.nodeName + "/" +commentNode.nodeValue); //结果: #comment/nodeName,nodeValue实验 console.log(document.doctype.nodeName + "/" + document.doctype.nodeValue); //结果: html/null var frag = document.createDocumentFragment(); console.log(frag.nodeName + "/" + frag.nodeValue); //结果: #document-fragment/null </script> </body> </html>
根据实验,得出如下汇总表格:
还记得刚开始学习JavaScript时候,常常会犯这样的错误:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Dom not ready</title> <script> document.getElementById("header").style.color = "red"; </script> </head> <body> <h1 id="header">这里是h1元素包含的内容</h1> </body> </html>
最后发现结果并非咱们想要的,文字并无变成红色,我想最早入门学习JavaScript操做DOM时候多多少少会遇到这种困惑和错误,其实出现这种问题的缘由就是咱们没有区分HTML标签和DOM节点的区别的缘故了,由这个问题就引出下面要说的domReady和浏览器渲染解析原理了。
html是一种标记语言,它告诉咱们这个页面有什么内容,但行为交互是须要经过DOM操做来实现的。咱们不要觉得有两个尖括号就觉得它是一个DOM了,html标签要经过浏览器解析才会变成DOM节点,当咱们向地址栏传入一个url的时候,咱们开始加载页面,就能看到内容,在这期间就有一个DOM节点构建的过程。节点是以树的形式组织的,当页面上全部的html都转换为节点之后,就叫作DOM树构建完毕,简称为domReady。
实际上浏览器是经过渲染引擎来实现的。渲染引擎的职责就是把请求的内容显示到浏览器屏幕上。默认状况下渲染引擎能够显示html、xml文档及图片。经过插件(浏览器扩展)它能够显示其余类型的文档,好比咱们安装pdf viewer插件,咱们就能够显示pdf文档。这里专一渲染引擎的主要用途,便是将css格式化的html和图片在浏览器上进行显示。
浏览器渲染要作的事就是把CSS,HTML,图片等静态资源展现到用户眼前。
渲染引擎首先经过网络得到所请求文档的内容,一般以8k分块的方法来完成:
上图就是html渲染的基本过程,但这并不包含解析过程当中浏览器加载外部资源,好比图片、脚本、iframe等的一些过程。说白了,上面的4步仅仅是html结构的渲染过程。而外部资源的加载在html结构的渲染过程当中是贯彻始终的,即使绘制DOM节点已经完成,而外部资源仍然可能正在加载或者还没有加载。
Firefox浏览器Gecko渲染流程跟Webkit内核渲染相似,大同小异,WebKit 和 Gecko 使用的术语略有不一样,但总体流程是基本相同的。这里以Webkit内核做为例子来讲明浏览器渲染的主要流程。
浏览器的渲染原理并不是三言两语,几个图就能说明白的,上图说的只是介绍一个大环节的过程和步骤,这里抛砖引玉象征性说个大概,更多关于浏览器内部工做原理的文章,请阅读:浏览器的工做原理:新式网络浏览器幕后揭秘
上面的各个代码实例中,并无考虑domReady,程序也能正常运行,由于咱们把javascript代码写在了body元素最后的位置。由于浏览器是从上到下,从左向右渲染元素的,这样实例中的js代码必定在domReady以后去执行的。那为何还要用domReady呢?事实上,咱们在编写大型项目的时候,js文件每每很是多,并且之间会相互调用,大多数都是外部引用的,不把js代码直接写在页面上。这样的话,若是有个domReady这个方法,咱们想用它就调用,无论逻辑代码写在哪里,都是等到domReady以后去执行的。
window.onload方法,表示当页面全部的元素都加载完毕,而且全部要请求的资源也加载完毕才触发执行function这个匿名函数里边的具体内容。这样确定保证了代码在domReady以后执行。使用window.onload方法在文档外部资源很少的状况下不会有什么问题,可是当页面中有大量远程图片或要请求的远程资源时,咱们须要让js在点击每张图片时,进行相应的操做,若是此时外部资源尚未加载完毕,点击图片是不会有任何反应的,大大下降了用户体验。那既然window.onload方法不可行,又该怎么作呢?
你确定想到了jquery中的$(document).ready(function(){})方法了,其实jquery中的domReady应该和window.onload的实现原理是大同小异的。为了解决window.onload的短板,w3c 新增了一个 DOMContentLoaded 事件。
这里提到了DOMContentLoaded事件,这里因为篇幅有限,就很少作介绍,这里面也有不少细节能够学习,有兴趣的童鞋,能够看看我以前收藏的两篇文章:
你不知道的 DOMContentLoaded&version=12020110&nettype=WIFI&fontScale=100&pass_ticket=2uOqWTPNenLxF7wD%2F%2Bi%2F0TK60XMDQLdZ%2Bk2hyDjtKZsM9jitnQM4c%2B5cVfq0SJLP)
浅谈DOMContentLoaded事件及其封装方法
学习就是一个无底洞,由于深不可测,才让人不断探索。
参考jquery中domReady的实现原理,来看一下javascript中domReady的实现策略。
在页面的DOM树建立完成后(也就是HTML解析第一步完成)即触发,而无需等待其余资源的加载。即domReady实现策略:
1. 支持DOMContentLoaded事件的,就使用DOMContentLoaded事件。 2. 不支持的就用来自Diego Perini发现的著名Hack兼容。兼容原理大概就是经过IE中的document, documentElement.doScroll('left')来判断DOM树是否建立完毕。
JavaScript实现domReady,【domReady.js】
function myReady(fn){ //对于现代浏览器,对DOMContentLoaded事件的处理采用标准的事件绑定方式 if ( document.addEventListener ) { document.addEventListener("DOMContentLoaded", fn, false); } else { IEContentLoaded(fn); } //IE模拟DOMContentLoaded function IEContentLoaded (fn) { var d = window.document; var done = false; //只执行一次用户的回调函数init() var init = function () { if (!done) { done = true; fn(); } }; (function () { try { // DOM树未建立完以前调用doScroll会抛出错误 d.documentElement.doScroll('left'); } catch (e) { //延迟再试一次~ setTimeout(arguments.callee, 50); return; } // 没有错误就表示DOM树建立完毕,而后立马执行用户回调 init(); })(); //监听document的加载状态 d.onreadystatechange = function() { // 若是用户是在domReady以后绑定的函数,就立马执行 if (d.readyState == 'complete') { d.onreadystatechange = null; init(); } } } }
在页面中引入donReady.js文件,引用myReady(回调函数)方法便可。
感兴趣的童鞋能够看看各个主流框架domReady的实现:点击我查看
下面经过一个案例,来比较domReady与window.onload实现的不一样,很明显,onload事件是要在全部请求都完成以后才执行,而domReady利用hack技术,在加载完dom树以后就能执行,因此domReady比onload执行时间更早,建议采用domReady。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>domReady与window.onload</title> <script src="domReady.js"></script> </head> <body> <div id="showMsg"></div> <div> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zofelhdj20xc0xc42s.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zofahw3j20m80etq4a.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zoi3ny6j20l20dw4gd.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zog3tauj20m80et0uw.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zofi2o5j20m80ettaq.jpg" alt=""> <img src="http://ww1.sinaimg.cn/large/ae49ba57gy1fe9zohjuvhj20tb0cdwvp.jpg" alt=""> </div> <script> var d = document; var msgBox = d.getElementById("showMsg"); var imgs = d.getElementsByTagName("img"); var time1 = null, time2 = null; myReady(function() { msgBox.innerHTML += "dom已加载!<br>"; time1 = new Date().getTime(); msgBox.innerHTML += "时间戳:" + time1 + "<br>"; }); window.onload = function() { msgBox.innerHTML += "onload已加载!<br>"; time2 = new Date().getTime(); msgBox.innerHTML += "时间戳:" + time2 + "<br>"; msgBox.innerHTML += "domReady比onload快:" + (time2 - time1) + "ms<br>"; }; </script> </body> </html>
执行结果对比,发现DomReady比onload快乐2秒多。
为何要判断元素的节点?
由于要判断元素节点类型,由于属性的一系列操做与元素的节点类型息息相关,若是咱们不区分它们,咱们就不知道用元素的直接属性操做(例如:ele.xxx=yyy)仍是用一个方法操做(el.setAttribute(xxx,yyy))。
设计元素类型的断定,这里给出有4个方法:
(1). isElement :断定某个节点是否为元素节点 (2). isHTML :断定某个节点是否为html文档的元素节点 (3). isXML : 断定某个节点是否为xml文档的元素节点 (4). contains :用来断定两个节点的包含关系
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isElement</title> </head> <body> <div id="test">aaa</div> <!--这是一个注释节点--> <script> var isElement = function (el){ return !!el && el.nodeType === 1; } var a = { //随意定义一个变量,设置nodeType为1 nodeType: 1 } console.log(isElement(document.getElementById("test"))); //结果: true console.log(isElement(document.getElementById("test").nextSibling)); //这里的nextSibling属性查找下一个相邻节点,即注释节点 //结果: false console.log(isElement(a)); //结果: true </script> </body> </html>
注意代码中的!!用法:!!通常用来将后面的表达式转换为布尔型的数据(boolean).
由于javascript是弱类型的语言(变量没有固定的数据类型)因此有时须要强制转换为相应的类型,关于JavaScript的隐式转换,能够看看以前我写的一篇博客,这篇文章几乎分析到了全部的转换规则,感兴趣的童鞋能够点击查阅,学习了解一下。
[从++[[]][+[]]+[+[]]==10?深刻浅出弱类型JS的隐式转换](https://github.com/jawil/blog...
注意:上面的代码定义了一个变量a,将它的nodeType的值设为1,因为元素节点的节点类型的数值常量为1,因此这里在打印的的时候,会将a认为是元素节点,因此打印true。这种结果明显不是咱们想要的,即便这种状况不多出现。下面给出解决方案:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isElement</title> </head> <body> <div id="test">aaa</div> <!--这是一个注释节点--> <script> var testDiv = document.createElement('div'); var isElement = function (obj) { if (obj && obj.nodeType === 1) {//先过滤最简单的 if( window.Node && (obj instanceof Node )){ //若是是IE9,则断定其是否Node的实例 return true; //因为obj多是来自另外一个文档对象,所以不能轻易返回false } try {//最后以这种效率很是差但确定可行的方案进行断定 testDiv.appendChild(obj); testDiv.removeChild(obj); } catch (e) { return false; } return true; } return false; } var a = { nodeType: 1 } console.log(isElement(document.getElementById("test"))); //结果: true console.log(isElement(document.getElementById("test").nextSibling)); //结果: false console.log(isElement(a)); //结果: false </script> </body> </html>
这样,在判断a是不是元素节点时,结果就是false了。
更多关于元素节点的判断请参考:How do you check if a JavaScript Object is a DOM Object?
咱们能够简单的将全部的元素节点化为两类:一类是HTML,一类是XML。不过从严格意义上来讲,HTML只是XML的一个子集,它拥有更多的特性,而XML在矢量绘图的处理上又派生出了两大类:SVG和VML。那么按照这种方法,咱们能够简单的认为若是不是HTML,就是XML的元素节点了。而HTML是比较容易识别的,由于它有更多的特性。好比说,XML是没有className的,或者咱们经过一个元素的ownerDocument获得它的文档对象,XML是没有document.getElementById()和document.getElementsByTagName()这些方法的.此外,最大的区别是HTML元素的nodeName老是大写的,当你使用createElement()方法建立HTML元素的时候,不管你传入的字母是大写仍是小写,最后获得的都是大写。
接下来就看看各大类库是怎么实现HTML和XML文档的元素节点的断定的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isXML</title> </head> <body> <script> //Sizzle, jQuery自带的选择器引擎 var isXML = function(elem) { var documentElement = elem && (elem.ownerDocument || elem).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; console.log(isXML(document.getElementById("test"))); //但这样不严谨,由于XML的根节点,也多是HTML标签,好比这样建立一个XML文档 try { var doc = document.implementation.createDocument(null, 'HTML', null); console.log(doc.documentElement); console.log(isXML(doc)); } catch (e) { console.log("不支持creatDocument方法"); } </script> </body> </html>
浏览器随便找个HTML页面验证一下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isXML</title> </head> <body> <script> //咱们看看mootools的slick选择器引擎的源码: var isXML = function(document) { return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') || (document.nodeType == 9 && document.documentElement.nodeName != 'HTML'); }; //精简版 var isXML = window.HTMLDocument ? function(doc) { return !(doc instanceof HTMLDocument); } : function(doc) { return "selectNodes" in doc; } </script> </body> </html>
不过,这些方法都只是规范,javascript对象是能够随意添加的,属性法很容易被攻破,最好是使用功能法。功能法的实现代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isXML</title> </head> <body> <script> var isXML = function(doc) { return doc.createElement("p").nodeName !== doc.createElement("P").nodeName; } </script> </body> </html>
咱们知道,不管是HTML文档,仍是XML文档都支持createELement()方法,咱们断定建立的元素的nodeName是区分大小写的仍是不区分大小写的,咱们就知道是XML仍是HTML文档,这个方法是目前给出的最严谨的函数了。
判断是否是HTML文档的方法以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isHTML</title> </head> <body> <script> var isHTML = function(doc) { return doc.createElement("p").nodeName === doc.createElement("P").nodeName; } console.log(isHTML(document)); </script> </body> </html>
有了以上判断XML和HTML文档的方法,咱们就能够实现一个元素节点属于HTML仍是XML文档的方法了,实现代码以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>isHTMLElement</title> </head> <body> <script> var testDiv = document.createElement('div'); var isElement = function (obj) { if (obj && obj.nodeType === 1) {//先过滤最简单的 if( window.Node && (obj instanceof Node )){ //若是是IE9,则断定其是否Node的实例 return true; //因为obj多是来自另外一个文档对象,所以不能轻易返回false } try {//最后以这种效率很是差但确定可行的方案进行断定 testDiv.appendChild(obj); testDiv.removeChild(obj); } catch (e) { return false; } return true; } return false; } var isHTML = function(doc) { return doc.createElement("p").nodeName === doc.createElement("P").nodeName; } var isHTMLElement = function(el){ if(isElement){ return isHTML(el.ownerDocument); } return false; } console.log(isHTMLElement(testDiv)); </script> </body> </html>
DOM能够将任何HTML描绘成一个由多层节点构成的结构。节点分为12种不一样类型,每种类型分别表示文档中不一样的信息及标记。每一个节点都拥有各自的特色、数据和方法,也与其余节点存在某种关系。节点之间的关系构成了层次,而全部页面标记则表现为一个以特定节点为根节点的树形结构。DOM间的节点关系大体以下。
节点关系不只仅指元素节点的关系,document文档节点也包含在内。在最新的浏览器中,全部的节点都已经装备了contains()方法,
元素之间的包含关系,用自带的contains方法,只有两个都是元素节点,才能兼容各个浏览器,不然ie浏览器有的版本是不支持的,能够采用hack技术,本身写一个contains方法去兼容。
元素之间的包含关系:contains()方法.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>contains</title> </head> <body> <div id="p-node"> <div id="c-node">子节点内容</div> </div> <script> var pNode = document.getElementById("p-node"); var cNode = document.getElementById("c-node").childNodes[0]; alert(pNode.contains(cNode)); //true </script> </body> </html>
兼容各浏览器的contains()方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>contains</title> </head> <body> <div id="p-node"> <div id="c-node">子节点内容</div> </div> <script> //兼容的contains方法 function fixContains(a, b) { try { while ((b = b.parentNode)){ if (b === a){ return true; } } return false; } catch (e) { return false; } } var pNode = document.getElementById("p-node"); var cNode = document.getElementById("c-node").childNodes[0]; alert(fixContains(pNode, cNode)); //true alert(fixContains(document, cNode)); //true </script> </body> </html>
DOM节点是一个很是复杂的东西,对它的每个属性的访问,不走运的话,就可能会向上溯寻到N多个原型链,所以DOM操做是个很是耗性能的操做。风头正盛的react为了解决这个问题,提出了虚拟DOM的概念,合并和屏蔽了不少无效的DOM操做,效果很是惊人。接下来看看DOM节点到底是如何继承的。
使用document.createElement("p")建立p元素,其实document.createElement("p")是HTMLParagraphElement的一个实例,而HTMLParagraphElement的父类是HTMLElement,HTMLElement的父类是Element,Element的父类是Node,Node的父类是EventTarget,EventTarget的父类是Function,Function的父类是Object。
建立一个p元素一共溯寻了7层原型链:
下面咱们来分析一下建立一个元素所继承的属性分别是啥。
document.createElement("p")首先就是一个实例对象,它是由构造函数HTMLParagraphElement产生的,你能够这么看这个问题:
function HTMLParagraphElement() { [native code] } document.createElement("p")=new HTMLParagraphElement('p');
由以上继承关系能够看出来:
`document.createElement("p").constructor===HTMLParagraphElement
document.createElement("p").__proto__===HTMLParagraphElement.prototype
`
对实例对象,构造函数,以及JavaScript原型链和继承不太熟悉的童鞋,该补习一下基础看看了。
咱们先来看看document.createElement("p")自身有哪些属性,遍历对象属性方法通常有三种:
先来说一讲遍历对象属性三种方法的差别性,当作补充复习。
遍历数组属性目前我知道的有:for-in
循环、Object.keys()
和Object.getOwnPropertyNames()
,那么三种到底有啥区别呢?
for-in循环:会遍历对象自身的属性,以及原型属性,包括enumerable 为 false(不可枚举属性); Object.keys():能够获得自身可枚举的属性,但得不到原型链上的属性; Object.getOwnPropertyNames():能够获得自身全部的属性(包括不可枚举),但得不到原型链上的属性,Symbols属性 也得不到.
Object.defineProperty
顾名思义,就是用来定义对象属性的,vue.js
的双向数据绑定主要在getter
和setter
函数里面插入一些处理方法,当对象被读写的时候处理方法就会被执行了。 关于这些方法和属性的更具体解释,能够看MDN上的解释(戳我);
简单看一个小demo例子加深理解,对于Object.defineProperty
属性不太明白,能够看看上面介绍的文档学习补充一下.
'use strict'; class A { constructor() { this.name = 'jawil'; } getName() {} } class B extends A { constructor() { super(); this.age = 22; } //getAge不可枚举 getAge() {} [Symbol('fullName')]() { } } B.prototype.get = function() { } var b = new B(); //设置b对象的info属性的enumerable: false,让其不能枚举. Object.defineProperty(b, 'info', { value: 7, writable: true, configurable: true, enumerable: false }); //Object能够获得自身可枚举的属性,但得不到原型链上的属性 console.log(Object.keys(b)); //[ 'name', 'age' ] //Object可A以获得自身全部的属性(包括不可枚举),但得不到原型链上的属性,Symbols属性也得不到 console.log(Object.getOwnPropertyNames(b)); //[ 'name', 'age', 'info' ] for (var attr in b) { console.log(attr);//name,age,get } //in会遍历对象自身的属性,以及原型属性 console.log('getName' in b); //true
有了上面的知识做为扩充,咱们就能够清晰明了的知道,建立元素P标签每一步都继承了哪些属性,继承对象自身有哪些属性,因为篇幅有限,你们能够自行子在浏览器测试,看看这些对象的一些属性和方法,便于咱们理解。
例如咱们想看:HTMLElement对象有哪些自身属性,咱们能够这么查看:
Object.getOwnPropertyNames(HTMLElement)
咱们想看:HTMLElement的原型对象有哪些自身属性,咱们能够这么查看:
Object.getOwnPropertyNames(HTMLElement.prototype)
HTMLElement的原型对象有哪些自身属性,根据原型链,咱们也能够这么查看:
由于:document.createElement("p").__proto__.__proto__===HTMLElement.prototype
Object.getOwnPropertyNames(document.createElement("p").__proto__.__proto__)
使用document.createTextNode("xxx")建立文本节点,其实document.createTextNode("xxx")是Text的一个实例,而Text的父类是CharactorData,CharactorData的父类是Node,Node的父类是EventTarget,EventTarget的父类是Function,Function的父类是Object。
建立一个文本节点一共溯寻了6层原型链。
所以,全部节点的继承层次都不简单,但相比较而言,元素节点是更可怕的。从HTML1升级到HTML3.2,再升级到HTML4.1,再到HTML5,除了不断地增长新类型、新的嵌套规则之外,每一个元素也不断的添加新属性。
下面看一个例子:建立一个p元素,打印它第一层原型的固有的属性的名字,经过Object的getOwnPropertyNames()获取当前元素的一些属性,这些属性都是他的原始属性,不包含用户自定义的属性。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>DOM inheritance hierarchy</title> </head> <body> <script> console.log(Object.getOwnPropertyNames(document.createElement("p").__proto__)); //访问p元素上一层原型控制台打印: ["align","constructor"] console.log( Object.getOwnPropertyNames(document.createElement("p").__proto__.__proto__) ); /*访问p元素上一层原型的再上一层原型,控制台会打印不少属性,感兴趣的伙伴能够本身贴代码到控制台看 一下,它要比访*问第一层原型的属性多得多。这也就是说,每往上一层,原型链就为它添加一些属性。 */ </script> </body> </html>
下面看一个空的div元素,而且没有插入到DOM里边,看它有多少自有属性(不包括原型链继承来的属性)
在新的HTML规范中,许多元素的固有属性(好比value)都放到了原型链当中,数量就更加庞大了。所以,将来的发展方向是尽可能使用现成的框架来实现,好比MVVM框架,将全部的DOM操做都转交给框架内部作精细处理,这些实现方案固然就包括了虚拟DOM的技术了。可是在使用MVVM框架以前,掌握底层知识是很是重要的,明白为何这样作,为何不这样作的目的。这也是为何要理解DOM节点继承层次的目的。
HTML存在许多种类型的标签,有的标签下面只容许特定的标签存在,这就叫HTML嵌套规则。
不按HTML嵌套规则写,浏览器就不会正确解析,会将不符合嵌套规则的节点放到目标节点的下面,或者变成纯文本。
关于HTML嵌套规则,必定要掌握块状元素和内联元素的区别。
块状元素:通常是其余元素的容器,可容纳内联元素和其余块状元素,块状元素排斥其余元素与其位于同一行,宽度(width)高度(height)起做用。常见块状元素为div和p
内联元素:内联元素只能容纳文本或者其余内联元素,它容许其余内联元素与其位于同一行,但宽度(width)高度(height)不起做用。常见内联元素为a.
块状元素与内联元素嵌套规则:
(1).块元素能够包含内联元素或某些块元素,但内联元素却不能包含块元素,它只能包含其余的内联元素
例: <div><h1></h1><p></p></div> <a href="#"><span></span></a>
(2).块级元素不能放在<p>里面
例:<p><ol><li></li></ol></p><p><div></div></p>
(3).有几个特殊的块级元素提倡只能包含内联元素,不能再包含块级元素,这几个特殊的标签是:
h一、h二、 h三、h四、 h五、 h六、 p 、dt
(4).li标签能够包含div标签
例: <li><div></div></li>
(5).块级元素与块级元素并列,内联元素与内联元素并列
例: <div><h2></h2><p></p></div> <div><a href="#"></a><span></span></div>