WEB前端开发之DOM编程
百度Web前端高级工程师 赵先烈(@阿烈先生)
Site: http://www.baidufe.com
开课时间:2013-07-24
开课地点:清华大学 六教6A203

课程大纲

第一章:DOM基础

一、认识DOM

1、DOM是什么?

Document Object Model,文档对象模型,可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构,是表示和处理一个HTML或XML文档的常用方法。

2、DOM能做什么?

能让网页实现更丰富的交互效果,下面随便列几个网站看欣赏一下DOM的奇幻魅力!

  1. Feed数据的下拉无限加载:http://hi.baidu.com/
  2. Fehelper首页上的图片幻灯切换功能:http://www.baidufe.com/fehelper
  3. 无尽的隧道:http://js1k.com/2013-spring/demo/1555
  4. 百度图片的识图功能:http://shitu.baidu.com/
  5. Web QQ:http://web2.qq.com/

二、DOM节点

一个完整的网页,就是由不同类型的HTML DOM节点,按照一定的依赖规则组合而成的。

1、DOM中对节点的定义

2、节点类型

DOM节点类型

实例分析

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树体现出来

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="图片" />';
        

实例分析

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>
        

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

五、实例分析

  1. Feed数据的下拉无限加载:http://hi.baidu.com/
  2. Fehelper首页上的图片幻灯切换功能:http://www.baidufe.com/fehelper
  3. 无尽的隧道: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监听; 注意,执行顺序与添加顺序相关

实例分析

三、移除事件处理程序

移除的方法必须和添加的方法相对应

实例分析

四、深入Event对象

Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。 事件通常与函数结合使用,函数不会在事件发生前被执行!

1、Event对象的一些重要属性和方法

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节点就接收不到此次事件命令。

有两点需要注意:

实例分析

3、事件默认行为

在项目过程中,我们会遇到各种各样的需求,比如:

其实,这些需求都可以通过阻止事件的默认行为来完成,具体可以看下面的这个示例:

<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:其他情况均可通过这样的方式进行事件默认行为的阻止,至于阻止的方法,在不同浏览器中会存在差异,后续章节将会介绍兼容性的处理办法

实例分析

五、实例分析

  1. 百度图片的识图功能:http://shitu.baidu.com/
  2. 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

二、一些常见的浏览器兼容性问题

常见的一些浏览器兼容性问题,可以通过一些工具来进行检测, 如:WEB前端助手(FeHelper)

fehelper

三、错误调试技巧

本节将重点现场演练Chrome DevTool的使用方法

chrome开发者工具

Chrome devTool命令行API:

  1. console api
  2. command line api

四、随堂综合练习

如下是2013年Google I/O大会上关于实时WEB应用的一个简短视频,在多个端(pad、phone、pc)上同时打开网页,并拼接一个环形赛车跑道,玩家可以在这个跑道上进行赛车游戏。请看完视频后,结合本次DOM编程的相关知识,分析该功能的实现原理

五、课后作业

//www.baidufe.com/DOMScripting/work.html

Q/A

/

#