# BOM 和 DOM

# 1. BOM:浏览器对象模型

在浏览器中使用 JavaScript 时,有一些【天然存在】的对象你可以直接使用,无需你来声明/创建。因为这些对象都是由浏览器创建供你使用。通过这些对象,你可以利用到浏览器的某些功能。

早期这些【天然可用】的对象、方法等一系列的概念比较混乱,不成体系。后来,为了建立一个统一的概念,HTML 标准中将这些对象都【收拢于一个叫做 Window 的对象下】,作为这个对象的各种属性。这整个体系就被称为 浏览器对象模型(BOM)。

window 对象代表着当前浏览器中的窗口。

常用属性和方法 说明
window.location window 对象当前的URL
window.history 指向一个 history 对象,其中包含了本页浏览过的页面的详细记录
window.alert() 创建含有消息的对话框

JavaScript 是单线程语言,但它允许通过设置超时值和间歇时间值来调度代码在特定的时刻执行。

前者在指定的时间过后执行代码,而后者则是每隔指定的时间就执行一次代码。

超时调用需要使用 window 对象的 setTimeout() 方法,它接受两个参数:要执行的代码和毫秒数的超时时间。

调用 setTimeout() 之后,该方法会返回一个数值 ID,用于表示这个超时调用。在调用执行前,可以通过它和 clearTimeout() 方法取消超时调用。

间歇调用与超时调用类似,只不过它会按照指定的时间间隔重复执行代码,直至间歇调用被取消或者页面被卸载。

设置间歇调用的方法是 ***setInterval()***,它接受的参数与 setTimeout() 相同:要执行的代码和每次执行之前需要等待的毫秒数。

取消间歇调用方法和取消超时调用类似,使用 clearInterval() 方法。

# 2. DOM:文档对象模型

# 1. 核心概念

当浏览器加载 Web 页面时,会在内存中创建页面的模型。

创建什么样的对象,这些对象有什么样的属性和方法,对象和对象之间有何种关系,这都是有统一的标准化的规定:DOM 模型。

DOM 对象与页面上的各种元素之间有明确的一一对应关系,访问和修改 DOM 对象等同于修改页面(视觉效果)。

DOM 对象的种类有:

  • 文档节点(Document)
  • 元素节点(Element)
  • 属性节点(Attribute)
  • 文本节点(Text)

注意,HTML 元素的属性习惯性使用单词 attribute,编程领域中的对象的属性(也叫特征)则习惯使用单词 property

文档节点 是整棵 DOM 树的顶点,它代表着整个页面。当需要访问任何元素、属性和文本节点时,都需要通过它来进行导航。

元素节点 对应着 HTML 页面上的 HTML 元素。对元素节点的访问和操作,就代表着对页面上的 HTML 元素进行访问和操作。HTML 元素的父子关系,也会导致其对应的元素节点间有父子关系。

HTML 元素的开始标签中可以包含若干属性,这些属性在 DOM 树中形成 属性节点 。需要注意的是,属性节点 并非 元素节点的子节点。

当访问元素节点时可以访问元素内部的文本,这些文本保存在其 文本节点 中。文本节点是元素节点的子节点,而文本节点自己并没有子节点。如果一个元素包含文本和其它子元素,这些子元素和文本节点即为“兄弟”关系。

所有的【节点】都有以下关键属性:

nodeType
表示该节点的类型
9 代表 Document 节点
1 代表 Element 节点
3 代表 Text 节点
8 代表 Comment 节点
nodeName
元素的标签名,以大写形式表示。
nodeValue
Text 节点的文本内容

# 2. 选中页面元素

访问并更新 DOM 树需要两个步骤:

  1. 定位到需要操作的元素所对应的节点。

  2. 使用它的文本内容、子元素或属性。

常见的访问/定位元素的方法:

选中单个元素 选中多个元素
getElementById() getElementsByTagName()
querySelector() getElementByClassName()
querySelectorAll()

关于选择方法的几个注意事项:

  1. querySelector()querySelectorAll() 这两个方法是 DOM 原生 API 受 jQuery 的影响,【后来】才有的。

  2. querySelector()querySelectorAll() 这两个方法的性能比其它三个要差一些,因此,在有其它选择的情况下,尽量不要优先使用它俩。

  3. 你在使用 querySelector() 时,传给它的参数逻辑上可能会选中页面上的多个元素,但是这个方法只返回所匹配的第一个元素。

  4. 上述 3 个能选中多个元素的方法的返回值是 NodeList(即便其中只有一个元素),逻辑上,它是所选中的 DOM 对象的集合。

    NodeList 看起来像是数组,它有

    • length 属性
    • item() 方法
    • 另外对它可以使用数组的 [] 语法。优先考虑使用它,而非 item() 方法。

# 3. 通过亲属关系选中元素

