课程大纲
第一章:DOM基础
- 一、认识DOM
- 二、DOM节点
- 三、DOM节点的查改增删
- 四、DOM相关的几个重要对象
第二章:DOM事件模型
- 一、事件的分类
- 二、添加事件处理程序
- 三、移除事件处理程序
- 四、深入Event
第三章:浏览器兼容性处理与错误调试技巧
- 一、浏览器类型判断
- 二、DOM处理在各浏览器中的兼容性
- 三、错误调试技巧
第一章:DOM基础
一、认识DOM
1、DOM是什么?
Document Object Model,文档对象模型,可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构,是表示和处理一个HTML或XML文档的常用方法。
2、DOM能做什么?
能让网页实现更丰富的交互效果,下面随便列几个网站看欣赏一下DOM的奇幻魅力!
- Feed数据的下拉无限加载:http://hi.baidu.com/
- Fehelper首页上的图片幻灯切换功能:http://www.baidufe.com/fehelper
- 无尽的隧道:http://js1k.com/2013-spring/demo/1555
- 百度图片的识图功能:http://shitu.baidu.com/
- Web QQ:http://web2.qq.com/
二、DOM节点
一个完整的网页,就是由不同类型的HTML DOM节点,按照一定的依赖规则组合而成的。
1、DOM中对节点的定义
- 整个文档是一个文档节点
- 每个 HTML 标签是一个元素节点
- 包含在 HTML 元素中的文本是文本节点
- 每一个 HTML 属性是一个属性节点
- 注释属于注释节点
2、节点类型
3、节点层次
1)、HTML文档
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>文档标题</title>
</head>
<body>
<h1>我的标题</h1>
<a href="http://www.baidu.com">我的链接</a>
</body>
</html>
2)、DOM树
如上HTML文档所示的DOM节点层次,可以通过一棵HTML DOM树体现出来
要理解DOM树在浏览器中的渲染顺序:从根节点自上而下进行渲染
BTW:通过之后章节中即将介绍的DOM操作,可以访问整个DOM树上的所有节点,包括节点的查改增删。
三、DOM节点的查改增删
本节将主要学习HTML DOM节点的查找、修改、创建与插入、删除等操作。
1、节点查询
1)、查询DOM节点
简要介绍getElementById、getElementsByTagName、getElementsByClassName,示例代码:
// 查询id为 helloWorld 的节点
var helloWorld = document.getElementById('helloWorld');
// 查询页面上的所有 a 标签
var aTags = document.getElementsByTagName('a');
// 查询class为 .slide 的节点
var slides = document.getElementsByClassName('slide');
BTW:如上功能可用querySelectorAll取代实现
2)、访问父节点与子节点
简要介绍parentNode、childNodes、firstChild、lastChild,示例代码:
// 查询id为 helloWorld 的节点
var helloWorld = document.getElementById('helloWorld');
// 获得helloWorld的父节点
// 这里使用的是parentNode,在非w3c标准下还有一个parentElement,必须理解二者的区别:
// ElementNode只是Node中的一种,nodeType为1
var parentNodeForHelloWorld = helloWorld.parentNode;
// 获得helloWorld的所有子节点
var childNodesForHelloWorld = helloWorld.childNodes;
// 获取hellWorld的第一个子节点
var firstChildForHelloWorld = helloWorld.firstChild;
// 获取hellWorld的最后一个子节点
var lastChildForHelloWorld = helloWorld.lastChild;
BTW:必须理解firstChild和lastChild都是获取所有类型的Node,而不仅仅是ElementNode。
3)、访问兄弟节点
简要介绍previousSibling、nextSibling,示例代码
// 查询id为 helloWorld 的节点
var helloWorld = document.getElementById('helloWorld');
// 获得helloWorld的前一个兄弟节点
var previousNodeForHelloWorld = helloWorld.previousSibling;
// 获得helloWorld的后一个兄弟子节点
var nextNodeForHelloWorld = helloWorld.nextSibling;
BTW:必须理解previousSibling和nextSibling都是获取所有类型的Node,而不仅仅是ElementNode。 如果只想获取ElementNode,可以使用previousElementSibling和nextElementSilbling方法, 但这两个属性不是w3c标准,存在浏览器兼容性问题,慎用。
2、节点修改
1)、修改节点属性
节点属性的修改,可以通过element.setAttribute、element.attributeName的方式进行修改,比如针对如下节点:
<a id="helloWorld" class="cls-demo red" href="http://www.baidufe.com">http://www.baidufe.com</a>
可通过下面的两种方式,直接修改该节点的链接地址:href属性
// 查询id为 helloWorld 的A节点
var helloWorld = document.getElementById('helloWorld');
// 修改helloWorld的href值:通过element.setAttribute的形式
helloWorld.setAttribute('href','http://www.baidu.com');
// 修改helloWorld的href值:通过element.attributeName的形式
helloWorld.href = 'http://www.baidu.com';
但是,不是所有的属性都可以直接通过element.attributeName赋值的形式来实现,比如,节点的CSS class就是一个特例:
// 我们可以通过element.setAttribute的形式来修改class
helloWorld.setAttribute('class','cls-demo-changed'); // success
// 但我们不能通过element.attributeName的形式来修改class
helloWorld.class = 'cls-demo-changed'; // failed
// 对于CSS class,正确的做法是:
helloWorld.className = 'cls-demo-changed'; // success
BTW:关于节点CSS class的访问,在HTML5标准中新增了classList API,该API中新增了CSS class的add、remove、contains、item、toggle等方法
// 通过classList的contains方法,判断节点是否包含某个CSS class
var hasClass = helloWorld.classList.contains('red'); // true
// 其他API可线下单独尝试
2)、修改节点内容
本小节主要介绍节点内容的修改方法,包括修改HTML片段,以及文本内容;主要API为:innerHTML、innerText
// 继续以上一小节的<a>节点作为示例
var helloWorld = document.getElementById('helloWorld');
// 修改链接文本为:清华大学
helloWorld.innerHTML = '清华大学';
// 或通过innerText属性修改
helloWorld.innerText = '清华大学';
// 将链接文本替换成一张图片,即插入一个<img/>标签,此时只能使用innerHTML属性
helloWorld.innerHTML = '<img src="static/img/picture.png?v=78964693&v=59764449" alt="图片" />';
- 通过innerHTML属性,可以向节点中插入任意HTML片段,该API为w3c标准,但必须明白,不是所有标签都支持innerHTML属性
- 通过innerText属性,只能向节点内插入文本,并且不是w3c标准,所以存在浏览器兼容性问题,关于此问题,我们将在第三章详细介绍
3、增加新节点
本节将介绍如何通过DOM动态创建新节点,并插入到文档中,用到的API:createElement、appendChild,假定文档如下:
<div id="helloWorld">
<div id="inner"></div>
</div>
此时则可以通过如下的DOM操作,动态创建一个img节点并插入到helloWorld中
// 获得helloWorld节点
var helloWorld = docuement.getElementById('helloWorld');
// 通过createElement创建img节点,并设置相关属性
var imgElement = document.createElement('img'); // 注意API的使用方法
imgElement.src = '/static/img/picture.png?v=78964693&v=59764449'; // 设置图片地址
imgElement.alt = '图片';
// 将img追加到helloWorld子节点之后
helloWorld.appendChild(imgElement); // 采用子节点追加的方式插入文档
BTW:当有大批量的DOM节点需要插入到文档流中,需要用DocumentFragment来实现
通过上面的DOM操作,文档片段将发生如下的变化:
<div id="helloWorld">
<div id="inner"></div>
<img src="/static/img/picture.png?v=78964693&v=59764449" alt="图片" />
</div>
- 除了通过createElement创建ElementNode之外,还可以通过document.createTextNode创建TextNode
- 在节点的动态插入方面,除了使用appendChild之外,还可以通过element.insertBefore方法将节点插入到指定节点之前, 通过element.insertAjdacentElement方法在指定节点的指定位置处进行插入
BTW:在项目开发中,用得最多的还是createElement、appendChild,其他API一般都会进行封装后再使用,可留作课后作业自行练习。
4、删除节点
本节将简单介绍从文档中将DOM节点进行删除操作,主要通过父节点的removeChild方法来实现。已如下HTML文档为例:
<div id="container">
<div id="helloWorld"></div>
</div>
现在通过下面的DOM操作,来实现hellWorld节点的删除操作
// 获得待删除的节点
var helloWorld = document.getElementById('helloWorld');
// 调用父节点的removeChild方法来实现自身节点的删除
helloWorld.parentNode.removeChild(helloWorld); // 注意参数
如上代码执行成功后,文档将会发生如下变化:
<div id="container"></div>
四、DOM相关的几个重要对象
关于DOM操作,浏览器提供了较多内置对象,具体可以从下面文档中得到参考:
但是本节只简单介绍几个比较典型的对象。
1、window对象
包括了整个WEB页面中所有可执行的Javascript API;另外,可详细了解alert()、confirm()、prompt()、close()、onload()
2、document对象
DOM编程中,最常用的对象,可详细了解cookie、title、URL、write()、getElementById()
3、navigator对象
通过此对象可以获取操作系统信息、浏览器信息等,在HTML5中,还可以获取地理位置、联网状态等; 可详细了解platform、userAgent、geolocation、onLine
4、location对象
包含当前页面URL相关的信息,并能控制页面的跳转等操作,可详细了解href、hash、search、protocal、reload()、replace(); 另外,location.href值等同于document.URL
五、实例分析
- Feed数据的下拉无限加载:http://hi.baidu.com/
- Fehelper首页上的图片幻灯切换功能:http://www.baidufe.com/fehelper
- 无尽的隧道:http://js1k.com/2013-spring/demo/1555
用截止目前讲到的DOM操作,分析一下上面实例的实现原理。
第二章:DOM事件模型
一、事件的分类
1、鼠标事件
click、dbclick、mousedown、mouseup、mouseover、mousemove、mouseout
2、键盘事件
kyedown、keyup、keypress
3、HTML事件
load、error、select、change、reset、submit、resize、scroll、focus、blur
值得注意的是,不是所有HTML Element都具备这些事件,比如load事件,只有window对象、img对象才具备; select、change只有部分表单元素才具备
二、添加事件处理程序
给DOM节点增加事件处理程序也有多种方法,本节都将进行简单介绍
1、通过节点属性显式声明
如下示例代码,表示直接在HTML中,显式地为按钮绑定了click事件,当该按钮有用户点击行为时,便会触发myClickFunc方法
<button id="btnHello" onclick="myClickFunc()">ClickMe</button>
myClickFunc的定义则在js中完成,示例如下:
// 事件处理程序的定义
var myClickFunc = function(evt){
// TODO ...
};
2、通过节点属性动态绑定
这种事件处理程序的绑定,属于第一种的变种形式
<button id="btnHello">ClickMe</button>
通过DOM操作进行动态绑定:
// 事件处理程序的定义
var myClickFunc = function(evt){
// TODO ...
};
// 直接给DOM节点的 onclick 方法赋值,注意这里接收的是一个function
document.getElementById('btnHello').onclick = myClickFunc;
3、通过事件监听的方式
相比而言,是事件处理程序的升级模式,是最靠谱的绑定方式,并且能给DOM节点增加多个事件监听,但不能绑定多个onclick事件
<button id="btnHello">ClickMe</button>
通过DOM操作进行动态绑定:
// 获取btnHello节点
var btnHello = document.getElementById('btnHello');
// 增加第一个 click 事件监听处理程序
btnHello.addEventListener('click',function(evt){
// TODO sth 1...
},false);
// 增加第二个 click 事件监听处理程序
btnHello.addEventListener('click',function(evt){
// TODO sth 2...
},false);
BTW:通过此中形式,可以给btnHello按钮绑定任意多个click监听; 注意,执行顺序与添加顺序相关
三、移除事件处理程序
移除的方法必须和添加的方法相对应
如果事件是通过onclick的方式来绑定的,要删除该处理程序,可重新给onclick赋值即可,示例如下:
document.getElementById('btnHello').onclick = function(){}; // 空function即可
如果事件是通过addEventListener监听方式绑定的,要删除处理程序,需要用removeEventListener进行解绑,示例如下:
// 获取btnHello节点 var btnHello = document.getElementById('btnHello'); // 定义监听处理程序 var myClickFunc1 = function(evt){ // TODO ... }; // 增加事件监听 btnHello.addEventListener('click',myClickFunc1,false); // 指定句柄 // 移除事件监听 btnHello.removeEventListener('click',myClickFunc1,false); // 指定句柄
四、深入Event对象
Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。 事件通常与函数结合使用,函数不会在事件发生前被执行!
1、Event对象的一些重要属性和方法
键盘的相关状态
altKey(Alt键是否被按下)、ctrlKey(Ctrl键是否被按下)、metaKey(Meta键是否被按下)、 shiftKey(Shift键是否被按下)、keyCode/which(可通过键盘码判断出具体是那个键被按下)
鼠标的相关状态
button(判断是那个键被按下:左键、右键、中键)、clientX/clienY/screenX/screenY/offsetX/offsetY/x/y (获取事件触发时鼠标的具体位置)
其他标准属性与方法
type、target、timeStamp、bubbles、cancelable、currentTarget、preventDefault()、stopPropagation()
2、事件冒泡(传播)
事件触发时,会从目标DOM元素向上传播,直到文档根节点,一般情况下,会是如下形式传播:
targetDOM → parentNode → ... → body → document → window
如果希望一次事件触发能在整个DOM树上都得到响应,那么就需要用到事件冒泡的机制,如下示例代码:
<button id="btnHello">ClickMe</button>
// 给按钮增加click监听
document.getElementById('btnHello').addEventListener('click',function(evt){
alert('button clicked');
},false);
// 给body增加click监听
document.body.addEventListener('click',function(evt){
alert('body clicked');
},false);
在这种情况下,点击按钮“ClickMe”后,其自身的click事件会被触发,同时,该事件将会继续向上传播, 所有的祖先节点都将得到事件的触发命令,并立即触发自己的click事件;所以如上代码,将会连续弹出两个alert
// 给按钮增加click监听
document.getElementById('btnHello').addEventListener('click',function(evt){
alert('button clicked');
evt.stopPropagation(); // 这条命令就能将此次事件完全结束掉,阻止冒泡
},false);
// 给body增加click监听
document.body.addEventListener('click',function(evt){
alert('body clicked');
},false);
此时,点击按钮后,只会触发按钮本身的click事件,得到一个alert效果;该按钮的点击事件,不会向上传播,body节点就接收不到此次事件命令。
有两点需要注意:
- 不是所有的事件都能冒泡,如:blur、focus、load、unload都不能
- 不同的浏览器,阻止冒泡的方式也不一样,在w3c标准中,通过event.stopPropagation()完成, 在IE中则是通过自身的event.cancelBubble=true来完成
3、事件默认行为
在项目过程中,我们会遇到各种各样的需求,比如:
- 点击form表单中的submit按钮后,表单不允许提交
- 点击a标签后,不允许发送页面跳转
- 对键盘进行控制,禁止输入
其实,这些需求都可以通过阻止事件的默认行为来完成,具体可以看下面的这个示例:
<a id="aBaiduFe" href="http://www.baidufe.com">BaiduFe</a>
要求:点击BaiduFe链接后,页面不能发生跳转,而是弹出一个alert
// 给a标签增加click事件监听
document.getElementById('aBaiduFe').addEventListener('click',function(evt){
alert('对不起,页面不会发生跳转!');
evt.preventDefault(); // 阻止默认行为
},false);
BTW:其他情况均可通过这样的方式进行事件默认行为的阻止,至于阻止的方法,在不同浏览器中会存在差异,后续章节将会介绍兼容性的处理办法
五、实例分析
- 百度图片的识图功能:http://shitu.baidu.com/
- Web QQ:http://web2.qq.com/
用截止目前学到的DOM操作 & 事件模型 知识,分析如上实例的实现原理
第三章:浏览器兼容性处理与错误调试技巧
一、浏览器类型判断
在要处理浏览器兼容性之前,必须先知道如何判断浏览器类型,下面是一个判断浏览器类型的简单示例:
// 通过userAgent判断浏览器
var browserInfo = (function(){
var userAgent = navigator.userAgent.toLowerCase();
return {
version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
webkit: /webkit/.test( userAgent ),
opera: /opera/.test( userAgent ),
msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
mozilla: /mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)
};
})();
比如,要判断当前浏览器是否为IE6,则可通过如下方式得到:
var isIE6 = browserInfo.msie && parseInt(browserInfo.version,10) === 6
二、一些常见的浏览器兼容性问题
在IE浏览器中,如果在</body>之前通过DOM操作appendChild,则会抛出权限不足的Error,而其他浏览器正常
解决办法:所有DOM相关操作尽量都放在文档加载完成之后执行,比如window.onload后
在IE浏览器中,支持innerText属性的读写,而在其他部分浏览器中,不存在该属性,如Firefox
解决办法:
if(browserInfo.msie){ helloWorld.innerText = '我是IE浏览器,我支持innerText'; }else{ helloWorld.textContent = '我不是IE浏览器,我可以用textContent来取代'; }
在IE浏览器中,自定义的属性也可以直接通过属性名读取,但在其他部分浏览器中,则不行
解决办法:所有自定义属性的获取,都通过element.getAttribute(attributeName)的方式读取
在IE中event对象有srcElement属性,但是没有target属性; 在其他浏览器中event对象有target属性,但是没有srcElement属性
解决办法:用obj = event.srcElement ? event.srcElement : event.target;来解决浏览器兼容性问题
IE浏览器和其他浏览器对event对象的获取方式不同,IE中可以直接通过window.event获得, 而在其他浏览器中则必须通过事件参数获得。解决办法:
// 事件绑定程序 document.getElementById('btnHello').onclick = function(evt){ // evt是非IE浏览器的获取方式,window.event是IE浏览器的获取方式 var eventObj = evt || window.event; // TODO ... };
IE浏览器和其他浏览器对DOM节点增加事件监听的方式存在差异。解决办法:对addEventListener方法进行封装再使用
var addEvent = function(element,eventType,eventHandler){ if(window.addEventListener){ element.addEventListener(eventType,eventHandler,false); }else if(window.attachEvent){ element.attachEvent(eventType,eventHandler,false); }else{ element['on' + eventType] = eventHandler; } }
常见的一些浏览器兼容性问题,可以通过一些工具来进行检测, 如:WEB前端助手(FeHelper)
三、错误调试技巧
本节将重点现场演练Chrome DevTool的使用方法
Chrome devTool命令行API:
四、随堂综合练习
如下是2013年Google I/O大会上关于实时WEB应用的一个简短视频,在多个端(pad、phone、pc)上同时打开网页,并拼接一个环形赛车跑道,玩家可以在这个跑道上进行赛车游戏。请看完视频后,结合本次DOM编程的相关知识,分析该功能的实现原理
五、课后作业
Q/A
/
#