DOM与BOM对象

谈谈原生DOM与DOM的常用操作

Posted by QY on February 27, 2019

前言

DOM是Javascript操作HTML的常用且最直观的方式,熟练掌握DOM操作及相关事件就可以自己编写一个简单的动态交互页面了,首先先来看看DOM吧~

DOM简介

  • DOM全称document object model
  • 文档

    整个HTML文件就是一个文档

  • 对象

    HTML文件中的每个节点都在JS中是一个对象

  • 模型

    JS使用模型来表示各个对象之间的关系

  • JS中有宿主对象document,它是window对象的属性,也就是文档对象本身

    DOM中的对象又称为节点对象,共有4种常用节点分别为:

      nodeName nodeType nodeValue
    文档节点 #document 9 null
    元素节点 标签名 1 null
    属性节点 属性名 2 属性值
    文本节点 #text 3 文本内容

document对象

  • 常用属性:

    1. body

      该属性封装的是body元素对象的引用

    2. documentElement

      属性值为HTML元素对象

    3. all

      属性值为当前页面中的所有元素节点的数组

      这个属性值本身为undefined,它的typeof值也为undefined

    4. URL

      获取当前页面的url

    5. domain

      获取当前页面的域名部分

    6. referrer

      获取是哪个页面链接跳转到当前页面,没有则返回空字符串

  • 常用方法:

    ps: 这些方法对于普通的DOM对象同样适用,只是DOM范围为普通DOM对象内部元素而非全局元素

    • 基本语法:

      document.方法();

    1. getElementById()

      • 通过ID值获取对应的节点

      • IE8中不区分大小写

      • IE7中会匹配表单元素的name值

    2. getElementsByTagName()

      • 通过标签名获取一组节点

      • 返回值是一个HTMLCollection,即使只获取到一个节点时,也封装成HTMLCollection返回

    3. getElementsByName()

      • 通过name的值获取一组节点

      • 返回值是一个nodelist,与数组相似,可以使用forEach方法,即使只获取到一个节点时,也封装成nodelist返回

    4. getElementsByClassName()

      • 通过class属性获取一组节点

      • 返回值是一个HTMLCollection,即使只获取到一个节点时,也封装成HTMLCollection返回

      • 仅支持IE8以上

    5. querySlector()

      • 通过CSS选择器返回一个节点,当能匹配多个节点时,返回满足匹配的第一个节点 仅支持IE7以上
    6. querySlectorAll()

      • 通过CSS选择器返回一组节点

      • 返回值是一个nodelist,与数组相似,可以使用forEach方法,即使只获取到一个节点时,也封装成nodelist返回

      • 仅支持IE7以上

    HTMLCollection 支持通过索引或者字符串来查找需要的节点,在后台则分别使用它的item()namedItem()方法来获取

    NodeListHTMLCollection类似的一个node集合

    两者的值都是动态的,每次去取这些值的属性都会引发一次文档查询,因此需要尽量减少直接访问

事件

此处简单提及事件,详细内容会在下一篇博文中讲解

当用户与浏览器进行任何交互时都会产生相应的事件

JS可以通过给事件绑定响应函数来使事件触发时执行相应的响应函数,响应函数会在执行事件时才会被执行,因此响应函数内部使用的变量可能会与预期不一致

var div = document.getElementByTagName("test");
for(var i = 0; i < div.length; i++){
    div[i].onclick = function(){
        console.log(i);
    }
}
//结果永远会是div的长度的值
  • 绑定事件有两种方法:

    1. 第一种:

       <button id="btn" onclick="alert('a')">btn</button>
      

      结构与行为耦合,不要使用

    2. 第二种:

       <button id="btn">btn</button>
       <script>
           var btn = document.getElementById("btn");
           btn.onclick = function(){
               alert("a");
           }
       </script>
      

      常用方式

文档加载顺序

浏览器加载HTML文件时是自上向下加载,因此当js代码写在文档之前时可能出现无法获取到节点的情况

使用window.onload绑定响应函数可以使响应函数内的代码都在整个文档加载完成之后执行

或者将JS代码写在HTML文档的最后来保证文档先于JS代码加载

元素节点

