DOM和BOM

DOM文档对象模型

文档对象模型(Document Object Model,简称DOM),是一个用于表示HTML、XML等结构化文档的编程接口。

DOM将一个文档视作一个由节点构成的树型结构,每个节点代表文档中的一个部分,比如一个元素(如<p>标签)、文本节点、属性或者注释等。

DOM的核心概念包括:

  1. 节点(Node):文档中的每个内容都是一个节点,包括元素节点、属性节点、文本节点等。
  2. 元素(Element):HTML或XML中的标签即为元素节点,可以有属性和子节点(如文本节点或其它元素节点)。
  3. 属性(Attribute):元素上的特性,如<img src="image.jpg">中的src就是一个属性。
  4. 文档对象(Document Object):代表整个文档的根节点,是访问文档中其他所有节点的起点。

获取DOM元素

1. 根据ID获取元素

使用 getElementById 方法可以获取具有特定ID的元素。ID应该是文档中唯一的,因此这个方法返回的是一个单独的元素或null(如果没有找到匹配的元素)。

javascript
// 假设有一个ID为"myElement"的元素
var element = document.getElementById("myElement");
console.log(element);

上述代码会找到ID为myElement的DOM元素,并将其引用存储在变量element中。

2. 根据标签名获取元素

使用 getElementsByTagName 方法可以获取具有特定标签名的所有元素。这个方法返回一个HTMLCollection集合,即使只找到一个匹配的元素也是如此。

javascript
// 获取所有<p>元素
var paragraphs = document.getElementsByTagName("p");
console.log(paragraphs);

上述代码会找到所有<p>标签的DOM元素,并将它们的引用存储在一个名为paragraphs的HTMLCollection中。

3. H5新增获取元素方式

HTML5引入了新的DOM选择方法,例如:

  • getElementsByClassName:根据类名获取元素,返回一个HTMLCollection。
  • querySelector:根据CSS选择器获取第一个匹配的元素。
  • querySelectorAll:根据CSS选择器获取所有匹配的元素,返回一个NodeList。
javascript
// 使用getElementsByClassName
var elementsWithClass = document.getElementsByClassName("specialClass");
console.log(elementsWithClass);

// 使用querySelector获取第一个匹配的元素
var firstMatch = document.querySelector(".anotherClass");
console.log(firstMatch);

// 使用querySelectorAll获取所有匹配的元素
var allMatches = document.querySelectorAll("#container p");
console.log(allMatches);
  • getElementsByClassName 示例会找到所有拥有specialClass类的元素。
  • querySelector 示例会找到第一个拥有.anotherClass类的元素。
  • querySelectorAll 示例会找到#container内的所有<p>元素。

除了上述方法,可以通过属性选择器、组合选择器等方式利用querySelectorquerySelectorAll来获取。

javascript
// 获取所有checked的checkbox
var checkedCheckboxes = document.querySelectorAll('input[type="checkbox"]:checked');
console.log(checkedCheckboxes);

上述代码会找到所有被选中的复选框元素,即type属性为checkbox且处于checked状态的<input>元素。

