zoukankan      html  css  js  c++  java
  • JS事件模型

    观察者模式又叫做发布订阅者模式(Publish/Subscribe),它可以让多个观察者对象同时监听某一个主题对象,这个主题对象的状态变化时会通知所有的订阅者,使得它们能够做出反应。
    JS的事件模型就是一种观察者模式的体现,当对应的事件被触发时,监听该事件的所有监听函数都会被调用。

    下面是用JS实现的一个观察者模式的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    var events = (function() {
    var topics = {};

    return {
    publish: function(topic, info) {
    console.log('publish a topic:' + topic);
    if (topics.hasOwnProperty(topic)) {
    topics[topic].forEach(function(handler) {
    handler(info ? info : {});
    })
    }
    },
    subscribe: function(topic, handler) {
    console.log('subscribe an topic:' + topic);
    if (!topics.hasOwnProperty(topic)) {
    topics[topic] = [];
    }

    topics[topic].push(handler);
    },
    remove: function(topic, handler) {
    if (!topics.hasOwnProperty(topic)) {
    return;
    }

    var handlerIndex = -1;
    topics[topic].forEach(function(element, index) {
    if (element === handler) {
    handlerIndex = index;
    }
    });

    if (handlerIndex >= 0) {
    topics[topic].splice(handlerIndex, 1);
    }
    },
    removeAll: function(topic) {
    console.log('remove all the handler on the topic:' + topic);
    if (topics.hasOwnProperty(topic)) {
    topics[topic].length = 0;
    }
    }
    }
    })();

    使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    //主题监听函数
    var handler = function(info) {
    console.log(info);
    }
    //订阅hello主题
    events.subscribe('hello', handler);

    //发布hello主题
    events.publish('hello', 'hello world');

    事件与事件流


    事件是与浏览器或文档交互的瞬间,如点击按钮,填写表格等,它是JS与HTML之间交互的桥梁。
    DOM是树形结构,如果同时给父子节点都绑定事件时,当触发子节点的时候,这两个事件的发生顺序如何决定?这就涉及到事件流的概念,它描述的是页面中接受事件的顺序。

    事件流有两种:

    • 事件冒泡(Event Capturing): 是一种从下往上的传播方式。事件最开始由最具体的元素(文档中嵌套层次最深的那个节点接受, 也就是DOM最低层的子节点), 然后逐渐向上传播到最不具体的那个节点,也就是DOM中最高层的父节点。
    • 事件捕获(Event Bubbling): 与事件冒泡相反。事件最开始由不太具体的节点最早接受事件, 而最具体的节点最后接受事件。

    事件模型


    DOM0级模型

    又称为原始事件模型,在该模型中,事件不会传播,即没有事件流的概念。事件绑定监听函数比较简单, 有两种方式:

    • HTML代码中直接绑定:

      1
      <input type="button" onclick="fun()">
    • 通过JS代码指定属性值:

      1
      2
      var btn = document.getElementById('.btn');
      btn.onclick = fun;

      移除监听函数:

      1
      btn.onclick = null;

    这种方式所有浏览器都兼容,但是逻辑与显示并没有分离。

    IE事件模型

    IE事件模型共有两个过程:

    • 事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
    • 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。

    事件绑定监听函数的方式如下:

    1
    attachEvent(eventType, handler)

    事件移除监听函数的方式如下:

    1
    detachEvent(eventType, handler)

    参数说明:

    • eventType指定事件类型(注意加on)
    • handler是事件处理函数

    Example:

    1
    2
    3
    var btn = document.getElementById('.btn');
    btn.attachEvent(‘onclick’, showMessage);
    btn.detachEvent(‘onclick’, showMessage);

    DOM2级模型

    属于W3C标准模型,现代浏览器(除IE6-8之外的浏览器)都支持该模型。在该事件模型中,一次事件共有三个过程:

    • 事件捕获阶段(capturing phase)。事件从document一直向下传播到目标元素, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。
    • 事件处理阶段(target phase)。事件到达目标元素, 触发目标元素的监听函数。
    • 事件冒泡阶段(bubbling phase)。事件从目标元素冒泡到document, 依次检查经过的节点是否绑定了事件监听函数,如果有则执行。

    事件绑定监听函数的方式如下:

    1
    addEventListener(eventType, handler, useCapture)

    事件移除监听函数的方式如下:

    1
    removeEventListener(eventType, handler, useCapture)

    Example:

    1
    2
    3
    var btn = document.getElementById('.btn');
    btn.addEventListener(‘click’, showMessage, false);
    btn.removeEventListener(‘click’, showMessage, false);

    参数说明:

    • eventType指定事件类型(不要加on)
    • handler是事件处理函数
    • useCapture是一个boolean用于指定是否在捕获阶段进行处理,一般设置为false与IE浏览器保持一致。

    事件对象


    当一个事件被触发时,会创建一个事件对象(Event Object), 这个对象里面包含了与该事件相关的属性或者方法。该对象会作为第一个参数传递给监听函数。

    • DOM事件模型中的事件对象常用属性:
      • type用于获取事件类型
      • target获取事件目标
      • stopPropagation()阻止事件冒泡
      • preventDefault()阻止事件默认行为
    • IE事件模型中的事件对象常用属性:
      • type用于获取事件类型
      • srcElement获取事件目标
      • cancelBubble阻止事件冒泡
      • returnValue阻止事件默认行为

    Event Wrapper


    由于事件模型的差异以及Event对象的不同,为了达到兼容各个浏览器的目的,我们可以增加一个Event Wrapper, 它对各个浏览器应当提供一致的事件操作接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    var eventUtils={
    // 添加句柄
    addHandler:function(element,type,handler){
    if(element.addEventListener){
    element.addEventListener(type,handler,false);
    }else if(element.attachEvent){
    element.attachEvent('on'+type,handler);
    }else{
    element['on'+type]=handler;
    }
    },
    // 删除句柄
    removeHandler:function(element,type,handler){
    if(element.removeEventListener){
    element.removeEventListener(type,handler,false);
    }else if(element.detachEvent){
    element.detachEvent('on'+type,handler);
    }else{
    element['on'+type]=null;
    }
    },
    //获取事件对象
    //IE模型中event是一个全局唯一的对象绑定在window对象上
    getEvent:function(event){
    return event?event:window.event;
    },
    //获取类型
    getType:function(event){
    return event.type;
    },
    getElement:function(event){
    return event.target || event.srcElement;
    },
    //阻止默认事件
    preventDefault:function(event){
    if(event.preventDefault){
    event.preventDefault();
    }else{
    event.returnValue=false;
    }
    },
    //阻止冒泡
    stopPropagation:function(event){
    if(event.stopPropagation){
    event.stopPropagation();
    }else{
    event.cancelBubble=true;
    }
    }
    }

    参考:

    事件代理


    事件在冒泡过程中会上传到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理(Event delegation)。

    我们有一个div元素,它包含三个按钮:

    1
    2
    3
    4
    5
    <div id="box">
    <input type="button" value="按钮" id="btn">
    <input type="button" value="按钮2" id="btn2">
    <input type="button" value="按钮3" id="btn3">
    </div>

    我们可以在父节点上一次性的为所有子节点注册监听函数:

    1
    2
    3
    4
    5
    6
    7
    var box = document.getElementById('box');

    box.addEventListener('click', function(event) {
    if (event.target.tagName.toLowerCase() === 'input') {
    // some code
    }
    });

    自定义事件


    JS中已经内置了很多事件,如click, mouseover等等,但是内置事件毕竟有限,有时候我们想自己定义一些事件,例如三连击,threeclick。如何实现自定义事件呢?

    • 首先要创建一个事件。可以使用以下方式:

      1
      var event = new Event('threeclick', {"bubbles":true, "cancelable":false});
    • 然后我们需要为事件注册监听函数:

      1
      target.addEventListener('threeclick', hello, false);
    • 最后我们要在合适的时机触发该事件,我们可以使用dispatchEvent函数。该方法在当前节点触发指定事件,从而触发监听函数执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(), 则返回false, 否则返回true。

      1
      target.dispatchEvent(event);

    JQuery Event模型

    JQuery解决的一个主要问题就是浏览器的兼容问题,它有自己的事件模型实现方式。它主要有以下特性:

    • 重定义了JQuery.Event对象, 统一了事件属性和方法, 统一了事件模型
    • 可以在一个事件类型上添加多个事件处理函数, 可以一次添加多个事件类型的事件处理函数
    • 支持自定义事件(事件命名空间)
    • 提供了toggle, hover组合事件
    • 提供了one, live-die, delegate-undelegate
    • 提供了统一的事件封装, 绑定, 执行, 销毁机制
    • $(document).ready();
    • ….

    JQuery事件具体代码请参考: https://github.com/jquery/jqu…

    js实现事件模型bind与trigger


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    function Emitter() {
    this._listener = [];//_listener[自定义的事件名] = [所用执行的匿名函数1, 所用执行的匿名函数2]
    }

    //注册事件
    Emitter.prototype.bind = function(eventName, callback) {
    var listener = this._listener[eventName] || [];//this._listener[eventName]没有值则将listener定义为[](数组)。
    listener.push(callback);
    this._listener[eventName] = listener;
    }

    //触发事件
    Emitter.prototype.trigger = function(eventName) {
    var args = Array.prototype.slice.apply(arguments).slice(1);//atgs为获得除了eventName后面的参数(注册事件的参数)
    var listener = this._listener[eventName];

    if(!Array.isArray(listener)) return;//自定义事件名不存在
    listener.forEach(function(callback) {
    try {
    callback.apply(this, args);
    }catch(e) {
    console.error(e);
    }
    })
    }
    //实例
    var emitter = new Emitter();
    emitter.bind("myevent", function(arg1, arg2) {
    console.log(arg1, arg2);
    });

    emitter.bind("myevent", function(arg1, arg2) {
    console.log(arg2, arg1);
    });

    emitter.trigger('myevent', "a", "b");

    实现链式调用的例子,原理与事件模型相同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    function LazyMan(name) {
    return new _LazyMan(name);
    }
    function _LazyMan(name) {
    console.log("Hi This is " + name)
    this.task = [];
    var _this = this;
    var namer = (function(name) {
    return function() {
    console.log(name);
    _this.next();
    }
    })(name)
    this.task.push(namer);
    setTimeout(function() {
    _this.next();
    }, 0);
    return this;
    }
    _LazyMan.prototype.next = function() {
    var fn = this.task.shift();
    fn&&fn();
    }
    _LazyMan.prototype.eat = function(val) {
    var _this = this;
    var eat = (function(val) {
    return function() {
    console.log("eat" + val);
    _this.next();
    }
    })(val);
    this.task.push(eat);
    return this;
    }
    _LazyMan.prototype.sleep = function(time) {
    var _this = this;

    var timer = (function(time) {
    return function() {
    setTimeout(function() {
    console.log("wait");
    console.log("time=" + time);
    _this.next();
    }, time*1000);
    }

    })(time);
    this.task.push(timer);
    return this;
    }

    //LazyMan("Hank").eat("dinner").eat("supper");
    LazyMan("Hank").sleep(3).eat("dinner");
  • 相关阅读:
    wsl中的git问题
    接口测试框架实战(三)| JSON 请求与响应断言
    接口测试框架实战(二)| 接口请求断言
    面试大厂测试开发之前,你需要做好哪些准备?
    接口测试框架实战(一) | Requests 与接口请求构造
    实战 | UI 自动化测试框架设计与 PageObject 改造
    用 Pytest+Allure 生成漂亮的 HTML 图形化测试报告
    外包测试的职业发展利弊分析与建议
    做软件测试,到底能不能去外包?
    移动自动化测试入门,你必须了解的背景知识和工具特性
  • 原文地址:https://www.cnblogs.com/duanlibo/p/10969664.html
Copyright © 2011-2022 走看看