元素节点拥有一个方法和属性来获取它们内部的节点,除了元素的class属性以外,所有的属性都可以以元素节点对象属性的方式获取,class的值需要用className属性来获取,因为class是js中的保留字

  • 常用方法:
    1. getElementById()

      通过ID值获取当前元素节点内部对应的节点

    2. getElementsByTagName()

      用来获取当前元素节点内部的特定标签的元素节点

      返回值是一个HTMLCollection,与数组相似,不能使用forEach方法,即使只获取到一个节点时,也封装成HTMLCollection返回

    3. getElementsByName()

      通过name的值获取当前元素节点内部的一组节点

      返回值是一个nodelist,与数组相似,可以使用forEach方法,即使只获取到一个节点时,也封装成nodelist返回

    4. getElementsByClassName()

      通过class属性获取当前元素节点内部的一组节点

      返回值是一个HTMLCollection,与数组相似,不能使用forEach方法,即使只获取到一个节点时,也封装成HTMLCollection返回

      仅支持IE8以上

    5. querySlector()

      通过CSS选择器返回当前元素节点内部的一个节点,当能匹配多个节点时,返回满足匹配的第一个节点

      仅支持IE7以上

    6. querySlectorAll()

      通过CSS选择器返回当前元素节点内部的一组节点

      返回值是一个nodelist,与数组相似,可以使用forEach方法,即使只获取到一个节点时,也封装成nodelist返回

      仅支持IE7以上

    7. hasChildNodes()

      返回当前节点内部是否拥有1个或多个子节点

  • 常用属性:

    1. innerHTML

      获取当前元素节点中的HTML代码

    2. innerText

      获取当前元素节点中的文本

    3. childNodes

      获取当前元素节点中所有的子节点并以数组形式返回,按照ES标准如果当前元素节点中有空格时也会被包括在内

      IE8及以下浏览器没有实现这一标准,因此只会获取到内部的元素节点

    4. children

      获取当前元素节点中的所有子元素节点

    5. firstChild

      获取当前元素节点中的第一个子节点

    6. firstElementChild

      获取当前元素节点中的第一个子元素节点, 仅支持IE8以上

    7. lastChild

      获取当前元素节点中的最后一个子节点

    8. lastElementChild

      获取当前元素节点中的最后一个子元素节点, 仅支持IE8以上

    9. parentNode

      获取当前元素节点的父节点

    10. previousSibling

      获取当前元素节点的前一个兄弟节点

    11. previousElementSibling

      获取当前元素节点的前一个兄弟元素节点, 仅支持IE8以上

    12. nextSibling

      获取当前元素节点的后一个兄弟节点

    13. nextElementSibling

      获取当前元素节点的后一个兄弟元素节点, 仅支持IE8以上

ps:当响应函数给某个节点绑定时,这个响应函数中的this就是这个节点

DOM的增删改
  • 常用方法:

    1. createElement()

      新建一个元素节点,参数是元素节点的标签名

      • 语法:

        document.createElement(“标签名”);

    2. createTextNode()

      新建一个文本节点,参数是文本节点的内容

      • 语法:

        document.createTextNode(“文本内容”);

    3. appendChild()

      向一个父节点中添加一个子节点

      • 语法:

        父节点.appendChild(子节点);

    4. insertBefore()

      向一个子节点前添加一个新的节点

      • 语法:

        父节点.insertBefore(新节点, 原节点);

    5. removeChild()

      移除一个子节点

      • 语法:

        父节点.removeChild(子节点);

    6. replaceChild()

      将原节点替换成新节点

      • 语法:

        父节点.replaceChild(新节点, 原节点);

    7. cloneNode()

      返回值是一个节点的拷贝, 接受一个布尔值作为参数, true则为深度拷贝, false为浅拷贝

      • 语法:

        节点.cloneNode(boolean)

    8. normalize()

      返回一个经过一定处理后的节点, 这个过程会合并调用此方法的节点中的相邻文本节点并删除空白的文本节点

    9. splitText(offset)

      将一个文本节点按照偏移量参数分割成两个兄弟节点, 返回值是后一个文本节点, 此方法只可被用于文本节点

  • 由于许多方法都要通过父节点调用对应的方法来操作节点,因此可使用以下代码来对节点进行直接操作而不需要获取它的父节点

    子节点.parentNode.removeChild(子节点);

  • 对DOM的增删改操作可以用父节点的innerHTML的字符串操作来代替,由于是整体修改所以这个父节点包括它的子节点绑定的函数都会失效

    代码1:

          <html>
              <head>
              </head>
              <body>
                  <div>
                      <input type="checkbox" id="checkallbox"/>AAAAA
                  </div>
              </body>
          </html>
          <script>
          var input = document.createElement("input");
          var checkallbox = document.getElementById("checkallbox");
          input.type="checkbox";
          checkallbox.parentNode.insertBefore(input, checkallbox);
          </script>
    

    代码2

          <html>
              <head>
              </head>
              <body>
                  <div>
                      <input type="checkbox" id="checkallbox"/>AAAAA
                  </div>
              </body>
          </html>
          <script>
          var div = document.getElementByTagName("div")[0];
          div = "<input type='checkbox'/>" + div.innterHTML;
          </script>
    

    两部分代码功能基本相同,唯一区别是如果input标签绑定了响应函数,在代码2中会失效

