avatar


2.DOM和BOM

在上一章,我们说JavaScript这门语言,分为三部分:

  1. ECMAScript(JavaScript语法)
  2. DOM(页面文档对象类型)
  3. BOM(浏览器对象模型)

ECMAScript,JavaScript的语法和基础核心,是所有浏览器厂商共同遵守的一套JavaScript语法工业标准。
DOM,Document Object Model,文档对象模型,通过DOM可以对页面上的各种元素进行操作(大小、位置、颜色等)。
BOM,Browser Object Model,浏览器对象模型,通过BOM可以操作浏览器窗口,比如弹出框、控制浏览器跳转、获取分辨率等。

上一章,我们主要讨论了"ECMAScript",这一章,我们主要讨论"DOM"和"BOM"。

DOM简介

DOM,Document Object Model,文档对象模型。
是什么文档?
当然是HTML文档。
结构是怎么样的?

我们可以打开一个网页看一下。

文档对象模型

最顶端是HTML,然后HTML里面是head和body,body里面还有各种子节点。

就像这样

DOM树

这就是DOM树。

  • 文档:一个页面就是一个文档,DOM中使用document表示
  • 元素:页面中的所有标签都是元素,DOM中使用element表示
  • 节点:网页中的所有内容都是节点(标签、属性、文本、注释等),DOM中使用node表示

DOM把上述内容都看做是对象

获取元素

如何获取页面元素

那么,我们怎么获取页面元素呢?
什么是元素?页面中的所有标签都是元素。

我们知道,在一个HTML文档中,我们可以为每一个标签设置设置ID,所以可以根据ID获取?
然后每一个标签还有标签名,所以我们还可以根据标签名获取?
然后每一个标签还可能有class属性,所以还可以根据class属性获取?
这几种都对。

获取页面中的元素,有以下几种方式:

  1. 根据ID获取
  2. 根据标签名获取
  3. 通过HTML5新方法获取
  4. document对象的属性

我们依次来看看。

根据ID获取

getElementById(),根据ID获取元素对象。

示例代码:

1
document.getElementById("fullScreen")

运行结果:

1
<button id=​"fullScreen" class=​"dplayer-icon dplayer-full-icon" data-balloon=​"全屏" data-balloon-pos=​"up">​…​</button>​

获取属性和方法

还可以通过console.dir(),获取对象的属性和方法。

示例代码:

1
console.dir(document.getElementById("fullScreen"))

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
button#fullScreen.dplayer-icon.dplayer-full-icon
accessKey: ""
ariaAtomic: null
ariaAutoComplete: null
ariaBusy: null
ariaChecked: null
ariaColCount: null
ariaColIndex: null
ariaColSpan: null
ariaCurrent: null

【部分运行结果略】

textContent: "\n \n "
title: ""
translate: true
type: "submit"
validationMessage: ""
validity: ValidityState {valueMissing: false, typeMismatch: false, patternMismatch: false, tooLong: false, tooShort: false, …}
value: ""
virtualKeyboardPolicy: ""
willValidate: true
[[Prototype]]: HTMLButtonElement

根据标签名获取

getElementsByTagName(),根据标签名获取元素的对象集合。

为什么是集合?
因为在HTML中,id不能重复,但是标签名完全可以重复。

示例代码:

1
document.getElementsByTagName("style")

运行结果:

1
HTMLCollection(6) [style, style, style, style, style, style]

示例代码:

1
document.getElementsByTagName("kakawanyifan")

运行结果:

1
HTMLCollection []

特别的,还可以获取某个元素内部所有指定标签名的子元素.

示例代码:

1
2
element = document.getElementById("article-container")
element.getElementsByTagName("style")

运行结果:

1
HTMLCollection(3) [style, style, style]

根据name获取

document.getElementsByName(),根据"name"获取。
与id不一样,"name"属性的值不要求是唯一的,多个元素可以有同样的名字,如表单中的单选框和复选框。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head></head>
<body>
<p>请选择你最喜欢的水果(多选)</p>
<label><input type="checkbox" name="fruit" value="苹果">苹果</label>
<label><input type="checkbox" name="fruit" value="香蕉">香蕉</label>
<label><input type="checkbox" name="fruit" value="西瓜">西瓜</label>
<script>
var fruits = document.getElementsByName('fruit');
fruits[0].checked = true;
</script>
</body>
</html>

HTML

通过HTML5新方法获取

HTML5提供了一些新方法,帮助我们获取元素。

通过类名获取

document.getElementsByClassName(),通过类名获取元素的集合。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head></head>
<body>
<span class="one">英语</span> <span class="two">数学</span>
<span class="one">语文</span> <span class="two">物理</span>
<script>
var Ospan1 = document.getElementsByClassName('one');
var Ospan2 = document.getElementsByClassName('two');
Ospan1[0].style.fontWeight = 'bold';
Ospan2[1].style.background = 'red';
</script>
</body>
</html>

通过类名获取

通过选择器获取

querySelector(),返回通过选择器获取到的第一个元素对象。
querySelectorAll(),返回通过选择器获取到的所有元素对象集合。

注意,querySelectorquerySelectorAll,根据标签获取元素的时候,不需要加前缀符号;但是根据id和class获取元素的时候,需要加前缀符号。

  • id,前缀符号:#
  • class,前缀符号:.

