[收藏]Javascript循环元素并绑定事件

废话不多说,直接上代码!具体原理请参考JS闭包原理

<!DOCTYPE html PUBLIC ”-//W3C//DTD XHTML 1.0 Transitional//EN” ”http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
<html xmlns=“http://www.w3.org/1999/xhtml” >
<head>
<title>Untitled Page</title>
</head>
<body>
<ul id=“list”>
<li>第1条记录</li>
<li>第2条记录</li>
<li>第3条记录</li>
<li>第4条记录</li>
<li>第5条记录</li>
<li>第6条记录</li>
</ul>

<script type=“text/javascript”>
var list_obj = document.getElementById(“list”).getElementsByTagName(“li”); //获取list下面的所有li的对象数组
for (var i = 0; i <= list_obj.length; i++) {
(function(){
var p = i
list_obj[i].onmousemove = function() {
this.style.backgroundColor = “#cdcdcd”;
}
list_obj[i].onmouseout = function() {
this.style.backgroundColor = “#FFFFFF”;
}
list_obj[i].onclick = function() {
alert(“这是第” + p + “记录”);
}
})()
}
</script>

</body>
</html>

[收藏]学习Javascript闭包(Closure)

写在前面的话:闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

 

一、变量的作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

var n=999;

function f1(){
alert(n);
}

f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量。

function f1(){
var n=999;
}

alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

function f1(){
n=999;
}

f1();

alert(n); // 999

二、如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。

function f1(){

var n=999;

function f2(){
alert(n); // 999
}

}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

function f1(){

var n=999;

function f2(){
alert(n);
}

return f2;

}

var result=f1();

result(); // 999

三、闭包的概念

上一节代码中的f2函数,就是闭包。

