美文网首页
JS高程:读书摘要(七)DOM

JS高程:读书摘要(七)DOM

作者: Upcccz | 来源:发表于2019-01-02 19:42 被阅读0次

    一、 DOM

    DOM(文档对象模型)是针对HTMLXML文档的一个API(应用程序编程接口)。DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。DOM脱胎于Netscape 及微软公司创始的DHTML(动态HTML),但现在它已经成为表现和操作页面标记的真正的跨平台、语言中立的方式。

    Node类型

    DOM1级定义了一个Node接口,该接口将由DOM 中的所有节点类型实现。每个节点都有一个nodeType属性,用于表明节点的类型。节点类型由在Node 类型中定义的下列12 个数值常量来表示,任何节点类型必居其一:

    节点类型nodeType
    节点的nodeName和nodeValue
    • 节点关系

    每个节点都有一个childNodes属性,其中保存着一个NodeList 对象。NodeList是一种类数组对象(可以通过下标访问,有length属性,但它并不是Array的实例),用于保存一组有序的节点,可以通过位置(索引 / 方括号写法)来访问这些节点。NodeList对象的独特之处在于,它实际上是基于DOM 结构动态执行查询的结果,因此DOM结构的变化能够自动反映在NodeList 对象中。我们常说,NodeList是有生命、有呼吸的对象,而不是在我们第一次访问它们的某个瞬间拍摄下来的一张快照。

    如何访问保存在NodeList中的节点——可以通过方括号,也可以使用item()方法。

    var firstChild = someNode.childNodes[0];
    var secondChild = someNode.childNodes.item(1);
    var count = someNode.childNodes.length;
    

    使用Array.prototype.slice()方法可以将NodeList对象转换为数。

    var arrayOfNodes = Array.prototype.slice.call(someNode.childNodes,0);
    
    • 节点关系的引用属性

      • parentNode:该属性指向文档树中的父节点
      • previousSibling:该属性指向文档书中的前一个兄弟节点,第一个节点的previousSibling 属性值为null
      • nextSibling:该属性指向文档书中的前一个兄弟节点,最后一个节点的nextSibling属性值为null
      • 父节点的firstChildlastChild属性分别指向其childNodes 列表中的第一个和最后一个节点 中,someNode.firstChild 的值始终等于someNode.childNodes[0] , 而someNode.lastChild 的值始终等于someNode.childNodes [someNode.childNodes.length-1]
    • 操作子节点

      • appendChild():用于向childNodes列表的末尾添加一个节点。添加节点后,childNodes 的新增节点、父节点及以前的最后一个子节点的关系指针都会相应地得到更新。更新完成后,appendChild()返回新增的节点。如果传入到appendChild()中的节点已经是文档的一部分了,那么该节点就会成为父节点的最后一个子节点。
      • insertBefore():把节点放在childNodes列表中某个特定的位置上,这个方法接受两个参数:要插入的节点和作为参照的节点。插入节点后,被插入的节点会变成参照节点的前一个同胞节点(previousSibling),同时被方法返回。如果参照节点是null,则insertBefore()appendChild()执行相同的操作。
      • replaceChild():这个方法接受两个参数,要插入的节点和要替换的节点。要替换的节点将由这个方法返回并从文档树中被移除,同时由要插入的节点占据其位置。在使用replaceChild()插入一个节点时,该节点的所有关系指针都会从被它替换的节点复制过来。尽管从技术上讲,被替换的节点仍然还在文档中,但它在文档中已经没有了自己的位置。
      • removeChild():这个方法接受一个参数,即要移除的节点。被移除的节点将成为方法的返回值。
    • 操作节点的其他方法

      • cloneNode(),用于创建调用这个方法的节点的一个完全相同的副本。cloneNode()方法接受一个布尔值参数,表示是否执行深复制。在参数为true的情况下,执行深复制,也就是复制节点及其整个子节点树;在参数为false的情况下,执行浅复制,即只复制节点本身。复制后返回的节点副本属于文档所有,但并没有为它指定父节点。因此,这个节点副本就成为了一个“孤儿”,除非通过appendChild()insertBefore()replaceChild()将它添加到文档中。
      • normalize(),由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点。
    var deepList = myList.cloneNode(true);
    alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)
    
    var shallowList = myList.cloneNode(false);
    alert(shallowList.childNodes.length); //0
    

    cloneNode()方法不会复制添加到DOM节点中的JavaScript属性,例如事件处理程序等。这个方法只复制特性、(在明确指定的情况下也复制)子节点,其他一切都不会复制。IE 在此存在一个bug,即它会复制事件处理程序,所以我们建议在复制之前最好先移除事件处理程序。

    二、 Document 类型

    document对象:在浏览器中,document对象是HTMLDocument(继承自Document类型)的一个实例,表示整个HTML页面。而且,document 对象是window 对象的一个属性,因此可以将其作为全局对象来访问。通过这个文档对象,不仅可以取得与页面有关的信息,而且还能操作页面的外观及其底层结构。Document 节点具有下列特征: nodeType 的值为9;nodeName 的值为"#document"nodeValue 的值为nullparentNode 的值为null

    document对象的属性

    var html = document.documentElement; //取得对<html>的引用
    
    var body = document.body; //取得对<body>的引用
    
    var doctype = document.doctype; //取得对<DOCTYPE>文档类型的引用
    
    //取得文档标题
    var originalTitle = document.title;
    //设置文档标题
    document.title = "New page title";
    
    //取得完整的URL,不可设置
    var url = document.URL;
    
    //取得域名,可设置,但是不能随便设置
    var domain = document.domain;
    
    //假设页面来自p2p.wrox.com 域
    document.domain = "wrox.com"; // 成功
    document.domain = "nczonline.net"; // 出错! 只能省略域名
    
    //取得来源页面的URL,不可设置
    var referrer = document.referrer;
    
    查找元素

    1、getElementById():接收一个参数:要取得的元素的ID。如果找到相应的元素则返回该元素,如果不存在带有相应ID 的元素,则返回null。注意,这里的ID必须与页面中元素的id特性(attribute)严格匹配,包括大小写。IE8 及较低版本不区分ID的大小写,因此"myDiv""mydiv"会被当作相同的元素ID。如果页面中多个元素的ID 值相同,getElementById()只返回文档中第一次出现的元素。

    <input type="text" name="myElement" value="Text field">
    <div id="myElement">A div</div>
    

    基于这段HTML 代码,在IE7 中调用document.getElementById("myElement "),结果会返回<input>元素;而在其他所有浏览器中,都会返回对<div>元素的引用。为了避免IE中存在的这个问题,最好的办法是不让表单(<input><textarea><button><select>)字段的name特性与其他元素的ID相同。

    2、getElementsByTagName()。这个方法接受一个参数,即要取得元素的标签名,返回一个HTMLCollection对象,作为一个“动态”集合,返回所有的标签元素组成的类数组。HTMLCollection 对象还有一个方法,叫做namedItem(),使用这个方法可以通过元素的name特性取得集合中的项。例如,

    <img src="myimage.gif" name="myImage">
     // 那么就可以通过如下方式从images几个中取得对应name的<img>元素:
    var images = document.getElementsByTagName("img");
    var myImage = images.namedItem("myImage");
    
    // HTMLCollection 还支持按名称访问项
    var myImage = images["myImage"];
    

    在通过元素调用这个方法时,除了搜索起点是当前元素之外,其他方面都跟通过document调用这个方法相同,因此结果只会返回当前元素的后代。

    var ul = document.getElementById("myList");
    var items = ul.getElementsByTagName("li");
    

    3、getElementsByClassName():这个方法接受一个参数,即要取得元素的类名,返回一个HTMLCollection对象,作为一个“动态”集合,返回所有拥有该类的元素组成的类数组。

    4、为访问文档常用的部分提供了快捷方式:

    • document.anchors,包含文档中所有带name特性的<a>元素;
    • document.forms,包含文档中所有的<form>元素
    • document.images,包含文档中所有的<img>元素
    • document.links,包含文档中所有带href 特性的<a>元素。

    这些特殊集合始终都可以通过HTMLDocument 对象访问到,而且,与HTMLCollection 对象类似,集合中的项也会随着当前文档内容的更新而更新。

    文档写入

    write()writeln()方法都接受一个字符串参数,即要写入到输出流中的文本。write()会原样写入,而writeln()则会在字符串的末尾添加一个换行符(\n)。在页面被加载的过程中,可以使用这两个方法向页面中动态地加入内容。方法open()close()分别用于打开和关闭网页的输出流。

    <html>
        <head>
            <title>document.write() Example</title>
        </head>
        <body>
            <p>The current date and time is:
            <script type="text/javascript">
                document.write("<strong>" + (new Date()).toString() + "</strong>");
            </script>
            </p>
        </body>
    </html>
    

    这样做会创建一个DOM元素,而且可以在将来访问该元素。通过write()writeln()输出的任何HTML代码都将如此处理。

    还可以使用write()writeln()方法动态地包含外部资源,例如JavaScript文件等。在包含JavaScript文件时,必须注意不能像下面的例子那样直接包含字符串"</script>",因为这会导致该字符串被解释为脚本块的结束,它后面的代码将无法执行。应该使用转义符做处理"<\/script>"

    window.onload事件处理程序,等到页面完全加载之后延迟执行函数。函数执行之后,调用document.write()会重写整个页面内容。

    三、Element 类型

    Element 类型用于表现XMLHTML元素,提供了对元素标签名、子节点及特性的访问。Element节点具有以下特征:nodeType 的值为1;nodeName 的值为元素的标签名;nodeValue 的值为null

    要访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性;这两个属性会返回相同的值。

    所有HTML元素都由HTMLElement 类型表示,不是直接通过这个类型,也是通过它的子类型来表示。HTMLElement类型直接继承自Element并添加了一些属性。添加的这些属性分别对应于每个HTML元素中都存在的下列标准特性。

    id,元素在文档中的唯一标识符
    title,有关元素的附加说明信息
    lang,元素内容的语言代码,很少使用。
    dir,语言的方向,值有ltr从左到右  rtl从右到做 ,很少使用
    
    className,与元素的class 特性对应,即为元素指定的CSS类。
    没有将这个属性命名为class,是因为class 是ECMAScript 的保留字。
    通过className访问而不是class,**但是在使用操作元素属性的方法时需要使用class,与html上的属性保持一致**
    
    <div id="myDiv" class="bd" title="Body text" lang="en" dir="ltr"></div>
    
    操作元素属性

    每个元素都有一或多个特性,这些特性的用途是给出相应元素或其内容的附加信息。操作特性的DOM方法主要有三个,分别是getAttribute()setAttribute()removeAttribute(),接受特性字符串作为第一个参数。setAttribute()可以传第二个参数作为设置的新值。特性的名称是不区分大小写的,即"ID""id"代表的都是同一个特性。另外也要注意,根据HTML5规范,自定义特性应该加上data-前缀以便验证。

    注意,传递给getAttribute()的特性名与实际的特性名相同。因此要想得到class特性值,应该传入"class"而不是"className",后者只有在通过对象属性访问特性时才用。如果给定名称的特性不存在,getAttribute()返回null

    1、 getAttribute()获取属性

    任何元素的所有特性,也都可以通过DOM元素本身的属性来访问。不过,只有公认的(非自定义的)特性才会以属性的形式添加到DOM对象中。自定义属性只能通过getAttribute()来获得

    <div id="myDiv" align="left" my_special_attribute="hello!"></div>
    
    alert(div.id); //"myDiv"
    alert(div.my_special_attribute); //undefined(IE 除外)
    alert(div.align); //"left"
    

    有两类特殊的特性,它们虽然有对应的属性名,但属性的值与通过getAttribute()返回的值并不相同。

    • 第一类特性就是style,用于通过CSS 为元素指定样式。

      • 在通过getAttribute()访问时,返回的style特性值中包含的是CSS文本
      • 而通过属性来访问它则会返回一个对象。由于style属性是用于以编程方式访问元素样式的(本章后面讨论),因此并没有直接映射到style特性。
    • 第二类与众不同的特性是onclick这样的事件处理程序。

      • 如果通过getAttribute()访问,则会返回相应代码的字符串。
      • 在访问onclick属性时,则会返回一个JavaScript函数(如果未在元素中指定相应特性,则返回null)。这是因为onclick及其他事件处理程序属性本身就应该被赋予函数值。

    由于存在这些差别,在通过JavaScript 以编程方式操作DOM时,开发人员经常不使用getAttribute(),而是只使用对象的属性。只有在取得自定义特性值的情况下,才会使用getAttribute()方法。

    2、 setAttribute()获取属性
    setAttribute(),这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()会以指定的值替换现有的值;如果特性不存在,setAttribute()则创建该属性并设置相应的值。通过setAttribute()方法既可以操作HTML特性也可以操作自定义特性。通过这个方法设置的特性名会被统一转换为小写形式,即"ID"最终会变成"id"。如果是使用这样的方式oDiv.myColor="div1"的方式设置的自定义属性,用getAttribute()是访问不到的。

    3、 removeAttribute()删除属性
    接受一个参数,即删除的属性key,这个方法用于彻底删除元素的特性。调用这个方法不仅会清除特性的值,而且也会从元素中完全删除特性,oDiv.removeAttribute("class");

    创建元素

    1、 document.createElement() 创建元素
    这个方法只接受一个参数,即要创建元素的标签名(IE中可以传入完整的HTML标签字符串)。这个标签名在HTML文档中不区分大小写,而在XML(包括XHTML)文档中,则是区分大小写的。

    var oDiv = document.createElement("div");
    

    2、 增加元素

    • appendChild()ParentElement.appendChild(element); 在父元素末尾添加一个子元素
    • insertBefore()ParentElement.insertBefore(newElement,refElement); 指定的已有子节点之前插入新的子节点。

    3、删除元素

    • removeChild()ParentElement.removeChild(elelment); 在父元素中移除某个子元素

    4、改变元素

    • replaceChild()ParentElement.replaceChild(newElement,oldElement); 在父元素中替换某个子元素
    元素的子节点

    元素的childNodes属性中包含了它的所有子节点,这些子节点有可能是元素、文本节点、注释或处理指令。IE会返回所有的子元素节点,而其他浏览器则返回所有的子节点,这意味着在执行某项操作以前,通常都要先检查一下nodeTpye属性

    for (var i=0, len=element.childNodes.length; i < len; i++){
        if (element.childNodes[i].nodeType == 1){ 
            //对子元素节点执行某些操作
        }
    }
    

    四、Text 类型

    每个可以包含内容的元素最多只能有一个文本节点,而且必须确实有内容存在。(只有空格也算是有内容)

    • nodeType 的值为3
    • nodeName 的值为"#text"
    • nodeValue 的值为节点所包含的文本;

    如果这个文本节点当前存在于文档树中,那么修改文本节点的结果就会立即得到反映。另外,在修改文本节点时还要注意,此时的字符串会经过HTML(或XML,取决于文档类型)编码。换句话说,小于号、大于号或引号都会像下面的例子一样被转义。

    Some <strong>other</strong> message 
    转义为
    Some &lt;strong&gt;other&lt;/strong&gt; message
    
    创建文本节点

    document.createTextNode() : 创建新文本节点,这个方法接受一个参数——要插入节点中的文本。与设置已有文本节点的值一样,作为参数的文本也将按照HTMLXML 的格式进行编码。创建的文本节点,可以使用元素的appendChild()将其添加到元素中,在没有添加进DOM树之前,都是属于孤儿节点,文档碎片。

    var textNode = document.createTextNode("Hello world!");
    element.appendChild(textNode);
    

    一般情况下,每个元素只有一个文本子节点。不过,在某些情况下也可能包含多个文本子节点,比如使用appendChild()方法多次添加文本节点,那么这两个节点中的文本就会连起来显示,中间不会有空格。如果在一个包含两个或多个文本节点的父元素上调用normalize()方法,则会将所有文本节点合并成一个节点,结果节点的nodeValue等于将合并前每个文本节点的nodeValue 值拼接起来的值。

    浏览器在解析文档时永远不会创建相邻的文本节点。这种情况只会作为执行DOM操作的结果出现。

    五、DOM操作技术

    DOM操作往往是JavaScript程序中开销最大的部分,而因访问NodeList导致的问题最多。NodeList对象都是“动态的”,这就意味着每次访问NodeList对象,都会运行一次查询。有鉴于此,最好的办法就是尽量减少DOM操作。

    动态脚本

    指的是在页面加载时不存在,但将来的某一时刻通过修改DOM动态添加的脚本。

    1、插入外部文件

    function loadScript(url){
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = url; 
        document.body.appendChild(script); // 在这行代码执行之前都不会下载文件
    }
    
    loadScript("client.js");  // 方法调用时 动态添加script标签 并执行脚本
    

    2、行内方式,也是添加script标签 ,但是脚本代码吧不是从外部引入,而是本地添加进去。所有写在<script></script>标签对中的代码都算是script元素的文本节点

    var script = document.createElement("script");
    script.type = "text/javascript";
    script.text = "function sayHi(){alert('hi');}";
    document.body.appendChild(script);
    

    Safari 3.0 之前的版本不能正确地支持text属性,可以使用创建文本节点的方式解决。

    function loadScriptString(code){
        var script = document.createElement("script");
        script.type = "text/javascript";
        try {
            script.appendChild(document.createTextNode(code));
        } catch (ex){
            script.text = code;
        }
        document.body.appendChild(script);
    }
    
    loadScriptString("function sayHi(){alert('hi');}");
    
    动态样式

    能够把CSS 样式包含到HTML 页面中的元素有两个。其中,<link>元素用于包含来自外部的文件,而<style>元素用于指定嵌入的样式。

    同理。操作DOM代码添加标签可以很容易地动态创建出作用CSS样式的元素:

    1、外部引入link

    function loadStyles(url){
        var link = document.createElement("link");
        link.rel = "stylesheet";
        link.type = "text/css";
        link.href = url;
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(link);
    }
    
    loadStyles("myStyle.css");
    

    加载外部样式文件的过程是异步的,也就是加载样式与执行JavaScript代码的过程没有固定的次序。

    2、内部style

    这种方式会实时地向页面中添加样式,因此能够马上看到变化。

    function loadStyleString(css){
        var style = document.createElement("style");
        style.type = "text/css";
        try{
            style.appendChild(document.createTextNode(css));
        } catch (ex){
            style.styleSheet.cssText = css;
        }
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(style);
    }
    
    loadStyleString("body{background-color:red}");
    

    事实上,IE 此时抛出的错误(将<script><style>视为一个特殊的元素,不允许DOM访问其子节点。)。解决IE 中这个问题的办法,就是访问元素的styleSheet属性,该属性又有一个cssText属性,可以接受CSS代码

    操作表格

    为了方便构建表格,HTML DOM还为<table><tbody><tr>元素添加了一些属性和方法。

    <table>元素添加的属性和方法如下。

    • caption:保存着对<caption>元素(如果有)的指针。
    • tBodies:是一个<tbody>元素的HTMLCollection
    • tFoot:保存着对<tfoot>元素(如果有)的指针。
    • tHead:保存着对<thead>元素(如果有)的指针。
    • rows:是一个表格中所有行的HTMLCollection
    • createTHead():创建<thead>元素,将其放到表格中,返回引用。
    • createTFoot():创建<tfoot>元素,将其放到表格中,返回引用。
    • createCaption():创建<caption>元素,将其放到表格中,返回引用。
    • deleteTHead():删除<thead>元素。
    • deleteTFoot():删除<tfoot>元素。
    • deleteCaption():删除<caption>元素。
    • deleteRow(pos):删除指定位置的行。
    • insertRow(pos):向rows 集合中的指定位置插入一行。

    <tbody>元素添加的属性和方法如下。

    • rows:保存着<tbody>元素中行的HTMLCollection
    • deleteRow(pos):删除指定位置的行。
    • insertRow(pos):向rows 集合中的指定位置插入一行,返回对新插入行的引用。

    <tr>元素添加的属性和方法如下。

    • cells:保存着<tr>元素中单元格的HTMLCollection
    • deleteCell(pos):删除指定位置的单元格。
    • insertCell(pos):向cells 集合中的指定位置插入一个单元格,返回对新插入单元格的引用。
    使用NodeList

    理解NodeList及其“近亲”NamedNodeMapHTMLCollection,是从整体上透彻理解DOM的关键所在。这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会得到更新。因此,它们始终都会保存着最新、最准确的信息。从本质上说,所有NodeList对象都是在访问DOM文档时实时运行的查询。

    如果想要迭代一个NodeList,最好是使用length属性初始化第二个变量,然后将迭代器与该变量进行比较。因为NodeList是动态的,NodeList.length也是动态的,如果在迭代的过程对这个NodeList有添加Node的操作,将会无限迭代下去,所以可以使用变量暂存length:var len = NodeList.length

    一般来说,应该尽量减少访问NodeList 的次数。因为每次访问NodeList,都会运行一次基于文档的查询。所以,可以考虑将从NodeList中取得的值缓存起来。

    相关文章

      网友评论

          本文标题:JS高程:读书摘要(七)DOM

          本文链接:https://www.haomeiwen.com/subject/jeaalqtx.html