J'ai créé un objet qui a plusieurs méthodes. Certaines de ces méthodes sont asynchrones et je souhaite donc utiliser des événements pour pouvoir effectuer des actions lorsque les méthodes sont terminées. Pour ce faire, j'ai essayé d'ajouter addEventListener à l'objet.
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();
Cela retournera une erreur: Failed to add eventlisterens: TypeError: 'addEventListener' called on an object that does not implement interface EventTarget."
Maintenant, ce code sera utilisé dans une application phonegap et, lorsque je le ferai, il fonctionnera sous Android/iOS. Lors des tests, cependant, ce serait bien si je pouvais le faire fonctionner dans au moins un navigateur.
PS> Je sais que je pourrais activer le bouillonnement, puis écouter la racine du document, mais j'aimerais simplement avoir un peu OOP où chaque objet peut fonctionner seul.
addEventListener
est destiné aux éléments DOM qui implémentent certaines interfaces liées aux événements. Si vous souhaitez un système d’événements sur des objets JavaScript pur, vous recherchez un système d’événements personnalisé. Un exemple serait Backbone.Events
dans Backbone.js. L'idée de base est d'utiliser un objet comme un hachage pour garder une trace des rappels enregistrés.
Personnellement j'utilise ceci: https://github.com/component/emitter
C'est une solution assez simple et élégante - avec des noms de méthode succincts comme on()
, off()
et emit()
. vous pouvez créer de nouvelles instances avec new Emitter()
ou utiliser Emitter(obj)
pour combiner les fonctionnalités d'événement dans des objets existants. Notez que cette bibliothèque est écrite pour être utilisée avec un système de module CommonJS, mais vous pouvez l'utiliser n'importe où en supprimant la ligne module.exports = ...
.
Si vous voulez écouter un objet javascript, vous avez trois possibilités:
Object get/set operators
, Object.defineProperty
, Object.prototype.watch
ou Proxy API
Object.observe
. Fonctionne Chrome 25+ (Jan 2014). Mais est devenu obsolète en 2016À propos du motif sup/pub:
Vous devez publier des événements.
À propos des implémentations natives:
Object get/set operators
suffit pour écouter, ajouter, supprimer, modifier, obtenir des événements. Les opérateurs ont un bon support . Problèmes uniquement dans IE8 -. Mais si vous voulez utiliser get/set dans IE8, utilisez Object.defineProperty
mais Sur des objets DOM ou utilisez Object.defineProperty sham .Object.prototype.watch
a le bon ES5 polyfill .Proxy API
nécessite le support ES Harmony.Exemple 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
Si vous n'avez pas besoin de fonctionnalités d'événement réel (telles que bubbling, stopPropagation), vous pouvez alors implémenter vos propres événements. addEventListener est juste une API du DOM, vous n'en avez donc pas vraiment besoin pour vos propres objets en dehors du DOM. Si vous souhaitez créer un modèle événementiel autour d'un objet, voici une bonne méthode qui ne nécessite aucune API de navigateur supplémentaire et qui doit être très compatible avec les versions antérieures.
Supposons que vous ayez un objet sur lequel vous souhaitez déclencher un groupe d'événements lorsque la méthode de répartition est appelée:
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();
Cela n’a pas besoin d’être plus compliqué que cela. De cette façon, vous avez un contrôle décent sur vos événements et vous n'avez pas à vous soucier de la compatibilité ascendante ou des bibliothèques externes car tout y est largement supporté. Techniquement, vous pouvez même vous passer de setTimeout et gérer vos rappels sans aucune API. Tout ce qui est comme stopPropagation () devrait être manipulé vous-même.
https://jsfiddle.net/ozsywxer/
Il existe bien sûr des polyfills pour CustomEvent, mais à moins d’avoir besoin de fonctionnalités d’événement avancées, je préfère envelopper mon propre système d’événement dans une "classe" et en étendre d’autres classes/fonctions.
Voici la version de CoffeeScript, dont le code JavaScript est dérivé: https://jsfiddle.net/vmkkbbxq/1/
^^ Un peu plus facile à comprendre.
Il y a deux problèmes.
Premièrement, la méthode iSubmit.addEventListener()
est en réalité une méthode de l’interface EventTarget
DOM:
Celles-ci sont destinées à être utilisées uniquement sur des éléments DOM. En l'ajoutant à l'objet iSubmit
en tant que méthode, vous l'appelez sur un objet qui n'est pas un EventTarget
. C'est pourquoi Chrome génère une erreur JavaScript Uncaught TypeError: Illegal invocation
.
Le premier problème est critique, mais si vous pouviez utiliser EventTarget#addEventListener()
, votre code ne fonctionnerait pas car l'événement est ajouté à iSubmit
mais distribué à partir de document
. En règle générale, les méthodes du même objet doivent être utilisées lors de l'attachement d'écouteurs d'événement et de la répartition d'événements (sauf si vous utilisez des événements bouillonnants, qui sont un histoire différente - Remarque: le bouillonnement n'est pas limité aux événements JavaScript ou liés à DOM , par exemple ).
Utiliser des événements personnalisés avec vos propres objets est très normal. Comme Evan Yu a mentionné , il existe des bibliothèques pour cela. Voici un couple:
J'ai utilisé js-signals
et l'aime un peu. Je n'ai jamais utilisé Wolfy87/EventEmitter
, mais il a un aspect agréable.
Votre exemple pourrait ressembler à ceci si vous avez utilisé js-signals
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');
Si vous êtes dans un environnement Node.js, vous pouvez utiliser la classe Node's 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;
Utilisation:
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();
Si vous souhaitez utiliser EventEmitter de Node en dehors d'un environnement Node.js, vous pouvez utiliser webpack (de préférence version 2.2 ou ultérieure) pour obtenir un paquet de votre CustomClass
avec un polyfill EventEmitter (construit par webpack).
Voici comment cela fonctionne (en supposant que vous avez installé Webpack globalement à l'aide de npm install -g webpack
):
webpack CustomObject.js bundle.js --output-library=CustomObject
bundle.js
dans votre page HTML (cela exposera 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>
Juste spéculation; Je n'ai pas essayé moi-même. Mais vous pouvez créer un élément factice et déclencher/écouter des événements sur cet élément factice . En outre, je préfère utiliser des bibliothèques.
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!!!"});
Cet article explique la création d'événements personnalisés: http://www.sitepoint.com/javascript-custom-events/
voici un exemple:
créer l'événement -
var event = new CustomEvent(
"newMessage",
{
detail: {
message: "Hello World!",
time: new Date(),
},
bubbles: true,
cancelable: true
}
);
attribuer l'événement à quelque chose -
document.getElementById("msgbox").dispatchEvent(event);
s'inscrire à l'événement -
document.addEventListener("newMessage", newMessageHandler, false);
Voici comment procéder avec la syntaxe de style Node.js dans le navigateur.
La classe Events
:
Pour ajouter le comportement à vos propres classes personnalisées, développez simplement l'objet Events (exemple ci-dessous).
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}`)
})
Voici un lien gistub Gist avec ce code: https://Gist.github.com/alextaujenis/0dc81cf4d56513657f685a22bf74893d
Je pense que vous pouvez utiliser Object $ Deferred et promesses . Cela vous permettra de faire quelque chose comme ceci:
Stacked: lier plusieurs gestionnaires n'importe où dans l'application au même événement de promesse.
var request = $.ajax(url);
request.done(function () {
console.log('Request completed');
});
// Quelque part ailleurs dans l'application
request.done(function (retrievedData) {
$('#contentPlaceholder').html(retrievedData);
});
Tâches parallèles: demander à plusieurs promesses de retourner une promesse qui indique leur achèvement mutuel.
$.when(taskOne, taskTwo).done(function () {
console.log('taskOne and taskTwo are finished');
});
Tâches séquentielles: exécuter les tâches dans un ordre séquentiel.
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);
});
Source ici: http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use
Utilisation: jsfiddle
C'est une approche naïve mais qui pourrait fonctionner pour certaines applications:
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 }