各种专业文献上的”闭包”(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

四、闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。

function f1(){

var n=999;

nAdd=function(){n+=1}

function f2(){
alert(n);
}

return f2;

}

var result=f1();

result(); // 999

nAdd();

result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

五、使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

六、思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

代码片段一。

var name = "The Window";

var object = {
name : "My Object",

getNameFunc : function(){
return function(){
return this.name;
};

}

};

alert(object.getNameFunc()());

代码片段二。

var name = "The Window";

var object = {
name : "My Object",

getNameFunc : function(){
var that = this;
return function(){
return that.name;
};

}

};

alert(object.getNameFunc()());

(完)

[收藏]IE和Firefox的Javascript兼容性总结

长久以来JavaScript兼容性一直是Web开发者的一个主要问题。在正式规范、事实标准以及各种实现之间的存在的差异让许多开发者日夜煎熬。为此,主要从以下几方面差异总结IE和Firefox的Javascript兼容性:

一、函数和方法差异

二、样式访问和设置

三、DOM方法及对象引用

四、事件处理

五、其他差异的兼容处理

 

一、函数和方法差异

1. getYear()方法

【分析说明】先看一下以下代码:

var year= new Date().getYear();
document.write(year);

在IE中得到的日期是”2010″,在Firefox中看到的日期是”110″,主要是因为在 Firefox 里面 getYear 返回的是 “当前年份-1900” 的值。

【兼容处理】

加上对年份的判断,如:

var year= new Date().getYear();
year = (year&lt;1900?(1900+year):year);
document.write(year);

也可以通过 getFullYear getUTCFullYear 去调用:

var year = new Date().getFullYear();
document.write(year);

2. eval()函数

【分析说明】在IE中,可以使用eval(“idName”)或getElementById(“idName”)来取得id为idName的HTML对象;Firefox下只能使用getElementById(“idName”)来取得id为idName的HTML对象。

【兼容处理】统一用getElementById(“idName”)来取得id为idName的HTML对象。

 

3. const声明

【分析说明】在 IE 中不能使用 const 关键字。如:

const constVar = 32;

在IE中这是语法错误。

【兼容处理】不使用 const ,以 var 代替。

 

4. var

【分析说明】请看以下代码:

echo=function(str){
document.write(str);
}

这个函数在IE上运行正常,Firefox下却报错了。

【兼容处理】而在echo前加上var就正常了,这个就是我们提到var的目的。

 

5. const 问题

【分析说明】在 IE 中不能使用 const 关键字。如 const constVar = 32; 在IE中这是语法错误。

【解决方法】不使用 const ,以 var 代替。

 

二、样式访问和设置

1. CSS的”float”属性

【分析说明】Javascript访问一个给定CSS 值的最基本句法是:object.style.property,但部分CSS属性跟Javascript中的保留字命名相同,如”float”,”for”,”class”等,不同浏览器写法不同。

在IE中这样写:

document.getElementById("header").style.styleFloat = "left";

在Firefox中这样写:

document.getElementById("header").style.cssFloat = "left";

【兼容处理】在写之前加一个判断,判断浏览器是否是IE:

if(document.all){
  document.getElementById("header").style.styleFloat = "left";
}
else{
  document.getElementById("header").style.cssFloat = "left";
}

 

2. 访问<label>标签中的”for”

【分析说明】和”float”属性一样,同样需要使用不现的句法区分来访问<label>标签中的”for”。

在IE中这样写:

var myObject = document.getElementById("myLabel");
var myAttribute = myObject.getAttribute("htmlFor");

在Firefox中这样写:

var myObject = document.getElementById("myLabel");
var myAttribute = myObject.getAttribute("for");

【兼容处理】解决的方法也是先 判断浏览器类型。

 

3. 访问和设置class属性

【分析说明】同样由于class是Javascript保留字的原因,这两种浏览器使用不同的 JavaScript 方法来获取这个属性。

IE8.0之前的所有IE版本的写法:

var myObject = document.getElementById("header");
var myAttribute = myObject.getAttribute("className");

适用于IE8.0 以及 firefox的写法:

var myObject = document.getElementById("header");
var myAttribute = myObject.getAttribute("class");

另外,在使用setAttribute()设置Class属性的时候,两种浏览器也存在同样的差异。

setAttribute("className",value);

这种写法适用于IE8.0之前的所有IE版本,注意:IE8.0也不支持”className”属性了。

setAttribute(“class”,value);适用于IE8.0 以及 firefox。

【兼容处理】

方法一,两种都写上:

var myObject = document.getElementById("header");
myObject.setAttribute("class","classValue");
myObject.setAttribute("className","classValue");
 //设置header的class为classValue

方法二,IE和FF都支持object.className,所以可以这样写:

var myObject = document.getElementById("header");
myObject.className="classValue";//设置header的class为classValue

方法三,先判断浏览器类型,再根据浏览器类型采用对应的写法。

 

4. 对象宽高赋值问题

【分析说明】FireFox中类似 obj.style.height = imgObj.height 的语句无效。

【兼容处理】统一使用 obj.style.height = imgObj.height + ‘px’;

 

三、DOM方法及对象引用

1. getElementById

【分析说明】先来看一组代码:

<!– input对象访问1 –>
<input id=”id” type=”button”
value=”click me” ōnclick=”alert(id.value)”/>

在Firefox中,按钮没反应,在IE中,就可以,因为对于IE来说,一个HTML 元素的 ID 可以直接在脚本中当作变量名来使用,而Firefox中不可以。

【兼容处理】尽量采用W3C DOM 的写法,访问对象的时候,用document.getElementById(“id”) 以ID来访问对象,且一个ID在页面中必须是唯一的,同样在以标签名来访问对象的时候,用document.getElementsByTagName(“div”)[0] 。该方式得到较多浏览器的支持。

<!– input对象访问2 –>
<input id=”id” type=”button” value=”click me”
  onclick=”alert(document.getElementById(‘id’).value)” />

 

2. 集合类对象访问

【分析说明】IE下,可以使用()或[]获取集合类对象;Firefox下,只能使用[]获取集合类对象。如:

document.write(document.forms("formName").src);
//该写法在IE下能访问到Form对象的scrc属性

【兼容处理】将document.forms(“formName”)改为 document.forms[“formName”]。统一使用[]获取集合类对象。

 

3. frame的引用

【分析说明】IE可以通过id或者name访问这个frame对应的window对象,而Firefox只可以通过name来访问这个frame对应的window对象。

例如如果上述frame标签写在最上层的window里面的htm里面,那么可以这样访问:

IE: window.top.frameId或者window.top.frameName来访问这个window对象;

Firefox:只能这样window.top.frameName来访问这个window对象。

【兼容处理】使用frame的name来访问frame对象,另外,在IE和Firefox中都可以使用window.document.getElementById(”frameId”)来访问这个frame对象。

 

4. parentElement

【分析说明】IE中支持使用parentElement和parentNode获取父节点。而Firefox只可以使用parentNode。

【兼容处理】因为firefox与IE都支持DOM,因此统一使用parentNode来访问父节点。

 

5. table操作

【分析说明】IE下table中无论是用innerHTML还是appendChild插入<tr>都没有效果,而其他浏览器却显示正常。

【兼容处理】解决的方法是,将<tr>加到table的<tbody>元素中,如下面所示:

var row = document.createElement("tr");
var cell = document.createElement("td");
var cell_text = document.createTextNode("插入的内容");
cell.appendChild(cell_text);
row.appendChild(cell);
document.getElementsByTagName("tbody")[0].appendChild(row);

 

6. 移除节点removeNode()和removeChild()

【分析说明】appendNode在IE和Firefox下都能正常使用,但是removeNode只能在IE下用。

removeNode方法的功能是删除一个节点,语法为node.removeNode(false)或者node.removeNode(true),返回值是被删除的节点。

removeNode(false)表示仅仅删除指定节点,然后这个节点的原孩子节点提升为原双亲节点的孩子节点。

removeNode(true)表示删除指定节点及其所有下属节点。被删除的节点成为了孤立节点,不再具有有孩子节点和双亲节点。

【兼容处理】Firefox中节点没有removeNode方法,只能用removeChild方法代替,先回到父节点,在从父节点上移除要移除的节点。

node.parentNode.removeChild(node);</div>
// 为了在ie和firefox下都能正常使用,取上一层的父结点,然后remove。

 

7. childNodes获取的节点

【分析说明】childNodes的下标的含义在IE和Firefox中不同,看一下下面的代码:

<ul id=”main”>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<input type=button value=”click me!” onclick=
“alert(document.getElementById(‘main’).childNodes.length)”>

分别用IE和Firefox运行,IE的结果是3,而Firefox则是7。Firefox使用DOM规范,”#text”表示文本(实际是无意义的空格和换行等)在Firefox里也会被解析成一个节点,在IE里只有有实际意义的文本才会解析成”#text”。

【兼容处理】

方法一,获取子节点时,可以通过node.getElementsByTagName()来回避这个问题。但是 getElementsByTagName对复杂的DOM结构遍历明显不如用childNodes,因为childNodes能更好的处理DOM的层次结构。

方法二,在实际运用中,Firefox在遍历子节点时,不妨在for循环里加上:

if(childNode.nodeName==”#text”) continue;//或者使用nodeType == 1。

这样可以跳过一些文本节点。

延伸阅读

IE和FireFox中的childNodes区别

 

8. Firefox不能对innerText支持

【分析说明】Firefox不支持innerText,它支持textContent来实现innerText,不过textContent没有像innerText一样考虑元素的display方式,所以不完全与IE兼容。如果不用textContent,字符串里面不包含HTML代码也可以用innerHTML代替。也可以用js写个方法实现,可参考《为firefox实现innerText属性》一文。

【兼容处理】通过判断浏览器类型来兼容:

if(document.all){
document.getElementById('element').innerText = "my text";
} else{
document.getElementById('element').textContent = "my text";
}

 

四、事件处理

如果在使用javascript的时候涉及到event处理,就需要知道event在不同的浏览器中的差异,主要的JavaScript的事件模型有三种(参考《Supporting Three Event Models at Once》),它们分别是NN4、IE4+和W3C/Safar。

 

1. window.event

【分析说明】先看一段代码

function et()
{
alert(event);//IE: [object]
}

以上代码在IE运行的结果是[object],而在Firefox无法运行。

因为在IE中event作为window对象的一个属性可以直接使用,但是在Firefox中却使用了W3C的模型,它是通过传参的方法来传播事件的,也就是说你需要为你的函数提供一个事件响应的接口。

【兼容处理】添加对event判断,根据浏览器的不同来得到正确的event:

function et()
{
    evt=evt?evt:(window.event?window.event:null);
   //兼容IE和Firefox
    alert(evt);
}

 

2. 键盘值的取得

【分析说明】IE和Firefox获取键盘值的方法不同,可以理解,Firefox下的event.which与IE下的event.keyCode相当。关于彼此不同,可参考《键盘事件中keyCode、which和charCode 的兼容性测试

【兼容处理】

function myKeyPress(evt){
//兼容IE和Firefox获得keyBoardEvent对象
evt = (evt) ? evt : ((window.event) ? window.event : "")
//兼容IE和Firefox获得keyBoardEvent对象的键值
var key = evt.keyCode?evt.keyCode:evt.which;
if(evt.ctrlKey &amp;&amp; (key == 13 || key == 10)){
       //同时按下了Ctrl和回车键
//do something;
}
}

 

3. 事件源的获取

【分析说明】在使用事件委托的时候,通过事件源获取来判断事件到底来自哪个元素,但是,在IE下,event对象有srcElement属性,但是没有target属性;Firefox下,even对象有target属性,但是没有srcElement属性。

【兼容处理】

ele=function(evt){ //捕获当前事件作用的对象
evt=evt||window.event;
return (obj=event.srcElement?event.srcElement:event.target;);
}

 

4. 事件监听

【分析说明】在事件监听处理方面,IE提供了attachEvent和detachEvent两个接口,而Firefox提供的是addEventListener和removeEventListener。

【兼容处理】最简单的兼容性处理就是封装这两套接口:

function addEvent(elem, eventName, handler) {
if (elem.attachEvent) {
elem.attachEvent("on" + eventName, function(){
    handler.call(elem)});
    //此处使用回调函数call(),让this指向elem
} else if (elem.addEventListener) {
    elem.addEventListener(eventName, handler, false);
}
}
function removeEvent(elem, eventName, handler) {
if (elem.detachEvent) {
elem.detachEvent("on" + eventName, function(){
    handler.call(elem)});
    //此处使用回调函数call(),让this指向elem
} else if (elem.removeEventListener) {
elem.removeEventListener(eventName, handler, false);
}
}

需要特别注意,Firefox下,事件处理函数中的this指向被监听元素本身,而在IE下则不然,可使用回调函数call,让当前上下文指向监听的元素。

 

5. 鼠标位置

【分析说明】IE下,even对象有x,y属性,但是没有pageX,pageY属性;Firefox下,even对象有pageX,pageY属性,但是没有x,y属性。

【兼容处理】使用mX(mX = event.x ? event.x : event.pageX;)来代替IE下的event.x或者Firefox下的event.pageX。复杂点还要考虑绝对位置。

function getAbsPoint(e){
var x = e.offsetLeft, y = e.offsetTop;
while (e = e.offsetParent) {
x += e.offsetLeft;
y += e.offsetTop;
}
alert("x:" + x + "," + "y:" + y);
}
五、其他差异的兼容处理

1. XMLHttpRequest

【分析说明】new ActiveXObject(“Microsoft.XMLHTTP”);只在IE中起作用,Firefox不支持,但支持XMLHttpRequest。

【兼容处理】

 

function createXHR() {
var xhr=null;
if(window.XMLHttpRequest){
xhr=new ActiveXObject("Msxml2.XMLHTTP");
}else{
try {
xhr=new ActiveXObject("Microsoft.XMLHTTP");
}
catch() {
xhr=null;
}
}
if(!xhr)return;
return xhr;
}

 

2. 模态和非模态窗口

【分析说明】IE中可以通过showModalDialog和showModelessDialog打开模态和非模态窗口,但是Firefox不支持。

【解决办法】直接使用window.open(pageURL,name,parameters)方式打开新窗口。 如果需要传递参数,可以使用frame或者iframe。

 

3. input.type属性问题

 

IE下 input.type属性为只读,但是Firefox下可以修改

 

4. 对select元素的option操作

设置options,IE和Firefox写法不同:

Firefox:可直接设置

option.text = ‘foooooooo’;

IE:只能设置

option.innerHTML = ‘fooooooo’;

删除一个select的option的方法:

Firefox:可以

select.options.remove(selectedIndex);

IE7:可以用

select.options[i] = null;

IE6:需要写

select.options[i].outerHTML = null;

 

5. img对象alt和title的解析

【分析说明】img对象有alt和title两个属性,区别在于,alt:当照片不存在或者load错误时的提示。

title:照片的tip说明, 在IE中如果没有定义title,alt也可以作为img的tip使用,但是在Firefox中,两者完全按照标准中的定义使用

在定义img对象时。

【兼容处理】最好将alt和title对象都写全,保证在各种浏览器中都能正常使用 。

 

6. img的src刷新问题

【分析说明】先看一下代码:

<img id=”pic” onclick= “this.src= ‘a.jpg'”
  src=”aa.jpg” style=”cursor: pointer”/>

在IE 下,这段代码可以用来刷新图片,但在FireFox下不行。主要是缓存问题。

【兼容处理】在地址后面加个随机数就解决了:

<img id=”pic” onclick= “javascript:this.src=this.src+’?’
     +Math.random()”src=”a.jpg” style=”cursor: pointer”/>

 

总结

IE和Firefox的Javascript方面存在着不少的差异,要做到兼容,我觉得很有必要把一些常见的整理成一个js库,如DOM的操作,事件的处理,XMLHttpRequest请求等,或者也可以选择使用现有的一些库(如jQuery,YUI,ExtJs等),不过我觉得还是有必要了解一下这些差异,这样对于我们参加兼容性和可用性代码很有帮助。

办法总比问题多,无论浏览器兼容如何折腾人,做前端开发的总能迎刃而解的!

[收藏]Jetty 的工作原理以及与 Tomcat 的比较

Jetty 的基本架构

Jetty 目前的是一个比较被看好的 Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器,它有一个基本数据模型,这个数据模型就是 Handler,所有可以被扩展的组件都可以作为一个 Handler,添加到 Server 中,Jetty 就是帮你管理这些 Handler。

Jetty 的基本架构

下图是 Jetty 的基本架构图,整个 Jetty 的核心组件由 Server 和 Connector 两个组件构成,整个 Server 组件是基于 Handler 容器工作的,它类似与 Tomcat 的 Container 容器,Jetty 与 Tomcat 的比较在后面详细介绍。Jetty 中另外一个比不可少的组件是 Connector,它负责接受客户端的连接请求,并将请求分配给一个处理队列去执行。
图 1. Jetty 的基本架构
图 1. Jetty 的基本架构

Jetty 中还有一些可有可无的组件,我们可以在它上做扩展。如 JMX,我们可以定义一些 Mbean 把它加到 Server 中,当 Server 启动的时候,这些 Bean 就会一起工作。
图 2. Jetty 的主要组件的类图
图 2. Jetty 的主要组件的类图

从上图可以看出整个 Jetty 的核心是围绕着 Server 类来构建,Server 类继承了 Handler,关联了 Connector 和 Container。Container 是管理 Mbean 的容器。Jetty 的 Server 的扩展主要是实现一个个 Handler 并将 Handler 加到 Server 中,Server 中提供了调用这些 Handler 的访问规则。

整个 Jetty 的所有组件的生命周期管理是基于观察者模板设计,它和 Tomcat 的管理是类似的。下面是 LifeCycle 的类关系图
LifeCycle 的类关系图

每个组件都会持有一个观察者(在这里是 Listener 类,这个类通常对应到观察者模式中常用的 Observer 角色,关于观察者模式可以参考 《Tomcat系统架构与设计模式,第2部分:设计模式分析》一文中关于观察者模式的讲解)集合,当 start、fail 或 stop 等事件触发时,这些 Listener 将会被调用,这是最简单的一种设计方式,相比 Tomcat 的 LifeCycle 要简单的多。

Handler 的体系结构

前面所述 Jetty 主要是基于 Handler 来设计的,Handler 的体系结构影响着整个 Jetty 的方方面面。下面总结了一下 Handler 的种类及作用:
图 3. Handler 的体系结构
图 3. Handler 的体系结构

Jetty 主要提供了两种 Handler 类型,一种是 HandlerWrapper,它可以将一个 Handler 委托给另外一个类去执行,如我们要将一个 Handler 加到 Jetty 中,那么就必须将这个 Handler 委托给 Server 去调用。配合 ScopeHandler 类我们可以拦截 Handler 的执行,在调用 Handler 之前或之后,可以做一些另外的事情,类似于 Tomcat 中的 Valve;另外一个 Handler 类型是 HandlerCollection,这个 Handler 类可以将多个 Handler 组装在一起,构成一个 Handler 链,方便我们做扩展。

Jetty 的启动过程

Jetty 的入口是 Server 类,Server 类启动完成了,就代表 Jetty 能为你提供服务了。它到底能提供哪些服务,就要看 Server 类启动时都调用了其它组件的 start 方法。从 Jetty 的配置文件我们可以发现,配置 Jetty 的过程就是将那些类配置到 Server 的过程。下面是 Jetty 的启动时序图:
图 4. Jetty 的启动流程
图 4. Jetty 的启动流程

因为 Jetty 中所有的组件都会继承 LifeCycle,所以 Server 的 start 方法调用就会调用所有已经注册到 Server 的组件,Server 启动其它组件的顺序是:首先启动设置到 Server 的 Handler,通常这个 Handler 会有很多子 Handler,这些 Handler 将组成一个 Handler 链。Server 会依次启动这个链上的所有 Handler。接着会启动注册在 Server 上 JMX 的 Mbean,让 Mbean 也一起工作起来,最后会启动 Connector,打开端口,接受客户端请求,启动逻辑非常简单。

接受请求

Jetty 作为一个独立的 Servlet 引擎可以独立提供 Web 服务,但是它也可以与其他 Web 应用服务器集成,所以它可以提供基于两种协议工作,一个是 HTTP,一个是 AJP 协议。如果将 Jetty 集成到 Jboss 或者 Apache,那么就可以让 Jetty 基于 AJP 模式工作。下面分别介绍 Jetty 如何基于这两种协议工作,并且它们如何建立连接和接受请求的。

基于 HTTP 协议工作

如果前端没有其它 web 服务器,那么 Jetty 应该是基于 HTTP 协议工作。也就是当 Jetty 接收到一个请求时,必须要按照 HTTP 协议解析请求和封装返回的数据。那么 Jetty 是如何接受一个连接又如何处理这个连接呢?

我们设置 Jetty 的 Connector 实现类为 org.eclipse.jetty.server.bi.SocketConnector 让 Jetty 以 BIO 的方式工作,Jetty 在启动时将会创建 BIO 的工作环境,它会创建 HttpConnection 类用来解析和封装 HTTP1.1 的协议,ConnectorEndPoint 类是以 BIO 的处理方式处理连接请求,ServerSocket 是建立 socket 连接接受和传送数据,Executor 是处理连接的线程池,它负责处理每一个请求队列中任务。acceptorThread 是监听连接请求,一有 socket 连接,它将进入下面的处理流程。

当 socket 被真正执行时,HttpConnection 将被调用,这里定义了如何将请求传递到 servlet 容器里,有如何将请求最终路由到目的 servlet,关于这个细节可以参考《 servlet 工作原理解析》一文。

下图是 Jetty 启动创建建立连接的时序图:
图 5. 建立连接的时序图
图 5. 建立连接的时序图

Jetty 创建接受连接环境需要三个步骤:

  1. 创建一个队列线程池,用于处理每个建立连接产生的任务,这个线程池可以由用户来指定,这个和 Tomcat 是类似的。
  2. 创建 ServerSocket,用于准备接受客户端的 socket 请求,以及客户端用来包装这个 socket 的一些辅助类。
  3. 创建一个或多个监听线程,用来监听访问端口是否有连接进来。

相比 Tomcat 创建建立连接的环境,Jetty 的逻辑更加简单,牵涉到的类更少,执行的代码量也更少了。

当建立连接的环境已经准备好了,就可以接受 HTTP 请求了,当 Acceptor 接受到 socket 连接后将转入下图所示流程执行:
图 6. 处理连接时序图
图 6. 处理连接时序图

Accetptor 线程将会为这个请求创建 ConnectorEndPoint。HttpConnection 用来表示这个连接是一个 HTTP 协议的连接,它会创建 HttpParse 类解析 HTTP 协议,并且会创建符合 HTTP 协议的 Request 和 Response 对象。接下去就是将这个线程交给队列线程池去执行了。

基于 AJP 工作

通常一个 web 服务站点的后端服务器不是将 Java 的应用服务器直接暴露给服务访问者,而是在应用服务器,如 Jboss 的前面在加一个 web 服务器,如 Apache 或者 nginx,我想这个原因大家应该很容易理解,如做日志分析、负载均衡、权限控制、防止恶意请求以及静态资源预加载等等。

下图是通常的 web 服务端的架构图:
图 7. Web 服务端架构
图 7. Web 服务端架构

这种架构下 servlet 引擎就不需要解析和封装返回的 HTTP 协议,因为 HTTP 协议的解析工作已经在 Apache 或 Nginx 服务器上完成了,Jboss 只要基于更加简单的 AJP 协议工作就行了,这样能加快请求的响应速度。

对比 HTTP 协议的时序图可以发现,它们的逻辑几乎是相同的,不同的是替换了一个类 Ajp13Parserer 而不是 HttpParser,它定义了如何处理 AJP 协议以及需要哪些类来配合。

实际上在 AJP 处理请求相比较 HTTP 时唯一的不同就是在读取到 socket 数据包时,如何来转换这个数据包,是按照 HTTP 协议的包格式来解析就是 HttpParser,按照 AJP 协议来解析就是 Ajp13Parserer。封装返回的数据也是如此。

让 Jetty 工作在 AJP 协议下,需要配置 connector 的实现类为 Ajp13SocketConnector,这个类继承了 SocketConnector 类,覆盖了父类的 newConnection 方法,为的是创建 Ajp13Connection 对象而不是 HttpConnection。如下图表示的是 Jetty 创建连接环境时序图:
Figure xxx. Requires a heading

与 HTTP 方式唯一不同的地方的就是将 SocketConnector 类替换成了 Ajp13SocketConnector。改成 Ajp13SocketConnector 的目的就是可以创建 Ajp13Connection 类,表示当前这个连接使用的是 AJP 协议,所以需要用 Ajp13Parser 类解析 AJP 协议,处理连接的逻辑都是一样的。如下时序图所示:
Figure xxx. Requires a heading

基于 NIO 方式工作

前面所描述的 Jetty 建立客户端连接到处理客户端的连接都是基于 BIO 的方式,它也支持另外一种 NIO 的处理方式,其中 Jetty 的默认 connector 就是 NIO 方式。

关于 NIO 的工作原理可以参考 developerworks 上关于 NIO 的文章,通常 NIO 的工作原型如下:

Selector selector = Selector.open(); 
 ServerSocketChannel ssc = ServerSocketChannel.open(); 
 ssc.configureBlocking( false ); 
 SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT ); 
 ServerSocketChannel ss = (ServerSocketChannel)key.channel(); 
 SocketChannel sc = ss.accept(); 
 sc.configureBlocking( false ); 
 SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ ); 
 Set selectedKeys = selector.selectedKeys();

 

