前言:html
本系列从原型,原型链,属性类型等方面下手学习了DOM文档对象模型,旨在弄清咱们在DOM中经常使用的每个属性和方法都清楚它从哪里来要到哪里作什么事,这样对于理解代码有必定启发。全靠本身在总结中摸索,因此对于一个问题要是深究还真能挖出许多有意思的东西,如今以为JavaScript这个东西简直愈来愈有意思了。node
正文:
DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM描绘了一个层次化的节点树,容许开发人员添加,移除,修改页面某一部分,如今它已成为表现和操做页面标记的真正的跨平台,语言中立的方式。
DOM1为基本文档结构及查询提供了接口。本系列主要讨论JavaScript对DOM1级的实现,可是还会穿插一点DOM2和DOM3的内容。编程
节点层次数组
DOM能够将任何HTML和XML文档描绘成一个由多层节点构成的结构。节点分为好几种不一样的类型,每种类型分别表示文档中不一样的信息及标记。每一个节点都有各自特色,属性和方法,及和其余节点存在的关系。节点之间的关系构成了层次,而全部页面标记则表现为一个以特定节点为根节点的树形结构。节点比元素的概念大,元素只是节点的一种类型。浏览器
文档节点:每一个文档的根节点。HTML文档中文档节点( window.document=>#document )只有一个子节点即 HTML 元素。缓存
文档元素:文档中最外层元素,文档中的其余元素都包含在文档元素中,每一个文档只能有一个文档元素。在HTML页面中,文档元素始终都是 HTML 元素。XML中,没有预约义的元素,任何元素都能成为文档元素。app
每一段标记均可经过树中一个节点表示:HTML元素经过元素节点表示,特性经过特性节点表示,文档类型经过文档类型节点表示,注释经过注释节点表示...共有12种节点类型,这些类型都继承自一个基类型Node类型。下面来挨个看这些节点类型,可是本篇着重学习Node类型,其余类型和DOM操做技术在后续系列的总结中。函数
DOM操做技术工具
Node类型:学习
DOM1级定义了一个Node接口,该接口将由DOM中全部节点类型实现。这个Node接口在JavaScript中做为Node类型实现,JavaScript中全部节点类型(Element,Attr,Text,CDATASection,Comment,Document,DocumentType,DocumentFragment等)都继承自Node类型( Element.prototype instanceof Node;// true ),所以全部节点类型的实例都共享着原型链(某类型实例->某类型.prototype->Node.prototype->EventTarget.prototype->Object.prototype)上的属性和方法, document instanceof Node;// true 好比文档节点 #document 就是Document类型实例也是Node类型的实例,文档元素 html 是HTMLElement类型的实例是Element类型实例也是Node类型的实例。
下面这段能够略过,只是个人一个小思考:
document.documentElement.__proto__==HTMLElement.prototype;// false //竟然是false,这个html元素节点实例的原型指向的不是HTMLElement构造函数的prototype??可是... HTMLElement.prototype.isPrototypeOf(document.documentElement);// true //判断HTMLElement.prototype确实是在html元素的原型链上啊
有没有以为很奇怪, __proto__ 不是指向构造这个实例的函数的原型吗??为何会是 false ,并且 document.documentElement.__proto__ 和 HTMLelement.prototype 居然不是同一类型的,按理说 html 元素做为HTMLElement的实例,默认它们指向同一个 HTMLElement.prototype 对象的。
百思不得其解,一度认为我对 __proto__ 这个东西是否是理解有误,查看相关文档MDN Object.prototype.__proto__后受了点启发,
是否是 document.documentElement.__proto__ 被JS引擎重写了!!?让其从新指向一个为HTMLElement类型的实例对象(假设就叫 HTMLEleObj 吧,实际上是 HTMLHtmlElement.prototype ),查看 HTMLEleObj 的 __proto__ ,发现这个东西类型为Element类型实例,想到 HTMLElement.prototype 也是Element类型的,那这两个是否是同一个对象?
好像是这样的: document.documentElement.__proto__.__proto__==HTMLElement.prototype;// true 。这也就能解释为何html元素改变了 [[prototype]] 指向但还仍在原来的原型链上,JS引擎是给这个自己默认的原型链( html.__proto__->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype )又增长了一个对象,如今原型链变成( html.__proto__->HTMLEleObj;HTMLEleObj.__proto__->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype )。仍是画个图比较好理解点。可是仍是不明白为什么JS引擎要在原型链上增长这么个对象,有什么用处?发现html元素身上有两个属性,版本和构造器,然而并不能直接经过 HTMLHtmlElement.prototype.version 访问 version 属性(每一个元素的 __proto__ 除了 constructor 属性外其他的这些属性还都不同,不过都是HTML5以前元素上的属性),须要经过它的实例(html元素)访问。 constructor 指向 HTMLHtmlElement 接口。
---补充---
经过 document.documentElement.__proto__.constructor 访问获得,HTMLEleObj对象实际上是HTMLHtmlElement接口的原型对象,虽然以上思考有些误解,可是仍是留下这个思考的过程吧。真正的原型链是 某元素.__proto__->HTML某元素Element.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype 。好比对于html元素就是 document.documentElement.__proto__->HTMLHtmlElement.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype 。对于body元素就是 document.body.__proto__->HTMLBodyElement.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype 。其实都是能够经过 某元素.__proto__.constructor 属性访问到其对应的构造器。
扯远了,回归主题来看Node类型:
Node.prototype上的属性及方法
注意到 Node.prototype 是 EventTarget 类型的实例对象,怪不得 Node.prototype.__proto__ 会指向 EventTarget.prototype 。
Object.getOwnPropertyNames(EventTarget.prototype);//
["addEventListener", "removeEventListener", "dispatchEvent", "constructor"]
全部这些关系总结来讲就是Node和EventTarget是JavaScript中两种类型,用组合继承模式实现的话就内部实现多是这样子:
function EventTarget(){ //初始化实例的属性和方法 } function Node(){ //初始化实例的属性和方法 } Node.prototype=new EventTarget(); Node.prototype.construct=Node;
//以Element类型举例 Element.prototype=new Node(); Element.construct=Element; ...
Node.prototype 指向 EventTarget.prototype ,而且 Node.prototype 会被初始化上一些属性和实例。不过事实上咱们并不能成功构造 Node 和 EventTarget 类型的实例,控制台会提示报错不合法的构造。那应该是JS引擎内部本身实现的吧,否则谁都能构造这种底层接口的实例那就乱套了。
Node.prototype常见属性和方法:
这些关系指针属性大部分都是只读的由于访问器属性的 set 被设置为 undefiend 了,即便 set 不为 undefiend ,但 set 方法能被使用的前提是该节点的对应要访问的那个属性不为 null 。因此DOM提供了一些操做节点的方法(从第9小点开始总结,这些方法都是可写的,而且在Node.prototype上能够重写这些方法)
Node.prototype.COMMENT_NODE==Node.COMMENT_NODE;// true Node.prototype.COMMENT_NODE;// 8 Node.COMMENT_NODE;// 8 document.COMMENT_NODE;// 8
if(someNode.nodeType==1){ console.log("这是一个元素节点"); }
nodeName和nodeValue属性:
表示节点具体信息。
(1).对于Element元素节点:原型链继承关系为:某元素节点.__proto__->HTML某元素Element.prototype->HTMLElement.prototype->Element.prototype->Node.prototype->EventTarget.prototype。
nodeName保存的为元素的标签名,nodeValue的值为null。
var someNode=document.documentElement;// 获取HTML元素 if(someNode.nodeType==1){ console.log(someNode.nodeName+"是元素节点名"); console.log("它的nodeValue:"+someNode.nodeValue); } /*HTML是元素节点名 它的nodeValue:null */
var html=document.documentElement; //获取特性实例所在的对象 html.attributes;//attributes属性是Element.prototype上的属性
这个对象是NamedNodeMap类型的实例,这个对象的原型链关系为html.attributes.__proto__->NamedNodeMap.prototype->Object.prototype。这个对象里面又有几个属性,这几个属性才是咱们须要的真正特性对象。
html.attributes["0"];// lang="zh-cn" 是一个特性节点 html.attributes["1"];// style="overflow:hidden;" 是另外一个特性节点 html.attributes["lang"].nodeName;// "lang" lang特性节点的nodeName值 html.attributes["lang"].nodeValue;// "zh-cn" html.attributes["0"] instanceof Attr;// true
返回 null 。
等价的写法是 nodeList[idx] , 不过这种状况下越界访问将返回 undefined (由于是以数组形式访问的)。
对arguments对象使用Array.prototype.slice()方法将其转换为数组,采用一样方法也能够将NodeList类型集合转换为数组类型,其实就是就是在类数组对象的上下文中调用原生的slice方法。function transToArr(collections,start,end){ var length=arguments.length; if(length==0){ return; }else if(length==1){ return Array.prototype.slice.call(collections); }else{ //判断start,end类型 if(typeof arguments[1]=='number'){ if(typeof arguments[2]=='number'){ return Array.prototype.slice.call(collections,start,end); }else{ //end参数不是number类型时,slice返回length以前的项 end=collections.length; return Array.prototype.slice.call(collections,start,end); } } } }
//删除childNodes中的全部文本节点,由于child.length是动态变化的,因此分状况i++ var child=parent.childNodes; for(var i=0;i<child.length;){ if(child[i].nodeType==3){ ul.removeChild(child[i]); //不用i=0回归到开时就用上次的i就可 }else{ i++; } }
var a=document.body.firstChild; document.body.appendChild(document.body.firstChild)==a;// true a==document.body.lastChild;// true
//要插入到desEle以后,至关于插入desEle.nextSibling以前,返回被插入的srcEle Node.prototype.insertAfter=function(srcEle,desEle){ this.insertBefore(srcEle,desEle.nextSibling); return srcEle; }
var parent=$('#hdtb-msb'); var first=parent.firstChild; var last=parent.lastChild; var firstnode=parent.replaceChild(last,first); firstnode;// <div class="hdtb-mitem hdtb-msel hdtb-imb">所有</div> firstnode.ownerDocument;// #document 节点 证实还在文档树
var clone1=last.cloneNode(true); clone1;// <a class="hdtb-tl" id="hdtb-tls" role="button" tabindex="0" data-ved="0ahUKEwiYht2SptbLAhVDao4KHSbJBqIQ2x8ICigF">搜索工具</a> var clone2=last.cloneNode(false); clone2;// <a class="hdtb-tl" id="hdtb-tls" role="button" tabindex="0" data-ved="0ahUKEwiYht2SptbLAhVDao4KHSbJBqIQ2x8ICigF"></a> clone1.ownerDocument==clone2.ownerDocument // true
cloneNode()方法不会复制添加DOM节点的JavaScript属性,例如事件处理程序。这个方法只复制特性(包括经过特性绑定的事件处理程序 <h1 onclick="console.log('xxx')">xxx</h1> 会将事件复制成功),子节点(深复制状况下),其余一切都不会复制。
document.createTextNode(''); document.createTextNode(' '); document.createTextNode(' '); ...
也就是看该文本节点的data(ChacterData.prototype上的属性)值就能够了,图片上的data值是回车虽然在呈现上和空文本节点同样,但并非空因此不能被删除了,因此注意这样编代码ul的childNodes里的文本节点实际上是回车符。
<ul> <li></li> <li></li> </ul>
参考:
《JavaScript高级程序设计》