首页 > javascript > Javascript and the DOM系列教程(二)

Javascript and the DOM系列教程(二)

2009年4月2日

     欢迎来到Javascript and the DOM系列教程的第二讲,本系列教程将为大家介绍有关Javascript和DOM API的核心内容.上一讲我们温习了一些Javascript和DOM的基础知识,这一讲我们来学习一下如何操作页面元素以及浏览器事件模型.请留意本系列系列的其他部分.

Javascript and the 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节点的所有属性都罗列出来.如下图:

DOM Element properties, in the Firebug addon for Firefox 
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 graphic

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

收藏到:
  • del.icio.us
  • Google
  • Digg
  • Baidu
  • QQ
  • TwitThis
  • Facebook
  • fanfou
  • digbuzz
  • 365key
  • poco
  • quzhai
  • sina

javascript ,

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.