创建一个 Selector 相当于一个观察者,打开一个 Server 端通道,把这个 server 通道注册到观察者上并且指定监听的事件。然后遍历这个观察者观察到事件,取出感兴趣的事件再处理。这里有个最核心的地方就是,我们不需要为每个被观察者创建一个线程来监控它随时发生的事件。而是把这些被观察者都注册一个地方统一管理,然后由它把触发的事件统一发送给感兴趣的程序模块。这里的核心是能够统一的管理每个被观察者的事件,所以我们就可以把服务端上每个建立的连接传送和接受数据作为一个事件统一管理,这样就不必要每个连接需要一个线程来维护了。

这里需要注意的地方时,很多人认为监听 SelectionKey.OP_ACCEPT 事件就已经是非阻塞方式了,其实 Jetty 仍然是用一个线程来监听客户端的连接请求,当接受到请求后,把这个请求再注册到 Selector 上,然后才是非阻塞方式执行。这个地方还有一个容易引起误解的地方是:认为 Jetty 以 NIO 方式工作只会有一个线程来处理所有的请求,甚至会认为不同用户会在服务端共享一个线程从而会导致基于 ThreadLocal 的程序会出现问题,其实从 Jetty 的源码中能够发现,真正共享一个线程的处理只是在监听不同连接的数据传送事件上,比如有多个连接已经建立,传统方式是当没有数据传输时,线程是阻塞的也就是一直在等待下一个数据的到来,而 NIO 的处理方式是只有一个线程在等待所有连接的数据的到来,而当某个连接数据到来时 Jetty 会把它分配给这个连接对应的处理线程去处理,所以不同连接的处理线程仍然是独立的。

