This article analyzes the programming paradigm in jQuery in detail. Share it with everyone for your reference. The details are as follows:
The face of browser front-end programming has undergone profound changes since 2005. This does not simply mean that a large number of basic libraries with rich functions have emerged, allowing us to write business code more conveniently. More importantly, There has been a major change in the way we look at front-end technology, with a clear awareness of how to unleash programmer productivity in a front-end-specific way. Here, we will give a brief introduction to the programming paradigms and common techniques emerging in JavaScript based on the implementation principles of jQuery source code.
1. AJAX: state persistence, asynchronous update
First, a little history.
A. In 1995, Brendan Eich of Netscape developed the JavaScript language, which is a dynamic, weakly typed, prototype-based scripting language.
B. In 1999, Microsoft IE5 was released, which included the XMLHTTP ActiveX control.
C. Microsoft IE6 was released in 2001, partially supporting DOM level 1 and CSS 2 standards.
D. Douglas Crockford invented the JSON format in 2002.
At this point, it can be said that the technical elements on which Web 2.0 relies have basically taken shape, but it has not immediately had a significant impact on the entire industry. Although some "asynchronous partial page refresh" techniques are secretly circulated among programmers, and even spawned huge and bloated class libraries like bindows, in general, the front end is regarded as a barren and dirty swamp, with only Backend technology is king. What is missing?
When we look back at the js code before 2005 from today's perspective, including those written by the talented people at that time, we can clearly feel their weakness in program control. It’s not that the js technology before 2005 had problems in itself, it’s just that they were scattered at the conceptual level, lacking a unified concept, or lacking their own unique style and soul. At that time, most people and most technologies were trying to simulate traditional object-oriented languages and use traditional object-oriented technologies to implement imitations of traditional GUI models.
2005 was a year of change and a year of concept creation. With the release of a series of refreshing interactive applications by Google, an article "Ajax: A New Approach to Web Applications" by Jesse James Garrett has been widely circulated. Ajax, a front-end-specific concept, quickly unified many scattered practices under the same slogan, triggering a paradigm shift in Web programming. As the saying goes, if the name is not correct, the words will not be correct. Now the unknown masses can find an organization. Before Ajax, people had long recognized that the essential feature of the B/S architecture was that the state spaces of the browser and the server were separated. However, the general solution was to hide this distinction and synchronize the foreground state to the background. Unified logical processing, such as ASP.NET. Because of the lack of mature design patterns to support foreground state persistence, the loaded js object will be forced to be discarded when changing pages. In this way, who can expect it to complete any complicated work?
Ajax clearly states that the interface is partially refreshed and the state resides in the foreground, which promotes a need: js objects need to exist in the foreground for a longer time. This also means the need to effectively manage these objects and functions, which means more complex code organization technology, which means the desire for modularity and a common code base.
There are actually very few parts of jQuery’s existing code that are actually related to Ajax (using XMLHTTP controls to asynchronously access background return data), but without Ajax, jQuery would have no reason to exist as a public code base.
2. Modularization: managing namespaces
When a large amount of code is generated, the most basic concept we need is modularization, which is to decompose and reuse work. The key to decomposing work is that the results of each person's independent work can be integrated together. This means that each module must be based on a consistent underlying concept and can interact. That is to say, it should be based on a common code base, shield the inconsistency of the underlying browser, and implement a unified abstraction layer, such as a unified event management mechanism. More important than a unified code base, there must be no name conflicts between modules. Otherwise, the two modules will not work together even without any interaction between them.
One of the main selling points that jQuery currently promotes is its good control over namespaces. This is even more important than providing more and more complete function points. Good modularity allows us to reuse code from any source, and everyone's work can be accumulated and superimposed. And function implementation is just a matter of temporary workload. jQuery uses a variant of the module pattern to reduce the impact on the global namespace, only adding a jQuery object (that is, $function) to the window object.
The so-called module pattern code is as follows. The key is to use anonymous functions to limit the scope of temporary variables.
//Private variables and functions
var privateThing = 'secret',
PublicThing = 'not secret',
changePrivateThing = function() {
privateThing = 'super secret';
},
sayPrivateThing = function() {
console.log(privateThing);
changePrivateThing();
};
// Return to the public API
return {
PublicThing : publicThing,
sayPrivateThing : sayPrivateThing
}
})();
JS itself lacks a package structure, but after years of attempts, the industry has gradually unified its understanding of package loading, forming a solution like the RequireJs library that has gained a certain consensus. jQuery can be well integrated with the RequireJS library to achieve more complete module dependency management. http://requirejs.org/docs/jquery.html
3. Magic$: Object promotion
What did you think of when you first saw the $function? Traditional programming theory always tells us that function naming should be accurate and should clearly express the author's intention. It even claims that long names are better than short names because it reduces the possibility of ambiguity. But what is $? Garbled code? The message it conveys is too obscure and ambiguous. $ was invented by the prototype.js library and is truly a magical function because it can enhance a primitive DOM node into an object with complex behavior. In the original implementation of prototype.js, the $function was defined as
This basically corresponds to the following formula
e = $(id)
This is not only a clever function name abbreviation, but more importantly, it establishes a one-to-one correspondence between text id and DOM element at a conceptual level. Before there is $, the distance between the id and the corresponding element is very far. Generally, the element needs to be cached in a variable, such as
prototype.js later expanded the meaning of $,
This corresponds to the formula:
[e,e] = $(id,id)
Unfortunately, prototype.js went astray in this step, and this approach has little practical value.
It is jQuery that really promotes $, and its $ corresponds to the formula
[o] = $(selector)
Here are three enhancements:
A. The selector is no longer a single node locator, but a complex collection selector
B. The returned elements are not original DOM nodes, but objects with rich behaviors further enhanced by jQuery, which can start complex function call chains.
C. The packaging object returned by $ is shaped into an array form, which naturally integrates collection operations into the call chain.
Of course, the above is just an oversimplified description of the magical $, and its actual function is much more complicated. In particular, there is a very commonly used direct construction function.
jQuery will directly construct a series of DOM nodes based on the incoming html text and package them as jQuery objects. This can be seen as an extension of the selector to a certain extent: the html content description itself is a Unique designation.
$(function{}) This function is really a bit speechless. It means that this callback function is called when document.ready. Really, $ is a magical function. If you have any questions, please ask.
To sum up, $ is the transition channel from the ordinary DOM and text description world to the jQuery world with rich object behavior. After crossing this door, we arrived at the Utopia.
4. Amorphous parameters: focus on expression rather than constraints
Since weakly typed languages have the word "weak" on their heads, it is inevitable that people will feel a little innately deficient. Is the lack of type constraints in the program really a major shortcoming? In traditional strongly typed languages , the type and number of function parameters are all constraints checked by the compiler, but these constraints are still far from enough. In general applications, in order to strengthen constraints, a large amount of defensive code is always added, such as in C We commonly use ASSERT, and in java we often need to determine the range of parameter values
Look at the event binding function bind in jQuery,
A. Bind one event at a time
Value value = o.val(), set value o.val(3)
How can a function be so excessive, how can it behave differently depending on the type and number of parameters passed in? Isn’t it displeasing to the eye? But this is our value. Since it cannot be prevented, then it is allowed deliberately. Although there are many forms Change, but not a word of nonsense. The lack of restraint does not hinder expression (I am not here to scare people).
5. Chain operation: gradual refinement of linearization
The main selling point of jQuery in the early days was the so-called chain operation.
In general imperative languages, we always need to filter data in nested loops, and the code to actually operate the data is entangled with the code to locate the data. However, jQuery uses the method of constructing a collection first and then applying functions to the collection. This method achieves the decoupling of the two logics and the linearization of the nested structure. In fact, we can intuitively understand a collection without resorting to procedural thinking, such as $('div.my input:checked') Can be seen as a direct description rather than a tracking of process behavior.
Loop means that our thinking is in a state of repeated rewinding, and after linearization, it goes straight in one direction, which greatly reduces the thinking burden and improves the composability of the code. In order to reduce the interruption of the call chain , jQuery invented a wonderful idea: jQuery wraps the object itself like an array (collection). Collections can be mapped to new collections, and collections can be restricted to their own subcollections. The initiator of the call is a collection, and the returned result is also a collection. Collections can Some structural changes have occurred, but it is still a set. A set is a conceptual fixed point. This is a design idea absorbed from functional languages. Collection operations are too common operations. In Java, we can easily find that a large number of so-called encapsulation functions actually encapsulate some collection traversal operations. In jQuery, collection operations are too straightforward and do not need to be encapsulated.
Chained calls mean that we always have a "current" object, and all operations are performed on this current object. This corresponds to the following formula
x = dx
Each step in the call chain is an incremental description of the current object and a step-by-step refinement process toward the final goal. This idea is also widely used in the Witrix platform. Especially in order to realize the integration of platform mechanism and business code, the platform will provide the default content of the object (container), and the business code can be gradually refined and revised on this basis, including canceling the default settings.
Having said that, although jQuery’s chain call is very simple on the surface, you have to write an extra layer of loops when implementing it internally, because the compiler does not know about "automatically applying to each element in the collection".
As a js library, a big problem it must solve is the state association and collaborative management between js objects and DOM nodes. Some js libraries choose to focus on js objects, and save DOM node pointers in member variables of js objects. When accessing, they always use js objects as the entry point, and indirectly operate DOM objects through js functions. Under this kind of encapsulation, the DOM node is actually just a low-level "assembly" displayed as an interface. The choice of jQuery is similar to the Witrix platform, which is based on the structure of HTML itself. It enhances the function of the DOM node through js and promotes it to an extended object with complex behavior. The idea here is non-intrusive design (non-intrusive) and graceful degradation mechanism (graceful degradation). The semantic structure is complete at the basic HTML level. The role of js is to enhance interactive behavior and control the presentation form.
If we access the corresponding packaging object through $('#my') every time, where are some state variables that need to be maintained for a long time stored? jQuery provides a unified global data management mechanism.
Get data:
The data set in HTML can be read through $('#my').data('myAttr').
When accessing data for the first time, jQuery will assign a unique uuid to the DOM node, and then set it on a specific expando attribute of the DOM node. jQuery ensures that this uuid is not repeated in this page.
Because all data is managed uniformly through the data mechanism, especially all event listening functions (data.events), jQuery can safely implement resource management. When cloning a node, its related event listening functions can be automatically cloned. When the content of the DOM node is replaced or the DOM node is destroyed, jQuery can also automatically cancel the event listening function and safely release the relevant js data.
7. event: unified event model
The picture of "events propagating along the object tree" is the essence of the object-oriented interface programming model. The composition of objects constitutes a stable description of the interface structure. Events continuously occur at a certain node of the object tree and propagate upward through the bubbling mechanism. The object tree naturally becomes a control structure. We can listen to events on all child nodes on the parent node without explicitly establishing an association with each child node.
In addition to establishing a unified abstraction for the event models of different browsers, jQuery has mainly made the following enhancements:
A. Added a custom event (custom) mechanism. The event propagation mechanism has nothing to do with the event content itself in principle, so custom events can pass through the same processing path and use the same monitoring method as the browser's built-in events. Use Custom events can enhance the cohesion of code and reduce code coupling. For example, if there are no custom events, associated code often needs to directly operate related objects
If another li node is created after calling bind, the click event of this node will not be monitored.
jQuery’s delegate mechanism can register the listening function to the parent node, and the events triggered on the child node will be automatically dispatched to the corresponding handlerFn according to the selector. In this way, if you register now, you can listen to the nodes created in the future.
Recently, jQuery 1.7 has unified the bind, live and delegate mechanisms, and the world is unified, only on/off.
Putting aside the implementation of jQuery, first consider what we need to do if we want to achieve animation effects on the interface? For example, we want to increase the width of a div from 100px to 200px within 1 second. It is easy to imagine that over a period of time we need to adjust the width of the div from time to time, [and at the same time] we also need to execute other code. Unlike ordinary function calls, after issuing the animation command, we cannot expect to get what we want immediately The result, and we can't just wait for the result to arrive. The complexity of animation lies in that it must be executed within a period of time after a one-time expression, and there are multiple logical execution paths that need to be unfolded at the same time. How to coordinate?
The great Sir Isaac Newton wrote in "Mathematical Principles of Natural Philosophy": "Absolute, true and mathematical time itself passes". All events can be aligned on the timeline, which It's their inherent coordination. So in order to execute steps A1 to A5 and step B1 to B5 at the same time, we only need to execute [A1, B1] at time t1, [A2, B2] at time t2, and so on. .
t1 | t2 | t3 | t4 | t5 ...
A1 | A2 | A3 | A4 | A5 ...
B1 | B2 | B3 | B4 | B5 ...
A specific implementation form can be
A. For each animation, divide it into an Animation object, which is divided into multiple steps internally.
animation = new Animation(div,"width",100,200,1000,
The interpolation function responsible for step segmentation and the callback function when the animation is completed);
B. Register the animation object in the global manager
timerFuncs.add(animation);
C. At each triggering moment of the global clock, advance each registered execution sequence one step further, and if it has ended, delete it from the global manager.
Having solved the principle problem, let’s look at the expression problem. How to design interface functions to express our intentions in the most compact form? Practical problems we often need to face:
B. Each element has multiple attributes that need to change at the same time
C. After executing one animation, start another animation
jQuery’s answers to these questions can be said to squeeze out the last remaining value of js’s grammatical expression.
A. Use jQuery’s built-in selector mechanism to naturally express the processing of a collection.
B. Use Map to express multiple attribute changes
C. Use microformats to express domain-specific delta concepts. ' =200px' means adding 200px to the existing value
D. Use the order of function calls to automatically define the order of animation execution: animations appended to the execution queue will naturally wait until the previous animation is completely executed before starting.
The implementation details of jQuery animation queue are roughly as follows,
A. The animate function actually calls queue(function(){dequeue needs to be called at the end of execution, otherwise the next method will not be driven})
When the queue function is executed, if it is an fx queue and no animation is currently running (if animate is called twice in a row, the second execution function will wait in the queue), the dequeue operation will be automatically triggered to drive the queue to run.
If it is an fx queue, the "inprogress" string will be automatically added to the top of the queue when dequeueing, indicating that animation is about to be executed.
B. For each property, create a jQuery.fx object. Then call the fx.custom function (equivalent to start) to start the animation.
C. In the custom function, register the fx.step function to the global timerFuncs, and then try to start a global timer.
timerId = setInterval( fx.tick, fx.interval );
D. The static tick function will call the step function of each fx in sequence. In the step function, the current value of the attribute is calculated through easing, and then the update of fx is called to update the attribute.
E. The step function of fx determines that if all attribute changes have been completed, dequeue is called to drive the next method.
What is very interesting is that there are obviously many relay trigger codes in the jQuery implementation code: if you need to execute the next animation, take it out and execute it, if you need to start the timer, start the timer, etc. This is because the js program is single-threaded. There is only one real execution path. In order to ensure that the execution thread is not interrupted, the functions have to help each other. It is conceivable that if there are multiple execution engines inside the program, or even infinite execution engines, then the appearance of the program will change essentially. changes. In this case, recursion will become a more natural description compared to loop.
9. Promise pattern: identification of causal relationships
In reality, there are always so many timelines evolving independently, and people and things intersect in time and space, but no cause and effect occurs. In software, functions are lined up in the source code, and some questions will inevitably arise. Why should the one at the front be executed first? Wouldn't there be me without it? Let the whole universe move forward in unison shouting 1, 2, 3. From God's point of view, management is probably too difficult, so there is the theory of relativity. . If there is no exchange of information and no mutual dependence, then the events that occur sequentially in one coordinate system may appear to be in reverse order when viewed in another coordinate system. The programmer follows the example, and then Invented the promise pattern.
Promise and future patterns are basically the same thing. Let’s first take a look at the familiar future pattern in Java.
Issuing a function call only means that something has happened, and does not necessarily mean that the caller needs to know the final result of the matter. What the function immediately returns is just a promise (Future type) that will be fulfilled in the future, which is actually Some kind of handle. The handle is passed around, and the code that changes hands in the middle is indifferent to what the actual result is and whether it has been returned. Until a piece of code needs to rely on the result returned by the call, so it opens the future and checks it. If the actual result has been returned , then future.get() returns the actual result immediately, otherwise it will block the current execution path until the result is returned. Calling future.get() after that will always return immediately, because the causal relationship has been established, [result return] This event must have happened before this and will not change again.
The future mode generally means that the external object actively checks the return value of the future, while the promise mode means that the external object registers a callback function on the promise.
jQuery introduces the Deferred structure, reconstructs ajax, queue, document.ready, etc. according to the promise mode, and unifies the asynchronous execution mechanism. then(onDone, onFail) will add a callback function to the promise. If the call is successfully completed ( resolve), the callback function onDone will be executed, and if the call fails (reject), then onFail will be executed. when can wait on multiple promise objects. The clever thing about promise is that after the asynchronous execution has started or even ended, It is still possible to register callback functions
someObj.done(callback).sendRequest() vs. someObj.sendRequest().done(callback)
Registering the callback function before issuing an asynchronous call or registering after issuing an asynchronous call is completely equivalent. This reveals that program expression is never completely accurate, and there is always an inherent dimension of change. If this inherent dimension can be effectively utilized The variability can greatly improve the performance of concurrent programs.
The specific implementation of promise mode is very simple. jQuery._Deferred defines a function queue, which has the following functions:
A. Save the callback function.
B. Execute all saved functions at the time of resolve or reject.
C. After it has been executed, any additional functions will be executed immediately.
Some languages specifically oriented towards distributed computing or parallel computing will have built-in promise mode at the language level, such as E language.
JS is a prototype-based language and does not have a built-in inheritance mechanism. This has always bothered many students who are deeply educated in traditional object-oriented education. But is inheritance necessary? What can it bring us? ? The simplest answer is: code reuse. So, let’s first analyze the potential of inheritance as a means of code reuse.
There used to be a concept called "multiple inheritance", which was the Super Saiyan version of the concept of inheritance. Unfortunately, it was later diagnosed as having a congenital defect, so that an interpretation of the concept of inheritance emerged: inheritance is " is a" relationship, a derived object "is a" has many base classes, which will inevitably lead to schizophrenia, so multiple inheritance is bad.
If class D inherits from two base classes A and B, and both classes A and B implement the same function f, then the f in class D is the f in A or the f in B, or is it What about f in A and f in B? The emergence of this dilemma actually stems from the fact that the base classes A and B of D are in a parallel relationship. They satisfy the commutative law and the associative law. After all, at the conceptual level, it may be difficult for us to recognize two Subordination relationships will appear between any concepts. But if we relax some conceptual level requirements and consider code reuse issues more from the operational level, we can simply think that B operates on the basis of A, then we can get a linear The result of transformation. In other words, giving up the commutative law between A and B and only retaining the associative law, extends A, B and extends B, A will have two different results, and there will no longer be any ambiguity in interpretation. scala The so-called trait mechanism in the language actually adopts this strategy.
Long after the invention of object-oriented technology, the so-called aspect-oriented programming (AOP) appeared. It is different from OOP in that it is a positioning and modification technology in the code structure space. AOP only sees classes and methods, and does not know what meaning is. . AOP also provides a code reuse method similar to multiple inheritance, which is mixin. The object is regarded as a Map that can be opened and modified arbitrarily. A set of member variables and methods are directly injected into the object body and directly changed. its behavior.
The prototype.js library introduces the extend function,
is a covering operation between Maps, but it is very effective and has been extended in the jQuery library. This operation is similar to mixin, which is the main technical means of code reuse in jQuery---it is no big deal if there is no inheritance. of.
11. Name mapping: everything is data
If the code is good, there must be less loop judgments. Loops and judgment statements are the basic components of the program, but they are often not found in excellent code libraries, because the interweaving of these statements will blur the main line of logic of the system. Let our minds get lost in the exhausting code tracing. jQuery itself has greatly reduced the need for loop statements through functions such as each and extend. For judgment statements, it is mainly processed through mapping tables. For example, jQuery's val( ) function needs to perform different processing for different tags, so define a function mapping table with tagName as the key
The so-called plug-ins of jQuery are actually functions added to $.fn. So what is this fn?
Obviously, $.fn is actually the abbreviation of jQuery.prototype.
A stateless plug-in is just a function, very simple.
13. browser sniffer vs. feature detection
Browser sniffer was once a very popular technology, such as in the early days of jQuery
In the specific code, different processing can be done for different browsers
However, as competition in the browser market escalates, competitors imitate and disguise each other, resulting in userAgent chaos. Coupled with the birth of Chrome and the rise of Safari, IE has also begun to accelerate its move towards standards, and sniffers can no longer do it. Positive effect. Feature detection (feature detection), as a more fine-grained and more specific detection method, has gradually become the mainstream way to deal with browser compatibility.
Only based on what you actually saw, rather than what you once knew, this makes it easier to be compatible with the future.
14. Prototype vs. jQuery
prototype.js is a library with lofty aspirations. Its goal is to provide a new user experience, transform JavaScript from the language level with reference to Ruby, and ultimately really change the face of js greatly. $, extends, each, bind... These familiar concepts are all introduced into the js field by prototype.js. It unscrupulously adds various concepts to the window global namespace. Whoever takes advantage of it first is in the right. Whose momentum? jQuery, on the other hand, is more pragmatic and its goal is just to write less and do more.
However, the fate waiting for radical idealists is often to die before their ambitions are fulfilled. When the iconic bind function of prototype.js was absorbed into the ECMAScript standard, its decline was doomed. Modify native everywhere The prototype of the object is the unique secret skill of prototype.js, and it is also its Achilles heel. Especially when it tries to imitate jQuery and returns enhanced objects through Element.extend(element), it is completely thrown into the ditch by jQuery. Prototype.js is different from jQuery. It always directly modifies the prototype of the native object. However, browsers are a field full of bugs, lies, historical baggage and commercial conspiracies. Solving problems at the native object level is destined to be a tragedy. . Performance issues, name conflicts, compatibility issues, etc. cannot be solved by the ability of a help library. The 2.0 version of Prototype.js is said to be undergoing major changes. I don’t know whether to break with history, give up compatibility, or continue Struggling to survive in the cracks.
I hope this article will be helpful to everyone’s jQuery programming.