【问题标题】:addEventListener on custom object自定义对象上的 addEventListener
【发布时间】:2014-01-17 02:47:05
【问题描述】:

我创建了一个具有多种方法的对象。其中一些方法是异步的,因此我想使用事件来在方法完成时执行操作。为此,我尝试将 addEventListener 添加到对象中。

jsfiddle

var iSubmit = {
    addEventListener: document.addEventListener || document.attachEvent,
    dispatchEvent: document.dispatchEvent,
    fireEvent: document.fireEvent,   


    //the method below is added for completeness, but is not causing the problem.

    test: function(memo) {
        var name = "test";
        var event;
        if (document.createEvent) {
            event = document.createEvent("HTMLEvents");
            event.initEvent(name, true, true);
        } else {
            event = document.createEventObject();
            event.eventType = name;
        }
        event.eventName = name;
        event.memo = memo || { };

        if (document.createEvent) {
            try {
                document.dispatchEvent(event);
            } catch (ex) {
                iAlert.debug(ex, 'iPushError');
            }
        } else {
            document.fireEvent("on" + event.eventType, event);
        }
    }
}

iSubmit.addEventListener("test", function(e) { console.log(e); }, false);


//This call is added to have a complete test. The errors are already triggered with the line before this one.

iSubmit.test();

这将返回错误:Failed to add eventlisterens: TypeError: 'addEventListener' called on an object that does not implement interface EventTarget."

现在这段代码将在 phonegap 应用程序中使用,当我这样做时,它可以在 android/ios 上运行。但是,在测试期间,如果我能让它在至少一个浏览器中工作,那就太好了。

PS>我知道我可以启用冒泡,然后监听文档根目录,但我希望有一点 OOP,每个对象都可以独立工作。

【问题讨论】:

  • Nodejs 有这个功能。他们会做类似var emitter = require('events').EventEmitter; 的事情。但是我们可以像ourObject.prototype.__proto__ = EventTarget.prototype; 那样做吗?在这里,调节 JS ??

标签: javascript object cordova addeventlistener


【解决方案1】:

addEventListener 适用于实现某些事件相关接口的 DOM 元素。如果您想要纯 JavaScript 对象上的事件系统,那么您正在寻找自定义事件系统。 Backbone.js 中的 Backbone.Events 就是一个例子。基本思想是使用一个对象作为哈希来跟踪注册的回调。

我个人使用这个:emitter

这是一个相当简单而优雅的解决方案 - 使用简短的方法名称,如 on()off()emit()。您可以使用new Emitter() 创建新实例,或使用Emitter(obj) 将事件功能混合到现有对象中。请注意,此库是为与 CommonJS 模块系统一起使用而编写的,但您可以通过删除 module.exports = ... 行来在其他任何地方使用它。

