The role of event distribution
The easiest thing to do when adding a variety of interactivity to a page is to bind the event to the page element, and then in the event handler, do what we want to do. It's like this code:
Element.onclick = function (event) {
//do anything.
};
If the action we want to do is not complex, then the actual logic function of the code, placed here is OK. If you need to modify it in the future, go to the location of the event handler to modify it.
Further, in order to do the proper code reuse, we may split part of the logical function into a function:
Element.onclick = function (event) {
//other code here.
Dosomethingelse ();
};
The function dosomethingelse the corresponding function may be used elsewhere, so the split is done. In addition, there may be functions such as setting coordinates (assuming that the function name is SetPosition), you also need to use information such as the pointer position provided by the browser event object event:
Element.onclick = function (event) {
//other code here.
Dosomethingelse ();
SetPosition (Event.clientx, Event.clienty);
};
One of the deprecated practices here is to pass the event object directly to SetPosition. This is because it is a good practice to distinguish between logical functions and event interception. Only the event handler itself is exposed to the browser event object event, which helps reduce code coupling and facilitates independent testing and maintenance.
So what happens when more and more functions become more complex? If you follow the previous approach, this might be the case:
Element.onclick = function (event) {
doMission1 ();
DoMission2 (Event.clientx, event.clienty);
DoMission3 ();
// ...
Domissionxx ();
};
Although this is not a problem to use, but this time in fact you can consider more elegant writing:
Element.onclick = function (event) {
amplify.publish ("aya:clicked", {
x:event.clientx,
y: Event.clienty
});
This is a form of event distribution, and note that the events here do not refer to browser-native events (event objects), but to logical-level custom events. The aya:clicked above is a custom event name that is written casually (really?).
Obviously it's not over yet, and in order to complete the complex functionality we need to relate the custom events to what we want to do:
Amplify.subscribe ("aya:clicked", doMission1);
// ...
Amplify.subscribe ("aya:clicked", doMission2);
// ...
Looks like it's coming back around again? Yes, but it's useful. On the one hand, the listener's native event listening is detached and cured, and later if the logic functions change, such as reducing the number of functions, then only need to go to the custom event of the associated code part of the deletion, without needing to care about the native event. On the other hand, the tuning of logical functions becomes more flexible and can be added by subscribe at arbitrary code locations, and can be managed by itself (custom event names).
In simple terms, event distribution increases the redundancy of a single layer of custom events (which you think is redundant when you have simple logic functions), reduces the coupling between code modules, makes logical functions clearer and more orderly, and facilitates subsequent maintenance.
Wait, the front of the exit several times the very existence of a sense of amplify is what?
Nice, finally it's time to introduce this.
Amplifyjs
Event distribution requires a certain method to achieve. One of the design patterns for implementing event distribution is publish/subscribe (publish/subscribe).
Amplifyjs is a simple JavaScript library that offers AJAX requests, data storage, publish/Subscribe three features (each can be used independently). Among them, publish/subscribe is the core function, corresponding name is Amplify.core.
Amplify.core is a concise, clear implementation of the Publish/subscribe design pattern, plus a total of more than 100 lines of comments. After reading the amplify source code, you can better understand how to implement a publish/subscribe design pattern.
Code Panorama
Amplify.core's source code overall structure is as follows:
(function (global, undefined) {
var slice = [].slice,
subscriptions = {};
var amplify = Global.amplify = {
publish:function (topic) {
//...
},
subscribe:function (topic, Contex T, callback, priority) {
//...
},
unsubscribe:function (topic, context, callback) {
//...
}
};
} (this));
As you can see, amplify defines a global variable named Amplify (as a property of global) that has 3 methods publish, subscribe, and unsubscribe. In addition, subscriptions as a local variable that holds all the custom event names and their associated functions that are involved in the Publish/subscribe pattern.
Publish
Publish is a publication that requires the designation of a topic, which is a custom event name (or topic), after which all functions associated to a topic are called in turn:
Publish:function (topic) {
//[1]
if (typeof topic!== "string") {
throw new Error ("You must provide a VA Lid topic to publish. ");
}
[2]
var args = slice.call (arguments, 1),
topicsubscriptions,
subscription,
length,
i = 0,
ret;
if (!subscriptions[topic]) {return
true;
}
[3]
topicsubscriptions = subscriptions[topic].slice ();
for (length = Topicsubscriptions.length i < length; i++) {
subscription = topicsubscriptions[i];
ret = subscription.callback.apply (Subscription.context, args);
if (ret = false) {break
;
}
}
return ret!== false;
[1], the parameter topic must require a string, otherwise an error is thrown.
[2],args will get all the arguments passed to the Publish function except topic and save as an array. If the corresponding topic is not found in subscriptions, it is returned directly.
[3],topicsubscriptions, as an array, obtains all the associated elements under a topic, each of which includes the callback and context two parts. Then, the element is traversed, the callback of each associated element is called, and the element's context and the preceding extra parameter args are brought in. If the callback function of any one of the associated elements returns false, the other is stopped and returns false.
Subscribe
Subscriptions, such as the word's own meaning (like a subscription to a magazine or something), are the steps to establish an association between topic and callback. More specifically, Amplify here also adds the concept of priority (priority), the smaller the priority value, the higher the priority, the default is 10. Higher-priority callback will be called first when publish. The principle of this order can be seen from the previous publish source, in fact, in advance, in order to prioritize from high to low all the associated elements of a topic.
Subscribe:function (topic, context, callback, priority) {if (typeof topic!== "string") {throw new Error (
"You are must provide a valid topic to create a subscription.");
}//[1] if (arguments.length = = 3 && typeof callback = = "number") {priority = callback;
callback = context;
context = NULL;
} if (arguments.length = = 2) {callback = context;
context = NULL; } priority = Priority | |
10;
[2] var topicindex = 0, topics = Topic.split (/\s/), topiclength = Topics.length, added;
for (; Topicindex < topiclength; topicindex++) {topic = topics[Topicindex];
Added = false;
if (!subscriptions[topic]) {subscriptions[topic] = [];
}//[3] var i = subscriptions[topic].length-1, Subscriptioninfo = {callback:callback,
Context:context, priority:priority};
[4] for (; I >= 0; i--) {if (subscriptions[topic] [I].priority <= priority) {Subscripti
ons[Topic].splice (i + 1, 0, subscriptioninfo);
Added = true;
Break
}//[5] if (!added) {subscriptions[topic].unshift (subscriptioninfo);
} return callback;
},
[1] To understand this part, see the API Amplify provided:
Amplify.subscribe (string topic, function Callback)
amplify.subscribe (String topic, object context, function Callba CK)
amplify.subscribe (string topic, function callback, number priority)
Amplify.subscribe (
string topic, Object context, function callback, number priority)
As you can see, Amplify allows a variety of parameter forms, and when the number and type of arguments are different, the parameters at a particular location may be treated as different content. This can also be seen in many other JavaScript libraries. In this way, the design of this multi-parameter form can be achieved by judging the number and type of parameters.
[2], when the subscription, topic is allowed to space, whitespace will be used as a separator, that is to associate a callback to multiple topic, so will use a loop. Added is used as an identifier to indicate whether the newly added element has been added to the array and is initially false.
[3], each callback save, is actually an object, in addition to callback also with the context (default is null) and priority.
[4] This loop is where the associated element should be located according to the value of the priority. Any topic associated elements are from scratch and are arranged (sorted) from small to large according to the priority values. Therefore, in comparison, it is assumed that the newly added elements have a larger priority value (lower priority), from the end of the array forward comparison, as long as the original array has an associated element of the priority value than the newly added elements of the smaller, the loop can be interrupted, It is also possible to add the newly added elements to this by using the splice method of the array. If the loop has been run to completion, you can determine that the priority value of the newly added element is minimal, and the added will remain at the initial value false.
[5] If the element has not been added to this location, then the add is executed, and the element is determined to be at the front of the array (or the first element).
Unsubscribe
Although the release and subscription is the most important, but there will be a need to unsubscribe when (the magazine does not want to see decisive retreat!) )。 So, you'll need a unsubscribe.
Unsubscribe:function (topic, context, callback) {
if (typeof topic!== ' string ') {
throw new Error ("You must Provide a valid topic to remove a subscription. ");
}
if (arguments.length = = 2) {
callback = context;
context = null;
}
if (!subscriptions[topic]) {return
;
}
var length = subscriptions[topic].length,
i = 0;
for (; i < length; i++) {
if (subscriptions[topic] [I].callback = = callback) {
if (!context | | subsc riptions[topic] [I].context = = Context] {
subscriptions[topic].splice (I, 1);
Adjust counter and length for removed item
i--;
length--}}}}
After reading the previous source code, this part looks very easy to understand. Traverses the associated element according to the specified topic, finds callback consistent, and then deletes it. Because the splice method is used, the original array is modified directly, so you need to manually adjust I and length again.
Amplify Use example
One of the official examples of use is:
Amplify.subscribe ("Dataexample", function (data) {
alert (data.foo);//Bar
});
//...
Amplify.publish ("Dataexample", {foo: "Bar"});
Combined with the previous source section, is there a clearer understanding of the design pattern of publish/subscribe?
Supplementary Notes
You may also notice that the typical publish/subscribe implemented by AMPLIFYJS is synchronous (synchronous). In other words, when running Amplify.publish (topic), there will be no delay to all the callbacks that come with a topic, running all over again.
Conclusion
Pub/sub is a relatively easy to understand design pattern, but very useful to deal with the complex logic of large applications. The amplifyjs of this article is a JavaScript library that I think is more methodical and concise to the point (for a single function), so share it here.