document对象的属性

还有一些属性,属于document文档对象的属性,可以直接通过document对象获取。

属性 说明
document.body 文档的body元素
document.title 文档的title元素
document.documentElement 文档的html元素
document.forms 对文档中所有Form对象引用
document.images 文档中所有Image对象引用

示例代码:

1
console.log(document.images)

运行结果:

1
HTMLCollection(22) [img.avatar-img, img.post_bg.ls-is-cached.lazyloaded, img.post_bg.ls-is-cached.lazyloaded, img.post_bg.ls-is-cached.lazyloaded, img.post_bg.ls-is-cached.lazyloaded, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.post_bg.lazyload, img.avatar-img]

操作元素

通过上文的讨论,我们已经知道了如何获取元素。
接下来,我们讨论如何操作元素。

操作元素内容

三种方法

属性 说明
element.innerHTML 元素开始和结束标签之间的HTML,包括HTML标签,同时保留空格和换行
element.innerText 元素的文本内容,在返回的时候会去除HTML标签和多余的空格、换行,在设置的时候会进行特殊字符转义
element.textContent 节点的文本内容,同时保留空格和换行

接下来通过案例进行演示。
分别利用innerHTML、innerText、textContent属性在控制台输出一段HTML文本,示例代码如下。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head></head>
<body>
<div id="box">
The first paragraph...
<p>
The second paragraph...
<a href="http://www.example.com">third</a>
</p>
</div>
</body>
</html>
1
2
3
4
var oBox = document.getElementById('box')
oBox.innerHTML
oBox.innerText
oBox.textContent

运行结果:

1
2
3
'\n            The first paragraph...\n            <p>\n                The second paragraph...\n                <a href="http://www.example.com">third</a>\n            </p>\n        '
'The first paragraph...\n\nThe second paragraph... third'
'\n The first paragraph...\n \n The second paragraph...\n third\n \n '

兼容性问题

innerText是非标准的方法,存在兼容性问题。
innerHTML是标准方法,不存在兼容性问题。

建议:innerHTML

操作元素属性

在上文我们讨论过,DOM把每一个节点(元素)都看做对象。
我们怎么访问对象的属性?
对象.属性即可。

示例代码:

1
2
3
4
5
var imgDemo = document.getElementById('imgDemo')
imgDemo.src = '图片地址'
imgDemo.title = '图片名称'
var inputDemo = document.getElementsByTagName('input')
inputDemo.value = 'input输入框的值'

操作元素样式

怎么操作元素的样式?
元素的样式由两个属性控制,style属性和className属性。

所以,操作元素样式有两种方法:

  1. 操作style属性
  2. 操作className属性

我们分别讨论。

操作style属性

元素对象.style.样式属性名
样式属性名对应CSS样式名,但需要去掉CSS样式名中的-,并将-后面的英文的首字母大写。
例如,设置字体大小的样式名font-size,对应的样式属性名为fontSize。

常见的style属性操作的样式名如下。

名称 说明
background 元素的背景属性
backgroundColor 元素的背景色
display 元素的显示类型
fontSize 元素的字体大小
width 元素的宽度
height 元素的高度
overflow 如何处理呈现在元素框外面的内容
textAlign 文本的水平对齐方式

示例代码:

1
2
3
4
// 获取元素对象
var ele = document.querySelector('#box');
ele.style.width = '100px';
ele.style.height = '100px';

操作className属性

如果样式修改较多,可以采取操作类名的方式更改元素样式,语法为元素对象.className

示例代码:

1
2
3
4
5
6
7
8
9
<html>

<head></head>

<body>
<div class="first">文本</div>
</body>

</html>
1
2
var t = document.querySelector('div')[0];
t.className = 'change';

属性操作

上文不是已经讨论过了属性操作吗?
为什么这里再讨论?

因为通过element.属性的方式来获取属性值的方法,只对内置的属性值有效。
对于我们自定义的属性的值,需要利用getAttribute('属性')获取指定元素的属性值。
当然,getAttribute('属性'),对于内置属性同样有效。

获取属性

getAttribute

id可以直接用.属性的方法获取,但是index,无法直接用.属性的方法获取。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>

<head></head>

<body>
<div id="demo" index="1" class="nav"></div>
<script>
var div = document.querySelector('div');
console.log(div.id);
console.log(div.getAttribute('id'));
console.log(div.index)
console.log(div.getAttribute('index'));
</script>
</body>

</html>

运行结果:

1
2
3
4
demo
demo
undefined
1

element.dataset.属性

除了上述方法,还可以通过element.dataset.属性element.dataset['属性']的方式获取。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head></head>
<body>
<div getTime="20" data-index="2" data-list-name="andy"></div>
<script>
var div = document.querySelector('div');
// 结果为:2
console.log(div.getAttribute('data-index'));
// 结果为:andy
console.log(div.getAttribute('data-list-name'));
// HTML5新增的获取自定义属性的方法,只能获取“data-”开头的属性
// DOMStringMap {index:"2",listName:"andy"}
console.log(div.dataset);
// 结果为:2
console.log(div.dataset.index);
// 结果为:2
console.log(div.dataset['index']);
// 结果为:andy
console.log(div.dataset.listName);
// 结果为:andy
console.log(div.dataset['listName']); // 结果为:andy
</script>
</body>
</html>

