(This does not mean that jQuery has excellent performance. On the contrary, it can only be said that it is a relatively closed library and cannot be optimized by external intervention ). This article records a failed optimization experience.
Optimization ideas
The idea of this optimization comes from the database. During database optimization, we often say "putting a large number of operations in one transaction can effectively improve efficiency ". Although I don't know the reason why I don't know the database, the idea of "transaction" has pointed out the direction for me (although it is wrong ......).
Therefore, I tried to introduce the "transaction" concept to jQuery and optimize jQuery externally by "Opening" and "committing" transactions, the most important thing is to reduce the number of cycles of the each function.
As we all know, jQuery's DOM operations are based on get all and set first. Operations used to set DOM attributes/styles are almost all traversal of Selected elements. the access function is the core part. The code used for loop is as follows:
Copy codeThe Code is as follows:
// Setting one attribute
If (value! = Undefined ){
// Optionally, function values get executed if exec is true
Exec =! Pass & exec & jQuery. isFunction (value );
For (var I = 0; I <length; I ++ ){
Fn (
Elems [I],
Key,
Exec? Value. call (elems [I], I, fn (elems [I], key): value,
Pass
);
}
Return elems;
}
For example, the jquery.fn.css function is like this:
Copy codeThe Code is as follows:
JQuery.fn.css = function (name, value ){
// Setting 'undefined' is a no-op
If (arguments. length ===2 & value === undefined ){
Return this;
}
Return jQuery. access (this, name, value, true, function (elem, name, value ){
Return value! = Undefined?
JQuery. style (elem, name, value ):
JQuery.css (elem, name );
});
};
Therefore, the following code assumes that there are 5000 selected div elements, and 10000 nodes need to be accessed cyclically:
Jquery('div'hangzhou.css ('height', 3002.16.css ('width', 200 );
In my mind, in a "transaction", it is possible to perform database operations in a unified manner by saving all the operations, reducing 10000 node accesses to 5000 times is equivalent to increasing the performance by one time.
Simple implementation
In a "transaction" jQuery operation, two functions are provided:
Begin: opens a "transaction" and returns the object of a transaction. This object has all functions of jQuery, but calling the function does not take effect immediately. It takes effect only after the transaction is committed.
Commit: commit a "transaction" to ensure that all previously called functions take effect and return the original jQuery object.
It is easy to implement:
Create a "transaction object" and copy all functions on jQuery. fn to this object.
When calling a function, add the called function name and related parameters to the prepared "queue.
When a transaction is committed, the selected elements are traversed once, And all functions in the queue are applied to each node in the traversal.
The simple code is as follows:
Copy codeThe Code is as follows:
Var slice = Array. prototype. slice;
JQuery. fn. begin = function (){
Var proxy = {
_ Core: c,
_ Queue: []
},
Key,
Func;
// Copy the function on jQuery. fn
For (key in jQuery. fn ){
Func = jQuery. fn [key];
If (typeof func = 'function '){
// The key generated by the for loop is always the value of the last loop.
// Therefore, a closure must be used to ensure the validity of the key (LIFT effect)
(Function (key ){
Proxy [key] = function (){
// Put the function call into the queue
This. _ queue. push ([key, slice. call (arguments, 0)]);
Return this;
};
}) (Key );
}
}
// Prevent the commit function from being blocked.
Proxy. commit = jQuery. fn. commit;
Return proxy;
};
JQuery. fn. commit = function (){
Var core = this. _ core,
Queue = this. _ queue;
// Only one each loop
Core. each (function (){
Var I = 0,
Item,
Jq = jQuery (this );
// Call all functions
For (; item = queue [I]; I ++ ){
Jq [item [0]. apply (jq, item [1]);
}
});
Return this. c;
};
Test Environment
The test uses the following conditions:
Put the 5000 divs in a container (<div id = "container"> </div>.
Use $ ('# container> div') to select the 5000 Divs.
Each div requires a random background color (randomColor function) and a random width below 800px (randomWidth function ).
Three call methods are available for testing:
Normal usage:
Copy codeThe Code is as follows:
$ ('# Container> div ')
. Css ('background-color', randomColor)
. Css ('width', randomWidth );
Single Cycle method:
Copy codeThe Code is as follows:
$ ('# Container> div'). each (function (){
Watermark (this).css ('background-color', randomColor).css ('width', randomWidth );
});
Transaction method:
Copy codeThe Code is as follows:
$ ('# Container> div ')
. Begin ()
. Css ('background-color', randomColor)
. Css ('width', randomWidth)
. Commit ();
Object Assignment Method:
Copy codeThe Code is as follows:
$ ('# Container> div'hangzhou.css ({
'Background-color': randomColor,
'Width': randomWidth
});
Select the Chrome 8 series as the test browser (the Chrome 8 series will be suspended after IE testing ).
Sad results
The original prediction result is that the efficiency of the single cycle method is much higher than that of the normal use method. At the same time, although the transaction method is slower than the single cycle method, It should be faster than the normal use method, the object Assignment Method is actually a single cycle method supported by jQuery, and the efficiency should be the highest.
Unfortunately, the results are as follows:
Normal use method single cycle method transaction method object Assignment Method
18435 ms 18233 ms 18918 ms 17748 ms
As a result, the transaction method is the slowest method. At the same time, a single loop has no obvious advantages over normal use, and even the object Assignment Method Based on jQuery's internal implementation has not opened a big gap.
Since the operation of 5000 elements is already a very large cycle, such a large cycle has not opened the performance gap, at ordinary times, the most commonly used element operations of about 10 may not have obvious advantages, or even expand the disadvantages.
The reason is that the single-cycle method does not significantly improve the performance. Therefore, it relies on a single loop and is a transaction method built on a single loop, naturally, on the basis of a single loop, additional overhead such as creating transaction objects, saving function queues, and traversing function queues are also required. It is also reasonable to lose the result to the normal use method.
At this point, we can announce the failure to imitate the optimization of the "transaction. However, this result can be further analyzed.
Performance
First, analyze the code usage and compare the normal use method with the fastest object Assignment Method in the test, it can be said that the difference between the two lies only in the difference in the number of elements in the loop (aside from the internal problems of jQuery, in fact jQuery. the poor implementation of access does drag the object assignment method, but it is not serious). The normal use method is 10000 elements, and the object Assignment Method is 5000 elements. Therefore, we can simply think that 18435-17748 = 5000 MS is the time consumption of 3.5% elements in a loop, which accounts for about of the execution process and is not the backbone of the entire execution process, in fact, there is really no need for optimization.
So where does the additional 96.5% overhead go? Remember Doglas's sentence: "Javascript is actually not slow, but DOM operations are slow ". In fact, the remaining 96.5% overhead, except for the basic consumption of function calls, at least 95% of the time is spent on the re-rendering after the DOM element style is changed.
After discovering this fact, there is actually a more correct direction for optimization. It is also one of the basic principles in front-end performance: when modifying large quantum elements, first, remove the root parent DOM node from the DOM tree. Therefore, if the following code is used for testing:
Copy codeThe Code is as follows:
// No reuse $ ('# iner') is already bad.
$ ('# Iner'). detach (). find ('div ')
. Css ('background-color', randomColor)
. Css ('width', randomWidth );
$ ('# Iner'). appendTo (document. body );
The test results remain around MS, which is no more than an order of magnitude than the previous data. The optimization is successful.
Lessons learned and summary
You must find the correct performance bottleneck before optimization. Blind guesses will only lead to a wrong and extreme path.
No one speaks before the data!
I don't think the "transaction" direction is wrong. If jQuery native can support the concept of "transaction", will there be other points that can be optimized? For example, a transaction automatically disconnects the parent element from the DOM tree ......