因为 DOM 树上的元素是以【父子关系】为依据关联在一起的,因此,当你选中某个元素之后,你可以【顺藤摸瓜】地【摸】出很多其它的元素。

元素之间的关系:

父子关系 兄弟关系
parentNode 属性 previousSibling 属性
firstChild 属性 nextSibling 属性
lastChild 属性

# 4. 操作所选元素

# 读写元素的文本内容

读取元素的文本内容有两种方式:

  1. 在获得元素节点(Element Node)之后,通过 Element 的 textContent 属性获得其文本内容。

  2. 在获得元素节点(Element Node)之后,进一步获得其子元素文本节点(Text Node),通过 Text 的 nodeValue 属性。

var el = document.getElementById("...");

console.info( el.textContent );
el.textContent = "...";
console.info( el.textContent );

# 读写元素的 HTML 内容

var el = document.getElementById("...");

console.info( el.innerHTML );
el.textContent = "...";
console.info( el.innerHTML );

# 读写元素的 class / id 属性

操作属性值
className / id 属性
hasAttribute ( )
getAttribute ( )
setAttribute ( )
removeAttribute ( )

# 5. 操作 DOM 结构

DOM 层次结构操作:

  • document.createElement ( )
  • document.createTextNode ( )
  • document.appendChild ( ) / document.removeChild ( )

# 添加节点

function click_handler() {
    var textNode = document.createTextNode('粒粒皆辛苦');
    var liNode = document.createElement('li');
    liNode.appendChild(textNode);

    var olNode = document.getElementsByTagName("ol")[0];
    olNode.appendChild(liNode);
}
  • document.createTextNode('...'); createTextNode() 方法创建一个新的文本节点。

  • document.createElement("li"); createElement() 方法创建一个新的元素节点。

  • liNode.appendChild(textNode);

# 添加节点的另一种方式

上面的新增节点的方式比较罗嗦,IE 提出了一个 API 并且被 HTML5 所接纳:***.insertAdjacentHTML()*** 。

insertAdjacentHTML() 不仅可以接纳 Element 对象作为参数,更方便的是它可以接受 html 字符串作为参数。

const ul = document.getElementById('ul');
ul.insertAdjacentHTML('afterbegin', '<li>梨子</li>');

insertAdjacentHTML() 方法的第一个参数是四个固定的字符串,分别代表四种不同的插入位置:

参数字符串 说明
"beforebegin" 当前元素的开始标签之前
"afterbegin" 当前元素的开始标签之后
"beforeend" 当前元素的结束标签之前
"afterend" 当前元素的结束标签之后

# 删除节点

集合父节点(parentNode),删除一个节点的代码可以写成:

var el = document.getElementById('...');
el.parentNode.removeChild(el);

# 清空节点

清空节点指的就是删除一个节点下的所有子节点。

var el = document.getElementById('...');
el.innerHTML = '';

# 插入节点

【插入节点】可以实现和【添加节点】一样的功能。区别在于,【插入节点】是站在【父子关系】的角度上实现的功能,而【添加节点】是站在【兄弟关系】上实现的。

function click_handler() {
    var text = document.createTextNode('谁知盘中餐');
    var li = document.createElement('li');
    li.appendChild(text);

    var ol = document.getElementsByTagName('ol')[0];
    var last_li = document.getElementById('last');
    ol.insertBefore(li, last_li);
}

# 替换节点

function click_handler() {
    var text = document.createTextNode('谁知盘中餐');
    var li = document.createElement('li');
    li.appendChild(text);

    var ol = document.getElementsByTagName('ol')[0];
    var last_li = document.getElementById('last');
    ol.replaceChild(li, last_li);
}

# DOM 和 CSS

在 DOM 节点对象里,可以找到与 HTML 元素 class 属性对应的 className 属性。(注意,这里不是 DOM 对象的 class 属性,因为 class 是 JavaScript 中的 保留字)。

每个 DOM 节点都有一个 style 属性,用于操作该节点的样式。style 属性引用了一个对象,这个对象中有 HTML 元素可能存在的所有属性。例如:

p.style.opacity = "0.0";	// 不透明度

# 3. DOM:事件处理

事件是用户在访问页面时执行的操作。当浏览器探测到一个事件时,比如用鼠标单击或按键,它可以触发与这个事件相关联的 JavaScript 对象,这些对象称为 事件处理程序(event handler)。

# 1. 添加事件处理程序

为元素添加事件处理程序的方式有三种:

  • 在 html 元素中指定事件的处理程序:

    <div ... onclick="click_handler();">...</div>
    <div ... onclick="click_handler(event);">...</div>
    

    早期的方案,现在不建议使用。

  • 在 javascript 为 onXXX 属性赋值:

    window.onload = function () {
      const div = document.getElementById('hello');
    
      div.onclick = function (event) {
          ...
      }
    };
    
    <div id="hello" ...></div>
    
  • 在 javascript 调用 addEventListener() 方法:

    window.onload = function () {
      const div = document.getElementById('hello');
    
      div.addEventListener("click", function(event){ ... });
    }
    

    新标准,优先考虑使用。

