# DOM
# JavaScript 的组成
JavaScript 基础分为三个部分:
ECMAScript:JavaScript 的语法标准。包括变量、表达式、运算符、函数、if 语句、for 语句等。
DOM:文档对象模型,操作网页上的元素的 API。比如让盒子移动、变色、轮播图等。
BOM:浏览器对象模型,操作浏览器部分功能的 API。比如让浏览器自动滚动。
# 事件
JS 是以事件驱动为核心的一门语言。
# 事件的三要素
事件的三要素:事件源、事件、事件驱动程序。
比如,我用手去按开关,灯亮了。这件事情里,事件源是:开关。事件是:按开关。事件驱动程序是:灯的开和关。
再比如,网页上弹出一个广告,我点击右上角的X
,广告就关闭了。这件事情里,事件源是:X
。事件是:onclick。事件驱动程序是:广告关闭了。
于是我们可以总结出:谁引发的后续事件,谁就是事件源。
总结如下:
事件源:引发后续事件的 html 标签。
事件:js 已经定义好了(见下图)。
事件驱动程序:对样式和 html 的操作。也就是 DOM。
代码书写步骤如下:(重要)
(1)获取事件源:document.getElementById(“box”);
(2)绑定事件: 事件源 box.事件 onclick = function(){ 事件驱动程序 };
(3)书写事件驱动程序:关于 DOM 的操作。
最简单的代码举例:(点击 box1,然后弹框)
<body>
<div id="box1"></div>
<script type="text/javascript">
// 1、获取事件源
var div = document.getElementById("box1");
// 2、绑定事件
div.onclick = function() {
// 3、书写事件驱动程序
alert("我是弹出的内容");
};
</script>
</body>
常见的事件如下:
属性 | 当一下情况发生时,出现此事件 |
---|---|
onabort | 图像加载被中断 |
onblur | 元素失去焦点 |
onchange | 用户改变域的内容 |
onclick | 鼠标点击某个对象 |
ondblclick | 鼠标双击某个对象 |
onerror | 当加载文档或图像时发生某个错误 |
onfocus | 元素获得焦点 |
onkeydown | 某个键盘的键被按下 |
onkeypress | 某个键盘的键被按下或按住 |
onkeyup | 某个键盘的键被松开 |
onload | 某个页面或图像被完成加载 |
onmousedown | 某个鼠标按键被按下 |
onmousemove | 鼠标被移动 |
onmouseout | 鼠标从某元素移开 |
onmouseover | 鼠标被移到某元素之上 |
onmouseup | 某个鼠标按键被松开 |
onreset | 重置按钮被点击 |
onresize | 窗口或框架被调整尺寸 |
onselect | 文本被选定 |
onsubmit | 提交按钮被点击 |
onunload | 用户退出页面 |
下面针对这事件的三要素,进行分别介绍。
# 获取事件源的方式(DOM 节点的获取)
获取事件源的常见方式如下:
var div1 = document.getElementById("box1"); //方式一:通过id获取单个标签
var arr1 = document.getElementsByTagName("div"); //方式二:通过 标签名 获得 标签数组,所以有s
var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获得 标签数组,所以有s
# 绑定事件的方式
方式一:直接绑定匿名函数
<div id="box1"></div>
<script type="text/javascript">
var div1 = document.getElementById("box1");
//绑定事件的第一种方式
div1.onclick = function() {
alert("我是弹出的内容");
};
</script>
方式二:行内绑定
<!--行内绑定-->
<div id="box1" onclick="fn()"></div>
<script type="text/javascript">
function fn() {
alert("我是弹出的内容");
}
</script>
注意第一行代码,绑定时,是写的"fn()"
,不是写的"fn"
。因为绑定的这段代码不是写在 js 代码里的,而是被识别成了字符串。
# 事件驱动程序
我们在上面是拿 alert 举例,不仅如此,我们还可以操作标签的属性和样式。举例如下:
点击鼠标时,原本粉色的 div 变大了,背景变红:
<script type="text/javascript">
var div1 = document.getElementById("box1");
//点击鼠标时,原本粉色的div变大了,背景变红了
div1.onclick = function() {
div1.style.width = "200px"; //属性值要写引号
div1.style.height = "200px";
div1.style.backgroundColor = "red"; //属性名是backgroundColor,不是background-color
};
</script>
上方代码的注意事项:
- 在 js 里写属性值时,要用引号
- 在 js 里写属性名时,是
backgroundColor
,不是 CSS 里面的background-color
。
# onload 事件
onload 事件比较特殊,这里单独讲一下。
当页面加载(文本和图片)完毕的时候,触发 onload 事件。
# DOM 的介绍
# 什么是 DOM
DOM:Document Object Model,文档对象模型。DOM 为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构。目的其实就是为了能让 js 操作 html 元素而制定的一个规范。
DOM 就是由节点组成的。
# 解析过程
HTML 加载完毕,渲染引擎会在内存中把 HTML 文档,生成一个 DOM 树,getElementById 是获取内中 DOM 上的元素节点。然后操作的时候修改的是该元素的属性。
# DOM 树(一切都是节点)
DOM 的数据结构如下:
上图可知,在 HTML 当中,一切都是节点:(非常重要)
元素节点:HMTL 标签。
文本节点:标签中的文字(比如标签之间的空格、换行)
属性节点::标签的属性。
整个 html 文档就是一个文档节点。所有的节点都是 Object。
# DOM 可以做什么
找对象(元素节点)
设置元素的属性值
设置元素的样式
动态创建和删除元素
事件的触发响应:事件源、事件、事件的驱动程序
# DOM 节点的获取
DOM 节点的获取方式其实就是获取事件源的方式,在上一段已经讲到。这里再重复一下。
操作元素节点,必须首先找到该节点。有三种方式可以获取 DOM 节点:
var div1 = document.getElementById("box1"); //方式一:通过id获取单个标签
var arr1 = document.getElementsByTagName("div"); //方式二:通过 标签名 获得 标签数组,所以有s
var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获得 标签数组,所以有s
既然方式二、方式三获取的是标签数组,那么习惯性是先遍历之后再使用。
特殊情况:数组中的值只有 1 个。即便如此,这一个值也是包在数组里的。这个值的获取方式如下:
document.getElementsByTagName("div1")[0]; //取数组中的第一个元素
document.getElementsByClassName("hehe")[0]; //取数组中的第一个元素
# DOM 访问关系的获取
DOM 的节点并不是孤立的,因此可以通过 DOM 节点之间的相对关系对它们进行访问。如下:
节点的访问关系,是以属性的方式存在的。
JS 中的父子兄访问关系:
这里我们要重点知道parentNode和children这两个属性的用法。下面分别介绍。
# 获取父节点
调用者就是节点。一个节点只有一个父节点,调用方式就是
节点.parentNode;
# 获取兄弟节点
1、下一个节点 | 下一个元素节点:
Sibling 的中文是兄弟。
(1)nextSibling:
火狐、谷歌、IE9+版本:都指的是下一个节点(包括标签、空文档和换行节点)。
IE678 版本:指下一个元素节点(标签)。
(2)nextElementSibling:
- 火狐、谷歌、IE9+版本:都指的是下一个元素节点(标签)。
总结:为了获取下一个元素节点,我们可以这样做:在 IE678 中用 nextSibling,在火狐谷歌 IE9+以后用 nextElementSibling,于是,综合这两个属性,可以这样写:
下一个兄弟节点 = 节点.nextElementSibling || 节点.nextSibling;
2、前一个节点 | 前一个元素节点:
previous 的中文是:前一个。
(1)previousSibling:
火狐、谷歌、IE9+版本:都指的是前一个节点(包括标签、空文档和换行节点)。
IE678 版本:指前一个元素节点(标签)。
(2)previousElementSibling:
- 火狐、谷歌、IE9+版本:都指的是前一个元素节点(标签)。
总结:为了获取前一个元素节点,我们可以这样做:在 IE678 中用 previousSibling,在火狐谷歌 IE9+以后用 previousElementSibling,于是,综合这两个属性,可以这样写:
前一个兄弟节点 = 节点.previousElementSibling || 节点.previousSibling;
# 获取单个的子节点
1、第一个子节点 | 第一个子元素节点:
(1)firstChild:
火狐、谷歌、IE9+版本:都指的是第一个子节点(包括标签、空文档和换行节点)。
IE678 版本:指第一个子元素节点(标签)。
(2)firstElementChild:
- 火狐、谷歌、IE9+版本:都指的是第一个子元素节点(标签)。
总结:为了获取第一个子元素节点,我们可以这样做:在 IE678 中用 firstChild,在火狐谷歌 IE9+以后用 firstElementChild,于是,综合这两个属性,可以这样写:
第一个子元素节点 = 节点.firstElementChild || 节点.firstChild;
2、最后一个子节点 | 最后一个子元素节点:
(1)lastChild:
火狐、谷歌、IE9+版本:都指的是最后一个子节点(包括标签、空文档和换行节点)。
IE678 版本:指最后一个子元素节点(标签)。
(2)lastElementChild:
- 火狐、谷歌、IE9+版本:都指的是最后一个子元素节点(标签)。
总结:为了获取最后一个子元素节点,我们可以这样做:在 IE678 中用 lastChild,在火狐谷歌 IE9+以后用 lastElementChild,于是,综合这两个属性,可以这样写:
最后一个子元素节点 = 节点.lastElementChild || 节点.lastChild;
# 获取所有的子节点
(1)childNodes:标准属性。返回的是指定元素的子节点的集合(包括元素节点、所有属性、文本节点)。是 W3C 的亲儿子。
- 火狐 谷歌等高本版会把换行也看做是子节点。
用法:
子节点数组 = 父节点.childNodes; //获取所有节点。
(2)children:非标准属性。返回的是指定元素的子元素节点的集合。【重要】
- 它只返回 HTML 节点,甚至不返回文本节点。
- 在 IE6/7/8 中包含注释节点(在 IE678 中,注释节点不要写在里面)。
用法:(用的最多)
子节点数组 = 父节点.children; //获取所有节点。用的最多。
兼容问题:
// 自己封装方法 实现 获取元素的 素有子元素节点
function myChildren(el) {
var children = el.children;
var myChildrenArr = [];
console.log(children);
for (var i = 0; i < children.length; i++) {
// 如何 判断 他是 注释节点 还是 元素几点
// nodeType = 8 注释节点
// nodeType = 1 元素节点
// nodeType = 3 是文本节点
if (children[i].nodeType === 1) {
myChildrenArr.push(children[i]);
}
}
return myChildrenArr;
}
var ulElChildren = myChildren(ulEl);
# nodeType 属性
这里讲一下 nodeType 属性。
nodeType == 1 表示的是元素节点(标签) 。记住:元素就是标签。
nodeType == 2 表示是属性节点。
nodeType == 3 是文本节点。
nodeType == 8 注释节点
# 获取除自己以外的所有兄弟节点
function getSiblings(el) {
var siblings = [];
var pNode = el.parentNode;
var children = pNode.children;
for (var i = 0; i < children.length; i++) {
// 兼容ie8
if (children[i].nodeType === 1) {
// 不是el再push
if (children[i] !== el) {
siblings.push(children[i]);
}
}
}
return siblings;
}
# DOM 节点的操作(重要)
上一段的内容:节点的访问关系都是属性。
本段的内容:节点的操作都是函数(方法)。
# 创建节点
格式如下:
新的标签(元素节点) = document.createElement("标签名");
比如,如果我们想创建一个 li 标签,或者是创建一个不存在的 adbc 标签,可以这样做:
<script type="text/javascript">
var a1 = document.createElement("li"); //创建一个li标签
var a2 = document.createElement("adbc"); //创建一个不存在的标签
console.log(a1);
console.log(a2);
console.log(typeof a1);
console.log(typeof a2);
</script>
打印结果:
# 插入节点
插入节点有两种方式,它们的含义是不同的。
方式 1:
父节点.appendChild(新的子节点);
解释:父节点的最后插入一个新的子节点。
方式 2:
父节点.insertBefore(新的子节点, 作为参考的子节点);
解释:
- 在参考节点前插入一个新的节点。
- 如果参考节点为 null,那么他将在父节点里面的最后插入一个子节点。
btn.onclick = function() {
box.appendChild(imgEl);
// 同一个节点 只能 添加一次
// box.appendChild(imgEl);
// box.appendChild(pEl);
// 使用方法:父节点.insertBefore(要插入的节点,参考节点);
box.insertBefore(pEl, imgEl);
// 如果参考节点为null,那么他将在节点最后插入一个节点。
};
# 删除节点
格式如下:
父节点.removeChild(子节点);
解释:用父节点删除子节点。必须要指定是删除哪个子节点。
如果我想删除自己这个节点,可以这么做:
node1.parentNode.removeChild(node1);
# 复制节点(克隆节点)
格式如下:
要复制的节点.cloneNode(); //括号里不带参数和带参数false,效果是一样的。
要复制的节点.cloneNode(true);
括号里带不带参数,效果是不同的。解释如下:
不带参数/带参数 false:只复制节点本身,不复制子节点。
带参数 true:既复制节点本身,也复制其所有的子节点。
# 节点的属性操作
我们可以获取节点的属性值、设置节点的属性值、删除节点的属性。
我们就统一拿下面这个标签来举例:
<img src="images/1.jpg" class="image-box" title="美女图片" alt="地铁一瞥" id="a1" />
下面分别介绍。
# 1、获取节点的属性值
方式 1:
元素节点.属性;
元素节点[属性];
方式 2:
元素节点.getAttribute("属性名称");
举例:
console.log(myNode.getAttribute("src"));
console.log(myNode.getAttribute("class")); //注意是class,不是className
console.log(myNode.getAttribute("title"));
# 2、设置节点的属性值
方式 1 举例:(设置节点的属性值)
myNode.src = "images/2.jpg"; //修改src的属性值
myNode.className = "image2-box"; //修改class的name
方式 2:
元素节点.setAttribute(属性名, 新的属性值);
方式 2 举例:(设置节点的属性值)
myNode.setAttribute("src", "images/3.jpg");
myNode.setAttribute("class", "image3-box");
myNode.setAttribute("id", "你好");
# 3、删除节点的属性
格式:
元素节点.removeAttribute(属性名);
举例:(删除节点的属性)
myNode.removeAttribute("class");
myNode.removeAttribute("id");
总结:
获取节点的属性值和设置节点的属性值,都有两种方式,但这两种方式是有区别的。
方式一的
元素节点.属性
和元素节点[属性]
:绑定的属性值不会出现在标签上。方式二的
get/set/removeAttribute
: 绑定的属性值会出现在标签上。
这其实很好理解,方式一操作的是属性而已,方式二操作的是标签本身。
# 节点内容操作
DOM 对象的属性和 HTML 的标签属性几乎是一致的。例如:src、title、className、href 等。
# innerHTML 和 innerText 的区别
value:标签的 value 属性。
innerHTML:双闭合标签里面的内容(识别标签)。
innerText:双闭合标签里面的内容(不识别标签)。(老版本的火狐用 textContent)
获取内容举例:
如果我们想获取 innerHTML 和 innerText 里的内容,看看会如何:(innerHTML 会获取到标签本身,而 innerText 则不会)
修改内容举例:(innerHTML 会修改标签本身,而 innerText 则不会)
btn.onclick = function() {
// inputEl.value = '不凡学院';
// 修改表单的值
inputEl.value = Math.floor(Math.random() * 10);
};
btn2.onclick = function() {
// 插入文本内容
boxEl.innerText = "今天天气很热";
boxEl.innerText = "<p></p>"; // 当做了文本处理,并没有解析成标签
// 每一次innerText 都会替换掉box里边 之前的所有内容
};
btn3.onclick = function() {
boxEl.innerHTML = "<p>我是一个p标签</p>";
//解析成了 标签
// 每一次innerHTML 都会 替换掉 掉 box 里边之前的所有内容
// 可以动态地生成页面
};
# DOM 扩展 (H5)
获取元素
document.querySelector("selector")
html5 新选择器,参数是 css 选择器参数,选择选中的第一个document.querySelectorAll("selector")
选择多个类名操作
Node.classList.add('class')
添加 classNode.classList.remove('class')
移除 classNode.classList.toggle('class')
切换 class,有则移除,无则添加Node.classList.contains('class')
检测是否存在 class 非常好用 但是出现的太晚了 。。。
自定义属性
在 HTML5 中我们可以自定义属性,其格式如下
data-*=""
,例如:
data-info="我是自定义属性"
,通过Node.dataset['info']
我们便可以获取到自定义的属性值。
Node.dataset
是以类对象形式存在的
当我们如下格式设置时,则需要以小驼峰格式才能正确获取
data-my-name="mm"
,获取Node.dataset['myName']
# 案例练习
二维码显示和隐藏
隔行变色和高亮显示
搜索文本框
相册走廊
全选反选
闪现轮播
# JS 补充
# 立即执行函数的应用
- 循环绑定事件时,解决如何获取当前点击元素的下标。
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = (function(n) {
return function() {
console.log(n);
};
})(i);
}
# 事件监听器
# addEventListener
addEventListener 是 W3C DOM 规范中提供的注册事件监听器的方法。它的优点包括:
它允许给一个事件注册多个 listener。
它提供了一种更精细的手段控制 listener 的触发阶段。(即可以选择捕获或者冒泡)。
它对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。
# addEventListener 使用
- 语法:target.addEventListener(type,listener,useCapture]);
- target: 文档节点、document、window 或 XMLHttpRequest。 (事件源)
- type: 字符串,事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等。
- listener :实现了 EventListener 接口或者是 JavaScript 中的函数。
- useCapture :是否使用捕获,一般用 false。true 代表捕获,false 代表冒泡。
d1.addEventListener('click',function(){
alert('d1);
},true)
d2.addEventListener('click',foo,false)
function foo(){
alert('d2');
}
# removeEventListener 移除绑定
- 如果同一个监听事件分别为“事件捕获”和“事件冒泡”注册了一次,一共两次,这两次事件需要分别移除。两者不会互相干扰。
- 移除的事件必须为外部事件(外部封装的函数)。
- 总结来讲,就是移除时,必须和绑定时一一对应。
d2.addEventListener("click", foo, false);
d2.removeEventListener("click", foo, false);
function foo() {
alert("d2");
}
# IE8 以下兼容问题
- target.attachEvent(type, listener);
- target.detachEvent(type,listener);
/**
* 兼容IE8和标准浏览器
* el 绑定元素
* type 事件类型,IE8要加on
* func 执行方法
**/
function myAddEventListener(el, type, func) {
// attachEvent 是IE 专有的方法
if (el.attachEvent) {
el.attachEvent("on" + type, func);
} else {
el.addEventListener(type, func);
}
}
# 定时器
- setTimeout(); 延迟执行
- setInterval(); 循环执行
- clearTimeout(); 清除延迟执行的定时器
- clearInterval(); 清除循环执行的定时器
# 使用
setTimeout(function() {
console.log("延迟了5s才执行");
}, 5000);
var timer = setInterval(function() {
console.log("每隔1s执行一次");
}, 1000);
// 清除定时器时 需要给定时器命名(用一个变量接受设置的定时器)
btn.onclick = function() {
clearInterval(timer);
};
# 倒计时案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
h1 {
width: 250px;
margin: 100px auto 50px auto;
}
.item {
width: 500px;
height: 50px;
margin: 0 auto;
text-align: center;
font-size: 30px;
color: orange;
}
strong {
background-color: orange;
padding: 0 10px;
color: #fff;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>距离光棍节,还有</h1>
<div class="item">
<span><span class="day">00</span>天</span>
<strong><span class="hour">00</span>时</strong>
<strong><span class="min">00</span>分</strong>
<strong><span class="second">00</span>秒</strong>
</div>
<script>
var endTime = new Date("2019-11-11");
var dayEl = document.querySelector(".day");
var hourEl = document.querySelector(".hour");
var minEl = document.querySelector(".min");
var secondEl = document.querySelector(".second");
setInterval(function() {
// 获取当前时间
var nowTime = new Date();
var cha = endTime - nowTime; // 获取相差的毫秒数
// console.log(cha);
// 转换 天 时 分 秒
var DAY_MS = 1000 * 60 * 60 * 24;
var HOUR_MS = 1000 * 60 * 60;
var MIN_MS = 1000 * 60;
var SECOND_MS = 1000;
var day = Math.floor(cha / DAY_MS);
console.log(day);
var hour = Math.floor((cha % DAY_MS) / HOUR_MS);
var min = Math.floor((cha % HOUR_MS) / MIN_MS);
var second = Math.floor((cha % MIN_MS) / SECOND_MS);
dayEl.innerText = wrap(day);
hourEl.innerText = wrap(hour);
minEl.innerText = wrap(min);
secondEl.innerText = wrap(second);
}, 1000);
function wrap(num) {
return num < 10 ? "0" + num : num;
}
</script>
</body>
</html>