运行结果:

1
2
3
4
5
6
7
2
andy
DOMStringMap {index: '2', listName: 'andy'}
2
2
andy
andy

解释说明:

  1. 该方法只能获取"data-"开头的属性
  2. 如果自定义属性里面包含有多个连字符-时,获取的时候采取驼峰命名法
  3. 该方法存在兼容性问题

设置属性

element.setAttribute('属性', '值'),设置属性。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head></head>
<body>
<div></div>
<script>
var div = document.querySelector('div');
div.id = 'test';
div.className = 'navs';
div.setAttribute('index', 2);
</script>
</body>
</html>

设置属性

与获取属性类似,对于前缀为data-的属性,还可以通过元素对象.dataset.属性名='值'元素对象.dataset[属性名]='值'设置属性。

移除属性

在DOM中使用element.removeAttribute('属性')的方式来移除元素属性。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head></head>
<body>
<div id="test" class="footer" index="2"></div>
<script>
var div = document.querySelector('div');
div.removeAttribute('id');
div.removeAttribute('class');
div.removeAttribute('index');
</script>
</body>
</html>

移除属性

节点操作

节点属性

一个节点,至少拥有三个属性:

  1. nodeType:节点类型
    • 1:元素节点
    • 2:属性节点
    • 3:文本节点
  2. nodeName:节点名称
  3. nodeValue:节点值

节点层级

节点层级关系

假设存在一份HTML文档如下:

示例代码:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<a href="#">链接</a>
<p>段落...</p>
</body>
</html>
  • 根节点:<html>标签是整个文档的根节点,有且仅有一个。
  • 父节点:某一个节点的上级节点,例如,<html><head><body>的父节点。
  • 子节点:某一个节点的下级节点,例如,<head><body>节点是<html>节点的子节点。
  • 兄弟节点:两个节点同属于一个父节点,例如,<head><body>互为兄弟节点。

获取父节点

parentNode属性来获得离当前元素的最近的一个父节点,如果找不到父节点就返回null,语法格式为:obj.parentNode

示例代码:

1
2
3
4
5
6
7
8
9
10
<html>
<head></head>
<body>
<div class="demo">
<div class="box">
<span class="child">span元素</span>
</div>
</div>
</body>
</html>
1
2
3
var child = document.querySelector('.child');
console.log(child);
console.log(child.parentNode);

运行结果:

1
2
<span class=​"child">​span元素​</span>​
<div class=​"box">​…​</div>​

获取子节点

获取子节点有两种方法,childNodeschildren
我们分别讨论。

childNodes

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>

<head></head>

<body>
<ul>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
</body>

</html>
1
2
3
4
5
6
7
var ul = document.querySelector('ul');
var lis = ul.querySelectorAll('li');
console.log(lis);
console.log(ul.childNodes);
console.log(ul.childNodes[0]);
console.log(ul.childNodes[0].nodeType);
console.log(ul.childNodes[1].nodeType);

运行结果:

1
2
3
4
5
6
console.log(ul.childNodes[1].nodeType);
NodeList(4) [li, li, li, li]
NodeList(9) [text, li, text, li, text, li, text, li, text]
#text
3
1

解释说明:childNodes返回子元素节点和子文本节点

children

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>

<head></head>

<body>
<ul>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
</body>

</html>
1
2
3
4
5
6
7
var ul = document.querySelector('ul');
var lis = ul.querySelectorAll('li');
console.log(lis);
console.log(ul.children);
console.log(ul.children[0]);
console.log(ul.children[0].nodeType);
console.log(ul.children[1].nodeType);

运行结果:

1
2
3
4
5
6
console.log(ul.children[1].nodeType);
NodeList(4) [li, li, li, li]
HTMLCollection(4) [li, li, li, li]
<li>​…​</li>​
1
1

解释说明:children只返回子元素节点,其余节点不返回。

比较

  1. 返回节点
    childrenNodes返回子元素节点和子文本节点。
    children只返回子元素节点,其余节点不返回。
  2. 兼容性
    childrenNodes存在兼容性问题,而且在某些浏览器下不返回子文本节点。
    children不存在兼容性问题。

获取第一个子节点和最后一个子节点

firstChildlastChild,可以获取第一个子节点和最后一个子节点。
firstElementChildlastElementChild,获取第一个子元素节点和最后一个子元素节点。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>

<head></head>

<body>
<ul>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
</body>

</html>
1
2
3
var ul = document.querySelector('ul');
console.log(ul.firstChild);
console.log(ul.firstElementChild);

运行结果:

1
2
#text
<li>​…​</li>​

但是,firstElementChildlastElementChild,可能存在兼容性问题,在IE9以上才支持。

虽然该兼容问题可以忽略不计,谁会去兼容IE9以下的的浏览器呢?

如果确实需要兼容,可以考虑这么做

1
2
3
4
// 获取第一个子元素节点
obj.children[0]
// 获取最后一个子元素节点
obj.children[obj.children.length - 1]

获取兄弟节点

