作者:
toyota2006
链接:
http://toyota2006.javaeye.com/blog/39669
发表时间: 2006年12月20日
声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!
引自:
http://www.blogjava.net/renyangok/archive/2006/12/12/87183.html
深入理解abstract class和interface
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。
理解抽象类
abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?
在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。
从语法定义层面看abstract class和interface
在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。
使用abstract class的方式定义Demo抽象类的方式如下:
abstract class Demo {
abstract void method1();
abstract void method2();
…
}
使用interface的方式定义Demo抽象类的方式如下:
interface Demo {
void method1();
void method2();
…
}
在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。
从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。
首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。
在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。
同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。
从设计理念层面看abstract class和interface
上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。
前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。
考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:
使用abstract class方式定义Door:
abstract class Door {
abstract void open();
abstract void close();
}
使用interface方式定义Door:
interface Door {
void open();
void close();
}
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。
如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。
解决方案一:
简单的在Door的定义中增加一个alarm方法,如下:
abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}
或者
interface Door {
void open();
void close();
void alarm();
}
那么具有报警功能的AlarmDoor的定义方式如下:
class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}
或者
class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }
}
这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。
解决方案二:
既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。
显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。
如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。
如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:
abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。
结论
abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法
已有 0 人发表留言,猛击->>这里<<-参与讨论
JavaEye推荐
文章来源:
http://toyota2006.javaeye.com/blog/39669
作者:
toyota2006
链接:
http://toyota2006.javaeye.com/blog/58268
发表时间: 2007年03月08日
声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="/common/taglibs.jsp"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
<link type="text/css" rel="stylesheet"
href="<c:url value="/styles/dojoTable.css"/>">
</head>
<body>
<%@include file="/common/yahooUi.jsp"%>
<link type="text/css" rel="stylesheet" href="templates/dojo.css">
<link type="text/css" rel="stylesheet" href="<c:url value='/styles/style.css'/>">
<script src="<c:url value='/js/prototype.js'/>" type="text/javascript"></script>
<script src="<c:url value='/js/dojo/dojo.js'/>" type="text/javascript"></script>
<script src="<c:url value="/dwr/interface/deptDojoAction.js"/>"
type="text/javascript"></script>
<script src="<c:url value="/dwr/engine.js"/>" type="text/javascript"></script>
<script src="<c:url value="/dwr/util.js"/>" type="text/javascript"></script>
<script>
YAHOO.namespace("dept.dlg");
dojo.require("dojo.lang.*");
dojo.require("dojo.widget.Tree");
dojo.require("dojo.widget.TreeRPCController");
dojo.require("dojo.widget.TreeSelector");
dojo.require("dojo.widget.TreeNode");
dojo.require("dojo.widget.TreeContextMenu");
dojo.require("dojo.widget.*");
dojo.require("dojo.collections.*");
dojo.require("dojo.ex.FilteringTableEx");
/**
* 设置根目录style,并且加载第一级目录
*/
dojo.addOnLoad(function() {
//初始化YahooUI对话框
YAHOO.dept.dlg.Editor = new YAHOO.widget.Dialog("editDeptDlg", {visible:false,
modal:true,
shadow:false,
close: true,
fixedcenter : true ,
width:"300px" } );
YAHOO.dept.dlg.Editor.render();
YAHOO.dept.dlg.manager = new YAHOO.widget.OverlayManager();
YAHOO.dept.dlg.manager.register([YAHOO.dept.dlg.Editor]);
YAHOO.dept.dlg.Editor.beforeHideEvent.fire = resetDlg;//对话框关闭之前的事件。
//设置根节点style
var rootNode = dojo.widget.manager.getWidgetById('root');
rootNode.childIcon.style["width"] = "14px";
rootNode.childIcon.style["height"] = "14px";
//加载第一级目录
loadRemoteChildren(rootNode);
//注册菜单事件
dojo.event.topic.subscribe('treeContextMenuCreate/engage',
function (menuItem) { createClicked( menuItem.getTreeNode()); });
dojo.event.topic.subscribe('treeContextMenuRemove/engage',
function (menuItem) { removeClicked( menuItem.getTreeNode()); });
dojo.event.topic.subscribe('treeContextMenuEdit/engage',
function (menuItem) { editClicked( menuItem.getTreeNode()); });
dojo.event.topic.subscribe('treeContextMenuRefresh/engage',
function (menuItem) { refreshClicked(menuItem.getTreeNode());});
}
);
/**
* 新建部门菜单事件
*/
function createClicked(treeNode) {
var dlgTextNode = document.getElementById("dlgHead");
dlgTextNode.innerHTML = treeNode.title + '--' + '<fmt:message key="global.new" />';
$('parentId').value = treeNode.objectId;
$('treeNodeId').value = treeNode.widgetId;
YAHOO.dept.dlg.Editor.show();
}
/**
* 编辑目录菜单事件
*/
function editClicked(treeNode) {
deptDojoAction.get(treeNode.objectId, function(dept){
var dlgTextNode = document.getElementById("dlgHead");
dlgTextNode.innerHTML = treeNode.title + '--' + '<fmt:message key="global.edit" />';
DWRUtil.setValue("treeNodeId", treeNode.widgetId);
DWRUtil.setValue("name", dept.name);
DWRUtil.setValue("id", dept.id);
DWRUtil.setValue("descn",dept.descn);
YAHOO.dept.dlg.Editor.show();
});
}
/**
* 删除部门菜单事件
*/
function removeClicked(treeNode) {
if(confirm('<fmt:message key="global.delete.warn"/>')) {
deptDojoAction.removeDept(treeNode.objectId,
function() {
if(!treeNode.children || treeNode.children.length == 0){
treeNode.tree.removeNode(treeNode);
} else {
alert('<fmt:message key="dept.delete.warn"/>');
}
});
}
}
/**
* 在给定的父节点下,建立子节点.
*/
function buildChildNode(parent, nodeData) {
node = dojo.widget.createWidget(parent.widgetType, nodeData);
node.childIconSrc = '<c:url value="/images/icons/foldericon.png"/>';
//isFolder是node一个内置属性,缺省为false在树中是不会显示的,为true时才会显示出来
node.isFolder = true;
parent.addChild(node);
node.childIcon.style["width"] = "16px";
node.childIcon.style["height"] = "16px";
return node;
}
/**
* 根据给定的父节点,展开子节点。远程调用DWR函数。
*/
function loadRemoteChildren(parent) {
deptDojoAction.getDeptsByParentId(parent.objectId, function(children) {
for(var i=0; i<children.length; i++) {
buildChildNode(parent, children[i]);
}
parent.state = parent.loadStates.LOADED;
});
parent.expand();
}
/**
* 展开子节点
*/
function getChildren(node, sync, callObj, callFunc) {
nodeJSON = this.getInfo(node);
var children = loadRemoteChildren(node);
$('deptMsg').style.display = 'none';
}
/**
* 调用DWR函数,保存编辑结果
*/
function onSave() {
var formValues = DWRUtil.getValues("editDeptFrm");
if(formValues.id == "" || !formValues.id){//当id不存在时,表示新建
deptDojoAction.saveDept(formValues.parentId,
{name: formValues.name,
descn: formValues.descn},
function(newId) {
var treeNode = dojo.widget.byId(formValues.treeNodeId);
if(treeNode) {//当前节点下加入一个新节点
if(treeNode.state == treeNode.loadStates.LOADED) {
var child =
buildChildNode(treeNode, {title:formValues.name,
objectId:newId,
isFolder:true});
//child.isFolder = true;
}
}
});
}
else { //如果id存在则表示更新
deptDojoAction.updateDept({id: formValues.id,
name: formValues.name,
descn: formValues.descn},
function(id){
var treeNode = dojo.widget.byId(formValues.treeNodeId);
if(treeNode) {//更新节点
treeNode.edit({title:formValues.name});
}
});
}
YAHOO.dept.dlg.Editor.hide();
}
/**
* 刷新树形节点
*/
function refreshClicked(node) {
//更新当前节点
deptDojoAction.get(node.objectId, function(dept) {
node.edit({title: dept.name, objectId: dept.id})
});
//如果没有子节点,则直接返回
if(!node.children || node.children.length == 0) {
return;
}
//首先要删除所有子节点,删除的时候要将子节点们复制到一个数组
//如果不这样,而采用dojo.lang.forEach(node.children...
//则相当于修改了循环下标
var nodes = new Array;
for(i = 0; i < node.children.length; i ++) {
//hasChildren用于标记节点是否已经展开,如果展开则加载子节点,但是,本次并未实现该功能
nodes[i] = {body : node.children[i],
hasChildren : (node.children[i].chilren && node.children[i].chilren.length > 0)};
}
dojo.lang.forEach(nodes, function(elem){elem.body.tree.removeNode(elem.body);});
//重新加载子节点
loadRemoteChildren(node);
}
/**
* 移动(Drag and Drop)一个节点到新的节点下
*/
function moveNode(child, newParent, index) {
alert("childId=" + child.objectId + ";\nnewParentId=" + newParent.objectId + ";\nIndex=" + index);
}
var files = new dojo.collections.ArrayList();
function dblselect(message) {
alert();
}
/**
* 选中一个节点的时候触发的事件。
*/
function onSelectNode(message) {
var node = message.source;
var e = message.event;
var table = dojo.widget.byId('files');
if(node) {
//alert(node.objectId + node.object);
$('deptMsg').style.display = 'block';
//$('deptDesc').innerHTML = node.object;
DWRUtil.setValue('deptDesc',node.object);
//var aa="";
deptDojoAction.getDeptsNameByParentId(node.objectId, function(children) {
for(var i=0; i<children.length; i++) {
var file = {};
file.filename = children[i];
files.add(file);
}
//alert(aa);
table.store.setData(files.toArray());
files.clear();
});
}
doSelect(message, 'treeSelector');//使Tree恢复选中状态
}
/**
* Copy dojo的源代码,使得Tree恢复选中状态
*/
function doSelect(message, treeSelectorId) {
var node = message.source;
var e = message.event;
var selector = dojo.widget.byId(treeSelectorId);
if (selector.selectedNode === node) {
if(e.ctrlKey || e.shiftKey || e.metaKey){
selector.deselect();
return;
}
dojo.event.topic.publish(this.eventNames.dblselect, { node: node });
return;
}
if (selector.selectedNode) {
selector.deselect();
}
selector.doSelect(node);
dojo.event.topic.publish(selector.eventNames.select, {node: node} );
}
/**
* 复选框选中事件。选中复选框,则清空DatePicker
*/
function neverExpired(never) {
if(never) {
resetDataPicker();
}
}
/**
* 重新设置对话框
*/
function resetDlg() {
DWRUtil.setValues({parentId: "",
id: "",
treeNodeId: ""}, "editDeptFrm");
DWRUtil.setValue("name", "");
DWRUtil.setValue("descn","");
}
</script>
<table width="100%" cellspacing="10px" cellpadding="10px">
<tr>
<td width="50%" valign="top">
<table width="100%" border="0" cellpadding="0" cellspacing="1" bgcolor="#6290d2">
<tr><td bgcolor="#E8F2FE" height="20px"><fmt:message key='dept.tree'/></td></tr>
<tr>
<td style="padding-top:10px;padding-right:10px;" bgcolor="#ffffff">
<div dojoType="TreeRPCController" RPCUrl=""
widgetId="deptTreeController" DNDController="create"
loadRemote="getChildren" doMove="moveNode"></div>
<div dojoType="TreeSelector" widgetId="treeSelector"
select="onSelectNode" dblselect="dblselect"></div>
<!-- 上下文菜单 -->
<div dojoType="TreeContextMenu" toggle="explode"
contextMenuForWindow="false" widgetId="treeContextMenu"><!-- 新建目录 -->
<div dojoType="TreeMenuItem" treeActions="addChild"
iconSrc="<c:url value='/images/icons/folder_add.gif'/>"
widgetId="treeContextMenuCreate"
caption="<fmt:message key='global.new'/>"></div>
<!-- 删除目录 -->
<div dojoType="TreeMenuItem" treeActions="remove"
iconSrc="<c:url value='/images/icons/folder_remove.gif'/>"
widgetId="treeContextMenuRemove"
caption="<fmt:message key='global.remove'/>"></div>
<!-- 编辑目录 -->
<div dojoType="TreeMenuItem" treeActions="remove"
iconSrc="<c:url value='/images/icons/folder_edit.gif'/>"
widgetId="treeContextMenuEdit"
caption="<fmt:message key='global.edit'/>"></div>
<div dojoType="MenuSeparator2"></div>
<div dojoType="TreeMenuItem" widgetId="treeContextMenuRefresh"
caption="<fmt:message key='cms.refresh'/>"></div>
</div>
<!-- 树形目录 -->
<div dojoType="Tree" menu="treeContextMenu" DNDMode="between"
selector="treeSelector" widgetId="deptTree"
DNDAcceptTypes="deptTree" controller="deptTreeController"><!-- 树形目录根节点 -->
<div dojoType="TreeNode" title="<fmt:message key='dept.root'/>"
widgetId="root" isFolder="true"
childIconSrc="<c:url value='/images/icons/home_1.gif'/>" objectId="-1"></div>
</div>
<div id="editDeptDlg" style="visibility:hidden">
<div class="hd" id="dlgHead" style="font:12px"></div>
<div class="bd" id="dlgBody" style="font:12px">
<form id="editDeptFrm" onsubmit="return false">
<input type="hidden" id="parentId" name="parentId"/>
<input type="hidden" id="id" name="id"/>
<input type="hidden" id="treeNodeId" name="treeNodeId"/>
<br>
<table width="100%">
<tr>
<td width="20%" align="right"><fmt:message key="dept.name"/>:</td>
<td><input id="name" name="name" type="text" size="28" class="thin-input"/></td>
<td width=10%" align="left"><font color="#990000">*</font></td>
</tr>
<tr>
<td width="20%" align="right"><fmt:message key="dept.descn"/>:</td>
<td><textarea id="descn" name="descn" rows="3" cols="30" class="thin-input"></textarea></td>
<td></td>
</tr>
<tr>
<td width="20%"></td>
<td>
<table>
<tr>
<td width="20%" align="right">
<input type="button" value="<fmt:message key='global.save'/>"
onclick="onSave()"/>
</td>
<td>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</div>
<div class="ft" id="dlgFoot" style="font:12px;"></div>
</div>
</td>
</tr>
</table>
</td>
<td valign="top">
<table width="100%" border="0" cellpadding="0" cellspacing="1" bgcolor="#6290d2">
<tr><td bgcolor="#E8F2FE" height="20px" ><fmt:message key='dept.msg'/> : </td></tr>
<tr>
<td>
<div id="deptDesc" style="height:50px; border-width:10px; color='#003366'; border='1px solid #6290D2'; background-color: #ffffff"></div>
</td>
</tr>
<tr>
<td bgcolor="#ffffff">
<div id='deptMsg' class='dojoTable' style="display:none;">
<table dojoType="FilteringTableEx" widgetId="files" id="files" minRows="0"
valueField="filename" headerClass="tableHeader"
tbodyClass="tableBody" rowAlternateClass="odd"
headerUpClass="tableHeaderSort" headerDownClass="tableHeaderSort"
alternateRows="true" cellpadding="0" cellspacing="0" border="0"
class='tableRegion' style="width:100%">
<thead>
<tr>
<th field="filename" dataType="String" class="tableHeader" align="center" ><fmt:message key="dept.subList"/></th>
</tr>
</thead>
</table>
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
已有 0 人发表留言,猛击->>这里<<-参与讨论
JavaEye推荐
文章来源:
http://toyota2006.javaeye.com/blog/58268
作者:
toyota2006
链接:
http://toyota2006.javaeye.com/blog/59311
发表时间: 2007年03月12日
声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!
Javascript的IE和Firefox兼容性汇编
以下以 IE 代替 Internet Explorer,以 MF 代替 Mozzila Firefox。
document.form.item 问题
现有问题:
现有代码中存在许多 document.formName.item("itemName") 这样的语句,不能在 MF 下运行
解决方法:
改用 document.formName.elements["elementName"]
集合类对象问题
现有问题:
现有代码中许多集合类对象取用时使用 (),IE 能接受,MF 不能。
解决方法:
改用 [] 作为下标运算。如:document.forms("formName") 改为 document.forms["formName"]。
又如:document.getElementsByName("inputName")(1) 改为 document.getElementsByName("inputName")[1]
window.event
现有问题:
使用 window.event 无法在 MF 上运行
解决方法:
MF 的 event 只能在事件发生的现场使用,此问题暂无法解决。可以这样变通:
原代码(可在IE中运行):
<input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit()"/>
<script language="javascript">
function gotoSubmit() {
alert(window.event); // use window.event
}
</script>
新代码(可在IE和MF中运行):
<input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit(event)"/>
<script language="javascript">
function gotoSubmit(evt) {
evt = evt ? evt : (window.event ? window.event : null);
alert(evt); // use evt
}
</script>
此外,如果新代码中第一行不改,与老代码一样的话(即 gotoSubmit 调用没有给参数),则仍然只能在IE中运行,但不会出错。所以,这种方案 tpl 部分仍与老代码兼容。
HTML 对象的 id 作为对象名的问题
现有问题:
在 IE 中,HTML 对象的 ID 可以作为 document 的下属对象变量名直接使用。在 MF 中不能。
解决方法:
用 getElementById("idName") 代替 idName 作为对象变量使用。
用idName字符串取得对象的问题
现有问题:
在IE中,利用 eval(idName) 可以取得 id 为 idName 的 HTML 对象,在MF 中不能。
解决方法:
用 getElementById(idName) 代替 eval(idName)。
变量名与某 HTML 对象 id 相同的问题
现有问题:
在 MF 中,因为对象 id 不作为 HTML 对象的名称,所以可以使用与 HTML 对象 id 相同的变量名,IE 中不能。
解决方法:
在声明变量时,一律加上 var ,以避免歧义,这样在 IE 中亦可正常运行。
此外,最好不要取与 HTML 对象 id 相同的变量名,以减少错误。
event.x 与 event.y 问题
现有问题:
在IE 中,event 对象有 x, y 属性,MF中没有。
解决方法:
在MF中,与event.x 等效的是 event.pageX。但event.pageX IE中没有。
故采用 event.clientX 代替 event.x。在IE 中也有这个变量。
event.clientX 与 event.pageX 有微妙的差别(当整个页面有滚动条的时候),不过大多数时候是等效的。
如果要完全一样,可以稍麻烦些:
mX = event.x ? event.x : event.pageX;
然后用 mX 代替 event.x
其它:
event.layerX 在 IE 与 MF 中都有,具体意义有无差别尚未试验。
关于frame
现有问题:
在 IE中 可以用window.testFrame取得该frame,mf中不行
解决方法:
在frame的使用方面mf和ie的最主要的区别是:
如果在frame标签中书写了以下属性:
<frame src="/xx.htm" id="frameId" name="frameName" />
那么ie可以通过id或者name访问这个frame对应的window对象
而mf只可以通过name来访问这个frame对应的window对象
例如如果上述frame标签写在最上层的window里面的htm里面,那么可以这样访问
ie: window.top.frameId或者window.top.frameName来访问这个window对象
mf: 只能这样window.top.frameName来访问这个window对象
另外,在mf和ie中都可以使用window.top.document.getElementById("frameId")来访问frame标签
并且可以通过window.top.document.getElementById("testFrame").src = 'xx.htm'来切换frame的内容
也都可以通过window.top.frameName.location = 'xx.htm'来切换frame的内容
在mf中,自己定义的属性必须getAttribute()取得
父结点的问题
在mf中没有 parentElement parement.children 而用 parentNode parentNode.childNodes
childNodes的下标的含义在IE和MF中不同,MF使用DOM规范,childNodes中会插入空白文本节点。
一般可以通过node.getElementsByTagName()来回避这个问题。当html中节点缺失时,IE和MF对parentNode的解释不同,例如
<form>
<table>
<input/>
</table>
</form>
MF中input.parentNode的值为form, 而IE中input.parentNode的值为空节点
MF中节点没有removeNode方法,必须使用如下方法 node.parentNode.removeChild(node)
const 问题
现有问题:
在 IE 中不能使用 const 关键字。如 const constVar = 32; 在IE中这是语法错误。
解决方法:
不使用 const ,以 var 代替。
body 对象
MF的body在body标签没有被浏览器完全读入之前就存在,而IE则必须在body完全被读入之后才存在
url encoding
在js中如果书写url就直接写&不要写&例如var url = 'xx.jsp?objectName=xx&objectEvent=xxx';
frm.action = url那么很有可能url不会被正常显示以至于参数没有正确的传到服务器
一般会服务器报错参数没有找到
当然如果是在tpl中例外,因为tpl中符合xml规范,要求&书写为&
一般MF无法识别js中的&
nodeName 和 tagName 问题
现有问题:
在MF中,所有节点均有 nodeName 值,但 textNode 没有 tagName 值。在 IE 中,nodeName 的使用好象
有问题(具体情况没有测试,但我的IE已经死了好几次)。
解决方法:
使用 tagName,但应检测其是否为空。
元素属性
IE下 input.type属性为只读,但是MF下可以修改
document.getElementsByName() 和 document.all[name] 的问题
现有问题:
在 IE 中,getElementsByName()、document.all[name] 均不能用来取得 div 元素(是否还有其它不能取的元素还不知道)。
已有 0 人发表留言,猛击->>这里<<-参与讨论
JavaEye推荐
文章来源:
http://toyota2006.javaeye.com/blog/59311