Jetty 的 NIO 处理方式和 Tomcat 的几乎一样,唯一不同的地方是在如何把监听到事件分配给对应的连接的处理方式。从测试效果来看 Jetty 的 NIO 处理方式更加高效。下面是 Jetty 的 NIO 处理时序图:
Figure xxx. Requires a heading

处理请求

下面看一下 Jetty 是如何处理一个 HTTP 请求的。

实际上 Jetty 的工作方式非常简单,当 Jetty 接受到一个请求时,Jetty 就把这个请求交给在 Server 中注册的代理 Handler 去执行,如何执行你注册的 Handler,同样由你去规定,Jetty 要做的就是调用你注册的第一个 Handler 的 handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 方法,接下去要怎么做,完全由你决定。

要能接受一个 web 请求访问,首先要创建一个 ContextHandler,如下代码所示:

Server server = new Server(8080); 
 ContextHandler context = new ContextHandler(); 
 context.setContextPath("/"); 
 context.setResourceBase("."); 
 context.setClassLoader(Thread.currentThread().getContextClassLoader()); 
 server.setHandler(context); 
 context.setHandler(new HelloHandler()); 
 server.start(); 
 server.join();

 

当我们在浏览器里敲入 http://localhost:8080 时的请求将会代理到 Server 类的 handle 方法,Server 的 handle 的方法将请求代理给 ContextHandler 的 handle 方法,ContextHandler 又调用 HelloHandler 的 handle 方法。这个调用方式是不是和 Servlet 的工作方式类似,在启动之前初始化,然后创建对象后调用 Servlet 的 service 方法。在 Servlet 的 API 中我通常也只实现它的一个包装好的类,在 Jetty 中也是如此,虽然 ContextHandler 也只是一个 Handler,但是这个 Handler 通常是由 Jetty 帮你实现了,我们一般只要实现一些我们具体要做的业务逻辑有关的 Handler 就好了,而一些流程性的或某些规范的 Handler,我们直接用就好了,如下面的关于 Jetty 支持 Servlet 的规范的 Handler 就有多种实现,下面是一个简单的 HTTP 请求的流程。

