I have finally finished writing ~~ ^_^: During this period, I gave my colleagues a training course and wrote it once. The advantage of speaking it once again is that I have deepened my understanding of messages, events, and observer patterns.
For me, it is much harder to clarify than to write code.
Here we will share some miscellaneous content related to the message mechanism.
I. testable code
I described my js program structure to Rui earlier. He asked me a question: Can your js Code be measurable?
I got it ~ Although I have been paying attention to agility and have always been eager to test-driven development, I have never thought about testing JavaScript code (of course, there are also tests, but basically I have integrated testing and partial testing ), I have never thought about the js test driver.
At that time, I hesitated for a while before saying that it should be measurable.
After writing the previous article (forgive me, I thought it was too simple, I wrote it directly, and forgot to test the driver), I looked back and looked at it. Fortunately, message-based code can indeed be measurable.
For example:
Animal
Function Animal (config) {config = config | |{}; var othis = this; this. name = config ["name"] | "Anonymous"; this. x = config ["x"] | 0; var toward = 1; // right: 1, left-1 var _ timeout = 0; var _ speed = 5; // this. say = function (str) {this. trigger ({type: "say", msg: str}); return str ;}// stop this. stop = function () {clearTimeout (_ timeout); this. trigger ({type: "stop", x: this. x}); return this. x;} // run this. run = function (speed) {_ speed = speed | _ speed; this. x + =__ speed * toward; _ timeout = setTimeout (function () {othis. run ();}, 100); this. trigger ({type: "run", x: this. x, toward: toward, speed :__ speed}); return {x: this. x, toward: toward, speed :__ speed};} // turn left this. turnLeft = function () {toward =-1; this. trigger ({type: "turn", toward: toward}); return toward;} // turn this. turnRight = function () {toward = 1; this. trigger ({type: "turn", toward: toward}); return toward;} methods all have returned values, so that we can perform some unit tests, except for unit tests, we also need to test the message-based mechanism to construct a pseudo object to listen on messages sent by the Animal object.
However, this Animal is not completely complete, and there are some unmeasurable ones, such as toward, which is a variable that is completely closed inside. It is difficult for you to know the direction of Animal objects.
However, this is mainly because the Animal class is incomplete, but we cannot directly change it to this to access toward. toward is exposed, so that someone else may assign an incorrect value: this. toward = 100; it is very dangerous to publish toward so that users can assign values with others. In a good way, write a getToward () method.
An implementation that is difficult to test is written as follows:
Logger
/// Recorder function Logger () {var dom = document. getElementById ("log"); var log = function (str) {var time = new Date (); this. dom. innerHTML + = "<br/>" + str + '<span style = "color: gray"> (' + time. getHours () + ":" + time. getMinutes () + ":" + time. getSeconds () + ") </span>" ;}; this. handler = function (data, p) {switch (data. type) {case "say": this. log (p. name + "said:" + data. msg); break; case "stop": this. log ('<span style = "color: green">' + p. Name + "STOPPED" + data. x + '</span>'); break; case "turn": this. log (p. name + "switched to" + (data. toward = 1? "Right": "Left"); break ;};} the Logger object exposes only one handler method, and it writes the dom.
Of course, it will perform its duties well in the example running. To test it, we must first ensure that there is a dom element with the id of log on the page, and forge a message object, such as an Animal object, to send messages to it. This makes us feel like a whole test.
This is not a good example.
In general, message confidentiality may cause some trouble in testing. In reality, a function call may trigger many messages, not just one. These message names are all stored in the code. Unless you carefully read the code, messages may be missed.
Like Animal, try to trigger a message with a method value, or vice versa. For some large objects, the message names should be listed in the comments code before the object starts, so that they can be maintained and debugged easily.
Ii. Bubble messages
At the beginning, I had implemented message bubbling, just like the click Event of the internal a tag will bubble to the external div.
Example of message bubbling:
A. bind ("test", B) B. bind ("test", c) c. bind ("test", d)
If you implement bubbling, the test message of a will be transmitted to d along the path of a-> B-> c-> d.
A simple implementation is the handler function of each object, which directly triggers the incoming messages. This method is not automated. A simple change is as follows:
View Code
Function trigger (Y) {var queue = []; var qs = this. _ MSG_QS _ | |{}; var sqs = this. _ STATIC_MSG_QS __|{}; queue = queue. concat (qs [Y. type] | []); queue = queue. concat (sqs [Y. type] | []); for (var a = 0, X = queue. length; a <X; a ++) {if (queue [a]. handler) {queue [a]. handler (Y, this) if (queue [a]. trigger) {queue [a]. trigger (Y) ;}} else {queue [a]. call (this, Y, this) ;}} focus on this change:
If (queue [a]. trigger) {queue [a]. trigger (Y);} if the observer is found to be a monitor mode object, call its trigger
In this way, our messages can be bubbling. We can also add an attribute for the object to identify whether the object allows bubbling, or add another stopPropagation to prevent bubbling.
I recommend that you use the message bubbles with caution. Unlike dom, a custom object has a hierarchical structure. dom only bubbles up the parent element.
Custom objects are not hierarchical (unless forced). Sometimes we can even let the objects listen to their own messages. In many cases, many objects listen to your messages. In essence, at this time, the message stream is not like a bubble, but has a branch. When the processing is poor, there will be a closed loop, and there will be self-calls like recursion, self-calls and closed loops will lead to an endless loop.
Of course, you can also use a dictionary to store objects that have already passed messages (injected into the message body, or as another trigger parameter) to prevent closed loops. However, this will increase the processing of your monitor code.
3. asynchronous messages
Previously, all the examples and codes I mentioned were synchronous messages. When a message is called, The Listener function will be executed and trigger calls will end only after all the listener functions are executed.
For example:
Obj. trigger ({type: "test1 "});
Obj. trigger ({type: "test2 "});
The test2 message must be called after the test1 message is called.
Asynchronous messages are just one of my ideas. Using the setTimeout Function and using time slices, only one listener is called at a time. Then, when the trigger method is called on the message source object, the trigger can be executed quickly, but the real message processing is put in the time film and processed slowly.
Currently, I have not used the requirements for asynchronous message processing. I have not tested the proposed scheme. If you are interested, you can study it on your own.
It is estimated that some js frameworks have similar functions, but I have not studied them yet.
4. Avoid repeated registration and logout of observer
If you a. bind ("test", B) twice, what will happen?
The test event of object a will faithfully call B twice.
To avoid duplication, the direct method is to repeat the list and check whether B exists. This is very inefficient. One way is to add a unique identifier for the object that requires bind, and add a function (or add a function globally) to the monitor. The Code is as follows:
Var monitor = (function (){//...... Var _ guid = 0; function guid () {return ++ _ guid;} function bind (B) {var queue = this. _ MSG_QS __= this. _ MSG_QS _ | |{}; if (! Queue [B]) {queue [B] ={}} for (var a = 1, X = arguments. length, Y; a <X; a ++) {var _ guid = arguments [a]. _ guid = arguments [a]. _ guid | guid (); if (queue [B] [_ guid]) queue [B] [_ guid] = arguments [a];} //......}) (); Make some changes (and live) in the corresponding trigger. The Code will not be provided here and you will be interested in implementing it yourself.
Logout is a counter operation of the bind. If you do not use a unique identifier for the object, you need to use a loop to determine whether the object is in the Observer queue, and if so, remove it from the queue. If the unique identifier is used, the operation is simple. Use delete.