nextSibling获取下一个兄弟节点,previousSibling获取上一个兄弟节点。其返回值包含元素节点或者文本节点等。
nextElementSibling获取下一个兄弟元素节点,previousElementSibling获取上一个兄弟元素节点。但是可能存在兼容性问题,在IE9以上才支持。

虽然该兼容问题可以忽略不计,谁会去兼容IE9以下的的浏览器呢?

如果确实要兼容,可以考虑这么做

1
2
3
4
5
6
7
8
9
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if (el.nodeType === 1) {
return el;
}
}
return null;
}

创建节点

document.write()

document.write()
如果页面文档流加载完毕,再调用会导致页面重绘。

element.innerHTML

element.innerHTML
将内容写入某个DOM节点,不会导致页面全部重绘。

document.createElement()

document.createElement()
效率稍微低一点,但是结构更加清晰。

添加和删除节点

appendChild()

appendChild(),将一个节点添加到指定父节点的子节点列表末尾。

insertBefore()

insertBefore(child, 指定元素),将一个节点添加到父节点的指定子节点前面。

removeChild(child)

removeChild(child),移除子节点。

复制节点

node.cloneNode(),返回调用该方法的节点的一个副本。如果括号参数为true,则是深拷贝,即会复制节点本身及里面所有的子节点。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>

<head></head>

<body>

<ul id="myList">
<li>苹果</li>
<li>橙子</li>
<li>橘子</li>
</ul>

<ul id="op"></ul>

<script>
var item = document.getElementById('myList').firstElementChild;
var cloneItem = item.cloneNode(true);
document.getElementById('op').appendChild(cloneItem);
</script>

</body>

</html>

运行结果:
复制节点

事件三要素

事件由事件源、事件类型和事件处理程序组成,称为事件三要素。

  1. 事件源:触发事件的元素。
    谁触发了事件
  2. 事件类型:如click单击事件。
    触发了什么事件
  3. 事件处理程序:事件触发后要执行的代码(函数形式),也称事件处理函数。
    触发事件以后要做什么

事件触发

注册事件

注册事件有两种方式,传统方式和事件监听方式。

传统方式

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>

<head></head>

<body>
<button id="btn">单击</button>
<script>
// 第1步:获取事件源
var btn = document.getElementById('btn');
// 第2步:注册事件btn.onclick
btn.onclick = function () {
// 第3步:添加事件处理程序(采取函数赋值形式)
alert('弹出');
};
</script>
</body>

</html>

事件监听方式

事件监听方式又有两种。

  1. attachEvent
  2. addEventListener

attachEvent

DOM对象.attachEvent(type, callback);

  • type指的是为DOM对象绑定的事件类型,它是由on与事件名称组成的,如onclick。
  • callback表示事件的处理程序。

这种方法主要存在于IE9之前的浏览器。

addEventListener

DOM对象.addEventListener(type, callback, [capture]);

  • type指的是DOM对象绑定的事件类型,它是由事件名称设置的,如click。
  • callback表示事件的处理程序。
  • capture默认值为false,表示在冒泡阶段完成事件处理,将其设置为true时,表示在捕获阶段完成事件处理。

(关于capture,我们在下文讨论事件流的时候看具体区别。)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>

<head></head>

<body>
<button id="btn">单击</button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', function(){
alert('hello')
});
</script>
</body>

</html>

事件流

在浏览器发展历史中,网景(Netscape)的事件流采用事件捕获方式,指的是事件流传播的顺序应该是从DOM树的最上层开始出发一直到发生事件的元素节点;微软(Microsoft)的事件流采用事件冒泡方式,指的是事件流传播的顺序应该是从发生事件的元素节点到DOM树的根节点。

事件流

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>

<head></head>

<body>
<button id="btn">单击</button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', function(e){
console.log('btn')
});
var b = document.body;
b.addEventListener('click', function(e){
console.log('body')
});
</script>
</body>

</html>

运行结果:

1
2
btn
body

解释说明:默认是冒泡。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>

<head></head>

<body>
<button id="btn">单击</button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', function(e){
console.log('btn')
});
var b = document.body;
b.addEventListener('click', function(e){
console.log('body')
},true);
</script>
</body>

</html>

运行结果:

1
2
body
btn

解释说明:设置为true之后,是事件捕获。

删除事件

对应三种注册事件的方式,有三种删除事件的方式。

  1. DOM对象.onclick = null;
  2. DOM对象.detachEvent(type, callback);
  3. DOM对象.removeEventListener(type, callback);

事件对象

什么是事件对象

事件对象是事件一系列相关数据的集合。
例如,鼠标单击的事件对象,包含了鼠标指针坐标等。键盘事件,包含了被按下的键值代码等。

获取事件对象

一般通过DOM对象.事件 = function (event) {}就可以获取事件对象。
但是在IE9之前的浏览器中,只能通过window.event才能获取事件对象,var 事件对象 = window.event

如果一定要兼容IE9之前的浏览器的话,可以采取这种方法。

1
2
3
4
5
btn.onclick = function(e) {
// 获取事件对象的兼容处理
var event = e || window.event;
console.log(event);
};

事件对象的属性和方法

常见的事件对象的属性和方法

