Javascript and the DOM系列教程(二)

欢迎来到Javascript and the DOM系列教程的第二讲,本系列教程将为大家介绍有关Javascript和DOM API的核心内容.上一讲我们温习了一些Javascript和DOM的基础知识,这一讲我们来学习一下如何操作页面元素以及浏览器事件模型.请留意本系列系列的其他部分.
Author: James Padolsey Translate: 德古拉
操作页面元素
上一讲中我们已经提到了如何访问DOM节点的方法,现在我们来看看如何来操作一个节点的属性(property). 每一个DOM节点都有若干属性,每个属性对应着相应的功能.如你可以改变一个id为’intro’的段落元素的颜色属性:
document.getElementById('intro').style.color = '#FF0000';
一种更易于理解的写法是,降每个对象的值付给一个变量:
var myDocument = document;
var myIntro = myDocument.getElementById('intro');
var myIntroStyles = myIntro.style;
// And now, we can set the color:
myIntroStyles.color = '#FF0000';
更改myIntroStyles的其他属性:
myIntroStyles.padding = '2px 3px 0 3px'; myIntroStyles.backgroundColor = '#FFF'; myIntroStyles.marginTop = '20px';
在Javascript中横线(’-')是代表减号,所以下面的写法是错误的,正确的写法是paddingTop:
myIntroStyles.padding-top = '10em'; // Produces a syntax error: // - The '-' character is the minus operator in JavaScript. // - Additionally, there's no such property name.
可以使用数组的方式来访问元素的属性,下面的方法就是一个很好的例子:
function changeStyle(elem, property, val) {
elem.style[property] = val; // Notice the square brackets used to access the property
}
// You would use the above plugin like this:
var myIntro = document.getElementById('intro'); // Grab Intro paragraph
changeStyle(myIntro, 'color', 'red');
This is just an example – to be honest it’s probably not a very useful function since, syntactically, it’s quicker to use the conventional means shown earlier (e.g. elem.style.color = ‘red’).
除了style属性,还有很多其他属性可以操作,使用firefox的firebug插件,它的’inspecting an element’功能可以将DOM节点的所有属性都罗列出来.如下图:
Firebug中的DOM属性
其中所有的属性都可以用对象+.的方式来访问.并不是所有的属性都是原始的数据类型,如style就是一个对象,它包含自己的属性.有一些元素的属性是只读的,也就是说不能改变他们的值.比如你不能指定一个节点的’parentNode’属性.
一种非常普遍的改变元素内容的方法:innerHTML:
var myIntro = document.getElementById('intro');
// Replacing current content with new content:
myIntro.innerHTML = 'New content for the <strong>amazing</strong> paragraph!';
// Adding to current content:
myIntro.innerHTML += '... some more content...';
值得一提的是,这个innerHTML方法并没有包含在DOM标准中.如果你不介意的话还是推荐使用它,毕竟它的操作比传动的DOM对象要快的多,我们下面会讲到这一点.
节点(Nodes)
DOM API可以操作很多种节点(节点列表),其中特别要注意的是两类节点:元素节点(element node)和文本节点(text node).
创建元素节点使用 ‘createElement’ ;创建文本节点使用 ‘createTextNode’ 方法:
var myIntro = document.getElementById('intro');
// We want to add some content to the paragraph:
var someText = 'This is the text I want to add';
var textNode = document.createTextNode(someText);
myIntro.appendChild(textNode);
上面的方法使用 ‘appendChild’ 方法添加新的文本节点到段落中,这种方法比’非标准’方法’innerHTML’速度要慢.下面是一个稍微复杂一点的例子:
var myIntro = document.getElementById('intro');
// We want to add a new anchor to the paragraph:
// First, we create the new anchor element:
var myNewLink = document.createElement('a'); // <a/>
myNewLink.href = 'http://google.com'; // <a href="http://google.com"/>
myNewLink.appendChild(document.createTextNode('Visit Google')); // <a href="http://google.com">Visit Google</a>
// Now we can append it to the paragraph:
myIntro.appendChild(myNewLink);
‘insertBefore’ 方法,顾名思义就是在节点前插入元素:
// 'Target' is the element already in the DOM
// 'Bullet' is the element you want to insert
function insertAfter(target, bullet) {
target.nextSibling ?
target.parentNode.insertBefore(bullet, target.nextSibling)
: target.parentNode.appendChild(bullet);
}
// We're using a ternary operator in the above function:
// Its format: CONDITION ? EXPRESSION IF TRUE : EXPRESSION IF FALSE;
事件(Events)
浏览器事件是web应用和大多数javascript高级应用的基础.
注:DOM和javascript是两个不同的概念.浏览器时间是DOM API的一部分,而不是javascript的一部分.Javascript只是提供了一系列操作它的方法.
鼠标事件
- ‘mousedown’ – 鼠标按下.
- ‘mouseup’ – 鼠标放开.
- ‘click’ – 单击时间.
- ‘dblclick’ – 双击事件.
- ‘mouseover’ – 鼠标悬停.
- ‘mouseout’ – 鼠标滑出
- ‘mousemove’ – 鼠标悬停时移动.
键盘事件
- ‘keypress’ – 键盘按键按下.
- ‘keydown’ – 键盘按键点击(在keypress之前触发).
- ‘keyup’ – 键盘按键释放.
表单事件
- ’select’ – 当一个文本区域被选中是触发此事件(input, textarea 等.).
- ‘change’ – 输入框失去焦点或值被更改时触发.
- ’submit’ – 表单提交事件.
- ‘reset’ – 表单重置.
- ‘focus’ – 一个元素得到焦点时触发此事件(如鼠标点击).
- ‘blur’ – 元素失去焦点.
其他事件
- ‘load’ – 当文档(document)内全部内容载入完成后触发,包括(文本内容,图片,框架(frame)等).
- ‘resize’ – 当document resize的时候触发. (如. 浏览器resize.)
- ’scroll’ – document滚动事件.
- ‘unload’ – 离开页面时触发.
以上一些事件只是众多浏览器事件中比较常见的一部分,需要注意的是不同在不同浏览器中,操作事件的方式可能有所不同.有些浏览器还实现了自己私有的事件,如Gecko-specific events 就实现了 ‘DOMContentLoaded’ 和 ‘DOMMouseScroll’ 两个事件,下面是关于Gecko-specific events的更多内容: https://developer.mozilla.org/en/Gecko-Specific_DOM_Events
事件处理
介绍了事件之后,接下来就该看看如何将事件和函数(function)进行绑定.这才是事件机制的意义所在.下面是事件绑定的基本方法:
Basic event registration:
<!-- HTML --> <button id="my-button">Click me!</button>
// JavaScript:
var myElement = document.getElementById('my-button');
// This function will be our event handler:
function buttonClick() {
alert('You just clicked the button!');
}
// This is the event-registration part:
myElement.onclick = buttonClick;
上面的代码将buttonClick函数和元素’my-button’的onclick事件进行了绑定.实现这个功能的传统做法是:
<button onclick="return buttonClick()">Click me!</button>
这种写法的缺点是比较难以维护,相较之下如果使用事件绑定的方法,你可以将代码全部放到外部的’.js’文件中,然后在页面之中进行引入,这种低耦合度的编码方法会给你的网站日后维护带来很多便利. 一个类似的情景是在css中应该避免将style=”"写在元素定义代码中.
高级事件绑定:
不要被’高级’两个字误导了,从而认为这是一种更好的方法.实际上上面的’基础’方法已经可以满足你绝大对数的需要.当你需要绑定多个事件时,才应该考虑下面的方法.
这个模型允许你绑定多个处理函数到同一个事件,也就是说当这个事件触发时,将执行多个函数.同时你也可以移除之前绑定的handler.
有两种事件绑定的模型: W3C 和Microsoft. 除IE之外的所有现代浏览器都支持W3C模型.而IE支持微软模型.先来看一下 W3C’s model:
// FORMAT: target.addEventListener( type, function, useCapture );
// Example:
var myIntro = document.getElementById('intro');
myIntro.addEventListener('click', introClick, false);
在IE里做同样的事情需要微软的模型: (Microsoft’s model):
// FORMAT: target.attachEvent ( 'on' + type, function );
// Example:
var myIntro = document.getElementById('intro');
myIntro.attachEvent('onclick', introClick);
上面用到的 ‘introClick’ 函数:
function introClick() {
alert('You clicked the paragraph!');
}
为了实现跨浏览器的需要,你可以设计一个自己的事件处理函数,如:
function addEvent( elem, type, fn ) {
if (elem.attachEvent) {
elem.attachEvent( 'on' + type, fn);
return;
}
if (elem.addEventListener) {
elem.addEventListener( type, fn, false );
}
}
移除一个事件绑定:
function removeEvent ( elem, type, fn ) {
if (elem.detachEvent) {
elem.detachEvent( 'on' + type, fn);
return;
}
if (elem.removeEventListener) {
elem.removeEventListener( type, fn, false );
}
}
绑定一个匿名的函数:
var myIntro = document.getElementById('intro');
addEvent(myIntro, 'click', function(){
alert('YOU CLICKED ME!!!');
});
如果你想某个方法仅在事件触发的第一次执行,可以在方法执行后立即解除绑定:
// Note that we've already defined the addEvent/removeEvent functions
// (In order to use them they must be included)
var myIntro = document.getElementById('intro');
addEvent(myIntro, 'click', oneClickOnly);
function oneClickOnly() {
alert('WOW!');
removeEvent(myIntro, 'click', oneClickOnly);
}
对于一个匿名方法,我们可以用下面的参数(arguments.callee)来解除对它的绑定:
addEvent(myIntro, 'click', function(){
alert('WOW!');
removeEvent(myIntro, 'click', arguments.callee);
});
‘arguments’ 对象包含了所有函数传递的参数,包括指向函数本身的一个引用(callee).有了这个引用我们就可以放心大胆的使用匿名方法了.
除了语法差别之外,W3C和微软的标准还有其他一些差异.应该知道一点是this引用使用方法的区别:
function myEventHandler() {
this.style.display = 'none';
}
// Works correctly, 'this' 引用指向当前元素 myIntro:
myIntro.onclick = myEventHandler;
// Works correctly, 'this' 引用指向当前元素 myIntro:
myIntro.addEventListener('click', myEventHandler, false);
// DOES NOT work correctly, 'this' 引用指向 Window object:
myIntro.attachEvent('onclick', myEventHandler);
解决这个问题有许多方法,最简单的就是使用基本的事件模型(上面第一种方法).如果你确实需要使用’this’引用的话,看看这两个事件绑定的实现: John Resig’s 或者 Dean Edward’s (这个没用到高级事件绑定机制而是自己实现了,牛~).Degula强烈推荐使用上面两个方法,能够给你省去不少麻烦.稍后我也会写一篇关于通用的事件绑定函数的文章.
Event 对象
当一个function被绑定到一个事件(如创建一个event handler),一个事件对象会传递给这个function. 很不幸的是,IE并不支持这种方法,在IE中你必须通过window.event的方式来访问这个对象,下面的代码能够说明一些问题:
function myEventHandler(e) {
// Notice the 'e' argument...
// When this function is called, as a result of the event
// firing, the event object will be passed (in W3C compliant agents)
// Let's make 'e' cross-browser friendly:
e = e || window.event;
// Now we can safely reference 'e' in all modern browsers.
}
// We would bind our function to an event down here...
为了检查’e'对象是否存在,我们使用e = e || window.event来判断,如果’e'是一个伪对象(null,undefined,0等等),则需要对它赋值: e=window.event; 否则我们直接使用’e’. 下面的代码和上面一样:
if (!e) {
e = window.event;
} // No ELSE statement is needed as 'e' will
// already be defined in other browsers
事件对象中许多有用的命令和属性,在不同的浏览器中使用都会有所不同(主要是IE和其他浏览器的差别).举例来说,取消一个事件的默认行为可以使用 ‘preventDefault()’ 方法,而在IE中则必须使用’returnValue’:
function myEventHandler(e) {
e = e || window.event;
// Preventing the default action of an event:
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
}
事件的默认行为是当该事件触发的时候发生的. 如点击一个超链接的时候,默认行为就是浏览器打开’href’中定义的路径.有些时候需要禁用事件的默认行为.
类似于’returnValue’/'preventDefault’ 这种不兼容的情况还有很多,借助于如今的许多Javascript框架可以让你几乎感觉不到这些不兼容的存在,如’e.preventValue’在IE中也将可以使用(框架会自动为IE调用”returnValue’).
Event bubbling
Event bubbling(冒泡), 即当一个事件被触发时发生的连锁效应.并不是所有的事件会发生bubbling.
bubbling的工作机理:一个事件在目标元素上被触发.接下来这个事件将会触发它的所有上级元素的相应事件,(sorry有点拗口,看下面图就清楚了),直到达到最高级元素:

Event bubbling, illustrated
上面的图举例说明了当一个链接点击事件被触发时,Event bubbling的顺序.
<a>—><p>—><div>–><body>(body拥有点击事件的最高级 DOM 元素).
这些事件是按顺序被触发而不是同时发生.
你可以在任何时候中断 Event bubbling 的传递,比如你仅仅想让事件传递到<p>这一层就停止,我们可以使用 “stopPropagation”来实现这个功能(注意IE中使用方法的不同):
function myParagraphEventHandler(e) {
e = e || window.event;
// Stop event from bubbling up:
if(e.stopPropagation) {
// W3C compliant browsers:
e.stopPropagation();
} else {
// IE:
e.cancelBubble = true;
}
}
// The function would be bound to the click event of the paragraph:
// Using our custom-made addEvent function:
addEvent( document.getElementsByTagName('p')[0], 'click', myParagraphEventHandler );
事件代理(Event delegation)
先用一个例子说明一下事件代理的应用场景,假设有一张数据量很大的table,你需要在每个<tr>上绑定click事件,这在执行效率和工作量上都是不能容忍的. 这时就需要用到事件代理了.下面的代码通过事件代理机制解决了上述问题:
var myTable = document.getElementById('my-table');
myTable.onclick = function() {
// Dealing with browser incompatibilities:
e = e || window.event;
var targetNode = e.target || e.srcElement;
// Test if it was a TR that was clicked:
if ( targetNode.nodeName.toLowerCase() === 'tr' ) {
alert ('You clicked a table row!');
}
}
事件代理机制依赖于event bubbling. 如果bubbling在事件到达’table’节点时被终止,事件代理也就无法实现了.
本教程的第二部分到此结束,后续内容请继续关注 Degula’s Blog
翻译来自: 吸血鬼也Coding|Degula’s Blog
欢迎订阅本博客: RSS