访问一个 Servlet 的代码:

 Server server = new Server(); 
 Connector connector = new SelectChannelConnector(); 
 connector.setPort(8080); 
 server.setConnectors(new Connector[]{ connector }); 
 ServletContextHandler root = new 
 ServletContextHandler(null,"/",ServletContextHandler.SESSIONS); 
 server.setHandler(root); 
 root.addServlet(new ServletHolder(new 
 org.eclipse.jetty.embedded.HelloServlet("Hello")),"/"); 
 server.start(); 
 server.join();

 

创建一个 ServletContextHandler 并给这个 Handler 添加一个 Servlet,这里的 ServletHolder 是 Servlet 的一个装饰类,它十分类似于 Tomcat 中的 StandardWrapper。下面是请求这个 Servlet 的时序图:
图 8. Jetty 处理请求的时序图
图 8. Jetty 处理请求的时序图

上图可以看出 Jetty 处理请求的过程就是 Handler 链上 handle 方法的执行过程,在这里需要解释的一点是 ScopeHandler 的处理规则,ServletContextHandler、SessionHandler 和 ServletHandler 都继承了 ScopeHandler,那么这三个类组成一个 Handler 链,它们的执行规则是:ServletContextHandler.handleServletContextHandler.doScope SessionHandler. doScopeServletHandler. doScopeServletContextHandler. doHandleSessionHandler. doHandleServletHandler. doHandle,它这种机制使得我们可以在 doScope 做一些额外工作。