addEventListener() 有第三个参数,缺省时的默认值是 false 。这个 boolean 参数表示是否开启【捕获期】触发。

【捕获期】触发意味着只有在本元素上发生了对应事件,由本元素捕获的事件,才触发该执行程序的运行。对于自己的子元素上触发,通过【冒泡】方式引起的事件,则不予理睬。

对于同一个元素 addEventListener() 方法可以调用多次,为一个事件绑定多个事件处理程序,而非覆盖。

addEventListener() 的反向操作是 removeEventListener()

div.addEventListener("click", click_handler);
div.removeEventListener("click", click_handler);

# 2. 常见的事件及其种类

# 窗口事件

当用户执行某些会影响整个浏览器窗口的操作时,就会发生窗口事件。

事件名 备注
load Web 页面加载完成时触发该事件

# 鼠标事件

事件名 备注
click 点击一个元素时触发
mousedown 在一个元素上按下鼠标按键时触发
mouseup 在一个元素上松开鼠标按键时触发
mousemove 移动鼠标时持续触发
mouseover 鼠标进入一个元素时触发
mouseout 鼠标移出一个元素时触发

# 焦点事件

事件名 备注
focus 元素得到焦点
blur 元素失去焦点

# 表单事件

事件名 备注
input <input>这样需要“动键盘”的表单元素中的值发生了变化时触发
change 单选框这样需要“动鼠标”的表单元素中得值发生了变化时触发
submit 用户提交表单时触发
reset 用于点击表单上的重置按钮时触发

事件触发 JavaScript 代码的过程分为三步,这些步骤成为【事件处理】:

  1. 选中需要进行事件响应的元素节点。

  2. 声明需要在所选节点上要响应的事件。

  3. 指定当事件发生时需要运行的代码。

常见的为元素绑定事件处理函数的方式:

  • 在 HTML 中通过元素属性指定处理函数(不建议使用)

  • 在 JavaScript 代码中通过元素节点的 onXXX 属性制定处理函数

# 3. EventHandler

# EventHandler 中的 Event 对象

当指定元素上发生事件后,浏览器会去执行与之关联的事件处理函数,并向事件处理函数中传入一个 事件对象(Event)。

考虑事件处理程序的两种写法,这里的 Event 对象的传递也有两种方式:

  • 方式一:

    function div_click(event) {
      console.info(event);
    }
    
    <div ... onclick="btn_click(event);"></div>
    

    需要注意的是,这种方式中必须使用单词 event

  • 方式二:

    window.onload = function () {
      const div = document.getElementById('hello');
    
      div.onclick = function (xxx) {
          ...
      }
    };
    
    <div id="hello" ...></div>
    

    这种方式不强求使用单词 event

事件对象(Event)会【告诉】你关于这个事件的信息,以及它发生在哪个元素上。

事件对象(Event)常见属性:

属性 备注
target 事件的目标,即发生事件的元素
type 事件的类型

事件对象(Event)常用方法:

方法 备注
preventDefault() 撤销这个事件的默认行为

# Event 对象的 target 和 currentTarget

Event 对象中除了有 target 属性,还有一个 currentTarget 属性。

有时候它们两个指向的是同一个 DOM 对象,有时候则不是:

  • target 是事件触发的真实元素

  • currentTarget 是事件绑定的元素

  • 当事件是子元素触发时,currentTarget 为绑定事件的元素,target 为子元素

  • 当事件是元素自身触发时,currentTargettarget 为同一个元素

以下列代码为例:

<ul id="box">
  <li id="apple">苹果</li>
  <li>香蕉</li>
  <li>桃子</li>
</ul>

当你点击了 <li id="apple"> 元素导致 ul 和 li 上绑定的 click 事件的处理程序执行时:

  • li 元素的处理程序中的 event.currentTargetevent.target 都是 li 元素它自己。

  • ul 元素的处理程序中 event.currentTarget 是 ul 元素自己,因为处理程序绑定在它的身上;而 event.target 则是它的子元素 li,因为是 li 上发生的触发事件,因为事件冒泡,才导致的 ul 的处理程序的执行。

它俩都是只读的,不能也不应该被你重新被赋值。

# EventHandler 中的 this

大多数情况下,我们对 EventHandler 中的 event.currentTargetevent.target 不会过于身就,讲解它们的关系和区别是为了讲解这里 this

所有的 EventHandler 中都有一个 this 变量可用,既不需要声明,又不需要传入,它是浏览器默认传入的一个对象。

this 始终等于 currentTarget 的值,即指向的是 EventHandler 所绑定的那个元素对象,这个元素对象不一定是发生/响应事件的对象。例如上例中的 ul 和 li 关系。