# 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 中的父子兄访问关系:

这里我们要重点知道parentNodechildren这两个属性的用法。下面分别介绍。

# 获取父节点

调用者就是节点。一个节点只有一个父节点,调用方式就是

节点.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)

  1. 获取元素

    document.querySelector("selector") html5 新选择器,参数是 css 选择器参数,选择选中的第一个

    document.querySelectorAll("selector") 选择多个

  2. 类名操作

  • Node.classList.add('class') 添加 class
  • Node.classList.remove('class') 移除 class
  • Node.classList.toggle('class') 切换 class,有则移除,无则添加
  • Node.classList.contains('class') 检测是否存在 class 非常好用 但是出现的太晚了 。。。
  1. 自定义属性

    在 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');
}

capture.jpg

# 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>
上次更新: 9/2/2019, 5:09:05 PM