DOM节点操作

  1. 创建元素节点

    • 方式1:使用document.createElement()创建了一个新的<p>元素,并设置了其文本内容。
    • 方式2:直接通过innerHTML属性给container的内部HTML赋值,这种方式简便但会替换原有的所有内容。
    • 方式3:使用insertAdjacentHTML()方法在container内部的末尾插入HTML字符串,这种方式不会影响已有的内容。
  2. 删除节点:找到container的最后一个子元素并使用removeChild()方法将其删除。

  3. 复制(克隆)节点:通过cloneNode(true)方法深复制第一个子元素(即第一个<p>元素),然后将克隆得到的元素追加到container的末尾。

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM操作示例</title>
<script>
window.onload = function() {
  // 获取节点
  var container = document.getElementById('container'); // 通过ID获取容器元素

  // 创建元素节点
  // 方式1:使用document.createElement()
  var newElement1 = document.createElement('p');
  newElement1.textContent = '我是通过createElement创建的段落。';
  
  // 方式2:innerHTML直接赋值(注意这会覆盖容器内原有内容)
  var newHTML = '<p>我是通过innerHTML直接赋值的方式创建的段落。</p>';
  container.innerHTML = newHTML;
  
  // 方式3:使用insertAdjacentHTML(),不会覆盖原有内容
  container.insertAdjacentHTML('beforeend', '<p>我是通过insertAdjacentHTML创建的段落。</p>');
  
  // 将新创建的元素添加到容器中
  container.appendChild(newElement1);

  // 删除节点
  var lastChild = container.lastElementChild;
  if (lastChild) {
    container.removeChild(lastChild); // 删除最后一个子元素
  }

  // 复制(克隆)节点
  var firstP = container.firstElementChild;
  if (firstP) {
    var clonedP = firstP.cloneNode(true); // true表示深复制,包括子元素和属性
    container.appendChild(clonedP); // 将克隆的节点添加到容器末尾
  }
};
</script>
</head>
<body>
<div id="container">
  <!-- 此处将进行DOM操作 -->
</div>
</body>
</html>

DOM事件

DOM事件是用户与网页交互(如点击、鼠标移动、键盘输入等)或某些页面状态变化(如加载完成)时触发的响应机制。事件处理是Web开发中的核心概念之一。

常见事件

  • click: 鼠标点击
  • mouseover / mouseout:鼠标悬停/离开
  • keydown / keyup:按键按下/释放
  • load:页面或资源加载完成
  • submit:表单提交
  • change:表单元素值改变
  • mousemove:鼠标移动

通过DOM API,可以为页面上的元素添加、移除事件监听器,以及阻止事件的默认行为或传播。

下面是一个演示了事件委托、自定义属性和事件对象使用的简单示例:

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DOM 事件示例</title>
<style>
  .list-item { cursor: pointer; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
  // 父元素委托处理子元素的点击事件
  document.getElementById('listContainer').addEventListener('click', function(event) {
    // 确保点击的是列表项
    if (event.target.classList.contains('list-item')) {
      // 使用事件对象访问自定义属性
      var itemId = event.target.dataset.itemId;
      alert('你点击了项目ID: ' + itemId);
      
      // 阻止事件冒泡到document级别
      event.stopPropagation();
    }
  });

});
</script>
</head>
<body>
<div id="listContainer">
  <div class="list-item" data-item-id="1">项目1</div>
  <div class="list-item" data-item-id="2">项目2</div>
  <div class="list-item" data-item-id="3">项目3</div>
</div>
</body>
</html>
  • 使用 DOMContentLoaded 事件确保DOM加载完成后再执行脚本。
  • listContainer 上通过事件委托监听 click 事件,节省资源,适用于动态添加的列表项。
  • 判断点击的目标是否含有 list-item 类,以确保只处理列表项的点击。
  • 通过 event.target.dataset.itemId 访问自定义属性 data-item-id,显示点击项目的ID。
  • 调用 event.stopPropagation(); 阻止事件继续向上冒泡。

BOM浏览器对象模型

浏览器对象模型(Browser Object Model,简称BOM)是针对浏览器窗口和框架的JavaScript对象的集合,它提供了与浏览器窗口进行交互的能力。

BOM没有一个统一的标准,这意味着不同的浏览器可能会有不同的实现,这为跨浏览器兼容性带来了挑战。尽管如此,大部分现代浏览器遵循了类似的模式,确保了基本功能的一致性。

BOM常用对象

BOM(Browser Object Model,浏览器对象模型)提供了与浏览器窗口及功能交互的一系列对象。

  1. window对象:==BOM的核心对象,代表浏览器窗口,也是ECMAScript的全局对象==。它包含了所有全局变量和函数,并且是其他BOM对象的宿主。

  2. document对象:代表当前页面的HTML文档,是window对象的一个属性。通过它可以访问和操作页面中的所有元素。

  3. location对象:提供了当前页面URL的信息,并且可以使用它来导航到新的URL。

  4. navigator对象:提供了关于浏览器的信息,如名称、版本和用户代理字符串。

  5. screen对象:提供了关于用户屏幕的信息,如可用宽度、高度、色彩深度等。