与 Jboss 集成

前面介绍了 Jetty 可以基于 AJP 协议工作,在正常的企业级应用中,Jetty 作为一个 Servlet 引擎都是基于 AJP 协议工作的,所以它前面必然有一个服务器,通常情况下与 Jboss 集成的可能性非常大,这里介绍一下如何与 Jboss 集成。

Jboss 是基于 JMX 的架构,那么只要符合 JMX 规范的系统或框架都可以作为一个组件加到 Jboss 中,扩展 Jboss 的功能。Jetty 作为主要的 Servlet 引擎当然支持与 Jboss 集成。具体集成方式如下:

Jetty 作为一个独立的 Servlet 引擎集成到 Jboss 需要继承 Jboss 的 AbstractWebContainer 类,这个类实现的是模板模式,其中有一个抽象方法需要子类去实现,它是 getDeployer,可以指定创建 web 服务的 Deployer。Jetty 工程中有个 jetty-jboss 模块,编译这个模块就会产生一个 SAR 包,或者可以直接从官网下载一个 SAR 包。解压后如下图:
图 9. jboss-jetty 目录
图 9. jboss-jetty 目录

在 jboss-jetty-6.1.9 目录下有一个 webdefault.xml 配置文件,这个文件是 Jetty 的默认 web.xml 配置,在 META-INF 目录发下发现 jboss-service.xml 文件,这个文件配置了 MBean,如下:

 <mbean code="org.jboss.jetty.JettyService" 
         name="jboss.web:service=WebServer" xmbean-dd="META-INF/webserver-xmbean.xml>

 

