认识DOM和BOM

前面我们花了很多时间学习JavaScript的基本语法,但是这些基本语法,但是这些语法好像和做网页没有什么关系,和前面学习的HTML、CSS也没有什么关系呢?

- 这是因为我们前面学习的部分属于ECMAScript,也就是JavaScript本身的语法部分;
- 除了语法部分之外,我们还需要学习浏览器提供给我们开发者的DOM、BOM相关的API才能对页面、浏览器进行操作;

前面我们学习了一个 window 的全局对象,window 上事实上就包含了这些内容:

- 我们已经学习了 JavaScript 语法部分的 Object、Array、Date 等;
- 另外还有DOM、BOM部分;

DOM:文档对象模型(Document Object Model)

- 简称 DOM,将页面所有的内容表示为可以修改的对象;

BOM:浏览器对象模型(Browser Object Model)

- 简称 BOM,由浏览器提供的用于处理文档(document)之外的所有内容的其他对象;
- 比如navigator、location、history等对象;

深入理解DOM

浏览器会对我们编写的HTML、CSS进行渲染,同时它又要考虑我们可能会通过JavaScript来对其进行操作:

- 于是浏览器将我们编写在 HTML 中的每一个元素(Element)都抽象成了一个个对象;
- 所有这些对象都可以通过 JavaScript 来对其进行访问,那么我们就可以通过JavaScript来操作页面;
- 所以,我们将这个抽象过程称之为 文档对象模型(Document Object Model);

整个文档被抽象到 document 对象中:

- 比如document.documentElement对应的是html元素;
- document.body对应的是body元素;
- document.head对应的是head元素;

下面的一行代码可以让整个页面变成红色:

所以我们学习DOM,就是在学习如何通过JavaScript对文档进行操作的;

DOM Tree的理解

一个页面不只是有html、head、body元素,也包括很多的子元素:

- 在html结构中,最终会形成一个树结构;
- 在抽象成DOM对象的时候,它们也会形成一个树结构,我们称之为DOM Tree;

DOM的学习顺序

DOM相关的API非常多,我们会通过如下顺序来学习:

1. DOM元素之间的关系
2. 获取DOM元素
3. DOM节点的type、tag、content
4. DOM节点的attributes、properies
5. DOM节点的创建、插入、克隆、删除
6. DOM节点的样式、类
7. DOM元素/window的大小、滚动、坐标

DOM的继承关系图

DOM相当于是JavaScript和HTML、CSS之间的桥梁

- 通过浏览器提供给我们的DOM API,我们可以对元素以及其中的内容做任何事情;

类型之间有如下的继承关系:

document对象

Document节点表示的整个载入的网页,它的实例是全局的document对象:

- 对DOM的所有操作都是从 document 对象开始的;
- 它是DOM的 入口点,可以从document开始去访问任何节点元素;

对于最顶层的html、head、body元素,我们可以直接在document对象中获取到:

- html元素:<html> = document.documentElement
- body元素:<body> = document.body
- head元素:<head> = document.head
- 文档声明:<!DOCTYPE html> = document.doctype

节点(Node)之间的导航(navigator)

如果我们获取到一个节点(Node)后,可以根据这个节点去获取其他的节点,我们称之为节点之间的导航。

节点之间存在如下的关系:

- 父节点:parentNode
- 前兄弟节点:previousSibling
- 后兄弟节点:nextSibling
- 子节点:childNodes
- 第一个子节点:firstChild
- 第二个子节点:lastChild 

尝试获取下面结构的节点:

一个空号或者一行注释和 div 都是节点,而div、h1才叫元素,childNodes 是获取子节点,children 则是获取子元素

    //  1.获取节点的导航
    var bodyEl = document.body

    // 1.1 获取body所有的子节点
    // console.log(bodyEl.childNodes)

    // 1,2获取body的第一个子节点
    var bodyElFirstChild = bodyEl.firstChild
    console.log(bodyElFirstChild)

    // 1.3获取body中的注释
    var bodyRlCommentChild = bodyElFirstChild.nextSibling
    console.log(bodyRlCommentChild)

    // 1.4获取body的父节点
    var bodyParent = bodyEl.parentElement
    console.log(bodyParent)