属性/方法 说明 浏览器
e.target 触发事件的对象 标准浏览器
e.srcElement 触发事件的对象 IE9之前
e.type 事件的类型 所有浏览器
e.stopPropagation() 阻止事件冒泡 标准浏览器
e.cancelBubble() 阻止事件冒泡 IE9之前
e.preventDefault() 阻止默认事件(例如,链接跳转) 标准浏览器
e.returnValue() 阻止默认事件(例如,链接跳转) IE9之前

我们解释一下,什么是事件冒泡。
当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序。

不阻止冒泡。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>

<head></head>

<body>
<button id="btn">单击</button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', function(e){
console.log('btn')
// e.stopPropagation()
});
var b = document.body;
b.addEventListener('click', function(e){
console.log('body')
});
</script>
</body>

</html>

运行结果:

1
2
btn
body

解释说明:事件冒泡。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>

<head></head>

<body>
<button id="btn">单击</button>
<script>
var btn = document.getElementById('btn');
btn.addEventListener('click', function(e){
console.log('btn')
e.stopPropagation()
});
var b = document.body;
b.addEventListener('click', function(e){
console.log('body')
});
</script>
</body>

</html>

运行结果:

1
btn

解释说明:阻止事件冒泡。

事件委托

事件委托:不给子元素注册事件,给父元素注册事件,让处理代码在父元素的事件中执行。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>

<head></head>

<body>

<ul>
<li>我是第 1 个li</li>
<li>我是第 2 个li</li>
<li>我是第 3 个li</li>
<li>我是第 4 个li</li>
<li>我是第 5 个li</li>
</ul>
<script>
var ul = document.querySelector('ul');
ul.addEventListener('click', function (e) {
e.target.style.backgroundColor = 'pink';
});
</script>
</body>

</html>

解释说明:首先获取到ul父元素,并且给父元素绑定单击事件,进而实现单击子元素li时,给当前项改变背景色。

鼠标事件

常见的鼠标事件

事件名称 事件触发机制
click 单击鼠标左键时触发
focus 获得鼠标指针焦点触发
blur 失去鼠标指针焦点触发
mouseover 鼠标指针经过时触发
mouseout 鼠标指针离开时触发
mousedown 当按下任意鼠标按键时触发
mouseup 当释放任意鼠标按键时触发
mousemove 在元素内当鼠标指针移动时持续触发
contextmenu 鼠标右击
selectstart 鼠标选中

鼠标事件对象的属性

属性 描述
clientX 鼠标指针位于浏览器页面当前窗口可视区的水平坐标(X轴坐标)
clientY 鼠标指针位于浏览器页面当前窗口可视区的垂直坐标(Y轴坐标)
pageX 鼠标指针位于文档的水平坐标(X轴坐标),IE9以下不兼容
pageY 鼠标指针位于文档的垂直坐标(Y轴坐标),IE9以下不兼容
screenX 鼠标指针位于屏幕的水平坐标(X轴坐标)
screenY 鼠标指针位于屏幕的垂直坐标(Y轴坐标)

IE9以下不兼容pageX和pageY属性。
如果一定要去兼容的话,可以考虑这种方式:

1
2
var pageX = event.pageX || event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft);
var pageY = event.pageY || event.clientY + (document.body.scrollTop || document.documentElement.scrollTop);

键盘事件

常见的键盘事件

事件名称 事件触发机制
keypress 某个键盘按键被按下时触发。不识别功能键,如CtrlShift箭头
keydown 某个键盘按键被按下时触发
keyup 某个键盘按键被松开时触发

键盘事件对象的属性

常用的键盘事件对象的属性,只有一个,keyCode,被按下的键的值。

例如:

按键 keyCode
Enter 13
Left Arrow 37
Up Arrow 38
Right Arrow 39
Down Arrow 40

键盘映射表