同样这个 org.jboss.jetty.JettyService 类也是继成 org.jboss.web.AbstractWebContainer 类,覆盖了父类的 startService 方法,这个方法直接调用 jetty.start 启动 Jetty。

与 Tomcat 的比较

Tomcat 和 Jetty 都是作为一个 Servlet 引擎应用的比较广泛,可以将它们比作为中国与美国的关系,虽然 Jetty 正常成长为一个优秀的 Servlet 引擎,但是目前的 Tomcat 的地位仍然难以撼动。相比较来看,它们都有各自的优点与缺点。

Tomcat 经过长时间的发展,它已经广泛的被市场接受和认可,相对 Jetty 来说 Tomcat 还是比较稳定和成熟,尤其在企业级应用方面,Tomcat 仍然是第一选择。但是随着 Jetty 的发展,Jetty 的市场份额也在不断提高,至于原因就要归功与 Jetty 的很多优点了,而这些优点也是因为 Jetty 在技术上的优势体现出来的。

架构比较

从架构上来说,显然 Jetty 比 Tomcat 更加简单,如果你对 Tomcat 的架构还不是很了解的话,建议你先看一下 《Tomcat系统架构与设计模式》这篇文章。

Jetty 的架构从前面的分析可知,它的所有组件都是基于 Handler 来实现,当然它也支持 JMX。但是主要的功能扩展都可以用 Handler 来实现。可以说 Jetty 是面向 Handler 的架构,就像 Spring 是面向 Bean 的架构,iBATIS 是面向 statement 一样,而 Tomcat 是以多级容器构建起来的,它们的架构设计必然都有一个“元神”,所有以这个“元神“构建的其它组件都是肉身。

从设计模板角度来看 Handler 的设计实际上就是一个责任链模式,接口类 HandlerCollection 可以帮助开发者构建一个链,而另一个接口类 ScopeHandler 可以帮助你控制这个链的访问顺序。另外一个用到的设计模板就是观察者模式,用这个设计模式控制了整个 Jetty 的生命周期,只要继承了 LifeCycle 接口,你的对象就可以交给 Jetty 来统一管理了。所以扩展 Jetty 非常简单,也很容易让人理解,整体架构上的简单也带来了无比的好处,Jetty 可以很容易被扩展和裁剪。

相比之下,Tomcat 要臃肿很多,Tomcat 的整体设计上很复杂,前面说了 Tomcat 的核心是它的容器的设计,从 Server 到 Service 再到 engine 等 container 容器。作为一个应用服务器这样设计无口厚非,容器的分层设计也是为了更好的扩展,这是这种扩展的方式是将应用服务器的内部结构暴露给外部使用者,使得如果想扩展 Tomcat,开发人员必须要首先了解 Tomcat 的整体设计结构,然后才能知道如何按照它的规范来做扩展。这样无形就增加了对 Tomcat 的学习成本。不仅仅是容器,实际上 Tomcat 也有基于责任链的设计方式,像串联 Pipeline 的 Vavle 设计也是与 Jetty 的 Handler 类似的方式。要自己实现一个 Vavle 与写一个 Handler 的难度不相上下。表面上看,Tomcat 的功能要比 Jetty 强大,因为 Tomcat 已经帮你做了很多工作了,而 Jetty 只告诉,你能怎么做,如何做,有你去实现。

打个比方,就像小孩子学数学,Tomcat 告诉你 1+1=2,1+2=3,2+2=4 这个结果,然后你可以根据这个方式得出 1+1+2=4,你要计算其它数必须根据它给你的公式才能计算,而 Jetty 是告诉你加减乘除的算法规则,然后你就可以根据这个规则自己做运算了。所以你一旦掌握了 Jetty,Jetty 将变得异常强大。

性能比较

单纯比较 Tomcat 与 Jetty 的性能意义不是很大,只能说在某种使用场景下,它表现的各有差异。因为它们面向的使用场景不尽相同。从架构上来看 Tomcat 在处理少数非常繁忙的连接上更有优势,也就是说连接的生命周期如果短的话,Tomcat 的总体性能更高。

而 Jetty 刚好相反,Jetty 可以同时处理大量连接而且可以长时间保持这些连接。例如像一些 web 聊天应用非常适合用 Jetty 做服务器,像淘宝的 web 旺旺就是用 Jetty 作为 Servlet 引擎。

另外由于 Jetty 的架构非常简单,作为服务器它可以按需加载组件,这样不需要的组件可以去掉,这样无形可以减少服务器本身的内存开销,处理一次请求也是可以减少产生的临时对象,这样性能也会提高。另外 Jetty 默认使用的是 NIO 技术在处理 I/O 请求上更占优势,Tomcat 默认使用的是 BIO,在处理静态资源时,Tomcat 的性能不如 Jetty。

特性比较

作为一个标准的 Servlet 引擎,它们都支持标准的 Servlet 规范,还有 Java EE 的规范也都支持,由于 Tomcat 的使用的更加广泛,它对这些支持的更加全面一些,有很多特性 Tomcat 都直接集成进来了。但是 Jetty 的应变更加快速,这一方面是因为 Jetty 的开发社区更加活跃,另一方面也是因为 Jetty 的修改更加简单,它只要把相应的组件替换就好了,而 Tomcat 的整体结构上要复杂很多,修改功能比较缓慢。所以 Tomcat 对最新的 Servlet 规范的支持总是要比人们预期的要晚。

总结

本文介绍了目前 Java 服务端中一个比较流行应用服务器 Jetty,介绍了它的基本架构和工作原理以及如何和 Jboss 工作,最后与 Tomcat 做了比较。在看这篇文章的时候最好是结合我前面写的两篇文章《 Tomcat 系统架构与设计模式》和《 Servlet 工作原理解析》以及这些系统的源代码,耐心的都看一下会让你对 Java 服务端有个总体的了解。

