Подтвердить что ты не робот

AddEventListener на пользовательский объект

Я создал объект, который имеет несколько методов. Некоторые из этих методов являются асинхронными, и поэтому я хочу использовать события, чтобы иметь возможность выполнять действия после завершения методов. Для этого я попытался добавить 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> Я знаю, что мог бы включить всплывающее окно и затем прослушать корень документа, но я хотел бы иметь немного ООП, где каждый объект может работать сам по себе.

4b9b3361

Ответ 1

addEventListener предназначен для элементов DOM, которые реализуют определенные интерфейсы, связанные с событиями. Если вам нужна система событий на чистых объектах JavaScript, вы ищете собственную систему событий. Примером может служить Backbone.Events в Backbone.js. Основная идея заключается в использовании объекта в качестве хэша для отслеживания зарегистрированных обратных вызовов.

Лично я использую это: эмиттер.

Это довольно простое и элегантное решение - с короткими именами методов, такими как on(), off() и emit(). вы можете создавать новые экземпляры с помощью new Emitter() или использовать Emitter(obj) для смешивания возможностей событий с существующими объектами. Обратите внимание, что эта библиотека написана для использования с модульной системой CommonJS, но вы можете использовать ее где угодно, удалив строку module.exports = ....

Ответ 2

Если вы хотите прослушать javascript-объект, у вас есть три способа:

О шаблоне sup/pub:

Вам нужно публиковать события.

О встроенных реализациях:

  • Object get/set operators достаточно, чтобы слушать, добавлять, удалять, получать события. Операторы имеют хорошую поддержку. Проблемы только в IE8-. Но если вы хотите использовать get/set в IE8, используйте 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

Ответ 3

Если вам не нужны истинные функции событий (например, пузырьки, stopPropagation), вы можете реализовать свои собственные события. addEventListener - это просто API DOM, поэтому вам действительно не нужно его для собственных объектов вне DOM. Если вы хотите создать связанный шаблон вокруг объекта, вот хороший способ сделать это, который не требует каких-либо дополнительных API-интерфейсов браузера, и должен быть очень обратным.

Скажем, у вас есть объект, где вы хотите, чтобы при запуске метода отправки включалась куча событий:

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 существуют полисы, но если мне не нужны расширенные функции событий, я предпочитаю переносить свою собственную систему событий в класс и расширять с ней другие классы/функции.

Здесь версия CoffeeScript, из которой происходит JavaScript: https://jsfiddle.net/vmkkbbxq/1/

^^ Немного легче понять.

Ответ 4

Есть две проблемы.

Во-первых, метод iSubmit.addEventListener() на самом деле является методом на интерфейсе DOM EventTarget:

Они предназначены для использования только для элементов DOM. Добавив его в объект iSubmit как метод, вы вызываете его на объект, который не является EventTarget. Вот почему Chrome создает ошибку Uncaught TypeError: Illegal invocation JavaScript.

Первая проблема критическая, но если вы можете использовать EventTarget#addEventListener(), ваш код не будет работать, потому что событие добавляется в iSubmit, но отправляется из document. Как правило, одни и те же объектные методы необходимо использовать при подключении прослушивателей событий и диспетчеризации событий (если вы не используете события пузырьков, которые являются разными историями - Примечание: пузырь не ограничиваясь событиями, связанными с JavaScript или DOM, например).

Использование пользовательских событий с вашими собственными объектами очень нормально. Как сказал Эван Юй, для этого есть библиотеки. Вот пара:

Я использовал 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

Если вы находитесь в среде Node.js, вы можете использовать Node класс EventEmitter:

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 EventEmitter вне среды Node.js, вы можете использовать webpack ( предпочтительно v2.2 или новее), чтобы получить пакет вашего CustomClass вместе с EventEmitter polyfill (созданный веб-пакетом).

Вот как это работает (при условии, что вы установили webpack глобально с помощью npm install -g webpack):

  • Запустить webpack CustomObject.js bundle.js --output-library=CustomObject
  • Включите bundle.js в свою HTML-страницу (она выведет window.CustomObject)
  • Нет третьего шага!

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>

Ответ 6

Просто спекуляция; Я сам не пробовал. Но вы можете создать фиктивный элемент и запустить/прослушать события на фиктивном элементе. Кроме того, я предпочитаю ходить без библиотек.

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!!!"});

Ответ 7

В этой статье объясняется создание настраиваемых событий: 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);

Ответ 8

Использование: 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 }

Ответ 9

Я думаю, вы можете использовать Object $Deferred и promises. Это позволит вам сделать что-то вроде этого:

Stacked: привязать несколько обработчиков в любом месте приложения к одному и тому же обещанию.

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

//Где-то еще в приложении

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

Параллельные задачи: попросите несколько promises вернуть обещание, которое предупреждает об их взаимном завершении.

$.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

Ответ 10

Вот как вы можете сделать это с помощью синтаксиса стиля 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 gist с этим кодом: https://gist.github.com/alextaujenis/0dc81cf4d56513657f685a22bf74893d

Ответ 11

Для тех, кто ищет простой ответ, который работает. Я посетил этот документ только для того, чтобы узнать, что большинство браузеров его не поддерживают. Но внизу страницы была ссылка на эту страницу GitHub, которая в основном делает то, что сделали бы 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+" value got updated from '"+oldValue+"' to '"+newValue+"'");
            // object.changeValue("hello world");
            // returns "Object object.value 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
});