元素(Element)之间的导航(navigator)

如果我们获取到一个元素(Element)后,可以根据这个元素去获取其他的元素,我们称之为元素之间的导航。

节点之间存在如下的关系:

- 父元素:parentElement
- 前兄弟节点:previousElementSibling
- 后兄弟节点:nextElementSibling
- 子节点:children
- 第一个子节点:firstElementChild
- 第二个子节点:lastElementChild

尝试获取下面结构的元素:

  <!-- 我是注释 -->
  我是文本,呵呵呵呵
  <div class="box">呵呵哈哈哈或</div>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
  </ul>
  <script>
    var bodyEl = document.body
    
    // 根据 body 元素去获取子元素(element)
    var childElement = bodyEl.children
    console.log(childElement)

    // 获取 box 元素
    var boxEl1 = bodyEl.firstElementChild
    var boxEl2 = bodyEl.children[0]
    console.log(boxEl1, boxEl2, boxEl1 === boxEl2)

    // 获取ul元素
    var ulEl = boxEl1.nextElementSibling
    console.log(ulEl)
    
    var liEls = ulEl.children
    console.log(liEls)
  </script>

表格(table)元素的导航(navigator)

<table> 元素支持 (除了上面给出的,之外) 以下这些属性:

- table.rows — <tr> 元素的集合;
- table.caption/tHead/tFoot — 引用元素 <caption>,<thead>,<tfoot>;
- table.tBodies — <tbody> 元素的集合;

<thead><tfoot><tbody> 元素提供了 rows 属性:

- tbody.rows — 表格内部 <tr> 元素的集合;

<tr>

- tr.cells — 在给定 <tr> 中的 <td> 和 <th> 单元格的集合;
- tr.sectionRowIndex — 给定的 <tr> 在封闭的 <thead>/<tbody>/<tfoot> 中的位置(索引);
- tr.rowIndex — 在整个表格中 <tr> 的编号(包括表格的所有行);

<td><th>

- td.cellIndex — 在封闭的 <tr> 中单元格的编号。
  <table>
    <thead>
      <tr>
        <th>姓名</th>
        <th>年龄</th>
        <th>升高</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>what</td>
        <td>19</td>
        <td>1.98</td>
      </tr>
      <tr>
        <td>jack</td>
        <td>29</td>
        <td>1.68</td>
      </tr>
    </tbody>
  </table>

  <script>
    var tableEl = document.body.firstElementChild

    // 通过 table 元素获取内部的后代元素
    console.log(tableEl.tHead, tableEl.tBodies, tableEl.tFoot)
    console.log(tableEl.rows)
    
    // 拿到一行元素
    var rowEl = tableEl.rows[2]
    console.log(rowEl.cells[0])
  </script>

表单(form)元素的导航(navigation)

<form> 元素可以直接通过document来获取:document.forms

<form> 元素中的内容可以通过elements 来获取:form.elements

我们可以设置表单子元素的name来获取它们

  <form action="">
    <!-- name:向服务器提交数据时作为 key -->
    <input name="account" type="text">
    <input name="password" type="password">
    <input name="hobbies" type="checkbox" checked>
    <select name="fruits" name="" id="">
      <option value="apple">苹果</option>
      <option value="orange">橘子</option>
    </select>
  </form>
  
  <script>
    // 1.获取 form
    // var formEl = document.body.firstElementChild
    var formEl = document.forms[0]
    
    // 2.获取 form 中子元素
    // var inputEl = formEl.elements[0]
    var inputEl = formEl.elements.account
    
    setTimeout(function () {
      console.log(inputEl.value)
    }, 2000)

  </script>