Jquery中对新增对象live绑定时间hover的另类处理

Jquery中对新增对象live绑定时间hover的另类处理:

在项目中发现对新增对象使用hover方法时,live的绑定方式貌似不大对,结果测试页的确说明绑定的hover时间无效,翻阅jquery API发现live只能绑定这类事件:

在jQuery 1.4.1 中,.live() 能接受多个,用空间分隔事件,在提供类似.bind()的功能 。例如,我们可以“live ” 同时绑定mouseovermouseout事件,像这样:

$('.hoverme').live('mouseover mouseout', function(event) {
if (event.type == 'mouseover') {
// do something on mouseover
} else {
// do something on mouseout
}
});

随后又发现:

$(".hoverme").live({ 
  mouseenter: function() { //todo }, 
  mouseleave: function() { //todo }
});

这个比较能够接近hover实现的效果,至于实际应该怎么用看实际应用情况而定。

JAVA jstl fn 详解

近日JAVA项目中使用Spring MVC 3,由于自己不是很懂,在获取同事帮助下,对JSP页面操作数据有了一定的了解。

 <%@ taglib prefix=”fn” uri=”http://java.sun.com/jsp/jstl/functions” %>

fn:contains 判断字符串是否包含另外一个字符串 <c:if test=”${fn:contains(name, searchString)}”>
fn:containsIgnoreCase 判断字符串是否包含另外一个字符串(大小写无关) <c:if test=”${fn:containsIgnoreCase(name, searchString)}”>
fn:endsWith 判断字符串是否以另外字符串结束 <c:if test=”${fn:endsWith(filename, “.txt”)}”>
fn:escapeXml 把一些字符转成XML表示,例如 <字符应该转为&lt; ${fn:escapeXml(param:info)}
fn:indexOf 子字符串在母字符串中出现的位置 ${fn:indexOf(name, “-“)}
fn:join 将数组中的数据联合成一个新字符串,并使用指定字符格开 ${fn:join(array, “;”)}
fn:length 获取字符串的长度,或者数组的大小 ${fn:length(shoppingCart.products)}
fn:replace 替换字符串中指定的字符 ${fn:replace(text, “-“, “&#149;”)}
fn:split 把字符串按照指定字符切分 ${fn:split(customerNames, “;”)}
fn:startsWith 判断字符串是否以某个子串开始 <c:if test=”${fn:startsWith(product.id, “100-“)}”>
fn:substring 获取子串 ${fn:substring(zip, 6, -1)}
fn:substringAfter 获取从某个字符所在位置开始的子串${fn:substringAfter(zip, “-“)}
fn:substringBefore 获取从开始到某个字符所在位置的子串 ${fn:substringBefore(zip, “-“)}
fn:toLowerCase 转为小写 ${fn.toLowerCase(product.name)}
fn:toUpperCase 转为大写字符 ${fn.UpperCase(product.name)}
fn:trim 去除字符串前后的空格 ${fn.trim(name)}

不管怎样先收藏一下,以备不时之需。

解决IE7中异步加载使用jquery的append方法导致Select无法自适应宽度的问题

解决IE7中异步加载使用jquery的append方法导致Select无法自适应宽度的问题

在项目中省市区三级联动用到了ajax请求数据,在页面处理方面使用Jquery框架的$().append(‘<option>xxx</option>’)方法写入Select控件。经测试在IE7中Select加载数据后宽度位置竟然无变化且重叠在一起。

尝试使用js原生方法的options.add可以解决此问题,几经纠结最终还是喜欢用jquery的方法:$(“Select”).hide().show();对Select对象进行hide show操作后可以恢复占位。

js中最简单实现replaceALL的方法

今天在实际项目中需要用到替换原字符串内所有的含有指定字符串的javascript方法,经过一番思考和查阅资料,可以通过js的正则进行全局替换,最简便方法如下:

String.prototype.replaceAll = function(s1,s2) { 
    return this.replace(new RegExp(s1,"gm"),s2); 
}

PS:博主最开始也走过弯路,通过循环替换的方式遍历字符串中所有的指定字符串进行替换,但显然这是一种伤人伤己的方案(一想到出现N个遍历替换,浏览器直接死掉)。

练习作品:雪花水晶球

博主先来无事,打开68ps,顿时发现好久没练练手了,遂找到一PS教程开始学习,在这里要感谢68P

看起来很不错的样子,推荐一下教程地址:68PS之圣诞雪花水晶球

关于Flash获取JS/HTML传递参数的问题

今天在帮朋友做一个Flash播放器的时候遇到的问题,flv播放地址可以自定义,那么则需要外部传递播放地址到flash中处理,在网上找了很多资料,最终选择了两个,大家可以根据自身需要选择。

方法一:

这个方法是利用actionscript来获取的,具体方法就是通过AS内置的ExternalInterface类来调用外部JS函数,首先我们要在AS中引入ExternalInterface类(import flash.external.ExternalInterface;),然后就可以调用外部JS函数(var url=ExternalInterface.call(“setFlv”);)。接下来我们发布好swf后回到页面,在页面写一个JS函数:

function setFlv(){

return "xxx.flv";

}

如果要测试效果,请在flash的AS中将url输出即可。

方法二:

这个方法是通过页面Object的param来传递参数,在object中定义name为FlashVars的param(<param name=”FlashVars” value=”url=xxx.flv” />)。最好将object的id和embed的name也设置为FlashVars,并在embed上加上(FlashVars=”url=xxx.flv”)。

设置好页面代码后进入flash AS界面中,就可以直接获取url参数值了(直接将url当做AS变量即可)。

演示地址   源码下载(包含flash源文件)

return top