下面的代码示例展示了如何使用这些对象来获取和显示一些基本信息:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>BOM常用对象示例</title>
</head>
<body>

<script>
    // 使用window对象的alert方法显示信息
    window.alert("欢迎来到BOM示例页面!");

    // 使用document对象获取并显示页面标题
    var pageTitle = document.title;
    alert("页面标题是: " + pageTitle);

    // 使用location对象获取当前页面URL并显示
    var currentURL = window.location.href;
    alert("当前页面的URL是: " + currentURL);

    // 使用navigator对象获取浏览器信息
    var browserInfo = "浏览器名称: " + navigator.appName + "\n版本: " + navigator.appVersion;
    alert(browserInfo);

    // 使用screen对象获取屏幕信息
    var screenWidth = screen.width;
    var screenHeight = screen.height;
    alert("您的屏幕分辨率为: " + screenWidth + "x" + screenHeight);
</script>

</body>
</html>

BOM常见事件

BOM(浏览器对象模型)提供了一系列事件,允许开发者监听和响应浏览器窗口、框架、以及文档的各种状态变化和用户交互

1. onload 事件

当整个页面(包括所有依赖资源如图片、样式表、脚本等)加载完成时触发。常用于执行那些需要确保所有页面资源都已加载完毕的代码,比如初始化脚本、计算布局等。

html
<!DOCTYPE html>
<html>
<head>
    <title>OnLoad 示例</title>
    <script>
        window.onload = function() {
            alert("页面加载完成了!");
            // 这里可以执行需要在页面加载完毕后执行的代码
        };
    </script>
</head>
<body>
    <h1>欢迎来到我的网站</h1>
    <img src="example.jpg" alt="示例图片" />
</body>
</html>

2. onbeforeunloadonunload 事件

  • onbeforeunload:在窗口、文档或资源即将卸载前触发,常用于提示用户确认是否离开页面(例如,当表单数据未保存时)。
  • onunload:在窗口、文档完全卸载后触发,适合执行清理工作,但由于兼容性和性能问题,此事件使用较少。
html
<!DOCTYPE html>
<html>
<head>
    <title>OnBeforeUnload 示例</title>
    <script>
        window.onbeforeunload = function() {
            return "你确定要离开吗?你可能有未保存的数据。";
        };
    </script>
</head>
<body>
    <h1>请填写表单</h1>
    <!-- 表单内容省略 -->
</body>
</html>

3. onscroll 事件

当元素的滚动条位置发生变化时触发。常用于实现滚动监听,如无限滚动加载、固定导航栏等效果。

html
<!DOCTYPE html>
<html>
<head>
    <title>OnScroll 示例</title>
    <style>
        #scrollArea {
            height: 200px;
            overflow-y: scroll;
            border: 1px solid black;
        }
    </style>
    <script>
        document.getElementById('scrollArea').onscroll = function() {
            console.log("滚动中...");
            // 这里可以添加滚动时执行的逻辑
        };
    </script>
</head>
<body>
    <div id="scrollArea">
        <p>内容...</p>
        <!-- 大量重复内容以产生滚动条 -->
    </div>
</body>
</html>

4. onresize 事件

当浏览器窗口被调整大小时触发。可用于响应式设计,调整页面布局或元素尺寸。

html
<!DOCTYPE html>
<html>
<head>
    <title>OnResize 示例</title>
    <script>
        window.onresize = function() {
            console.log("窗口大小改变了...");
            // 这里可以添加调整窗口大小时的响应逻辑
        };
    </script>
</head>
<body>
    <h1>调整窗口大小试试看</h1>
</body>
</html>

移动端触屏事件