完成 table 案例

  <table>
    <tr>
      <td>1-1</td>
      <td>2-1</td>
      <td>3-1</td>
      <td>4-1</td>
      <td>5-1</td>
    </tr>

    <tr>
      <td>1-2</td>
      <td>2-2</td>
      <td>3-2</td>
      <td>4-2</td>
      <td>5-2</td>
    </tr>

    <tr>
      <td>1-3</td>
      <td>2-3</td>
      <td>3-3</td>
      <td>4-3</td>
      <td>5-3</td>
    </tr>

    <tr>
      <td>1-4</td>
      <td>2-4</td>
      <td>3-4</td>
      <td>4-4</td>
      <td>5-4</td>
    </tr>

    <tr>
      <td>1-5</td>
      <td>2-5</td>
      <td>3-5</td>
      <td>4-5</td>
      <td>5-5</td>
    </tr>

  </table>

  <script>
    var tableEl = document.body.firstElementChild

    /*
        // 1.获取 1-1
        var row1El = tableEl.rows[0]
        var cell1El = row1El.cells[0]
        
        // 2.获取2-2
        var row1El = tableEl.rows[1]
        var cell1El = row1El.cells[1]
     */

    // for 循环
    for (var i = 0; i < tableEl.rows.length; i++) {
      var rowEl = tableEl.rows[i]
      var cellEl = rowEl.cells[i]
      // 设置样式
      cellEl.style.backgroundColor = 'red'
      cellEl.style.color = 'white'
    }
  </script>

获取元素的方法

当元素彼此靠近或者相邻时,DOM 导航属性(navigation property)非常有用。

- 但是,在实际开发中,我们希望可以任意的获取到某一个元素应该如何操作呢?

DOM为我们提供了获取元素的方法:

开发中如何选择呢?

- 目前最常用的是querySelector和querySelectAll;
- getElementById偶尔也会使用或者在适配一些低版本浏览器时;
    /* 一.通过导航获取
        // 1.拿到body
        var bodyEl = document.body

        // 2.拿到box
        var boxEl = bodyEl.firstElementChild

        // 3.拿container
        var containerEl = boxEl.children[1]

        // 4.拿p元素
        var pEl = containerEl.children[0]

        // 5.拿到 keyword
        var spanEl = pEl.children[0]
        spanEl.style.color = 'red'

     */

    /*  二.直接获取 getElementBy*
      // 1.通过className获取
      var keywordEls = document.getElementsByClassName('keyword')

      // 修改第一个
      // keywordEls[0].style.color = 'red'
      // 修改所有的
      for (var i = 0; i < keywordEls.length; i++) {
        keywordEls[i].style.color = 'red'
      }

      // 2.通过 id 获取元素
      var titleEl = document.getElementById('title')
      titleEl.style.color = 'orange'
    */

    /* 三.querySelector: 通过选择器查询
     */

    var keywordEl = document.querySelector('.keyword')
    // keywordEls 是对象,可迭代的
    // 可迭代对象: String/数组/节点的列表
    var keywordEls = document.querySelectorAll('.keyword')

    for (var el of keywordEls) {
      el.style.color = 'skyblue'
    }

    // 新增 keyword 元素
    console.log(keywordEls)

    var titleEl = document.querySelector('#title')
    titleEl.style.color = 'orange'

节点的属性 - nodeType

目前,我们已经可以获取到节点了,接下来我们来看一下节点中有哪些常见的属性:

- 当然,不同的节点类型有可能有不同的属性;
- 这里我们主要讨论节点共有的属性;

nodeType属性:

- nodeType 属性提供了一种获取节点类型的方法;
- 它有一个数值型值(numeric value);

常见的节点类型有如下

其他类型可以查看MDN文档: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType

  <!-- 我是注释 -->
  我是文本
  <div class="box">我是box</div>
  <script>
    // 1.获取三个节点
    var bodyChildNodes = document.body.childNodes
    var commentNode = bodyChildNodes[1]
    var textNode = bodyChildNodes[2]
    var divNode = bodyChildNodes[3]

    console.log(commentNode, textNode, divNode)

    // 2.节点属性
    for (var node of bodyChildNodes) {
      if (node.nodeType === 3) {
      } else if (node.nodeType === Node.COMMENT_NODE) {
      } else if (node.nodeType === 1) { }
    }

    console.log(commentNode.nodeType, textNode.nodeType, divNode.nodeType) // 8 3 1

