DOM编程艺术

1 文档树

1.1 概念

Document Object Model

文档对象模型

API 规范

  1. DOM Core
  2. DOM HTML
  3. DOM Style
  4. DOM Event

文档树

1.2 节点遍历

节点遍历

1.3 节点类型

节点类型

1.4 元素遍历

元素遍历

2 节点操作

2.1 获取节点

获取节点

2.1.1 getElementById

element = document.getElementById(id)

getElementById

2.1.2 getElementsByTagName

collection = element.getElementsByTagName(tagName)

getElementsByTagName

2.1.3 getElementsByClassName

collection = element.getElementsByClassName(className)

IE9+

getElementsByClassName

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>跨浏览器的getElementsByClassName</title>
<style>
html, body,h2, ul, li{margin: 0;padding: 0;}
body{font-family: arial;}
#users{margin: 30px;width: 210px;box-shadow: 0px 1px 2px #bbb;}
h2{height: 50px;line-height: 50px;padding-left: 20px;font-size: 18px;border-bottom: 1px solid #dfdfdf;}
ul{padding: 10px 0;overflow: hidden;}
.user{float: left;width: 50px;margin: 10px;list-style: none;}
.user a{color: #666;text-decoration: none;font-size: 12px;}
</style>
</head>
<body>
<div id="users">
<h2>列表:</h2>
<ul>
<li class="user">
aaaaa
</li>
<li class="user">
bbbbb
</li>
<li class="test user">
ccccc
</li>
<li class="user last">
ddddd
</li>
</ul>
</div>
<script>
function getElementsByClassName(root,clsName) {
if (root.getElementsByClassName) {
return root.getElementsByClassName(clsName);
} else {
var result = [],
elements = root.getElementsByTagName('*'),
_className,
flag;
clsName = clsName.split(' ');
for (var i=0; i<elements.length; i++) {
_className = ' ' + element[i].className + ' ';
flag = true;
for (var j=0; j<clsName.length; j++) {
if (_className.indexOf(' ' + clsName[j] + ' ') == -1) {
flag = false;
break;
}
}
if (flag) {
result.push(element[i]);
}
}
return result;
}
}
var users = getElementsByClassName(document, 'user');
// alert(users.length + ' users');
var lastUser = getElementsByClassName(document, 'user last')[0];
// alert('last user is ' + lastUser.innerText);
var testUser = getElementsByClassName(document, 'test user')[0];
alert('test user is ' + testUser.innerText);
</script>
</body>
</html>
2.1.4 querySelector querySelectorAll

list = element.querySelector(selector)

list = element.querySelectorAll(selector)

IE8+

querySelector  querySelectorAll

2.2 创建节点

element = document.createElement(tagName)

2.3 修改节点

2.3.1 textContent

IE9+

textContent

2.3.2 innerText
1
2
3
4
5
6
7
8
9
10
11
// 跨浏览器兼容
if (!('innerText' in document.body)) {
HTMLElement.prototype.__defineGetter__('innerText',function () {
return this.textContent;
});
HTMLElement.prototype.__defineSetter__('innerText',function (s) {
return this.textContent = s;
});
}

2.4 插入节点

2.4.1 appendChild

var aChild = element.createChild(tagName)

element.appendChild(aChild)

appendChild

2.4.2 insertBefore

var aChild = element.createChild(tagName)

element.insertBefore(aChild,referenceChild)

insertBefore

2.5 删除节点

element.removeChild(aChild)

removeChild

2.5 innerHTML

innerHTML

问题

  1. 原来的状态事件不会保留
  2. 内存泄露(旧浏览器)
  3. 安全(可能插入脚本)

建议仅用于新节点,节点内容需要是可控经过检查的

3 属性操作

3.1 property

3.1.1 读写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div>
<label for='userName'>User Name: </label>
<input id='userName' class='u-text' type='text' maxlength='10' disabled onclick='show();' />
</div>
<script>
var oLabel = document.getElementsByTagName('label')[0];
var oInput = document.getElementsByTagName('input')[0];
console.log(olnput.className); // u-text
console.log(oInput[id]); // userName
oInput.value = '某某';
</script>
3.1.2 类型
property value type
className ‘u-text’ String
maxLength 10 Number
disabled true Boolean
onclick function .. Function
3.1.3 特点
  1. 取得实用对象
  2. 通过属性访问,通用性不佳,会出现名字异常,而且扩展性也不好

3.2 get/set Attribute

3.2.1 读写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
<label for='userName'>User Name: </label>
<input id='userName' class='u-text' type='text' maxlength='10' disabled onclick='show();' />
</div>
<script>
var oLabel = document.getElementsByTagName('label')[0];
var oInput = document.getElementsByTagName('input')[0];
console.log(olnput.getAttribute('class')); // u-text
console.log(oInput.getAttribute('id')); // userName
oInput.setAttribute('value','某某');
oInput.setAttribute('disabled','');
</script>
3.2.2 类型
attribute value type
class ‘u-text’ String
maxLength 10 String
disabled true String
onclick function .. String
3.2.3 特点
  1. 仅仅字符串
  2. 通用性好

3.3 dataset

  1. HTMLElement.dataset(上的一个属性)
  2. data-*属性集
  3. 元素上保存数据
  4. 适合自定义属性数据,旧浏览器不兼容
1
2
3
4
5
6
7
8
<div id='user' data-id='123' data-account-name='wq' data-name='Tom' data-email='wq@XXX.com' data-mobile='123131213412123'>
WQ
</div>
<script>
var oDiv = document.getElementById('user');
console.log(oDiv.dataset.id); // '123'
</script>
dataset value type
id ‘123’ String
accountName ‘wq’ String
name ‘Tom’ String
email ‘..’ String
moblie ‘..’ String

4 样式操作

4.1 style

对应元素内嵌样式

style1

1
2
3
4
5
6
7
<input type='text'>hello<input>
<script>
var oInput = document.getElementsByTagName('input')[0];
oInput.style.borderColor = '#f00';
oInput.style.color = '#0f0';
</script>
  1. 更新一个属性需要一个语句
  2. 不是熟悉的CSS
  3. 样式混淆在脚本

4.2 style.cssText

1
2
3
4
5
6
<input type='text'>hello<input>
<script>
var oInput = document.getElementsByTagName('input')[0];
oInput.style.cssText = 'border-color: #f00; color: #0f0';
</script>
  1. 样式混淆在脚本

4.3 更新class

  1. 一次更新多个元素的样式

4.3 更新stylesheet

4.4 获取实际样式值

window.getComputedStyle(element[,paseudoElt]);

element.currentStyle 针对ie9

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>getComputedStyle - 获取样式</title>
<style>
div{margin: 20px auto;}
button{padding: 4px 5px;margin-left: 10px;}
input{width: 215px;padding: 4px 5px;border: 1px solid #ababab;border-radius: 3px;box-shadow: 2px 2px 3px #ededed inset;font-size: 14px;font-weight: bold;}
</style>
</head>
<body>
<div>
<input id="mobile0" value="13564782365">
<button id="get0">获取颜色</button>
</div>
<div>
<input id="mobile1" value="13564782365" style="color: red;">
<button id="get1">获取颜色</button>
</div>
<script>
function $(id) {
return typeof id === "string" ? document.getElementById(id) : id;
}
var Util = (function(document, util) {
util = util || {};
// 事件-兼容
util.addEventListener = function(element, type, listener) {
if (element.addEventListener) {
element.addEventListener(type, listener, false);
} else {
element.attachEvent('on' + type, listener);
}
}
return util;
})(document, Util);
// 获取样式-兼容
var getStyle = function (el,attr) {
return el.currentStyle ? el.currentStyle[attr] : window.getComputedStyle(el)[attr];
}
Util.addEventListener($('get0'), 'click', getStyle0);
Util.addEventListener($('get1'), 'click', getStyle1);
function getStyle0(){
var element = $('mobile0');
alert(getStyle(element,'color'))
}
function getStyle1(){
var element = $('mobile1');
alert(getStyle(element,'color'))
}
</script>
</body>
</html>

style2

5 事件

5.1 事件流

event1

5.2 事件注册

DOM 2级 为元素分配多个处理函数(而非覆盖)

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var Util = (function (document,util) {
util = util || {};
// 事件对象
util.getEvent(event) {
return event ? event : window.event
}
// 事件类型
util.getEventType(event) {
return event.type
}
// 事件触发节点
util.getEventTarget(event) {
return event.target ? event.target : event.srcElement
}
// 事件注册
util.addEventListener = function (el,type,listener) {
if (el.addEventListener) {
el.addEventListener(type,listener,false)
} else {
el.attachEvent('on'+type,function(e) {
listener.call(el,e) // 将this指向el
})
}
}
// 事件取消
util.removeEventListener = function (el,type,listener) {
if (el.removeEventListener) {
el.removeEventListener(type,listener,false)
} else {
el.dettachEvent('on'+type,listener)
}
}
// 事件触发器
util.dispatchEvent = function (el,type) {
if (document.createEventObject) {
var evt = document.createEventObject(); // IE创建event对象实例的方法
return el.fireEvent('on'+type,evt)
} else {
var evt = document.createEvent('HTMLEvents');
evt.initEvent(type,true,true); // 事件类型,冒泡,阻止浏览器的默认行为
return el.dispatchEvent(evt)
}
}
// 事件默认行为阻止
util.preventDefault = function (el) {
if (el.preventDefault) {
el.preventDefault()
} else {
el.returnValue = false
}
}
// 事件冒泡阻止
util.stopPropagation = function (el) {
if (el.stopPropagation) {
el.stopPropagation()
} else {
el.cancelBubble = true
}
}
// 事件进一步传播阻止(W3C)
util.stopImmediatePropagation = function (el) {
el.stopImmediatePropagation()
}
})(document,util);

5.3 事件类型

event2

鼠标事件

mouse event

mouse event 2

滚轮事件

wheel event

焦点事件

focus event

输入事件

input event

键盘事件

key event

其它事件

other event

5.4 事件代理

5.5 自定义事件和拖放

6 多媒体

1
2
<audio src='music.mp3'></audio>
<video src='movie.mov' width=200 height=200></video>
1
2
3
4
5
<audio>
<source src='music.mp3' type='audio/mpeg'>
<source src='music.wav' type='audio/x-wav'>
<source src='music.ogg' type='audio/ogg'>
</audio>
1
2
3
4
<video>
<source src='movie.mp4' type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'>
<source src='movie.webm' type='video/webm; codecs="vp8, vorbis"'>
</video>

音频
视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
audio|video.canPlayType(type))
规定要检测的音频/视频类型。
常用值:
video/ogg
video/mp4
video/webm
audio/mpeg
audio/ogg
audio/mp4
常用值,包括编解码器:
video/ogg; codecs="theora, vorbis"
video/mp4; codecs="avc1.4D401E, mp4a.40.2"
video/webm; codecs="vp8.0, vorbis"
audio/ogg; codecs="vorbis"
audio/mp4; codecs="mp4a.40.5"
注释:如果包含编解码器,则只能返回 "probably"。
表示支持的级别。可能的值:
"probably" - 最有可能支持
"maybe" - 可能支持
"" - (空字符串)不支持

media

多媒体

Web Audio API

W3C

mozilla

html5rocks

webaudioapi

7 CANVAS

canvas

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<canvas id='tutorial' width='300' height='300'></canvas>
<script>
// 图片资源
var sun = new Image();
var moon = new Image();
var earth = new Image();
function init() {
sun.src = 'http://www.w3school.com.cn/example/html/sun.html';
moon.src= 'http://www.w3school.com.cn/example/html/mercur.html';
earth.src= 'http://www.w3school.com.cn/example/html/venus.html';
// 定时器
window.requestAnimationFrame(draw);
}
function draw() {
var canvas = document.getElementById('tutorial');
var ctx = canvas.getContext('2d'); // 渲染上下文
ctx.globalCompositeOperation = 'destination-over'; // 设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上
ctx.clearRect(0,0,300,300) // clear canvas
ctx.fillStyle = 'rgba(0,0,0,.4'; // 填充
ctx.strokeStyle = 'rgba(0,153,255,.4)' // 线条
ctx.save(); // 保存状态
ctx.translate(150,150);
// draw earth
var time = new Date();
ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() ); // 旋转到圆上一点
ctx.translate(105,0);
ctx.fillRect(0,-12,50,24); // draw shadow
ctx.drawImage(earth,-12,-12); // draw earth
// moon
ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() ); // 旋转到圆上一点
ctx.translate(0,28.5);
ctx.drawImage(moon,-3.5,-3.5);
ctx.restore(); // 恢复状态
ctx.beginPath(); // draw line
ctx.arc(150,150,150,0,Math.PI*2,false) // draw earth orbit
ctx.stroke();
// sun
ctx.drawImage(sun,0,0,300,300);
window.requestAnimationFrame(draw);
}
init();
</script>
</body>
</html>