移动端触屏事件(Touch Events)是专为触摸屏设备设计的一组事件,允许开发者捕获和响应用户的触摸交互,如轻触、滑动、长按等。

以下是一个简单的HTML页面,使用JavaScript实现一个可拖动的<div>元素:

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>移动端拖动示例</title>
<style>
.draggable {
  width: 100px;
  height: 100px;
  background-color: red;
  position: absolute;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
  var draggable = document.getElementById('draggable');
  var startX, startY, isDragging = false;

  draggable.addEventListener('touchstart', function(e) {
    e.preventDefault(); // 防止页面滚动
    isDragging = true;
    startX = e.touches[0].clientX;
    startY = e.touches[0].clientY;
  });

  draggable.addEventListener('touchmove', function(e) {
    if (!isDragging) return;
    e.preventDefault(); // 防止页面滚动
    var newX = e.touches[0].clientX;
    var newY = e.touches[0].clientY;
    var diffX = newX - startX;
    var diffY = newY - startY;
    draggable.style.transform = `translate(${diffX}px, ${diffY}px)`;
  });

  draggable.addEventListener('touchend', function(e) {
    isDragging = false;
  });
});
</script>
</head>
<body>
<div id="draggable" class="draggable"></div>
</body>
</html>

在这个示例中首先获取了一个带有draggable类名的<div>元素,并为其添加了touchstarttouchmovetouchend事件监听器。当用户触摸并开始移动这个<div>时,它会跟随手指移动,实现拖动效果。注意,这里使用了CSS的transform属性来改变元素的位置,而不是直接修改lefttop,因为transform更高效且不会引发页面重排。

BOM定时器

BOM中的定时器功能允许开发者在特定的时间间隔之后执行代码,这对于创建动态和交互式的Web应用程序非常有用。主要通过两个函数来实现:setTimeoutsetInterval

1. setTimeout

setTimeout函数用于在指定的时间(以毫秒为单位)之后调用一次指定的函数或者执行一段代码。它返回一个ID,可以用来取消这个定时器(通过clearTimeout函数)。

javascript
var timeoutID = setTimeout(function|code, delay[, arg1, arg2, ...]);
  • function|code:要执行的函数或者一段代码(字符串形式)。
  • delay:延迟时间,单位为毫秒。
  • arg1, arg2, ...:传递给函数的参数(如果function|code是函数的话)。
javascript
function sayHello() {
    console.log("你好,世界!");
}

// 在2秒后执行sayHello函数
var timeoutID = setTimeout(sayHello, 2000);

2. setInterval

setInterval函数用于按照指定的周期(以毫秒为单位)重复调用函数或者执行代码段。与setTimeout不同,它会持续执行,直到被显式地清除(通过clearInterval函数)。

javascript
var intervalID = setInterval(function|code, interval[, arg1, arg2, ...]);

function|codeintervalarg1, arg2, ... 的含义同上。

javascript
function showTime() {
    var now = new Date();
    console.log(now.toLocaleTimeString());
}

// 每隔1秒执行一次showTime函数
var intervalID = setInterval(showTime, 1000);

清除定时器

  • 使用clearTimeout(timeoutID)来取消由setTimeout设置的定时器。
  • 使用clearInterval(intervalID)来取消由setInterval设置的定时器。
javascript
var timeoutID = setTimeout(function() {
    console.log("这个消息将不会显示");
}, 6000);

// 在3秒后清除定时器,防止消息显示
setTimeout(function() {
    clearTimeout(timeoutID);
    console.log("定时器已清除");
}, 3000);

this指向问题

在JavaScript中,this关键字的指向问题是一个复杂且经常引起混淆的概念,尤其是在BOM(Browser Object Model)的上下文中。this的值通常取决于函数调用的上下文,而不是函数定义的位置。在BOM中,this通常指向调用当前函数的对象,但在不同的场景下,它的指向可能会有所不同。

1. 全局上下文中的this

在全局作用域中,非严格模式下,this指向全局对象(在浏览器环境中是window对象)。在严格模式(‘use strict’;)下,this会是undefined

