DOM文档对象模型
文档对象模型(Document Object Model,简称DOM),是一个用于表示HTML、XML等结构化文档的编程接口。
DOM将一个文档视作一个由节点构成的树型结构,每个节点代表文档中的一个部分,比如一个元素(如<p>
标签)、文本节点、属性或者注释等。
DOM的核心概念包括:
- 节点(Node):文档中的每个内容都是一个节点,包括元素节点、属性节点、文本节点等。
- 元素(Element):HTML或XML中的标签即为元素节点,可以有属性和子节点(如文本节点或其它元素节点)。
- 属性(Attribute):元素上的特性,如
<img src="image.jpg">
中的src
就是一个属性。 - 文档对象(Document Object):代表整个文档的根节点,是访问文档中其他所有节点的起点。
获取DOM元素
1. 根据ID获取元素
使用 getElementById
方法可以获取具有特定ID的元素。ID应该是文档中唯一的,因此这个方法返回的是一个单独的元素或null
(如果没有找到匹配的元素)。
// 假设有一个ID为"myElement"的元素
var element = document.getElementById("myElement");
console.log(element);
上述代码会找到ID为myElement
的DOM元素,并将其引用存储在变量element
中。
2. 根据标签名获取元素
使用 getElementsByTagName
方法可以获取具有特定标签名的所有元素。这个方法返回一个HTMLCollection集合,即使只找到一个匹配的元素也是如此。
// 获取所有<p>元素
var paragraphs = document.getElementsByTagName("p");
console.log(paragraphs);
上述代码会找到所有<p>
标签的DOM元素,并将它们的引用存储在一个名为paragraphs
的HTMLCollection中。
3. H5新增获取元素方式
HTML5引入了新的DOM选择方法,例如:
- getElementsByClassName:根据类名获取元素,返回一个HTMLCollection。
- querySelector:根据CSS选择器获取第一个匹配的元素。
- querySelectorAll:根据CSS选择器获取所有匹配的元素,返回一个NodeList。
// 使用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>
元素。
除了上述方法,可以通过属性选择器、组合选择器等方式利用querySelector
或querySelectorAll
来获取。
// 获取所有checked的checkbox
var checkedCheckboxes = document.querySelectorAll('input[type="checkbox"]:checked');
console.log(checkedCheckboxes);
上述代码会找到所有被选中的复选框元素,即type
属性为checkbox
且处于checked
状态的<input>
元素。
DOM节点操作
-
创建元素节点:
- 方式1:使用
document.createElement()
创建了一个新的<p>
元素,并设置了其文本内容。 - 方式2:直接通过
innerHTML
属性给container
的内部HTML赋值,这种方式简便但会替换原有的所有内容。 - 方式3:使用
insertAdjacentHTML()
方法在container
内部的末尾插入HTML字符串,这种方式不会影响已有的内容。
- 方式1:使用
-
删除节点:找到
container
的最后一个子元素并使用removeChild()
方法将其删除。 -
复制(克隆)节点:通过
cloneNode(true)
方法深复制第一个子元素(即第一个<p>
元素),然后将克隆得到的元素追加到container
的末尾。
<!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>
实际上,document.write()
方法不推荐用于创建或修改现有的DOM元素,因为它主要用于在页面加载过程中直接向文档流中写入HTML内容。一旦文档加载完成(即document.readyState
变为interactive
或complete
),再调用document.write()
会清空当前文档并重新开始写入新的HTML,导致丢失原有的DOM结构和事件绑定。
以下是使用document.write()
直接在页面输出HTML内容的一个简单示例,但请注意这不是推荐的做法来创建或管理DOM元素:
<!DOCTYPE html>
<html>
<head>
<title>Document Write 示例</title>
<script>
window.onload = function() {
// 不推荐的做法:使用document.write在页面加载后写入内容
// 注意:这会覆盖页面现有内容!
document.write('<p>这是通过document.write添加的段落。</p>');
};
</script>
</head>
<body>
<!-- 页面原始内容 -->
</body>
</html>
重要提示:在现代Web开发实践中,应使用如createElement
、appendChild
、innerHTML
或insertAdjacentHTML
等方法来操作DOM,这些方法更加灵活且不会破坏已有页面结构。对于创建元素节点,推荐使用如下所示的更现代和安全的方法:
var newElement = document.createElement('p');
newElement.textContent = '通过createElement创建的内容';
document.body.appendChild(newElement);
这样不仅能够安全地添加新元素,还能够更好地控制和维护DOM结构。
DOM事件
DOM事件是用户与网页交互(如点击、鼠标移动、键盘输入等)或某些页面状态变化(如加载完成)时触发的响应机制。事件处理是Web开发中的核心概念之一。
常见事件:
click
: 鼠标点击mouseover
/mouseout
:鼠标悬停/离开keydown
/keyup
:按键按下/释放load
:页面或资源加载完成submit
:表单提交change
:表单元素值改变mousemove
:鼠标移动
通过DOM API,可以为页面上的元素添加、移除事件监听器,以及阻止事件的默认行为或传播。
注册和删除事件
- 内联方式:在HTML元素中直接使用
onclick
、onmouseover
等属性。通过元素的事件属性直接赋值一个函数。 删除事件方式:直接将事件属性设置为null
。 - 监听注册方式:使用
addEventListener
方法,可以为同一个元素的同一事件添加多个监听器 删除事件方式:使用removeEventListener
,需要传递与注册时相同的参数。
事件冒泡与捕获
- 事件冒泡:事件从最深的节点开始,逐级向上层节点传播直到文档根。
- 事件捕获:与冒泡相反,事件先从根节点开始,向目标节点传播。
事件对象和事件委托
事件对象(如 event
)在事件处理函数中自动传递,包含了与事件相关的信息,如 event.type
(事件类型)、event.target
(事件源)等。
事件委托是一种高效的事件处理方式,通过在父元素上监听事件,利用事件冒泡机制处理子元素的事件。这对于动态生成的元素特别有效。
<!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,浏览器对象模型)提供了与浏览器窗口及功能交互的一系列对象。
-
window对象:==BOM的核心对象,代表浏览器窗口,也是ECMAScript的全局对象==。它包含了所有全局变量和函数,并且是其他BOM对象的宿主。
-
document对象:代表当前页面的HTML文档,是window对象的一个属性。通过它可以访问和操作页面中的所有元素。
-
location对象:提供了当前页面URL的信息,并且可以使用它来导航到新的URL。
-
navigator对象:提供了关于浏览器的信息,如名称、版本和用户代理字符串。
-
screen对象:提供了关于用户屏幕的信息,如可用宽度、高度、色彩深度等。
下面的代码示例展示了如何使用这些对象来获取和显示一些基本信息:
<!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
事件
当整个页面(包括所有依赖资源如图片、样式表、脚本等)加载完成时触发。常用于执行那些需要确保所有页面资源都已加载完毕的代码,比如初始化脚本、计算布局等。
<!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. onbeforeunload
和 onunload
事件
onbeforeunload
:在窗口、文档或资源即将卸载前触发,常用于提示用户确认是否离开页面(例如,当表单数据未保存时)。onunload
:在窗口、文档完全卸载后触发,适合执行清理工作,但由于兼容性和性能问题,此事件使用较少。
<!DOCTYPE html>
<html>
<head>
<title>OnBeforeUnload 示例</title>
<script>
window.onbeforeunload = function() {
return "你确定要离开吗?你可能有未保存的数据。";
};
</script>
</head>
<body>
<h1>请填写表单</h1>
<!-- 表单内容省略 -->
</body>
</html>
3. onscroll
事件
当元素的滚动条位置发生变化时触发。常用于实现滚动监听,如无限滚动加载、固定导航栏等效果。
<!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
事件
当浏览器窗口被调整大小时触发。可用于响应式设计,调整页面布局或元素尺寸。
<!DOCTYPE html>
<html>
<head>
<title>OnResize 示例</title>
<script>
window.onresize = function() {
console.log("窗口大小改变了...");
// 这里可以添加调整窗口大小时的响应逻辑
};
</script>
</head>
<body>
<h1>调整窗口大小试试看</h1>
</body>
</html>
移动端触屏事件
移动端触屏事件(Touch Events)是专为触摸屏设备设计的一组事件,允许开发者捕获和响应用户的触摸交互,如轻触、滑动、长按等。
在实现拖动功能时,主要涉及到三个核心的触屏事件:touchstart
、touchmove
、touchend
,有时还会用到touchcancel
。
-
touchstart: 当用户的手指首次触摸屏幕时触发。这通常是拖动操作开始的信号。
-
touchmove: 当用户在屏幕上移动已放置的手指时触发。这个事件会在手指移动的过程中持续触发,可以用来跟踪手指的移动轨迹。
-
touchend: 当用户的手指从屏幕上抬起时触发。这标志着拖动操作的结束。
-
touchcancel: 当系统取消了触摸过程,如来电、系统对话框弹出等情况导致的触摸中断时触发。
实现拖动的三个步骤:
-
初始化: 在
touchstart
事件中记录初始触点的位置,并设置标志表示拖动已经开始。 -
跟踪移动: 在
touchmove
事件中计算手指移动的距离,并根据这个距离更新元素的位置,实现视觉上的拖动效果。 -
结束: 在
touchend
事件中清除拖动标志,可以做一些收尾工作,比如限制元素移动范围、保持一定的动画效果等。
<div>
元素:
<!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>
元素,并为其添加了touchstart
、touchmove
、touchend
事件监听器。当用户触摸并开始移动这个<div>
时,它会跟随手指移动,实现拖动效果。注意,这里使用了CSS的transform
属性来改变元素的位置,而不是直接修改left
和top
,因为transform
更高效且不会引发页面重排。
BOM定时器
BOM中的定时器功能允许开发者在特定的时间间隔之后执行代码,这对于创建动态和交互式的Web应用程序非常有用。主要通过两个函数来实现:setTimeout
和 setInterval
。
1. setTimeout
setTimeout
函数用于在指定的时间(以毫秒为单位)之后调用一次指定的函数或者执行一段代码。它返回一个ID,可以用来取消这个定时器(通过clearTimeout
函数)。
var timeoutID = setTimeout(function|code, delay[, arg1, arg2, ...]);
function|code
:要执行的函数或者一段代码(字符串形式)。delay
:延迟时间,单位为毫秒。arg1, arg2, ...
:传递给函数的参数(如果function|code
是函数的话)。
function sayHello() {
console.log("你好,世界!");
}
// 在2秒后执行sayHello函数
var timeoutID = setTimeout(sayHello, 2000);
2. setInterval
setInterval
函数用于按照指定的周期(以毫秒为单位)重复调用函数或者执行代码段。与setTimeout
不同,它会持续执行,直到被显式地清除(通过clearInterval
函数)。
var intervalID = setInterval(function|code, interval[, arg1, arg2, ...]);
function|code
、interval
、arg1, arg2, ...
的含义同上。
function showTime() {
var now = new Date();
console.log(now.toLocaleTimeString());
}
// 每隔1秒执行一次showTime函数
var intervalID = setInterval(showTime, 1000);
清除定时器:
- 使用
clearTimeout(timeoutID)
来取消由setTimeout
设置的定时器。 - 使用
clearInterval(intervalID)
来取消由setInterval
设置的定时器。
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
。
console.log(this === window); // true,非严格模式下
2. 函数调用中的this
- 在普通函数调用中,如果函数不是作为某个对象的方法被调用,
this
同样会指向全局对象(非严格模式)或undefined
(严格模式)。 - 如果函数是作为某个对象的方法调用,
this
则指向该对象。
var obj = {
func: function() {
console.log(this === obj); // true
}
};
obj.func(); // 方法调用,this指向obj
3. 构造函数中的this
当使用new
关键字调用构造函数时,this
指向新创建的实例对象。
function Person(name) {
this.name = name;
console.log(this); // 新创建的Person实例
}
var person = new Person("Alice");
4. DOM事件处理函数中的this
在为DOM元素绑定事件处理器时,无论使用匿名函数还是命名函数,this
通常指向触发事件的DOM元素本身。
<button id="myButton">点击我</button>
<script>
document.getElementById("myButton").addEventListener("click", function() {
console.log(this === document.getElementById("myButton")); // true
});
</script>
5. setTimeout
和setInterval
中的this
在使用setTimeout
或setInterval
时,由于它们是在全局上下文中执行回调函数,非箭头函数情况下,this
通常会指向全局对象。使用箭头函数可以维持外层的this
值。
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的)的最近父元素的左上角位置。这包括了任何边距、边框和滚动的距离。主要通过offsetTop
、offsetLeft
、offsetWidth
和offsetHeight
四个属性获取。
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)是指元素内部没有被滚动条隐藏的部分,即用户当前可见的部分的尺寸。主要通过clientTop
、clientLeft
、clientWidth
和clientHeight
属性获取。
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)反映了元素滚动条的位置以及可滚动区域的尺寸。主要通过scrollTop
、scrollLeft
、scrollWidth
和scrollHeight
属性获取。
scrollTop
/scrollLeft
: 获取或设置元素在垂直/水平方向上被卷起的像素数。scrollWidth
/scrollHeight
: 获取元素内容总宽度/高度,包括不可见的部分(可能由于滚动而未显示)。
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-sizing
、overflow
等)的影响,因此在使用时需要考虑样式上下文。