8 BOM

8.1 属性

bom属性

8.2 方法

bom方法

8.3 事件

bom事件

9 数据通信

9.1 http

http

9.2 ajax

ajax

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
31
32
33
34
35
36
37
38
39
40
41
42
// 创建XHR对象,跨浏览器兼容
function createXHR() {
if (typeof XMLHttpRequest != 'undefined') {
return new XMLHttpRequest();
} else {
return new ActiveXObject('Microsoft.XMLHTTP');
}
}
// 1. 创建XHR对象
var xhr = createXHR();
// 3. 处理返回数据
xhr.onreadystatechange = function (callback) {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
callback(xhr.responseText);
} else {
alert('Request was unsuccessful: ' + xhr.status);
}
}
}
// 2. 发送请求
xhr.open('get','example.json',true);
xhr.setRequestHeader('myHeader','myValue');
xhr.send(null);
// 请求参数序列化
function serialize(data) {
if (!data) return '';
var pairs = [];
for (var name in data) {
if (!data.hasOwnProperty(name)) continue; // 自身有属性
if (typeof data[name] === 'function') === continue; // 方法
var value = data[name].toString(); // 转成字符串
name = encodeURIComponent(name); // URI 编码
value = encodeURIComponent(value);
pairs.push(name + '=' + value);
}
return pairs.join('&'); // 数组转字符串
}