DOM的遍历
  1. document.createNodeIterator(root, whatToShow, filter, entityReferenceExpansion)

    返回值是一个NodeIterator对象

    • 参数一: 指定开始遍历的根节点,只能遍历body标签中的节点,其他标签会被跳过

    • 参数二: 标识要访问标签元素的哪些节点,它是一个数字代码,语义化信息保存在NodeFilter类型中

        NodeFilter.SHOW_ALL         显示所有节点
        NodeFilter.SHOW_ELEMENT     显示元素节点
        NodeFilter.SHOW_ATTRIBUTE   显示属性节点
        NodeFilter.SHOW_TEXT        显示文本节点
        NodeFilter.SHOW_DOCUMENT    显示文档节点
        NodeFilter.SHOW_COMMENT     显示注释节点
      
    • 参数三: 是一个NodeFilter对象,或者一个返回接受或者拒绝特定标签的函数

      • NodeFilter对象是一个只有acceptNode方法的对象,该方法是一个迭代回调函数,有一个node参数,迭代器中有几个节点则会被执行几次

      • 返回值应是NodeFilter.FILTER_ACCEPT或者NodeFilter.FILTER_SKIP

      • 通过返回值来判断是否能够访问当前回调函数中的标签元素

        var filter = {
            acceptNode: function(node){
                return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
            }
        }
      
      • 这个参数也可以是一个与acceptNode方法相似的函数
        function acceptNode(node){
            return node.tagName.toLowerCase() == "p" ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
        }
      
    • 参数四: 标识是否要扩展实体引用,在html中没有意义

    NodeIterator有两个方法nextNode()previousNode()来访问迭代器中的节点

    刚创建好的迭代器一开始是指向一个标识根节点的指针,第一次调用nextNode会指向第一个子节点

    当越界时返回值为null

    支持IE9+和其他浏览器

  2. document.createTreeWalker(root, whatToShow, filter, entityReferenceExpansion)

    返回一个TreeWalker对象

    可以实现在经过过滤的root节点中任意跳跃,非常灵活

    参数与上一个函数一致,唯一有区别的在于第三个参数的Nodefilter函数增加一个有效的返回值NodeFilter.FILTER_REJECT

    此时迭代器会跳过这个节点及其后代元素而不是像NodeFilter.FILTER_SKIP那样单纯跳过当前节点,仍然进行深度遍历

    createNodeIterator的第三个参数中返回NodeFilter.FILTER_REJECT作用与NodeFilter.FILTER_SKIP相同

    • 相较于NodeIterator对象,TreeWalker对象多出一些重要方法:

      1. parentNode()
      2. firstChild()
      3. lastChild()
      4. nextSibling()
      5. previousSibling()

      通过这些方法可以实现在TreeWalker对象中任意跳转到目标元素 W3C标准DOM2,但是IE不支持

DOM范围