节点的属性 – nodeName、tagName

nodeName:获取node节点的名字;

tagName:获取元素的标签名词;

tagName 和 nodeName 之间有什么不同呢?

- tagName 属性仅适用于 Element 节点;
- nodeName 是为任意 Node 定义的:
	- 对于元素,它的意义与 tagName 相同,所以使用哪一个都是可以的;
	- 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串;

节点的属性 - innerHTML、textContent

innerHTML 属性

- 将元素中的 HTML 获取为字符串形式;
- 设置元素中的内容;

outerHTML 属性

- 包含了元素的完整 HTML
- innerHTML 加上元素本身一样;

textContent 属性

- 仅仅获取元素中的文本内容;

innerHTML和textContent的区别:

- 使用 innerHTML,我们将其“作为 HTML”插入,带有所有 HTML 标签。
- 使用 textContent,我们将其“作为文本”插入,所有符号(symbol)均按字面意义处理。

节点的属性 - nodeValue

nodeValue/data

- 用于获取非元素节点的文本内容

节点的其他属性

hidden属性:也是一个全局属性,可以用于设置元素隐藏。

  <!-- 我是注释 -->
  我是文本
  <div class="box">
    <h2>我是标题</h2>
    <p>我是内容哈哈哈</p>
  </div>
  
  <script>
    // 1.获取三个节点
    var bodyChildNodes = document.body.childNodes
    var commentNode = bodyChildNodes[1]
    var textNode = bodyChildNodes[2]
    var divNode = bodyChildNodes[3]

    console.log(commentNode, textNode, divNode)

  

    // 2.节点属性
    // 2.1 nodeType 节点的类型
    for (var node of bodyChildNodes) {
      if (node.nodeType === 3) {
      } else if (node.nodeType === Node.COMMENT_NODE) {
      } else if (node.nodeType === 1) { }
    }
    console.log(commentNode.nodeType, textNode.nodeType, divNode.nodeType) // 8 3 1

    // 2.2 nodeName 任意node节点的名称
    // tagName:针对元素
    console.log(commentNode.nodeName, textNode.nodeName, divNode.nodeName)
    console.log(commentNode.tagName, textNode.tagName, divNode.tagName)

    // 2.3 data(nodeValue)/innerHTML/textContent
    // data针对非元素的节点获取数据
    // innerHTML:对应的html元素也会获取
    // textContent:只会获取文本内容
    console.log(commentNode.data, textNode.data, divNode.data)
    console.log(divNode.innerHTML)
    console.log(divNode.textContent)

    // 设置文本,作用是一样的
    // 设置文本中包含元素内容,那么innerHTML浏览器会解析,textContent会当成文本的一部分直接展示
    // divNode.innerHTML = '<h2>呵呵呵呵</h2>'
    // divNode.textContent = '<h2>呵呵呵呵</h2>'

    // 2.4 outerHTML
    console.log(divNode.outerHTML)
  </script>

DOM 元素还有其他属性:

- value
	- <input>,<select> 和 <textarea>(HTMLInputElement,HTMLSelectElement……)的 value。
- href
	- <a href="...">(HTMLAnchorElement)的 href。
- id
	- 所有元素(HTMLElement)的 “id” 特性(attribute)的值
  <button class="btn">切换</button>
  <!-- hidden 属性 -->
  <div id="box" class="cba" title="aaa" style="color: red;">
    hhhh
  </div>

  <script>
    // 1.获取元素
    var boxEl = document.querySelector('#box')
    var btnEl = document.querySelector('.btn')

    // 2.监听btn点击事件
    btnEl.onclick = function () {
      // boxEl.hidden = true
      // boxEl.style.display = 'none'
      // boxEl.hidden = false
      /* if (!boxEl.hidden) {
        boxEl.hidden = true
      } else {
        boxEl.hidden = false
      } */

      // 直接取反
      boxEl.hidden = !boxEl.hidden
    }
  </script>

Q.E.D.