10 数据存储

cookie

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// cookie 读取、写入、删除
var CookieUtil = {
get: function (name) {
// 名称和值必须经过URL编码
// document.cookie返回字符串-"name1=value1;name2=value2;"
var cookieName = encodeURIComponent(name) + '=',
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;
if (cookieStart > -1) {
var cookieEnd = document.cookie.indexOf(';',cookieStart);
if (cookieEnd == -1) {
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
}
return cookieValue;
},
// 名称、值、时间、路径、域、标志
set: function (name,value,expires,path,domain,secure) {
var cookieText = encodeURIComponent(name) + '=' + encodeURIComponent(value);
if (expires instanceof Date) {
cookieText += '; expires=' + expires.toGMTString();
}
if (path) {
cookieText += '; path=' + path;
}
if (domain) {
cookieText += '; domain=' + domain;
}
if (secure) {
cookieText += '; secure=' + secure;
}
document.cookie = cookieText;
},
unset: function (name,path,domain,secure) {
this.set(name,"",new Date(0),path,domain,secure);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function getCookie () {
var cookie = {};
var all = document.cookie;
if (all === '') return cookie;
var list = all.split('; '); // ['name1=value1','name2=value2']
for (var i=0; i<list.length; i++) {
var item = list[i]; // 'name=value'
var p = item.indexOf('=');
var name = item.substring(0,p);
name = decodeURIComponent(name);
var value = item.substring(p+1);
value = decodeURIComponent(value);
cookie[name] = value;
}
return cookie;
}
function removeCookie(name,path,domain) {
document.cookie = name + '=' + '; path=' + path + '; domain=' + domain + '; max-age=0';
}

11 动画

实现方式 - gif, flash, CSS3, JS

三要素 - 对象、属性、定时器

animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var animation = function (el,attr,from,to) {
var distance = Math.abs(to - from); // 绝对值
var stepLength = distance / 100; // 步长
var sign = (to - from) / distance; // 方向
var offset = 0;
var step = function () {
var tmpOffset = offset + stepLength; // 单次变化量
// 变化不能超过原有范围
if (tmpOffset < distance) {
el.style[attr] = from + tmpOffset*sign + 'px';
offset = tmpOffset;
} else {
el.style[attr] = to + 'px';
clearInterval(timer);
}
}
el.style[attr] = from + 'px';
var timer = setInterval(step, 10);
}

12 列表操作

list1

list2

13 表单操作

13.1 表单元素

form1

form2

form3

form4

13.2 表单验证

form5

13.3 表单提交

form6

13.4 表单应用

form7

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
<style>
body, button, input, legend{margin: 0;padding: 0;font: 16px "微软雅黑";}
.m-form{margin: 150px auto;width: 325px;border: 1px solid #ddd;}
.m-form legend{width: 100%;line-height: 30px;text-indent: 1em;color: #fff;background-color: #2d2d2d;}
.m-form fieldset{border: none;padding: 20px;}
.m-form div{margin: 20px;}
.m-form button{width: 100%;height: 30px;color: #fff;border: 1px solid #ddd;cursor: pointer;background-color: #2d7dca;}
.m-form .msg{margin:5px;text-align:center;display:none;}
.m-form .tip{padding-left:6em;font-size:12px;color:#C0C0C0;}
.m-form .j-err{display:block;color:#FF0000;}
.m-form .j-suc{display:block;color:#158226;}
.m-form .u-txt{width: 160px;padding: 3px;border:1px solid #aaa;}
.m-form .j-error{border-color: #f00;background-color: #FFE7E7;}
.m-form .j-disabled{cursor: default;background-color: #ddd;}
</style>
</head>
<body>
<form action="./login" class="m-form" name="loginForm" target="result" autocomplete="off">
<legend>手机号码登录</legend>
<fieldset>
<div class="msg" id="message"></div>
<div>
<label for="telephone">手机号:</label>
<input id="telephone" name="telephone" type="tel" class="u-txt" maxlength="11"
required pattern="^0?(13[0-9]|15[012356789]|18[0236789]|14[57])[0-9]{8}$"><br/>
<span class="tip">11位数字手机号码</span>
</div>
<div>
<label for="password">密 码:</label>
<input id="password" name="password" type="password" class="u-txt"><br/>
<span class="tip">至少6位,同时包含数字和字母</span>
</div>
<div><button name="loginBtn">登 录</button></div>
</fieldset>
</form>
<iframe name="result" id="result" style="display:none;"></iframe>
<script>
(function(){
var form = document.forms.loginForm,
nmsg = document.getElementById('message');
function md5(msg){
return msg;
}
function showMessage(clazz,msg){
if (!clazz){
nmsg.innerHTML = '';
nmsg.classList.remove('j-suc');
nmsg.classList.remove('j-err');
}else{
nmsg.innerHTML = msg;
nmsg.classList.add(clazz);
}
}
function disableSubmit(disabled){
form.loginBtn.disabled = !!disabled;
var method = !disabled?'remove':'add';
form.loginBtn.classList[method]('j-disabled');
}
function invalidInput(node,msg){
showMessage('j-err',msg);
node.classList.add('j-error');
node.focus();
}
function clearInvalid(node){
showMessage();
node.classList.remove('j-error');
}
form.telephone.addEventListener(
'invalid',function(event){
event.preventDefault();
var input = form.telephone;
invalidInput(input,'请输入正确的手机号码');
}
);
form.addEventListener(
'input',function(event){
// 还原错误状态
clearInvalid(event.target);
// 还原登录按钮状态
disableSubmit(false);
}
);
form.addEventListener(
'submit',function(event){
// 密码验证
var input = form.password,
pswd = input.value,
emsg = '';
if (pswd.length<6){
emsg = '密码长度必须大于6位';
}else if(!/\d/.test(pswd)||!/[a-z]/i.test(pswd)){
emsg = '密码必须包含数字和字母';
}
// 密码验证不通过
if (!!emsg){
event.preventDefault();
invalidInput(input,emsg);
return;
}
input.value = md5(pswd);
// 禁用提交按钮
disableSubmit(true);
}
);
var frame = document.getElementById('result');
frame.addEventListener(
'load',function(event){
try{
var result = JSON.parse(
frame.contentWindow.document.body.textContent
);
// 还原登录按钮状态
disableSubmit(false);
// 识别登录结果
if (result.code===200){
showMessage('j-suc','登录成功!');
form.reset();
}
}catch(ex){
// ignore
}
}
);
})();
</script>
</body>
</html>