可以通过范围来选择文档中的一个区域而不必考虑节点的界限(选择在后台完成,对用户是不可见的)

  • 范围创建语句

    document.createRange()

    返回值为range对象

  • 以下属性可以获取到当前范围在文档中的位置信息

    1. startContainer

      包含范围起点的节点,即起点的父节点

    2. startOffset

      范围起点在startContainer中的偏移量

      如果起点是文本节点,注释节点,cdata节点则值是范围起点节点距离startContainer需要跳过的字符串

      如果起点是元素节点,则值是范围起点节点在startContainer中的子节点索引

    3. endContainer

      包含范围终点的节点,即终点的父节点

    4. endOffset

      范围终点在startContainer中的位置

      值计算规则与startOffset相同, 表示索引时通常是起始位置加1

    5. commonAncestorContainer

      起点节点与终点节点共同的最近的那个祖先节点

  • 通过以下方法可以进行选择范围操作

    1. selectNode()

      选中传入参数的那个节点及内部节点

    2. selectNodeContents()

      选中传入参数的那个节点的内部节点

    3. setStartBefore(refnode)

      将范围起点设置为refnode之前,refnode就成为了范围选区的第一个节点

      startContainer变成refnode的父节点,startOffset的值变成refnode在父节点中的索引

    4. setStartAfter(refnode)

      将范围起点设置为refnode之后,refnode就成为了范围选区外的节点,它的下一个节点是范围选区内第一个节点

      startContainer变成refnode的父节点,startOffset的值变成refnode在父节点中的索引加1

    5. setEndBefore(refnode)

      将范围终点设置为refnode之前,refnode就成为了范围选区外的节点,它的上一个节点是范围选区内最后一个节点

      startContainer变成refnode的父节点,endOffset的值变成refnode在父节点中的索引

    6. setEndAfter(refnode)

      将范围终点设置为refnode之后,refnode就成为了范围选区 内的最后一个节点

      startContainer变成refnode的父节点,endOffset的值变成refnode在父节点中的索引加1

    7. setStart()

      传入两个参数,第一个参数是参照节点,第二个参数是偏移量

      参照节点会被当做startContainer,偏移量会变成startOffset

    8. setEnd()

      传入两个参数,第一个参数是参照节点,第二个参数是偏移量

      参照节点会被当做endContainer,偏移量会变成endOffset

    ps: 当改变范围起点到范围终点之后时, 范围终点会自动变成起点的偏移值, 同理改变范围终点到范围起点时, 范围起点也会自动变成终点的偏移值

  • 创建范围之后可以使用以下方法对选中内容进行操作

    1. deleteContents()

      将选中的内容从文档中删除

    2. extractContents()

      将选中的内容从文档中提取出来并作为返回值返回

    3. cloneContents()

      拷贝一份选中的内容并作为返回值返回

    4. insertNode()

      将一个节点插入到范围的最开始处

    5. surroundContents()

      将节点移动到范围之外,刚好包裹此范围,并且删除节点内原有的内容

    6. collapse()

      可以将选中的范围进行折叠,使范围起点与终点都指向同一个位置

      传入一个布尔值作参数,true表示折叠到原范围的起点,false表示折叠到原范围的终点

    7. compareBoundaryPoints()

      • 传入两个参数

        第一个参数是需要比较哪两个点的一个常量,值可以是Range.START_TO_START, Range.START_TO_END,Range.END_TO_START, Range.END_TO_END

        第二个参数是需要比较的范围,如果调用这个方法的范围与传入的范围的需要比较的点是前者在前则返回-1,相同返回0,前者在后则返回1

    8. detach()

      在使用完范围后最好使用此方法来使范围从文档中分离,然后再删除引用,方便垃圾回收机制清理

IE8及以下的文本范围
  • 创建文本范围

    input, button, textarea, body等几个元素可以使用createTextRange来创建文本节点

  • 选取文本范围

    1. findText()

      传入一个字符串参数,如果找到了对应的范围则返回值为true否则为false同时让范围包围这段文本

    2. moveToElementText()

      传入一个DOM节点,让范围包含这个节点的文本信息与标签信息,与selectNode作用类似

    3. move()

      将范围折叠到指定位置

    4. moveStart()

      将范围的起点移动到指定位置

    5. moveEnd()

      将范围的终点移动到指定位置

    6. expand()

      将部分文本的范围变成完整文本的范围

    • 这些方法都传入两个参数

      第一个参数是移动单位

      第二个参数是移动单位的数量

    • 移动单位可以是以下字符串

      character:逐个字符移动

      word:逐个单词移动

      sentence: 逐个句子(一系列以叹号问号句号结尾的字符)移动

      textedit:移动到当前选区开始或结束的位置

  • 修改范围内的内容

    text属性与pasteHTML()方法

    前者改变文本,后者可以包括标签

  • 折叠范围

    与其他浏览器相同使用collapse()来折叠范围

    但是没有collapsed属性来判断是否折叠,但是可以使用boundingWidth属性来判断范围宽度是否为0

  • 比较范围

    1. compareEndPoints()

      用法与作用同compareBoundaryPoints类似

      第一个参数是字符串,表示需要比较哪两个点如: StartToEnd等格式

      第二个参数是待比较的范围

  • 复制范围

    1. duplicate()

      作用与cloneContents()相同

操作样式

