标签:
前言:
本系列从原型,原型链,属性类型等方面下手学习了DOM文档对象模型,旨在弄清我们在DOM中常用的每一个属性和方法都清楚它从哪里来要到哪里做什么事,这样对于理解代码有一定启发。全靠自己在总结中摸索,所以对于一个问题要是深究还真能挖出许多有意思的东西,现在觉得JavaScript这个东西简直越来越有意思了。
正文:
DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序编程接口)。DOM描绘了一个层次化的节点树,允许开发人员添加,移除,修改页面某一部分,现在它已成为表现和操作页面标记的真正的跨平台,语言中立的方式。
DOM1为基本文档结构及查询提供了接口。本系列主要讨论JavaScript对DOM1级的实现,但是还会穿插一点DOM2和DOM3的内容。
节点层次
DOM可以将任何HTML和XML文档描绘成一个由多层节点构成的结构。节点分为好几种不同的类型,每种类型分别表示文档中不同的信息及标记。每个节点都有各自特点,属性和方法,及和其他节点存在的关系。节点之间的关系构成了层次,而所有页面标记则表现为一个以特定节点为根节点的树形结构。节点比元素的概念大,元素只是节点的一种类型。
文档节点:每个文档的根节点。HTML文档中文档节点( window.document=>#document )只有一个子节点即 HTML 元素。
文档元素:文档中最外层元素,文档中的其他元素都包含在文档元素中,每个文档只能有一个文档元素。在HTML页面中,文档元素始终都是 HTML 元素。XML中,没有预定义的元素,任何元素都能成为文档元素。
每一段标记都可通过树中一个节点表示: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高级程序设计》
MDN HTMLCollection
MDN NodeList
标签:
原文地址:http://www.cnblogs.com/venoral/p/5293575.html