JavaScript 编程艺术的读书笔记(三)
#DOM-CORE & HTML-DOM
对于什么是DOM而言,之前,已经讨论过了,现在我们重新回过头来,分析和思考一下,有关于DOM的一些分类上的区别。
什么是 DOM?
DOM 是 W3C(万维网联盟)的标准。
DOM 定义了访问 HTML 和 XML 文档的标准:
“W3C 文档对象模型 (DOM) 是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。”
W3C DOM 标准被分为 3 个不同的部分:
核心 DOM - 针对任何结构化文档的标准模型
XML DOM - 针对 XML 文档的标准模型
HTML DOM - 针对 HTML 文档的标准模型
上文是w3c对DOM的分类,基本上就是DOM-CORE 和HTML-DOM,XML-DOM不在讨论范围以内,我们暂且不管。
先说DOM-CORE吧
##DOM-CORE
满足软件开发者和Web脚本编写者,访问和操控产品项目中包含的可解析的HTML和XML内容。这个是COM-CORE的定义,从定义上看,HTML-CORE并不是单独提供给javascript的哦,也就是说,其他的语言也是可以通过HTML-CORE来操作DOM的哦,嗯,为了证实这一点,我们就用全世界最好的语言(php
)来写一段。1
<?php
$Doc = new DOMDocument();
$Doc->load("test.html");
$d = $Doc->documentElement;
foreach ($d->childNodes AS $item)
{
print $item->nodeName . " = " . $item->nodeValue . "<br />";
}
?>
这里用到了一段html1
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>test</title>
</head>
<body>
<p>hello world</p>
</body>
</html>
嗯,然后,你就会得到以下的输出:1
#text =
head = test
#text =
body = hello world
#text =
虽然不是很强大,但是也是可以的哦,嗯,以上代码的运行环境在php5以上。更多的内容参考这里。
回到js的上面,DOM-CORE对应的操作其实更多的是体现在DOM-CORE是基于节点的(NODE-BASE)
,其所有的操作都是基于该节点的操作,如下:1
<a href="url" id="id" >this is a a elemt </a>
<script>
//获取 #id 的href属性值
var node = document.getElementById('id');
var url = node.attattributes['href'].value;
//监听 #id 的click事件
node.onclick = function(e){
//do some thing
}
//创建一个节点
document.createElement();
document.createTextNode();
</script>
简单的说,其实HTML-DOM和DOM-CORE并不是一个相互排斥的的关系,而是一个包含和被包含的关系,HTML-DOM是DOM-CORE的对于html操作的子集,但是HTML-DOM还有一点他自己的独特个性。
HTML-DOM
HTML之中特定元素的功能,和恰到好处的、易用的、针对常见性任务的HTML文档操作机制。HTML模块的意义也在于解决了向后兼容的问题。
也就是说,HTML-DOM是专门针对html对HTML-CORE做的优化操作,使之能够更好的为html服务,但是呢,HTML-DOM还有它自己的个性,这个个性在于它有一套自己的体系,它是基于元素(Element-based)
来提供自身的接口的。
看下面的例子。1
<a href="url" id="id" >this is a a elemt </a>
<script>
//获取 #id 的href属性值
var node = document.getElementById('id');
var url = node.getAttributes('href');
//监听 #id 的click事件
node.addEventLisenter(function(e){
//do some thing
});
//创建一个节点
document.createElement();
document.createTextNode();
</script>
嗯,你会发现两者还是长的蛮像的哈,但是,HTML-DOM是有扩充哦,对于HTML-DOM的addEventListener
方法来说,往这个方法添加的点击事件是可以维系成一个点击事件队列的,在队列里面,所有的匿名函数都可以得到执行,但是,对于DOM-CORE来说,他对于点击事件的监听就要low的多了,他并不可以形成一个事件队列而是只会执行最后一次定义的click事件函数,这一点和css的渲染机制是一样的。看下面的例子:
一直html文档中有如下标签1
<a href="url" id="id">click me</a>
DOM—CORE的事件处理是这样的1
var node = document.getElementById('id');
node.onclick = function(e){
// do some thing
}
node.onclick = function(e){
//do other thing
}
然而,最后执行的只有other thing。。。
但是1
node.addEventListener("click",function(){
//do some thing
})
node.addEventListener("click",function(){
//do other thing
})
下的话,两个事件里的函数都是会被执行哦,很明显,HTML-DOM对于DOM-CORE有了自己的进一步优化,并不是单纯的只是包含和被包含哦。
##创建和添加节点
创建和添加节点其实都很简单,使用指定的函数就可以了。1
//创建一个新的元素节点
var a = docuemnt.createElement("p");
var p = document.createTextNode("hello world")
//把p添加到a节点上去
a.appendChild(a);
##总结一下DOM的常用方法
1 | //获取节点 documen.getElementById(id); document.getElementByTagName(tagName); //间接引用节点 //子节点 element.childNode //返回element的所有子节点,通过element.childNodes[i]的方式来调用 Element.firstChild = element.childNodes[0]; Element.lastChild = element.childNodes[element.childNodes.length; //父节点 element.parentNode //下一个兄弟节点 element.nextSibling; //上一个兄弟节点 element.previousSibling; //获得节点信息 //节点名称nodeName,tagName <a class="test"></a> document.getElementById('id').nodeName;//a document.getElementById('id').getAttributes("class").nodeName;//test //nodeType返回节点的类型。元素节点返回1;属性节点返回2;文本节点返回3; //nodeValue返回节点的值。元素节点返回null;属性节点返回undefined;文本节点返回文本内容。 //hasChildNodes()返回布尔值,判断是否含有子节点。 //添加属性 document.setAttributeNode() //获取属性 document.getAttribute() //创建元素节点 document.createElement() //创建文本节点 Document.createTextNode(); //注: document.createTextNode(" ");他不会通过html编码,也就是说这里创建的不是空格,而是字符串 //添加子节点 appendChild()。 parentElement.appendChild(childElement); //插入子节点 node.insertBefore() //使用replaceChild方法取代子结点。 parentNode.replaceChild(newNode,oldNode); //使用cloneNode方法复制结点 ,includeChildren为bool,表示是否复制其子结点 node.cloneNode(includeChildren); //使用removeChild方法删除子结点 parentNode.removeChild(childNode); |
#事件
以下所提都是基于DOM的事件,node部分不在讨论之列。
嗯,其实我们完全可以说,脚本化的html的核心就是对于各种事件的处理,而针对事件化的编程也使得我们的javascript能够将其事件驱动的思想体现出来。
如果想知道都有哪些事件可以处理的话,可以参考如下的代码。1
var log = document.getElementById('event'),
i = '',
out = [];
for (i in window) {
if ( /^on/.test(i)) { out[out.length] = i; }
}
console.log(out);
嗯,会有一大坨的~可以参考这里。
事件处理
添加事件
相对而言处理事件还是比较简单和常见的,正如我们在前面举得很多例子一样,对于事件的处理有两种方式,这里以click
事件为例1
//method one
node.onclick = function(){
//do some thing
}
//method two
node.addEventLisener("click",function(){
//do some thing
})
移除事件
如果想移除某一事件,我们应该这么做。1
var a = function(){
//do some thing
}
node.addEventLinsener("click",a);
node.removeEventListener("click",a);
注意一下,这里并不可以使用匿名函数了。只能将函数事先申明出来。根据这个机制,我们其实可以弄一个一次性函数,实现起来,也很简单。1
var a = function(){
//do some thing
node.removeEventListener('click',a);
}
node.addEventListener('click',a);
事件的触发过程
嗯,我们知道了怎么处理一个事件,或者说监听一个事件,我来看看事件是怎么被触发的。为了更好理解,我从w3c上偷了一张图
嗯,更多信息可以看这里。
嗯,这里有两个名词,一个是事件冒泡,一个是事件捕捉。对于这个东西,在代码上更直白的表述是,addEventListener函数的第三个参数。1
node.addEventLisenter("click",callback,false);
第三个参数如果是true,那么就是在捕捉阶段,如果是false,就是在冒泡阶段。
事件捕捉
正如所见,当我们在 DOM 的某个节点发生了一些操作,比如说点击,这个时候浏览器就会从 Window 发出一个事件查询,不断经过下级节点直到目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)。
所有经过的节点,都会触发这个事件。捕获阶段的任务就是建立这个事件传递路线,以便后面冒泡阶段顺着这条路线返回 Window。
监听某个在捕获阶段触发的事件,需要在事件监听函数传递第三个参数 true。1
element.addEventListener(<event-name>, <callback>, true);
事件冒泡
那么,冒泡的话,就是当事件捕捉完成后的回溯过程。嗯,因为IE
不支持在事件扑捉阶段对事件进行处理,所以为了统一效果,一般情况下,我们在事件冒泡阶段对事件进行处理。1
element.addEventListener(<event-name>, <callback>, false);
大家可以通过这个demo来更深入的体会和了解js的事件处理机制。
事件委托
如上所文,其实,对于一个DOM树上的节点而言,他的每一个事件动作,都不只是和他自己有关系,他的每一个事件都会被其父节点给捕捉到。有的时候,我们经常会遇到如下业务场景。
对下面所有的li
标签进行点击事件监听。1
<ul id="id">
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
<li>e</li>
<li>f</li>
<li>g</li>
<li>h</li>
<li>i</li>
<li>j</li>
<li>k</li>
<li>l</li>
<li>m</li>
<li>n</li>
<li>r</li>
</ul>
嗯,如果是往常的话,我们一般会这么写1
var li = document.getElementById('id').getElementsByTagName('li');
for(var i = 0; i < li.length; i++){
li[i].addEventListener("click",function(e){
// do some thing
},false)
}
但是我们稍微思考一下,就会发现一个问题啊,就是我们这样写事件监听从功能上而言是实现了,但是考虑一下空间复杂度,我们在这里创建了很多其实并不需要的中间变量,其实我们完全可以只在其父元素上进行事件监听,这样一样可以对事件进行处理,但是可以减少很多中间变量。嗯,我们就可以这么写这个代码。1
var ul = document.getElementById('id');
ul.addEvenListener("click",function(e){
if(e.target.tagName.toLocaleLowerCase()==="li"){
//do some thing
}
},false);
嗯,这样的话,我们一样可以处理ul
下面的li
的事件,但是可以减少很多我们并不需要的中间变量。提高代码效率。
这里我们用到了event事件的返回值,是一个属性很丰富的对象。详细的东西,看这里就好了。
事件拦截
事件拦截也是很简单事情吗,当我不想让一个事件向上传递的时候,我们可以把它拦截下来。这样他就不会触发后面的事件了。1
var ul = document.getElementById('id');
ul.addEvenListener("click",function(e){
if(e.target.tagName.toLocaleLowerCase()==="li"){
//do some thing
e.stopPropagation();//阻止事件冒泡
}
},false);
嗯,这个函数是不是很复杂,对,我们用更简单的。1
return false;
事件模拟
有的时候,我们想让代码自动触发一些事件,比如说,刷新完自动展开一个本来需要点击展开的div
。嗯,这里要用到事件对象。取个例子:1
function simulateClick() {
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': true
});//新建一个鼠标点击事件。当然,你也可以用document.createEvent来创建。
var cb = document.getElementById('checkbox');
var canceled = !cb.dispatchEvent(event);//查看事件是否触发了其默认事件
if (canceled) {
// A handler called preventDefault.
alert("canceled");
} else {
// None of the handlers called preventDefault.
alert("not canceled");
}
}
更多东西,看这里
自定义事件
我们可以自定义事件来实现更灵活的开发,事件用好了可以是一件很强大的工具,基于事件的开发有很多优势(后面介绍)。
与自定义事件的函数有 Event、CustomEvent 和 dispatchEvent。
直接自定义事件,使用 Event 构造函数:1
var event = new Event('build');
// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);
// Dispatch the event.
elem.dispatchEvent(event);
CustomEvent 可以创建一个更高度自定义事件,还可以附带一些数据,具体用法如下:1
var myEvent = new CustomEvent(eventname, options);
其中 options 可以是:
{
detail: {
...
},
bubbles: true,
cancelable: false
}
其中 detail 可以存放一些初始化的信息,可以在触发的时候调用。其他属性就是定义该事件是否具有冒泡等等功能。
内置的事件会由浏览器根据某些操作进行触发,自定义的事件就需要人工触发。dispatchEvent 函数就是用来触发某个事件:1
element.dispatchEvent(customEvent);
上面代码表示,在 element 上面触发 customEvent 这个事件。结合起来用就是:1
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
// create and dispatch the event
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}});
obj.dispatchEvent(event);
其实呢,我们还可以这样来新建一个事件1
function func1(){
console.log("func1 is execute");
}
function func2(){
console.log("func2 is execute");
}
var ev=document.createEvent('HTMLEvents');
ev.initEvent('youevent',false,false);
document.addEventListener("youevent",func1,false);
document.addEventListener("youevent",func2,false);
详细内容,参考这个博客。
createEvent的方法并不兼容IE,这一点要注意一下。
有兴趣的还可以看看这些博客,有更深的理解
http://unixpapa.com/js/key.html
http://chajn.org/project/javascript-events-responding-user/
http://yujiangshui.com/javascript-event/