web前端之MVC的JavaScript Web富应用开发二:事件和监听

web前端之MVC的JavaScript Web富应用开发二:事件和监听

事件是JavaScript应用程序的核心,是所有内容的驱动,它决定了在应用程序产生用户交互的起始时刻。然而在JavaScript诞生之初“ 事件” 的实现并不标准,甚至非常丑陋。在之后的浏览器大战中网景和微软分道扬镳,他们各自实现的事件模型互不兼容。尽管后来W3C对此做了标准化,但IE仍然坚持使用与W3C不兼容的事件模型,直到最新发布的IE9才遵循标准。
幸运的是,有很多诸如jQuery和Prototye的类库很好地处理了兼容性问题,对外提供了统一的API来实现事件。但是了解事件的机制仍然是非常重要的,因此这里首先讲解W3C中的事件模型,然后展示各种流行类库的一些实例。

监听事件:

绑定事件监听的函数叫做addEventListener(),它有三个参数:type(比如click),listener(比如callback)及useCapture(后续会讲到useCapture)。使用前两个参数可以给DOM元素绑定一个函数,当特定的事件(比如点击)被触发时执行这个函数:
var button=document.getElmentById(“createButton”);
button.addEventListener(“click”,function(){//},false);

可以使用removeEventListener()来移除事件监听,参数和传入addEventListener()的一样。如果监听的函数时匿名函数,没有任何引用指向它,在不销毁这个元素的前提下,这个监听是无法被移除的。
var div=document.getElementById(“div”);
var listener=function(event){//};
div.addEventListener(“click”,listener,false);
div.removeEventListener(“click”,listener,false);

带入listener函数的第一个参数是event对象,通过event对象可以得到事件的相关信息,比如时间戳、坐标和事件宿主元素(target)。它同样包含很多方法来停止事件冒泡和阻止事件的默认行为。
不同的浏览器对事件类型的支持也不尽相同,但所有的现代浏览器都支持这些事件:
click、dbclick、mousemove、mouseover、mouseout、focus、blur、change(表单输入框特有)、submit(表单特有)

事件顺序:

参考资料

在进一步讨论之前, 很有必要介绍一下事件顺序。 如果一个节点和它的一个父节点都绑定了相同事件类型的回调, 当事件触发时哪个回调会先执行?

你可以自行选择要注册的事件处理程序的调用类型,捕捉或冒泡,通过给addEventListener()传入第3个参数useCapture来设置。如果addEventListener()的最后一个参数是true,事件处理程序以捕捉模式触发:如果是false,事件处理程序以冒泡模式触发 。

取消事件:

当事件冒泡时, 可以通过 stopPropagation() 函数来终止冒泡, 这个函数是 event 对象中的方法。 比如这段代码, 任何父节点的事件回调都不会触发 :

button.addEventListener("click",function(event){
    event.stopPropagation();
    /*...*/
},false);

此外, 一些类库比如 jQuery 还支持 stopImmediatePropagation() 函数, 用来阻止后续所有的事件触发——哪怕这些事件是注册在同一个节点元素上的也不例外。

浏览器同样给事件赋予了默认行为。 比如, 当你点击一个链接时, 浏览器的默认行为是载入新页面。 可以通过调用 event 对象的 preventDefault() 函数来阻止默认行为, 同样也可以通过在回调中返回 false 来实现同样的效果 :

bform.addEventListener("submit", function(e){
    /* ... */
    return confirm("Are you super sure?");
}, false);

如果调用 confirm() 返回 false( 用户点击了对话框的取消按钮), 这个事件回调函数就返
回 false, 这样就会取消事件, 阻止表单的提交。

事件对象:

和上面提到的函数 stopPropagation() 和 preventDefault() 一样, event 对象还包含很多有用的属性。

bubbles :布尔值, 表示事件是否通过 DOM 以冒泡形式触发。

事件发生时, 反映当前环境信息的属性 :

button :表示( 如果有) 鼠标所按下的按钮。
ctrlKey :布尔值, 表示 Ctrl 键是否按下。
altKey :布尔值, 表示 Alt 键是否按下。
shiftKey :布尔值, 表示 Shift 键是否按下。
metaKey :布尔值, 表示 Meta 键注 1 是否按下。

表示键盘事件的属性 :

isChar :布尔值, 表示当前按下的键是否表示一个字符。
charCode :表示当前按键的 unicode 值( 仅对 keypress 事件有效)。
keyCode :表示非字符按键的 unicode 值。
which :表示当前按键的 unicode 值, 不管当前按键是否表示一个字符。

事件发生时的环境参数 :

pageX, pageY :事件发生时相对于页面( 如 viewport 区域) 的坐标。
screenX, screenY :事件发生时相对于屏幕的坐标。

和事件相关的元素 :

currentTarget :事件冒泡阶段所在的当前 DOM 元素。
target,originalTarget :原始的 DOM 元素。
relatedTarget :其他和事件相关的 DOM 元素( 如果有的话)。

不同的浏览器对这些属性的兼容性也不同,尤其是那些不兼容 W3C 的浏览器。 幸运的是,诸如 jQuery 和 Prototype 这些类库为我们解决了这些兼容性问题。

事件库:

jQuery 的 API 提供了 bind() 函数用来跨浏览器绑定事件监听。 在一个 jQuery 实例上调用此函数, 传入事件名称和回调函数 :
jQuery(“#element”).bind(eventName, handler);
比如, 给一个元素注册点击事件 :

jQuery("#element").bind("click", function(event) {
    // ...
});

jQuery 提供了一些常用事件的快捷方法, 比如 click、 submit 和 mouseover。 看这段代码:

$("#myDiv").click(function(){
    // ...
});

需要注意的是, 使用这个方法之前要确保 DOM 元素是存在的, 这一点很重要。 例如,应当在页面载入完成后绑定事件, 因此需要绑定 window 的 load 事件, 然后添加监听 :

jQuery(window).bind("load", function() {
    $("#signinForm").submit(checkForm);
});

这个函数是兼容各个浏览器的 :

jQuery.ready(function($)){
    $("#myForm"). bind("submit", function(){ /*...*/});
});

实际上, 可以不用 ready() 函数而直接将回调函数写入 jQuery 对象。

jQuery(function($){
    // 当页面内容可用时调用
});

切换上下文:

关于事件有一点经常让人感到迷惑,那就是调用事件回调函数时上下文的切换。当使用浏览器内置的 addEventListener() 时, 上下文从局部变量切换为目标 HTML 元素 :

new function(){
    this.appName="wem";
    document.body.addEventListener("click",function(e){
        //上下文发生改变时,因此appName是undefined
        alert(this.appName);
    },false);
};

要想保持原有的上下文, 需要将回调函数包装进一个匿名函数, 然后定义一个引用指向它。这在jQuery 中也是一种很常用的模式, 包括一个 proxy() 函数, 只需将指定的上下文传入函数即可 :
$(“signinForm”).submit($.proxy(function(){ /* … */ }, this));

委托事件:

从事件冒泡时开始就发生了事件委托, 我们可以直接给父元素绑定事件监听, 用来检测在其子元素内发生的事件。

//在ul列表上做了事件委托
list.addEventListener("click",function(e){
    if(e.currentTarget.tagName=="li"){
        /*....*/
        return false;
    }
},false);

jQuery 的处理方式更妙, 只需给 delegate() 函数传入子元素的选择器、 事件类型和回调函数即可。 如果使用事件绑定的话, 就会给每一个 li 元素都绑定 click 事件, 然而使用 delegate() 方法就能减少这种事件监听的数量, 改善代码性能 :

// 不要这样做, 这样会给每个 li 元素都添加事件监听( 非常浪费)
$("ul li").click(function(){ /* ... */ });
// 这样只会添加一个事件监听
$("ul").delegate("li", "click", /* ... */);

使用事件委托的另一个好处是, 所有为元素动态添加的子元素都具有事件监听。 因此,在上面的例子中, 在页面载入完成后添加的 li 节点同样可以触发点击事件的回调。

自定义事件:

jQuery 中可以使用 trigger() 函数来触发自定义事件。 可以通过命名空间的形式来管理事件名称, 命名空间中的单词用点号分隔注, 比如 :

// 绑定自定义事件
$(".class").bind("refresh.widget",function(){});
// 触发自定义事件
$(".class").trigger("refresh.widget");

通过给 trigger() 传入一个额外的参数来给事件处理程序传入数据。 数据会以附加参数
的形式带入回调 :

$(".class").bind("frob.widget", function(event, dataNumber){
    console.log(dataNumber);
});
$(".class").trigger("frob.widget", 5);

和内置事件一样, 自定义事件同样会沿着 DOM 树做冒泡。

自定义事件和 jQuery 插件:

我们来看一个简单的 jQuery 插件——选项卡。 我们让 ul 列表来响应点击事件。当用户点击一个列表项, 给这个列表项添加一个名为 active 的类, 同时将其他列表项中的 active 类移除 :

<ul>
    <li data-tab="users">Users</li>
    <li data-tab="groups">Groups</li>
</ul>
<div id="tabsContent">
    <div data-tab="users">Users...</div>
    <div data-tab="groups">Groups...</div>
</div>

另外, id 为 tabsContent 的 div 用来存放每个选项卡对应的实际内容。 根据当前激活的选项卡, 来对应地给 div 的子节点添加或删除 active 类。 实际的显示和隐藏选项卡和内容都由 CSS 来控制, 我们的插件仅仅处理 active 类 :

jQuery.fn.tabs=function(control){
    var element=$(this);
    control=$(control);
    element.delegate("li","click",function(){//只添加一个li事件
        //遍历选项卡名称
        var tabName=$(this).attr("data-tab");
        //在点击选项卡时触发自定义事件
        element.trigger("change.tabs",tabName);
    });
    element.bind("change.tabs",function(e,tabName){
        element.find("li").removeClass("active");
        element.find(">[data-tab='"+tabName+"']").addClass("active");
    });
    element.bind("change.tabs",function(e,tabName){
        control.find(">[data-tab]").removeClass("active");
        control.find(">[data-tab='"+tabName+"']").addClass("active");
    });
    //激活第一个选项卡
    var firstName=element.find("li:first").attr("data-tab");
    element.trigger("change.tabs",firstName);
    return this;
};

我们看到使用自定义事件回调可以让代码更加整洁。 这也意味着选项卡状态切换回调彼此分离, 这也让插件代码更具扩展性。 比如我们可以在程序中直接更改选项卡的状态,只需触发被观察列表的 change.tabs 事件即可 :$(“#tabs”).trigger(“change.tabs”, “users”);

同样, 我们可以将切换选项卡的动作和窗口的 hash 做关联, 这样就可以使用浏览器的后退按钮了 :

$("#tabs").bind("change.tabs", function(e, tabName){
    window.location.hash = tabName;
});
$(window).bind("hashchange", function(){
    var tabName = window.location.hash.slice(1);
    $("#tabs").trigger("change.tabs", tabName);
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值