在css的属性中如果有用 - 来连接的情况,在style需要使用驼峰命名法来修改属性名从而设置或者读取对应的样式

  • 获取节点样式的属性或方法
  1. style

    • 语法:

      节点对象.style.属性值

    通过这个属性能够修改和读取到节点的内联样式,当节点没有内联样式时获取到的是空字符串

    如果在其他地方的css样式中设置了!important,那么通过js操作这个属性会失效

  2. currentStyle

    此属性只能在IE浏览器中使用并且是只读的,当未给某个节点设置长度值时会返回auto

    • 语法:

      节点对象.currentStyle.属性值

  3. getComputedStyle()

    此方法是window的方法,可以直接使用,而且也是只读的

    在获取width属性时当未设置具体样式的情况下,会返回节点对象实际呈现的结果而不是auto

    • 语法:

      getComputedStyle(节点对象, 伪元素(常设置为null))[样式名]

    此方法支持IE8以上以及其他浏览器

  • 获取节点对象相关位置的常用属性:

    1. clientHeight

      获取元素的视图高度,包括元素的内容高度与内边距高度,返回值是一个数字,只读, 在根标签的此属性就是指代视口大小,与此规则无关

    2. clientwidth

      获取元素的视图宽度,包括元素的内容宽度与内边距宽度,返回值是一个数字,只读, 在根标签的此属性就是指代视口大小,与此规则无关

    3. offsetHeight

      获取元素的真实高度,包括元素的内容高度,内边距高度与边框高度,当被父元素隐藏或者滚动时不会影响这个值,返回值是一个数字,只读,在IE11以下浏览器中的根标签的此属性就是指代视口大小,与此规则无关

    4. offsetWidth

      获取元素的真实宽度,包括元素的内容宽度,内边距宽度与边框宽度,当被父元素隐藏或者滚动时不会影响这个值,返回值是一个数字,只读,在IE11以下浏览器中的根标签的此属性就是指代视口大小,与此规则无关

    5. offsetParent

      获取当前元素的定位父元素,即设置了position的最近父元素,当所有父元素都没有设置时,返回body元素, 但是在除了火狐的浏览器中当position设置为fixed,返回null

      在IE7包括IE7时,positionabsoluterelative时返回htmlbodyhtml间的margin清除后可看做body. 在IE7以下时当祖先元素开启hasLayout返回最近开启的元素

      PS: hasLayout相关的内容还是比较多的,现在只需有个概念,在进阶内容中会详细讲述

    6. offsetTop

      获取当前元素相对于定位父元素的上侧偏移量(相对于offsetParent的内边距),即使父元素自身有偏移量也不改变这个值的大小,返回值是一个数字,只读

    7. offsetLeft

      获取当前元素相对于定位父元素的左侧偏移量(相对于offsetParent的内边距),即使父元素自身有偏移量也不改变这个值的大小,返回值是一个数字,只读

    8. scrollHeight

      获取当前元素的真实高度,包括元素的内容高度与内边距高度,当被父元素隐藏或者滚动时不会影响这个值,返回值是一个数字,只读

    9. scrollWidth

      获取当前元素的真实宽度,包括元素的内容宽度与内边距宽度,当被父元素隐藏或者滚动时不会影响这个值,返回值是一个数字,只读

    10. scrollTop

      获取当前元素的右侧滚动条滚动的像素,返回值是一个数字,只读

    11. scrollLeft

      获取当前元素的下侧滚动条滚动的像素,返回值是一个数字,只读

    公式:scrollHeight - scrollTop = clientHeight 可以证明滚动条滚动到最底端

    1. getBoundingClientRect()

      这是DOM中唯一能够直接获取节点相对于视图位置的方法,且兼容性相当的好,需要熟练掌握,此处有个小案例来帮助学习这个方法

      获取一个元素四个角的相对位置与高宽且这些值是基于border-box的,也就是说返回的width, height属性都包括了节点的content, padding, border的高宽

      在IE7及以下没有widthheight属性

      获取节点的绝对位置可以直接让相对位置加上滚动的像素来得到

      BOM

  1. Window

    代表浏览器窗口并且保存浏览器的全局对象

    window.open当弹窗被浏览器内置工具屏蔽时会返回null,被工具屏蔽会报错

  2. Navigator

    代表浏览器信息

    由于历史原因大部分属性没有意义,只剩下userAgent可以判断浏览器类型

     /chrome/i.test(navigator.userAgent)
     /firefox/i.test(navigator.userAgent)
    

    IE11的userAgent中没有IE信息必须使用"ActiveXObject" in window来进行判断

    edge浏览器仍然可以使用userAgent中有无edge字符串来判断

  3. Location

    代表浏览器地址栏信息

    如果直接打印location,则能获取到当前网址栏的信息

    修改location属性为一个完整路径或相对路径则页面会直接跳转

    1. assign()

      直接跳转到某个页面,与直接修改location一样

    2. reload()

      用于重新加载当前页面,与刷新按钮一样,传递true作为参数时会强制清空缓存

    3. replace()

      assign类似但是它不会生成历史记录,无法回退

  4. History

    代表历史记录,只能前进后退

    1. length

      当次访问的历史次数,关闭浏览器时清零

    2. back()

      回到上一个页面

    3. forward()

      前往下一个页面

    4. go()

      使用整数作为参数来指定需要跳转到的页面

  5. Screen

    代表用户当前屏幕

这些属性都保存在window中,可以直接使用