Original article: http://www.sencha.com/blog/ext-js-4-1-performance/
This article describes several factors that affect the performance of ext JS applications.
- Network latency seriously affects the initialization Start time, especially the loading time of the store.
- CSS processing.
- Javascript execution.
- Dom operation.
Network latency
To minimize the application startup time, note that any domain has a limit on the number of concurrent connections to the browser.
This means that if many files are requested from a domain, once the upper limit is reached, subsequent downloads will be queued and will be processed only when a connection slot is released. New browsers have a high range, but it is important to optimize old and slow browsers.
The solution is to use the sencha SDK tool to generate a single series of script files required by the application.
For more information, see ext JS 4.0.
The "CREATE" command of the SDK analyzes the applications loaded to the page and loads all files referenced by the requires and user attributes in the class definition, then, create a script file that contains all the required class definitions in the correct order.
You can view detailed information about the sencha System in ext JS 4.1.
Another way to reduce network latency is to enable gzip compression for ext JS pages and their related scripts and style files on the web server.
CSS Processing
CSS selection matches the parent node pointer of DOM from right to left.
This means that the following style processing process is: Find the matched span in the document, and then search for the ancestor nodes that match the two specified style class names along the parent node axis.
.HeaderContainer .nav span
Therefore, using a single element makes it more efficient to determine the style of the class name.
JavaScript Execution
The following points must be kept in mind when optimizing JavaScript code:
- Avoid using the old or bad JavaScript Engine.
- Optimize frequently-repeated code.
- Optimize the code executed during rendering or layout
- It is best not to execute any additional code during initialization rendering or layout.
- Move the unchanged expression out of the loop.
- Use the for loop instead of Ext. array. Each.
- If the code in the function's condition is often called, move the condition outside the function if necessary (refer to the call to fireevent by ext js code library ).
- Installing and disassembling the calling framework (the device required for function calling) in poor Javascript Engines will be slow.
Code optimization example
Imagine that a statistical application provides some operations for the data column of the grid, and adds menu items to the Column Title menu of the grid to perform the required operations on any call column.
The handler must obtain the relevant context information and the column title of the current activity to be operated:
The GitHub example demonstrates two ways to perform this operation. It can run in the SDK sample directory of any version.
The first method used to aggregate the active columns is as follows:
function badTotalFn(menuItem) { var r = store.getRange(), total = 0; Ext.Array.each(r, function(rec) { total += rec.get(menuItem.up('dataIndex').dataIndex); });}
Here are several error practices. First, use Ext. Each to call the transfer function for each record in the array. As you can see, function settings affect performance. Secondly, the results of the menuitem. Up ('dataindex') expression remain unchanged. It only needs to be executed once and can be placed outside the loop.
Therefore, you can optimize the code into the following code:
function goodTotalFn(menuItem) { var r = store.getRange(), field = menuItem.up('dataIndex').dataIndex; total = 0; for (var j = 0, l = r.length; j < l; j++) { total += r[j].get(field); } }
This may seem insignificant, but the performance difference is significant.
In the following table, the computing function provides a measurable time after 10000 iterations:
Browser |
Bad |
Good |
Chrome |
1700 Ms |
10 ms |
Ie9 |
18000 Ms |
500 ms |
IE6 |
Gave up |
532 Ms |
As we can see, even if there is no iteration, ie9 takes 1.8 seconds to process the operation in the first method.
Use a page analyzer to measure performance
The page analyzer is an example of the SDK, which can be found in the example/page-analyzer directory. It runs pages of the same domain in the capture framework, and then captures ext JS examples to analyze the layout performance and the performance of any method of any object.
If Chrome is used, you need to use the "-enable-benchmarking" command on its command line to enable the microsecond timing accuracy.
To analyze the performance of the key locations in the preceding example, switch to the "performance" tag, select the "accumulators" tag in the tag panel in the lower left corner, and paste the following code to textarea:
{ "Component.up": { "Ext.Component": "up" }, "CQ.is": { "Ext.ComponentQuery": "!is" }}
Make sure that the two aggregate computing functions are iterated for 10000 times to obtain accurate performance.
Then, load the grid performance test example in the page analyzer, use "get total in bad way" to start the total, and click "Update stats" in the upper right corner of the page analyzer ".
Then, click "reset" to clear the accumulators, add the "build" timer, use "get total in good way" to aggregate, and click the "Update stats" button of the page analyzer.
In the "Grid" subtab of the "performance" tab, the following two running results are displayed:
As you can see, the number of times that the unchanged form method calls the componentquery method outside the loop is much less.
Merge multiple la s
Ext js 4 will automatically layout based on content changes or size changes. This means that when the text of a button changes, it will lead to a re-layout of its toolbar (because the height of the button may change ), the panel where the toolbar is located must be re-laid (because the height of the Toolbar may change ).
For this reason, it is very important to merge multiple layout operations when the content or size changes. You can use the following code to achieve this:
{ Ext.suspendLayouts(); // batch of updates Ext.resumeLayouts(true);}
If the parameter is set to true, the layout is re-enabled. When it runs to this point, any queued layout requests are cleared.
Reduce Dom burden
It is important to reduce the number of nested containers or components to achieve as few layers as possible, which can avoid repeated layout operations and Dom backflow because these overhead are expensive.
Another principle to be followed is to use the simplest container or layout for necessary work.
A common example is to nest a component when placing a grid in a tabpanel. The following is a common method:
{ xtype: "tabpanel", items: [{ title: "Results", items: { xtype: "grid" ... } }]}
I don't know why I want to add a common panel as a sub-entry of the label panel, and then place the grid in the Panel. Such a nested layer is useless.
In fact, this damages the operation of the label panel, because the layout of the encapsulated panel is not configured, so the size of its sub-component grid cannot be changed, this means that it cannot adapt the label panel size and Scroll Based on the label panel height.
The correct statement is as follows:
{ xtype:”tabpanel“, items: [{ title: ”Results“, xtype: ”grid“, ... }]}
Why is this principle important?
It is important to keep the component tree (or DOM tree) Light-weight as much as possible, mainly because in ext JS 4, many components are containers with sub-components and have their own layout manager. For example, the panel title is now a container class, you can configure other components in addition to the title text and tool buttons.
Although titles as hierarchical containers bring additional overhead, they make the uidesign more flexible.
In addition, in ext JS 4, components use the component layout manager to manage the size and position of the internal Dom structure, rather than using the onresize method as in ext JS 3. X.
Visualized component tree
When we follow the above principles to design the UI, we can think of the UI as a tree structure. For example, a viewport can be imagined:
To render the Yield Component tree, it operates twice.
The first time, The beforerender of each component will be called, and then the getrendertree will be called to generate the configuration object of domhelper with HTML tags and add it to the rendering cache.
After the first time, the HTML code representing the entire tree is inserted into the document at one time, which reduces Dom processing when the application structure is created.
The tree then goes over and calls the onrender method of each component to connect the component to their related Dom node. Then, call afterrender to complete rendering.
After that, the complete initial layout is complete.
This is why creating a lightweight UI is very important.
Study the structure of the lower panel:
Ext.create('Ext.panel.Panel', { width: 400, height: 200, icon: '../shared/icons/fam/book.png', title: 'Test', tools: [{ type: 'gear' }, { type: 'pin' }], renderTo: document.body});
It will lead to a very complex UI structure:
Try to avoid shrinking the encapsulation (shrinkwrapping, automatically resize according to the content)
Although ext JS 4 provides a container that can automatically resize Based on the content (called "shrinkwrapping" in ext JS), this will increase the burden on some la S, the second is that the calculation result will cause the browser to flow back, and then the calculated height and width will be used to re-layout.
Avoid Dom refresh size and improve performance.
Avoid limiting the size (minheight, maxheight, minwidth, maxwidth)
If the limit is hit, the entire layout needs to be re-computed. For example, when a sub-component using the Flex-defined box layout receives its calculated width less than the defined minwidth, it will be fixed to the minimum width, and the layout of the entire box will have to be recalculated.
Similarly, when the stretchmax configuration item is used in a box layout, all child components are switched to a fixed vertical size (for example, the hbox layout height), and the layout is recalculated.
Avoid Dom of post-rendering components
To avoid Dom backflow and re-painting, try to avoid the DOM structure after component rendering. The alternative is to use the provided hooks to modify the component configuration before generating HTML code.
If you really want to modify the ODM structure, rewrite the getrendertree method as the final method.
Grid Performance
The table size affects performance, especially the number of columns. Therefore, you must keep as few columns as possible.
If the dataset is very large and you do not want to use the paging toolbar in the UI, you can use a buffer rendering method commonly known as "infinite grid.
To achieve this, you only need to add the following configuration items to the store:
buffered: true,pageSize: 50, // Whatever works best given your network/DB latencyautoLoad: true
Then load and maintain it as usual.
How it works
Grid calculates the rendering table size and monitors the scroll position based on the configuration items of the pagingscroller object. The following configuration items need to be configured during rolling:
- Trailingbufferzone: Number of rendered records that are kept in the visible area.
- Leadingbufferzone: number of records rendered in the visible area.
- Numfromedge: the boundary value between the scrolling and visible area of the table before the table is refreshed.
The rendered table must contain enough rows to fill the height of the view, plus the buffer period and the size of the leading buffer, plus (numfromedge * 2) to create a scroll overflow.
When the scrolling result of the table is monitored and the number of rows between the end of the table and the view is smaller than numfromedge, use the next piece of data in the dataset to re-render the table and locate it, so that the visible position of the row remains unchanged.
In the best case, re-rendering the required rows is already in the page cache, and the operations are instantaneous and imperceptible.
To configure these values, you can configure them in the verticalscroller configuration item of the grid:
{ xtype: 'gridpanel', verticalScroller: { numFromEdge: 5, trailingBufferZone: 10, leadingBufferZone: 20 }}
This means that 40 rows of overflow data are provided to the visible area of the grid for smooth scrolling, and re-rendering will occur in less than 5 rows between the table boundary and the visible area.
Keep the MPs queue complete
It is a store job to keep the page cache and prepare data for future scrolling. Store also has trailingbufferzone and leadingbufferzone.
When a table Request re-renders a row, after the request row is returned, store will ensure that the cached data covers the data required for the two regions. If the data is not cached, it will request data to the server.
Both regions have relatively large default values. developers can reduce or increase the number of pages they keep in the pipeline.
Cache failed
When the data set is not cached, loading masking and delayed rendering are displayed because the data needs to be requested from the server. However, this situation has been optimized.
The page range that contains the data required for the display area will give priority to requests. When the data arrives, it will be re-rendered immediately. The data required by trailingbufferzone and leadingbufferzone will be sent immediately after the data required by the UI is loaded.
Cache trimming
By default, the cache calculates the maximum size. In addition, it also discards recently used pages. The page size is the leadingbufferzone of the scroll bar plus the visible area size, plus the trailingbufferzone and store purgepagecount configuration items. Adding purgepagecount means that once a page is accessed, it can be returned quickly, rather than sending a request to the server.
If the purgepagecount value is 0, it means that the cache can continue to grow without trimming, and may eventually grow to include the entire dataset. This is a very useful option when the dataset is not too big. Remember, humans cannot understand too much data, so displaying more than thousands of rows of data in the grid is actually not very useful, which may mean they use the wrong filtering conditions and need to re-query.
Place the entire dataset on the client
If the dataset is not an astronomical dataset, it is feasible to cache the entire dataset on the page.
You can test the feasibility by using the "infinite grid tuner" example of (examples/GRID/infinite-scroll-grid-tuner.html) under the SDK sample directory.
If you set the leadingbufferzone of the store to 50000 and set purgepagecount to 0, this will produce the expected results.
The leadingbufferzone will allow the store to maintain the integrity of the pipeline, which means that the 50000 discount is very complete.
Purgepagecount0 means there is no limit on the growth of the number of pages.
Therefore, when you click "reload", the data page required by the visible area will be requested first and then rendered.
Then, the store will try to fill up a huge leadingbufferzone. Soon, the entire dataset is cached, and data access anywhere in the scrolling area is real-time.
Author:Nige "animal" White
Nigel brings more than 20 years experience to his role as a Software Defined ect at sencha. he has been working with rich Internet applications, and dynamic browser updating techniques since before the term "ajax" was coined. since the germination of ext JS he has contributed code, documentation, and design input.