【讨论】:

  • 谢谢。我想答案是“做不到”。正如您已经提到的,您的插件只不过是一个回调系统。我宁愿重写自己的代码来处理回调,然后使用第 3 方插件。
  • 嗯,事件必须触发回调,所以如果你想这样称呼它,事件系统本质上就是一个回调系统。 EventEmitter 模式在 Node.js 和大型前端应用程序中都非常常用,因此使用成熟的现有解决方案并不是一个坏主意。
  • 使用原生方法的优势在于原生代码(HTMLElement.addEventListener 通常在性能方面优于用 javascript 编写的 javascript 代码。
  • 在我的例子中,我设置了一个代理文档片段,然后使用片段和对象的标识符(像 '__id'=4 这样的扩展属性)来关联它们。在对象上调度事件实际上会在片段上发生,这将触发处理程序设置 event.detail 和适当的目标。这一切都发生在一个包装数组对象(如 jQuery)中,所以它很简单。不确定它是否比直接回调、promise 或其他方法更快——DOM 通常非常复杂和缓慢——但它运行良好、足够快并且易于实现。
  • EventEmitter、EventTarget和EventDispatcher有什么区别?
【解决方案2】:

如果您不需要真正的事件功能(例如冒泡、停止传播),那么您可以实现自己的事件。 addEventListener 只是 DOM 的一个 API,因此您在 DOM 之外的自己的对象中并不需要它。如果您想围绕对象创建事件模式,这是一种不需要任何额外浏览器 API 并且应该非常向后兼容的好方法。

假设您有一个对象,您希望在调用 dispatch 方法时触发一堆事件:

var OurDispatcher, dispatcher;

OurDispatcher = (function() {
  function OurDispatcher() {
    this.dispatchHandlers = [];
  }

  OurDispatcher.prototype.on = function(eventName, handler) {
    switch (eventName) {
      case "dispatch":
        return this.dispatchHandlers.push(handler);
      case "somethingElse":
        return alert('write something for this event :)');
    }
  };

  OurDispatcher.prototype.dispatch = function() {
    var handler, i, len, ref;
    ref = this.dispatchHandlers;
    for (i = 0, len = ref.length; i < len; i++) {
      handler = ref[i];
      setTimeout(handler, 0);
    }
  };

  return OurDispatcher;

})();

dispatcher = new OurDispatcher();

dispatcher.on("dispatch", function() {
  return document.body.innerHTML += "DISPATCHED</br>";
});

dispatcher.on("dispatch", function() {
  return document.body.innerHTML += "DISPATCHED AGAIN</br>";
});

dispatcher.dispatch();

在大多数情况下,它真的不必比这更复杂。通过这种方式,您可以对您的事件进行一些不错的控制,并且您无需担心向后兼容性或外部库,因为那里的所有内容都得到了广泛支持。从技术上讲,您甚至可以不使用 setTimeout 并在不使用任何 API 的情况下处理您的回调。像 stopPropagation() 这样的其他任何事情都必须自己处理。

https://jsfiddle.net/ozsywxer/

当然,CustomEvent 有 polyfill,但除非我需要高级事件功能,否则我更喜欢将自己的事件系统包装到“类”中并用它扩展其他类/函数。

这里是 CoffeeScript 版本,它是 JavaScript 的来源: https://jsfiddle.net/vmkkbbxq/1/

^^ 比较容易理解。

【讨论】:

    【解决方案3】:

    如果你想监听一个 javascript 对象,你有三种方法:

    关于 sup/pub 模式:

    您需要发布事件。

    关于原生实现:

    • Object get/set operators 足以听添加,删除,更改, 获取事件。运营商有好的support。仅在 IE8- 中存在问题。 但是如果你想在 IE8 中使用 get/set 使用 Object.defineProperty 但是 在 DOM 对象上或使用 Object.defineProperty sham
    • Object.prototype.watch 具有良好的 ES5 polyfill
    • Proxy API 需要 ES Harmony 支持。

    Object.observe 示例

    var o = {};
    Object.observe(o, function (changes) {
      changes.forEach(function (change) {
        // change.object contains changed object version
        console.log('property:', change.name, 'type:', change.type);
      });
    });
    o.x = 1     // property: x type: add
    o.x = 2     // property: x type: update
    delete o.x  // property: x type: delete
    

    【讨论】:

    • 不幸的是,我无法解决当前的问题,因为对于我尝试执行的简单任务,有更简单的解决方案。但你确实向我展示了一些我很快会在另一个项目中尝试的新东西。谢谢!
    • 我编辑了我的答案,因为有 Object.observe 就像你需要的那样工作。让我们为未来的访客和用户留在这里。
    • Object.observe() 现在已弃用。我已经编辑了您的帖子,以警告其他人不要使用它。希望本次更新有所帮助! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
    • @sciencefyll 知道了。编辑了我的答案。谢谢。
    【解决方案4】:

    有两个问题。

    首先,iSubmit.addEventListener() 方法实际上是EventTarget DOM 接口上的一个方法:

    这些只用于 DOM 元素。通过将其作为方法添加到iSubmit 对象,您将在不是EventTarget 的对象上调用它。这就是 Chrome 抛出 Uncaught TypeError: Illegal invocation JavaScript 错误的原因。

    第一个问题很关键,但是如果您可以使用EventTarget#addEventListener(),您的代码将无法工作,因为该事件被添加到iSubmit,但从document 分派。通常,在附加事件侦听器和调度事件时需要使用相同对象的方法(除非您使用冒泡事件,这是 different story - 注意:冒泡不限于 JavaScript 或 DOM 相关事件,for example) .

    对自己的对象使用自定义事件是很正常的。作为Evan Yu mentioned,有一些库。以下是一对:

    我用过js-signals 并且非常喜欢它。我从未使用过Wolfy87/EventEmitter,但它看起来很漂亮。

    如果您使用js-signals,您的示例可能类似于以下内容

    jsFiddle

    var iSubmit = {
        finished: new signals.Signal(),
        test: function test(memo) {
            this.finished.dispatch(memo || {});
        }
    };
    
    iSubmit.finished.add(function(data) {
        console.log('finished:', data);
    });
    
    iSubmit.test('this is the finished data');
    
    
    // alternatively
    iSubmit.finished.dispatch('this is dispatched directly from the signal');
    

    【讨论】:

    • 谢谢。所以它不能完成..糟糕。但我宁愿使用自己的回调,然后使用插件为我做这件事。
    • 没问题,很高兴它有帮助。
    【解决方案5】:

    只是猜测;我自己没试过。但是您可以创建一个虚拟元素并触发/监听虚拟元素上的事件。 另外,我更喜欢不使用库。

    function myObject(){
        //create "dummy" element
        var dummy = document.createElement('dummy');
        //method for listening for events
        this.on = function(event, func){dummy.addEventListener(event, func);};
        //you need a way to fire events
        this.fireEvent = function(event, obj){
          dummy.dispatchEvent(new CustomEvent(event, {detail: obj}));
        }
    }
    //now you can use the methods in the object constructor
    var obj = new myObject();
    obj.on("custom", function(e){console.log(e.detail.result)});
    obj.fireEvent("custom", {result: "hello world!!!"});
    

    【讨论】:

    • 嘿!我将它用于我的原型,希望它能投入生产。 ps:我们很少有共同的想法
    【解决方案6】:

    如果你在 Node.js 环境中,那么你可以使用Node's EventEmitter class:

    CustomObject.js

    const EventEmitter = require('events');
    
    class CustomObject extends EventEmitter {
      constructor() {
        super();
      }
    
      doSomething() {
        const event = {message: 'Hello World!'};
        this.emit('myEventName', event);
      }
    }
    
    module.exports = CustomObject;
    

    用法:

    const CustomObject = require('./CustomObject');
    
    // 1. Create a new instance
    const myObject = new CustomObject();
    
    // 2. Subscribe to events with ID "myEventName"
    myObject.on('myEventName', function(event) {
      console.log('Received event', event);
    });
    
    // 3. Trigger the event emitter
    myObject.doSomething();
    

    如果您想在 Node.js 环境之外使用 Node 的 EventEmitter,那么您可以使用 webpack(最好是 v2.2 或更高版本)来获取您的 CustomClass 与 EventEmitter polyfill(由webpack)。

    这是它的工作原理(假设您使用 npm install -g webpack 全局安装了 webpack):

    1. 运行webpack CustomObject.js bundle.js --output-library=CustomObject
    2. 在您的 HTML 页面中包含 bundle.js(它将公开 window.CustomObject
    3. 没有第三步!

    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>Title</title>
        <script src="bundle.js"></script>
      </head>
      <body>
        <script>
          // 1. Create a new instance
          const myObject = new window.CustomObject();
    
          // 2. Subscribe to events with ID "myEventName"
          myObject.on('myEventName', function(event) {
            console.log('Received event', event);
          });
    
          // 3. Trigger the event emitter
          myObject.doSomething();
        </script>
      </body>
    </html>
    

    【讨论】:

    • 你不知道分享这个对我有多大帮助!!!谢谢!
    【解决方案7】:

    这是一个简单的事件发射器:

    class EventEmitter {
        on(name, callback) {
            var callbacks = this[name];
            if (!callbacks) this[name] = [callback];
            else callbacks.push(callback);
        }
    
        dispatch(name, event) {
            var callbacks = this[name];
            if (callbacks) callbacks.forEach(callback => callback(event));
        }
    }
    

    用法:

    var emitter = new EventEmitter();
    
    emitter.on('test', event => {
        console.log(event);
    });
    
    emitter.dispatch('test', 'hello world');
    

    【讨论】:

      【解决方案8】:

      我已经能够通过在 javascript 类中包装一个元素来实现这一点。 重要的一点是该元素不必存在于 dom 中。此外,元素标签名称可以是自定义类名称等任何名称。

      '''

      class MyClass
      {   
          
          constructor(options ) 
          {     
          
        
              this.el =  document.createElement("MyClass");//dummy element to manage events.    
              this.el.obj= this; //So that it is accessible via event.target.obj
            
          }
      
          addEventListener()
          {
       
              this.el.addEventListener(arguments[0],arguments[1]);
      
          }
          
       
      
           raiseEvent()
               {
      //call this function or write code below when the event needs to be raised.
      
                  var event = new Event('dataFound');
                  event.data = messageData;
                  this.el.dispatchEvent(event);
              }
      }
      
      
      let obj = new MyClass();
      obj.addEventListener('dataFound',onDataFound);
      
      function onDataFound()
      { 
      console.log('onDataFound Handler called');
      }
      

      '''

      【讨论】:

        【解决方案9】:

        本文介绍如何创建自定义事件:http://www.sitepoint.com/javascript-custom-events/

        这是一个例子:

        创建活动 -

        var event = new CustomEvent(
            "newMessage",
            {
                detail: {
                    message: "Hello World!",
                    time: new Date(),
                },
                bubbles: true,
                cancelable: true
            }
        );
        

        将事件分配给某事 -

        document.getElementById("msgbox").dispatchEvent(event);
        

        订阅活动 -

        document.addEventListener("newMessage", newMessageHandler, false);
        

        【讨论】:

        • 感谢您的回复。不幸的是,创建事件不是问题。它将侦听器方法添加到导致问题的对象。我不想在document 上收听,而是在iSubmit 上收听
        • 我明白了。此线程上的两个答案都可能解决您的问题:stackoverflow.com/questions/3903226/…
        【解决方案10】:

        用法:jsfiddle

        这是一种幼稚的方法,但可能适用于某些应用程序:

        CustomEventTarget.prototype = {
        
            'constructor': CustomEventTarget,
        
            on:   function( ev, el ) { this.eventTarget.addEventListener( ev, el ) },
            off:  function( ev, el ) { this.eventTarget.removeEventListener( ev, el ) },
            emit: function( ev ) { this.eventTarget.dispatchEvent( ev ) }
        
        }
        
        function CustomEventTarget() { this.eventTarget = new EventTarget }
        

        【讨论】:

          【解决方案11】:

          我认为你可以使用 Object $Deferred 和 promises。 它会让你做这样的事情:

          Stacked:将应用程序中任意位置的多个处理程序绑定到同一个 Promise 事件。

            var request = $.ajax(url);
            request.done(function () {
            console.log('Request completed');
          });
          

          // 应用程序中的其他位置

             request.done(function (retrievedData) {
               $('#contentPlaceholder').html(retrievedData);
             });
          

          并行任务:要求多个承诺返回一个提醒它们相互完成的承诺。

          $.when(taskOne, taskTwo).done(function () {
            console.log('taskOne and taskTwo are finished');
          });
          

          顺序任务:按顺序执行任务。

           var step1, step2, url;
          
           url = 'http://fiddle.jshell.net';
          
           step1 = $.ajax(url);
          
           step2 = step1.then(
             function (data) {
                 var def = new $.Deferred();
          
                 setTimeout(function () {
                     console.log('Request completed');
                     def.resolve();
                 },2000);
          
               return def.promise();
          
           },
             function (err) {
                 console.log('Step1 failed: Ajax request');
             }
           );
           step2.done(function () {
               console.log('Sequence completed')
               setTimeout("console.log('end')",1000);
           });
          

          来源: http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use

          【讨论】:

          • 不幸的是,这些都没有使用事件。他们都只是做一些事情,然后在准备好时使用回调函数。我可以更改 iSubmit 对象以包含回调,但我宁愿使用事件。
          【解决方案12】:

          这是在浏览器中使用 Node.js 样式语法执行此操作的方法。

          Events 类:

          • 将回调存储在与事件键关联的哈希中
          • 使用提供的参数触发回调

          要将行为添加到您自己的自定义类中,只需扩展 Events 对象(示例如下)。

          class Events {
            constructor () {
              this._callbacks = {}
            }
          
            on (key, callback) {
              // create an empty array for the event key
              if (this._callbacks[key] === undefined) { this._callbacks[key] = [] }
              // save the callback in the array for the event key
              this._callbacks[key].push(callback)
            }
          
            emit (key, ...params) {
              // if the key exists
              if (this._callbacks[key] !== undefined) {
                // iterate through the callbacks for the event key
                for (let i=0; i<this._callbacks[key].length; i++) {
                  // trigger the callbacks with all provided params
                  this._callbacks[key][i](...params)
                }
              }
            }
          }
          
          
          // EXAMPLE USAGE
          
          class Thing extends Events {
            constructor () {
              super()
              setInterval(() => {
                this.emit('hello', 'world')
              }, 1000)
            }
          }
          
          const thing = new Thing()
          
          thing.on('hello', (data) => {
            console.log(`hello ${data}`)
          })
          

          这是一个 github 要点的链接,其中包含此代码:https://gist.github.com/alextaujenis/0dc81cf4d56513657f685a22bf74893d

          【讨论】:

            【解决方案13】:

            对于任何正在寻找有效的简单答案的人。 我访问了this document,只是得知大多数浏览器不支持它。 但在页面底部,有一个指向this GitHub page 的链接,它基本上完成了 Object.watch() 和 Object.unwatch() 的工作,它对我有用!

            查看变化的方法如下

            /*
             * object.watch polyfill
             *
             * 2012-04-03
             *
             * By Eli Grey, http://eligrey.com
             * Public Domain.
             * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
             * https://gist.github.com/eligrey/384583
             */
            
            // object.watch
            if (!Object.prototype.watch) {
                Object.defineProperty(Object.prototype, "watch", {
                      enumerable: false
                    , configurable: true
                    , writable: false
                    , value: function (prop, handler) {
                        var
                          oldval = this[prop]
                        , newval = oldval
                        , getter = function () {
                            return newval;
                        }
                        , setter = function (val) {
                            oldval = newval;
                            return newval = handler.call(this, prop, oldval, val);
                        }
                        ;
            
                        if (delete this[prop]) { // can't watch constants
                            Object.defineProperty(this, prop, {
                                  get: getter
                                , set: setter
                                , enumerable: true
                                , configurable: true
                            });
                        }
                    }
                });
            }
            
            // object.unwatch
            if (!Object.prototype.unwatch) {
                Object.defineProperty(Object.prototype, "unwatch", {
                      enumerable: false
                    , configurable: true
                    , writable: false
                    , value: function (prop) {
                        var val = this[prop];
                        delete this[prop]; // remove accessors
                        this[prop] = val;
                    }
                });
            }
            

            这应该是你的代码:

            var object = {
                value: null,
                changeValue: function(newValue) {
                    this.value = newValue;
                },
                onChange: function(callback) {
                    this.watch('value', function(obj, oldVal, newVal) {
                        // obj will return the object that received a change
                        // oldVal is the old value from the object
                        // newVal is the new value from the object
            
                        callback();
                        console.log("Object "+obj+"'s value got updated from '"+oldValue+"' to '"+newValue+"'");
                        // object.changeValue("hello world");
                        // returns "Object object.value's value got updated from 'null' to 'hello world'";
            
                        // and if you want the function to stop checking for
                        // changes you can always unwatch it with:
                        this.unwatch('value');
            
                        // you can retrieve information such as old value, new value
                        // and the object with the .watch() method, learn more here:
                        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
                    })
                }
            };
            

            或短至:

            var object = { user: null };
            
            // add a watch to 'user' value from object
            object.watch('user', function() {
                // object user value changed
            });
            

            【讨论】:

              猜你喜欢
              • 2020-10-31
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-11-13
              • 1970-01-01
              • 2019-05-19
              • 2012-06-26
              相关资源
              最近更新 更多