javascript
console.log(this === window); // true,非严格模式下

2. 函数调用中的this

  • 在普通函数调用中,如果函数不是作为某个对象的方法被调用,this同样会指向全局对象(非严格模式)或undefined(严格模式)。
  • 如果函数是作为某个对象的方法调用,this则指向该对象。
javascript
var obj = {
    func: function() {
        console.log(this === obj); // true
    }
};
obj.func(); // 方法调用,this指向obj

3. 构造函数中的this

当使用new关键字调用构造函数时,this指向新创建的实例对象。

javascript
function Person(name) {
    this.name = name;
    console.log(this); // 新创建的Person实例
}
var person = new Person("Alice");

4. DOM事件处理函数中的this

在为DOM元素绑定事件处理器时,无论使用匿名函数还是命名函数,this通常指向触发事件的DOM元素本身。

html
<button id="myButton">点击我</button>
<script>
document.getElementById("myButton").addEventListener("click", function() {
    console.log(this === document.getElementById("myButton")); // true
});
</script>

5. setTimeoutsetInterval中的this

在使用setTimeoutsetInterval时,由于它们是在全局上下文中执行回调函数,非箭头函数情况下,this通常会指向全局对象。使用箭头函数可以维持外层的this值。

javascript
var obj = {
    myMethod: function() {
        setTimeout(function() {
            console.log(this === window); // true,非严格模式
            // 或者 undefined,严格模式
        }, 1000);
        
        setTimeout(() => {
            console.log(this === obj); // true,箭头函数维持外层的this
        }, 2000);
    }
};
obj.myMethod();

this的指向在JavaScript中较为灵活,需根据函数的调用方式、所在环境(全局、对象方法、构造函数等)、以及是否使用箭头函数等因素综合判断。在BOM编程中,理解this的行为对于正确处理事件、定时器等功能至关重要。

元素位置和尺寸

元素偏移量offset

偏移量(offset)是指元素相对于其带有定位(position不是static的)的最近父元素的左上角位置。这包括了任何边距、边框和滚动的距离。主要通过offsetTopoffsetLeftoffsetWidthoffsetHeight四个属性获取。

javascript
function logOffset(elementId) {
  var element = document.getElementById(elementId);
  console.log("Offset Top: " + element.offsetTop);
  console.log("Offset Left: " + element.offsetLeft);
  console.log("Offset Width: " + element.offsetWidth);
  console.log("Offset Height: " + element.offsetHeight);
}

logOffset('myElement');

元素可视区client

可视区(client)是指元素内部没有被滚动条隐藏的部分,即用户当前可见的部分的尺寸。主要通过clientTopclientLeftclientWidthclientHeight属性获取。

javascript
function logClient(elementId) {
  var element = document.getElementById(elementId);
  console.log("Client Top: " + element.clientTop);
  console.log("Client Left: " + element.clientLeft);
  console.log("Client Width: " + element.clientWidth);
  console.log("Client Height: " + element.clientHeight);
}

logClient('myElement');

元素滚动scroll

滚动属性(scroll)反映了元素滚动条的位置以及可滚动区域的尺寸。主要通过scrollTopscrollLeftscrollWidthscrollHeight属性获取。

  • scrollTop/scrollLeft: 获取或设置元素在垂直/水平方向上被卷起的像素数。
  • scrollWidth/scrollHeight: 获取元素内容总宽度/高度,包括不可见的部分(可能由于滚动而未显示)。
javascript
function logScroll(elementId) {
  var element = document.getElementById(elementId);
  console.log("Scroll Top: " + element.scrollTop);
  console.log("Scroll Left: " + element.scrollLeft);
  console.log("Scroll Width: " + element.scrollWidth);
  console.log("Scroll Height: " + element.scrollHeight);
}

logScroll('myScrollableElement');

在实际应用中,这些属性的准确值可能会受到CSS样式(如box-sizingoverflow等)的影响,因此在使用时需要考虑样式上下文。