字母和数字键的键码值(keyCode)
按键键码按键键码按键键码按键键码
A65J74S83149
B66K75T84250
C67L76U85351
D68M77V86452
E69N78W87553
F70O79X88654
G71P80Y89755
H72Q81Z90856
I73R82048957
数字键盘上的键的键码值(keyCode)
按键键码按键键码
0968104
1979105
298*106
399+107
4100Enter108
5101109
6102.110
7103/111
功能键键码值(keyCode)
按键键码按键键码
F1112F7118
F2113F8119
F3114F9120
F4115F10121
F5116F11122
F6117F12123
控制键键码值(keyCode)
按键键码按键键码按键键码按键键码
BackSpace8Esc27Right Arrow39-_189
Tab9Spacebar32Dw Arrow40.>190
Clear12Page Up33Insert45/?191
Enter13Page Down34Delete46`~192
Shift16End35Num Lock144[{219
Control17Home36;:186\220
Alt18Left Arrow37=+187]}221
Cape Lock20Up Arrow38,<188‘“222

触屏事件

常见的触屏事件

事件名称 事件触发机制
touchstart 手指触摸到一个元素时触发
touchmove 手指在一个元素上滑动时触发
touchend 手指从一个元素上移开时触发

触屏事件对象的属性

事件名称 事件触发机制
touches 正在触屏的所有手指的列表
targetTouches 正在触摸当前元素的手指的列表
changedTouches 手指状态发生了改变的列表

元素位置和滚动

offset概述

offset的含义是偏移量,使用offset的相关属性可以动态地获取该元素的位置、大小等。

属性 说明
offsetLeft 返回元素相对其带有定位的父元素左边框的偏移
offsetTop 返回元素相对其带有定位的父元素上方的偏移
offsetWidth 返回自身的宽度(包括padding、边框和内容区域的宽度),不带单位
offsetHeight 返回自身的高度(包括padding、边框和内容区域的高度),不带单位
offsetParent 返回作为该元素带有定位元素的父级元素(如果父级都没有定位则返回body)

offset与style的区别

offset系列和style属性都可以获得元素样式的属性和位置,那么两者有什么区别呢?

  1. 作用范围
    offset可以得到任意样式表中的样式值
    style只能得到行内样式表中的样式值
  2. 单位
    offset系列获得的数值是没有单位的
    style.width获得的是带有单位的字符串
  3. 度量标准
    offsetWidth包含padding、border、width的值
    style.width获得的是不包含padding、border的值
  4. 读写属性
    offsetWidth等属性是只读属性,只能获取不能赋值
    style.width是可读写属性,可以获取也可以赋值

元素可视区

使用client系列的相关属性可以获取元素可视区的相关信息。例如,可以动态地得到元素的边框大小、元素大小等。

属性 说明
clientLeft 返回元素左边框的大小
clientTop 返回元素上边框的大小
clientWidth 返回自身的宽度(包含padding),内容区域的宽度(不含边框)。注意返回数值不带单位
clientHeight 返回自身的高度(包含padding),内容区域的高度(不含边框)。注意返回数值不带单位

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<html>

<head></head>

<body>
<style>
div {
width: 200px;
height: 200px;
background-color: pink;
border: 10px solid red;
}
</style>

<div>
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容
</div>

<script>
var div = document.querySelector('div');
console.log(div.clientHeight);
console.log(div.clientTop);
console.log(div.clientLeft);
</script>
</body>
</html>

运行结果:
元素可视区

元素滚动

scroll的含义是滚动,使用scroll系列的相关属性可以动态地获取该元素的滚动距离、大小等。

属性 说明
scrollLeft 返回被卷去的左侧距离,返回数值不带单位
scrollTop 返回被卷去的上方距离,返回数值不带单位
scrollWidth 返回自身的宽度,不含边框。注意返回数值不带单位
scrollHeight 返回自身的高度,不含边框。注意返回数值不带单位

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html>

<head></head>

<style>
div {
width: 200px;
height: 200px;
background-color: pink;
overflow: auto;
border: 10px solid red;
}
</style>

<body>
<div>
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容我是内容
我是内容我是内容我是内容我是内容我是内容
</div>
<script>
var div = document.querySelector('div');
console.log(div.scrollHeight);
</script>
</body>

</html>

运行结果:
元素滚动

BOM简介

BOM,Brower Object Model,浏览器对象模型,提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象是window。

BOM提供了很多的对象。这些对象用于访问浏览器,被称为浏览器对象。
如图所示。

BOM

  • BOM比DOM更大,它包含DOM(document)。
  • BOM的核心对象是window,其他的对象称为window的子对象
  • 定义在全局作用域中的变量、函数都会变成window对象的属性和方法。

示例代码:

1
2
var num = 10;
console.log(window.num);

运行结果:

1
10

window对象的常见事件

window.onload

window.onload是窗口(页面)加载事件,当文档内容(包括图像、脚本文件、CSS文件等)完全加载完成会触发该事件,调用该事件对应的事件处理函数。

因为JavaScript代码是从上往下依次执行的,如果要在页面加载完成后执行某些代码,又想要把这些代码写到页面任意的地方,可以把代码写到window.onload事件处理函数中。

onload页面加载事件有两种注册方式,分别如下。

  1. window.onload = function () {};
  2. window.addEventListener('load', function () {});

之间的区别为:window.onload注册事件的只能写一次,如果有多个,会以最后一个window.onload为准;addEventListener没有限制。

window.onbeforeunload

window.onbeforeunload,当窗口即将被卸载(关闭)时,会触发该事件,此时页面文档依然可见,且该事件的默认动作可以被取消。

window.onunload

window.onunload,该事件在关闭窗口资源和内容的时候触发。

定时器

方法 说明
setTimeout() 在指定的毫秒数后调用函数或执行一段代码
setInterval() 按照指定的周期(以毫秒计)来调用函数或执行一段代码
clearTimeout() 取消由setTimeout()方法设置的定时器
clearInterval() 取消由setInterval()设置的定时器

格式如下:

1
setTimeout(调用的函数, [延迟的毫秒数])
1
setInterval(调用的函数, [延迟的毫秒数])

调用的函数可以是一段代码、一个函数或者一个函数名。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 参数形式1:用字符串表示一段代码
setTimeout('alert("JavaScript");', 3000);

// 参数形式2:传入一个匿名函数
setTimeout(function () {
alert('JavaScript');
}, 3000);

// 参数形式3:传入函数名
setTimeout(fn, 3000);
function fn() {
console.log('JavaScript');
}

注意:

  1. 当参数为一个函数名时,这个函数名不需要加()小括号,否则就变成了立即执行这个函数,将函数执行后的返回值传入。
  2. 延迟的毫秒数,默认为0。

在实际开发中,一个网页中可能会有很多个定时器,所以建议用一个变量保存定时器的id(唯一标识)。若想要在定时器启动后,取消该定时器操作。

示例代码:

1
2
3
4
5
// 在设置定时器时,保存定时器的唯一标识
var timer = setTimeout(fn, 3000);

// 如果要取消定时器,将唯一标识传递给clearTimeout()方法
clearTimeout(timer);

《基于Java的后端开发入门:99.iPhone设备的一个BUG》中,最后的解决方案,利用的就是定时任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let highlightSize = $('.highlight').length
let highlightIndex = 0;
let highlightInterval;

function highlightDisplay(){
$('.highlight')[highlightIndex].style.display = 'block';
highlightIndex = highlightIndex + 1;
if(highlightIndex >= highlightSize){
clearInterval(highlightInterval)
}
}

if(highlightSize > 0){
highlightInterval = self.setInterval(highlightDisplay,10)
}

观察者

观察者,Observer,在JavaScript中一共有4种观察者。

  1. Intersection Observer
  2. Mutation Observer
  3. Resize Observer
  4. Performance Observer

Intersection Observer

功能

有时候,我们需要监测某个元素是否出现可视范围内,例如:

  • 懒加载,当出现在可视范围内才进行加载。
  • 无限滚动(Infinite Scroll),当页面滚动到底部的时候,自动载入更多内容
  • 计算广告在页面的曝光次数。

在以往,我们需要绑定scroll事件,然后不断的计算当前scroll的坐标,进行相关的逻辑处理。
这个太麻烦了,而且性能不会太好。

我们可以把这些工作交给IntersectionObserver。

角色

在Intersection Observer中有两个角色:

  1. root
    外层的容器,target的ancestor元素(父元素,父元素的父元素,祖先元素)。
  2. target
    会在root容器内出现的元素

例如,在下文的例子中,随着滚动条的滚动,targetroot的可视范围不断变化,target上的数字表示的是可见程度。

See the Pen Intersection Observer Demo by KakaWanYifan (@KakaWanYifan) on CodePen.

当滚动条滚到target刚好出现在root可视范围或是完整离开可视范围的那一瞬间,可见程度是0;当target完整的出现在root的可视范围内,可见程度是1。
特别的,如果target的高度是root可视范围的两倍呢?那么最多可以看到半个target,即可见程度最高只会有0.5。

实例化

1
2
3
4
5
6
7
const options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 0.5,
};

const observer = new IntersectionObserver(callback, options);
  • callback,回调函数
  • options,可选参数
    • root:不指定或为空,代表浏览器的可视范围。
    • rootMargin:用來修改root元素的观察范围。
    • threshold:可見程度,可以是浮点数或者数组,例如[0, 0.25, 0.5, 0.75, 1],当满足可见程度时,调用callback(回调函数)。

观察target

上文,我们只是定义了一个观察者,现在我们要用这个观察者来观察target。示例代码:

1
2
const target = document.querySelector('#listItem');
observer.observe(target);

callback函数

1
2
3
4
5
const callback = (entries, observer) => {
entries.forEach(entry => {
// Do something...
});
}

callback函数接受两个参数:

  • entriesIntersectionObserverEntry的数组
    IntersectionObserverEntry的属性很多,我们会用到的有
    • isIntersecting:target是否可见,即使只有1px也算可见
    • intersectionRatio:target可见比例
  • observer:观察者本身(即是哪一位观察者触发了callback)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const root = document.querySelector('#root');

const options = {
root,
threshold: [0, 0.2, 0.4, 0.6, 0.8, 1],
};

const callback = (entries, observer) => {
entries.forEach(entry => {
// Do something...
});
};

const observer = new IntersectionObserver(callback, options);

const target = document.querySelector('#target');
observer.observe(target);

Mutation Observer

功能

监听某个元素在某个时候发生了哪些具体的变化。

使用

有了Intersection Observer的基础,其实Mutation Observer很简单了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 选择需要观察变动的节点
const targetNode = document.getElementById('Xxx');

// 观察器的配置(需要观察什么变动)
const config = { attributes: true};

// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
// 相关逻辑处理
};

// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);

// 以上述配置开始观察目标节点
observer.observe(targetNode, config);

config中的属性有:

  • attributes,是否观察,属性的变动。
  • childList,是否观察,子节点的变动(指新增,删除或者更改)
  • characterData,是否观察,节点内容或节点文本的变动。
  • subtree,是否将该观察器应用于该节点的所有子节点。
  • attributeOldValue,观察attributes变动时,是否需要记录变动前的属性值。
  • characterDataOldValue,观察characterData变动时,是否需要记录变动前的值。
  • attributeFilter,一个数组,观察的特定属性,例如['class','src']

Resize Observer

使用

监听元素的尺寸变化。
使用方法很简单,没有太多的属性配置,示例代码:

1
2
var observer = new ResizeObserver(callback);
observer.observe(target);

例子

1
2
3
4
5
6
7
8
9
10
<div id="target"></div>
<script>
const target = document.querySelector('#target');
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
console.log(`目标元素的大小变化为 ${entry.target.clientWidth} x ${entry.target.clientHeight}`);
}
});
observer.observe(target);
</script>

注意

  1. 对于不可替换内联元素不触发
    不可替换内联元素是指其内容不会被替换或继承,相对于其父元素的文本流位置是不可变的。例如,span元素就是一个不可替换的内联元素。
  2. CSS transform 操作不触发
  3. 元素被插入或移除时会触发,元素display从显示变成none或相反过程时会触发

Performance Observer

Performance Observer,用来监控各种性能相关的指标,本文不讨论。

localtion对象

URL的组成

location对象与url相关,在讨论location对象前,我们先讨论一下url的组成。

在一个url中,包含了网络协议、服务器的主机名、端口号、资源名称字符串、参数以及锚点,具体:

1
protocol://host[:port]/path/[?query]#fragment
各部分 说明
protocol 网络协议,常用的如http,ftp,mailto等
host 服务器的主机名,如www.example.com
port 端口号,可选,省略时使用协议的默认端口,如http默认端口为80
path 路径,如/web/index.html
query 参数,键值对的形式,通过&符号分隔,如a=3&b=4
fragment 锚点,如#res,表示页面内部的锚点

location常用属性

属性 说明
location.search 当前URL的查询部分(?之后的部分)
location.hash 锚部分(从#开始的部分)
location.host 主机名和端口
location.hostname 主机名
location.port 端口
location.href 完整的URL
location.pathname 路径名
location.protocol 协议

location常用方法

location对象提供的用于改变URL地址的方法。

方法 作用
reload() 重新加载当前文档
replace() 用新的文档替换当前文档,覆盖浏览器当前记录

reload(),从服务器上重新下载该文档,类似于"刷新页面"。
replace(),使浏览器位置改变,并且禁止在浏览器历史记录中生成新的记录。

那么,我们如何在前端页面实现重定向呢?
除了replace(),还有location.href等,这几种方法存在区别
具体可以参考我们在《基于Hexo的博客搭建:8.重定向》中的讨论。

navigator对象

navigator对象包含有关浏览器的信息,每个浏览器中的navigator对象中都有一套自己的属性。

常见的navigator对象的属性如下:

属性 说明
appCodeName 浏览器的内部名称
appName 浏览器的完整名称
appVersion 浏览器的平台和版本信息
cookieEnabled 浏览器中是否启用Cookie的布尔值
platform 运行浏览器的操作系统平台
userAgent 客户端发送到服务器的User-Agent头部的值

history对象

history,历史记录。
出于安全方面的考虑,history对象不能直接获取用户浏览过的URL,只能实现"后退"和"前进"的功能。

方法 作用
back() 加载history列表中的前一个URL
forward() 加载history列表中的下一个URL

本地存储

最后一个话题,本地存储。
本地存储方法有两种:

  1. cookie
  2. storage

我们分别讨论。

写cookies

示例代码:

1
2
3
4
5
6
function setCookie(name,value){
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
}

如果不设置expires,在浏览器关闭时就会删除cookie。

读取cookies

示例代码:

1
2
3
4
5
6
7
8
function getCookie(name){
var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
if(arr=document.cookie.match(reg)){
return unescape(arr[2]);
}else{
return null;
}
}

删除cookies

示例代码:

1
2
3
4
5
6
7
8
function delCookie(name){
var exp = new Date();
exp.setTime(exp.getTime() - 1);
var cval=getCookie(name);
if(cval!=null){
document.cookie= name + "="+cval+";expires="+exp.toGMTString();
}
}

解释说明:让cookie立即超时,失效。

storage

相比cookie,更推荐用storage来做本地存储。

storage分为两种,sessionStoragelocalStorage,我们分别讨论。

sessionStorage

sessionStorage,特点有:

  1. 生命周期为关闭浏览器窗口
  2. 在同一个窗口(页面)下数据可以共享
  3. 以键值对的形式存储使用

存储数据:

1
sessionStorage.setItem(key, value)

获取数据:

1
sessionStorage.getItem(key)

删除数据:

1
sessionStorage.removeItem(key)

删除所有数据:

1
sessionStorage.clear()

localStorage

localStorage,特点有:

  1. 永久有效,即使关闭页面也会存在。除非手动删除
  2. 可以多窗口(页面)共享,即同一浏览器可以共享
  3. 以键值对的形式存储使用

存储数据:

1
localStorage.setItem(key, value)

获取数据:

1
localStorage.getItem(key)

删除数据:

1
localStorage.removeItem(key)

删除所有数据:

1
localStorage.clear()

比较

相同点

cookiesessionStoragelocalStorage都是在客户端保存数据的,并且都是key-value类型。

不同点

生命周期

cookie如果不设置有效期,那么就是临时存储,浏览器关闭,cookie也就失效了。如果设置了有效期,那么有效期到了,就自动消失了。

sessionStorage仅在当前会话下有效。

localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。除非主动删除数据,否则数据永远不会消失。

大小限制

cookie大小限制在4KB

localstoragesessionStorage的大小限制为5M。

安全性

localstoragesessionStorage不会随着HTTP header发送到服务器端,安全性相对于cookie来说比较高一些。

便捷性

localstoragesessionStorage相关的一些操作数据的方法的确比cookie便捷很多。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/12002
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区