淺談jQuery事件綁定原理
jq里面有一個(gè)data的方法,給dom元素綁定相關(guān)的數(shù)據(jù)的。當(dāng)給dom用jq的方法綁定了事件,會(huì)生成對(duì)應(yīng)的時(shí)間列表
可以看下面的例子(請(qǐng)?jiān)趂irefox中查看 因?yàn)閒irefox中對(duì)象支持toSource())
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title></title>
</head>
<body>
<div id="test"></div>
<script type="text/javascript" src=">
<script type="text/javascript">
window.onload = function(){
alert($.data($('#test')[0],'events'));//null
alert($.data($('#test')[0],'handle'));//null
$('#test')
.bind('click',function(){
alert(1)
})
.bind('mouseover',function(){
alert(2)
})
.bind('click',function(){
alert(3)
})
.bind('click',function(){
alert(4)
})
alert($.data($('#test')[0],'events').toSource());//時(shí)間列表
alert($.data($('#test')[0],'handle').toSource());//執(zhí)行的函數(shù)
}
</script>
</body>
</html>
data是給元素綁定數(shù)據(jù)的
數(shù)據(jù)源是 cache對(duì)象
當(dāng)元素綁定數(shù)據(jù)的時(shí)候 會(huì)給元素添加一個(gè)屬性 jQueryxxx xxx為執(zhí)行jq的時(shí)間戳
這里要說(shuō)明一下,有一個(gè)uuid 他是累加的
jQueryxxx的值就是這個(gè)uuid
cache 的 key就是這個(gè) uuid
value就是要存的數(shù)據(jù)
data對(duì)于事件的綁定是很重要的................................
function now(){
return +new Date;
};
var win = this,
expando = "jQuery" + now(),
uuid = 0,
cache = {};
win.data = function(elem, name, data){
var id = elem[expando];
if(!id)
id = elem[expando] = ++uuid;
if(name&&!cache[id])
cache[id] = {};
if(data !== undefined)
cache[id][name] = data;
return name
? cache[id][name]
: id;
}
win.removeData = function(elem, name){
var id = elem[expando];
if (name){
if (cache[id]) {
delete cache[id][name];
name = "";
for ( name in cache[ id ] )
break;
if ( !name )
removeData(elem);
}
}else{
try {
delete elem[expando];
} catch(e){
if ( elem.removeAttribute )
elem.removeAttribute( expando );
}
delete cache[id];
}
}
win.each = function( object, callback, args ) {
var name, i = 0, length = object.length;
if ( args ) {
if ( length === undefined ) {
for ( name in object )
if ( callback.apply( object[ name ], args ) === false )
break;
} else
for ( ; i < length; )
if ( callback.apply( object[ i++ ], args ) === false )
break;
} else {
if ( length === undefined ) {
for ( name in object )
if ( callback.call( object[ name ], name, object[ name ] ) === false )
break;
} else
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
}
return object;
}
接著實(shí)現(xiàn)添加事件
jq里面是在 jQuery.event里面的add方法
在add方法里面實(shí)現(xiàn)了一下一些功能
取元素的events,handle這2個(gè)data綁定的數(shù)據(jù)
events存放的是事件列表
格式如下
{
click: [{handler:function(){},type:"click",guid:'xx'}.......],
mouse:[......]
}
handle是執(zhí)行的函數(shù)
(所有的執(zhí)行函數(shù)都是一樣的 他們遍歷事件列表 執(zhí)行對(duì)應(yīng)的事件)
然后遍歷types 因?yàn)榭梢越壎ǘ鄠€(gè)事件
回調(diào)函數(shù)也會(huì)給幾個(gè)屬性
假設(shè)回調(diào)函數(shù)是handler
handler.guid = gevent.guid++
handler.type = name
name應(yīng)該算一個(gè)特殊的命名 方便刪除用的
比如
$('#xx')
.bind('click',function(){})
.bind('click.d',handler)
name就是d了
刪除的時(shí)候可以只刪除d那個(gè)事件 不刪除上面的那個(gè) click事件
最后是給元素綁定事件 但是執(zhí)行的函數(shù)都是
function(){
gevent.handle.apply(arguments.callee.elem, arguments);
});
win.gevent = {
guid : 1,
add : function (elem, types, handler){
if ( elem.nodeType == 3 || elem.nodeType == 8 )
return;
if ( elem.setInterval && elem != window )
elem = window;
//給函數(shù)一個(gè)唯一標(biāo)識(shí)的索引 方便后面刪除該事件
if ( !handler.guid )
handler.guid = this.guid++;
//獲得該元素的events handle 下的數(shù)據(jù)
var events = data(elem, "events") || data(elem, "events", {}),
handle =data(elem, "handle") || data(elem, "handle", function(){
//gevent.handle才是各種行為觸發(fā)后會(huì)執(zhí)行的函數(shù)
gevent.handle.apply(arguments.callee.elem, arguments);
});
handle.elem = elem;
//遍歷事件名 因?yàn)榭梢允?click mouseover
each(types.split(/\s+/), function(index, type) {
var namespaces = type.split(".");
//獲得事件名
type = namespaces.shift();
//去掉點(diǎn)后面的東西 是個(gè)特殊的命名 在刪除的時(shí)候可以指定刪除他 如 click.d
//用事件的type 記錄住這個(gè)特殊的命名
handler.type = namespaces.slice().sort().join(".");
//獲得該事件是否已經(jīng)存在events 這個(gè)對(duì)象里面了
var handlers = events[type];
//如果不存在該事件 給元素綁定該事件
if (!handlers) {
handlers = events[type] = {};
if (elem.addEventListener)
elem.addEventListener(type, handle, false);
else if (elem.attachEvent)
elem.attachEvent("on" + type, handle);
}
//吧函數(shù)放到元素的該事件的列表里面
handlers[handler.guid] = handler;
});
elem = null;
}
}
gevent.hander是綁定事件真正執(zhí)行的函數(shù)
在gevent.hander里面也有取.特殊命名的地方 但是不知道做什么用的
hander里面先對(duì)event進(jìn)行包裝
包裝見(jiàn)gevent. fix 和 setEvent
主要是對(duì)做一個(gè)原生event的一個(gè)copy 然后把不兼容的方法 都合成兼容的寫(xiě)法
然后取元素的events (事件列表)
然后遍歷這個(gè)事件列表 判斷type是不是事件列表的key 是的話就執(zhí)行事件
在執(zhí)行列表函數(shù)的時(shí)候會(huì)判斷返回值
如果返回false 還可以組織事件冒泡 和 默認(rèn)行為
win.gevent = {
handle : function(event){
var all, handlers;
//包裝event
event = arguments[0] = gevent.fix( event || window.event );
event.currentTarget = this;
//這里的........
var namespaces = event.type.split(".");
event.type = namespaces.shift();
all = !namespaces.length;
var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
//取這個(gè)元素的該行為 的 事件列表
handlers = (data(this, "events") || {} )[event.type];
//遍歷這個(gè)事件列表 執(zhí)行該執(zhí)行的東西
for ( var j in handlers ) {
var handler = handlers[j];
if ( all || namespace.test(handler.type) ) {
// Pass in a reference to the handler function itself
// So that we can later remove it
// jq上的注釋是是這么寫(xiě)的 把event的handler 引用這個(gè)事件 方便之后移除
// 但是在remove里面 并沒(méi)有用到event的handler 不知道這里到底有什么用 且有多個(gè)事件的時(shí)候這個(gè)事件被取代
event.handler = handler;
//執(zhí)行事件 并且是用元素調(diào)用的事件 可以吧事件里面的this執(zhí)行元素 ret為函數(shù)的返回值
var ret = handler.apply(this, arguments);
//如果有返回值 且返回值是false 執(zhí)行阻止事件冒泡 阻止執(zhí)行事件默認(rèn)行為
if( ret !== undefined ){
event.result = ret;
if ( ret === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
},
props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix : function(event){
//new setEvent會(huì)給event給以個(gè)expando屬性 如果有中個(gè)屬性 說(shuō)明已經(jīng)生成了event了 不需要在次對(duì)event進(jìn)行包裝
if ( event[expando] )
return event;
//保留一個(gè)原始的event
// new一個(gè)新的event 這個(gè)與原始的event是不同的
var originalEvent = event;
event = new setEvent( originalEvent );
//獲得原始event的屬性值 有哪些屬性值 見(jiàn) this.props
for ( var i = this.props.length, prop; i; ){
prop = this.props[ --i ];
event[ prop ] = originalEvent[ prop ];
}
//將目標(biāo)元素同一成event.target
if ( !event.target )
event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
//如果發(fā)現(xiàn)是文本節(jié)點(diǎn) 取他的父節(jié)點(diǎn)
if ( event.target.nodeType == 3 )
event.target = event.target.parentNode;
if ( !event.relatedTarget && event.fromElement )
event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
return event;
}
}
win.setEvent = function(src){
// Allow instantiation without the 'new' keyword
// Event object
if( src && src.type ){
this.originalEvent = src;
this.type = src.type;
// Event type
}else
this.type = src;
// timeStamp is buggy for some events on Firefox(#3843)
// So we won't rely on the native value
this.timeStamp = now();
// Mark it as fixed
this[expando] = true;
}
function returnFalse(){
return false;
}
function returnTrue(){
return true;
}
setEvent.prototype = {
preventDefault: function() {
var e = this.originalEvent;
if( !e )
return;
// if preventDefault exists run it on the original event
if (e.preventDefault)
e.preventDefault();
// otherwise set the returnValue property of the original event to false (IE)
e.returnValue = false;
},
stopPropagation: function() {
var e = this.originalEvent;
if( !e )
return;
// if stopPropagation exists run it on the original event
if (e.stopPropagation)
e.stopPropagation();
// otherwise set the cancelBubble property of the original event to true (IE)
e.cancelBubble = true;
},
stopImmediatePropagation:function(){
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
},
isImmediatePropagationStopped: returnFalse
};
一個(gè)完整的例子
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " <html xmlns=" <head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title></title>
</head>
<body>
<div id="vv" style="height:200px;width:200px; background-color:#f00; padding:20px;">
<div id="xx" style="height:200px;width:200px; background-color:#000000;"></div>
</div>
<script type="text/javascript">
(function(doc,undefined){
function now(){
return +new Date;
};
var win = this,
expando = "jQuery" + now(),
uuid = 0,
cache = {};
win.data = function(elem, name, data){
var id = elem[expando];
if(!id)
id = elem[expando] = ++uuid;
if(name&&!cache[id])
cache[id] = {};
if(data !== undefined)
cache[id][name] = data;
return name
? cache[id][name]
: id;
}
win.removeData = function(elem, name){
var id = elem[expando];
if (name){
if (cache[id]) {
delete cache[id][name];
name = "";
for ( name in cache[ id ] )
break;
if ( !name )
removeData(elem);
}
}else{
try {
delete elem[expando];
} catch(e){
if ( elem.removeAttribute )
elem.removeAttribute( expando );
}
delete cache[id];
}
}
win.each = function( object, callback, args ) {
var name, i = 0, length = object.length;
if ( args ) {
if ( length === undefined ) {
for ( name in object )
if ( callback.apply( object[ name ], args ) === false )
break;
} else
for ( ; i < length; )
if ( callback.apply( object[ i++ ], args ) === false )
break;
} else {
if ( length === undefined ) {
for ( name in object )
if ( callback.call( object[ name ], name, object[ name ] ) === false )
break;
} else
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
}
return object;
}
win.gevent = {
guid : 1,
add : function (elem, types, handler){
if ( elem.nodeType == 3 || elem.nodeType == 8 )
return;
if ( elem.setInterval && elem != window )
elem = window;
//給函數(shù)一個(gè)唯一標(biāo)識(shí)的索引 方便后面刪除該事件
if ( !handler.guid )
handler.guid = this.guid++;
//獲得該元素的events handle 下的數(shù)據(jù)
var events = data(elem, "events") || data(elem, "events", {}),
handle =data(elem, "handle") || data(elem, "handle", function(){
//gevent.handle才是各種行為觸發(fā)后會(huì)執(zhí)行的函數(shù)
gevent.handle.apply(arguments.callee.elem, arguments);
});
handle.elem = elem;
//遍歷事件名 因?yàn)榭梢允?click mouseover
each(types.split(/\s+/), function(index, type) {
var namespaces = type.split(".");
//獲得事件名
type = namespaces.shift();
//去掉點(diǎn)后面的東西 是個(gè)特殊的命名 在刪除的時(shí)候可以指定刪除他 如 click.d
//用事件的type 記錄住這個(gè)特殊的命名
handler.type = namespaces.slice().sort().join(".");
//獲得該事件是否已經(jīng)存在events 這個(gè)對(duì)象里面了
var handlers = events[type];
//如果不存在該事件 給元素綁定該事件
if (!handlers) {
handlers = events[type] = {};
if (elem.addEventListener)
elem.addEventListener(type, handle, false);
else if (elem.attachEvent)
elem.attachEvent("on" + type, handle);
}
//吧函數(shù)放到元素的該事件的列表里面
handlers[handler.guid] = handler;
});
elem = null;
},
remove: function(elem, types, handler) {
if ( elem.nodeType == 3 || elem.nodeType == 8 )
return;
//獲取這個(gè)元素的所有行為列表 如 {click:{},mouseocer:{}}
var events = data(elem, "events"), ret, index;
if(events){
//如果沒(méi)出入行為類(lèi)型 則刪除這個(gè)元素的所有事件
//如果傳入的是.xx這種形式的 把所有行為的包含.xx命名的全部干掉
if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") ){
for ( var type in events )
this.remove( elem, type + (types || "") );
}else{
//不知道干嘛的
if ( types.type ) {
handler = types.handler;
types = types.type;
}
//因?yàn)閯h除事件可以一次支持刪除多個(gè) 如click mouseover 所有要遍歷刪除
each(types.split(/\s+/),function(index, type){
var namespaces = type.split(".");
type = namespaces.shift();
var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
if ( events[type] ) {
//如果傳了第3個(gè)參數(shù) 函數(shù) 則刪除這個(gè)事件
if ( handler )
delete events[type][handler.guid];
else{
//遍歷中個(gè)這個(gè)的所有行為
for ( var handle in events[type] ){
// Handle the removal of namespaced events
//刪除有特殊命名的函數(shù)
//如果沒(méi)有特殊命名 正則 則是/^|..|$/ 可以匹配空 所以也能刪除掉沒(méi)有特殊命名的函數(shù)
if ( namespace.test(events[type][handle].type) )
delete events[type][handle];
}
}
}
for ( ret in events[type] ) break;
//如果events[type]變成空的了 也就是{} 刪除這個(gè)元素的的綁定事件
if ( !ret ) {
if (elem.removeEventListener)
elem.removeEventListener(type, data(elem, "handle"), false);
else if (elem.detachEvent)
elem.detachEvent("on" + type, data(elem, "handle"));
ret = null;
delete events[type];
}
});
}
for ( ret in events ) break;
//如果發(fā)現(xiàn)元素的整個(gè)events都是空的了
//清空掉handle 并且清空掉他所有的引用
if ( !ret ) {
var handle = data( elem, "handle" );
if ( handle ) handle.elem = null;
removeData( elem, "events" );
removeData( elem, "handle" );
}
}
},
handle : function(event){
var all, handlers;
//包裝event
event = arguments[0] = gevent.fix( event || window.event );
event.currentTarget = this;
//這里的........
var namespaces = event.type.split(".");
event.type = namespaces.shift();
all = !namespaces.length;
var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
//取這個(gè)元素的該行為 的 事件列表
handlers = (data(this, "events") || {} )[event.type];
//遍歷這個(gè)事件列表 執(zhí)行該執(zhí)行的東西
for ( var j in handlers ) {
var handler = handlers[j];
if ( all || namespace.test(handler.type) ) {
// Pass in a reference to the handler function itself
// So that we can later remove it
// jq上的注釋是是這么寫(xiě)的 把event的handler 引用這個(gè)事件 方便之后移除
// 但是在remove里面 并沒(méi)有用到event的handler 不知道這里到底有什么用 且有多個(gè)事件的時(shí)候這個(gè)事件被取代
event.handler = handler;
//執(zhí)行事件 并且是用元素調(diào)用的事件 可以吧事件里面的this執(zhí)行元素 ret為函數(shù)的返回值
var ret = handler.apply(this, arguments);
//如果有返回值 且返回值是false 執(zhí)行阻止事件冒泡 阻止執(zhí)行事件默認(rèn)行為
if( ret !== undefined ){
event.result = ret;
if ( ret === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
},
props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix : function(event){
//new setEvent會(huì)給event給以個(gè)expando屬性 如果有中個(gè)屬性 說(shuō)明已經(jīng)生成了event了 不需要在次對(duì)event進(jìn)行包裝
if ( event[expando] )
return event;
//保留一個(gè)原始的event
// new一個(gè)新的event 這個(gè)與原始的event是不同的
var originalEvent = event;
event = new setEvent( originalEvent );
//獲得原始event的屬性值 有哪些屬性值 見(jiàn) this.props
for ( var i = this.props.length, prop; i; ){
prop = this.props[ --i ];
event[ prop ] = originalEvent[ prop ];
}
//將目標(biāo)元素同一成event.target
if ( !event.target )
event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
//如果發(fā)現(xiàn)是文本節(jié)點(diǎn) 取他的父節(jié)點(diǎn)
if ( event.target.nodeType == 3 )
event.target = event.target.parentNode;
if ( !event.relatedTarget && event.fromElement )
event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
return event;
}
}
win.setEvent = function(src){
// Allow instantiation without the 'new' keyword
// Event object
if( src && src.type ){
this.originalEvent = src;
this.type = src.type;
// Event type
}else
this.type = src;
// timeStamp is buggy for some events on Firefox(#3843)
// So we won't rely on the native value
this.timeStamp = now();
// Mark it as fixed
this[expando] = true;
}
function returnFalse(){
return false;
}
function returnTrue(){
return true;
}
setEvent.prototype = {
preventDefault: function() {
var e = this.originalEvent;
if( !e )
return;
// if preventDefault exists run it on the original event
if (e.preventDefault)
e.preventDefault();
// otherwise set the returnValue property of the original event to false (IE)
e.returnValue = false;
},
stopPropagation: function() {
var e = this.originalEvent;
if( !e )
return;
// if stopPropagation exists run it on the original event
if (e.stopPropagation)
e.stopPropagation();
// otherwise set the cancelBubble property of the original event to true (IE)
e.cancelBubble = true;
},
stopImmediatePropagation:function(){
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
},
isImmediatePropagationStopped: returnFalse
};
})(document);
var $ = function(id){return document.getElementById(id)}
var a = function(){alert(1)}
window.onload = function(){
gevent.add($('xx'),'click',a);
gevent.add($('xx'),'click',function(){alert(1)});
gevent.add($('xx'),'click',function(){alert(2)});
gevent.add($('xx'),'click',function(){alert(3)});
gevent.add($('xx'),'click.xx',function(){alert(4)});
}
</script>
</body>
</html>
以上內(nèi)容只是自己的一些理解,水平有限,難免有錯(cuò),望指正...
- jQuery事件綁定和委托實(shí)例
- 關(guān)于jQuery新的事件綁定機(jī)制on()的使用技巧
- jQuery事件綁定on()、bind()與delegate() 方法詳解
- jQuery事件綁定與解除綁定實(shí)現(xiàn)方法
- jQuery的三種bind/One/Live/On事件綁定使用方法
- jQuery事件綁定on()與彈窗實(shí)現(xiàn)代碼
- jquery 事件冒泡的介紹以及如何阻止事件冒泡
- Jquery阻止事件冒泡 event.stopPropagation
- 事件冒泡是什么如何用jquery阻止事件冒泡
- jquery取消事件冒泡的三種方法(推薦)
- jQuery事件綁定和解綁、事件冒泡與阻止事件冒泡及彈出應(yīng)用示例
相關(guān)文章
基于jquery的用dl模擬實(shí)現(xiàn)可自定義樣式的SELECT下拉列表(已封裝)
通過(guò)dl模擬實(shí)現(xiàn)SELECT下拉列表. 其實(shí)這是項(xiàng)目中要常用到的一個(gè)效果, 于是, 在之前寫(xiě)的基礎(chǔ)上封裝成了一個(gè)插件. 可自定義樣式, 可防止用戶(hù)本意劃過(guò)時(shí)觸發(fā)事件.2010-11-11關(guān)于jquery.validate1.9.0前臺(tái)驗(yàn)證的使用介紹
本篇文章介紹了,關(guān)于jquery.validate1.9.0前臺(tái)驗(yàn)證的使用。需要的朋友參考下2013-04-04jquery實(shí)現(xiàn)鼠標(biāo)滑過(guò)后動(dòng)態(tài)圖片提示效果實(shí)例
這篇文章主要介紹了jquery實(shí)現(xiàn)鼠標(biāo)滑過(guò)后動(dòng)態(tài)圖片提示效果,涉及jquery鼠標(biāo)事件及頁(yè)面元素的動(dòng)態(tài)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08jQuery ajax時(shí)間差導(dǎo)致的變量賦值問(wèn)題分析
這篇文章主要介紹了jQuery ajax時(shí)間差導(dǎo)致的變量賦值問(wèn)題,結(jié)合實(shí)例對(duì)比分析了jQuery的ajax調(diào)用中出現(xiàn)的時(shí)間差賦值問(wèn)題原因與解決方法,需要的朋友可以參考下2016-01-01jQuery實(shí)現(xiàn)首頁(yè)頂部可伸縮廣告特效代碼
一套使用jQuery 插件實(shí)現(xiàn)的廣告特效代碼,其效果類(lèi)似全屏廣告,打開(kāi)網(wǎng)頁(yè)后在網(wǎng)頁(yè)的第一屏顯示大幅廣告,停留幾秒后慢慢伸縮至標(biāo)準(zhǔn)小圖片顯示在網(wǎng)頁(yè)預(yù)留位置上,效果非常不錯(cuò),這里推薦給大家。2015-04-04jQuery實(shí)現(xiàn)滑動(dòng)開(kāi)關(guān)效果
這篇文章主要為大家詳細(xì)介紹了jQuery實(shí)現(xiàn)滑動(dòng)開(kāi)關(guān)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08jquery+ajax+C#實(shí)現(xiàn)無(wú)刷新操作數(shù)據(jù)庫(kù)數(shù)據(jù)的簡(jiǎn)單實(shí)例
本篇文章主要是對(duì)jquery+ajax+C#實(shí)現(xiàn)無(wú)刷新操作數(shù)據(jù)庫(kù)數(shù)據(jù)的簡(jiǎn)單實(shí)例進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-02-02