Introduction: as a web user, we know that the speed of page loading or refreshing is crucial to its success. This article will help you better understand the factors that affect the performance of web applications. Learn to identify these problems and find the bottleneck of the client content. Explore performance issues with JavaScript, DOM, CSS, and Dojo widgets. The following example shows how to properly adjust the Dojo widget using YSlow and Firebug.
Tags in this article: ajax, dhtml _ (dynamic_html), ecmascript, javascript, web, application performance, application integration, skills, and Improvement
Overview
Rich Internet applications (RIAs) is very popular in the Web 2.0 field. To provide a fresh and unique user experience, many websites use JavaScript or Flash to migrate complex content from the backend server to the front-end client. If there is less data, this provides a convenient, novel, and smooth User Interface (UI ). If you want to transfer a large amount of content from the server to the client and present it in the browser, the performance will be significantly reduced. The challenge is to find the bottleneck and determine the solution.
It is more difficult to adjust performance issues in browsers than in Java applications. There are much less methods for developers to debug JavaScript in various browsers. In Mozilla Firefox, you can use Firebug to debug JavaScript, but still cannot adjust many performance problems, such as browser rendering time consumption. To solve these problems, it is necessary to develop browser plug-ins to monitor time responses and determine other solutions such as partial rendering or delayed loading.
Learn to diagnose performance problems in web applications, locate bottlenecks in client content, and adjust performance.
JavaScript and HTML DOM
JavaScript is the most popular scripting language on the Internet. Tens of thousands of web page designers use JavaScript to improve design, validate forms, check browsers, and create cookies. The HTML Document Object Model (DOM) defines standard methods for accessing and operating HTML documents. It represents an HTML document as a node tree that contains elements, attributes, and text content.
By using html dom, JavaScript can access all nodes in the HTML document and operate on them. Many mainstream browsers support JavaScript and html dom, and many websites also use them. The performance they use significantly affects the performance of the entire RIAs.
JavaScript performance and functions
In JavaScript, a function is used when a certain function is required. Although strings can be used to replace functions in some cases, we recommend that you use functions as much as possible. In JavaScript, functions are pre-compiled before use.
For example, check the eval method in Listing 1.
Listing 1. Using strings as parameters for the eval Method
Function square (input ){
Var output;
Eval ('output = (input * input )');
Return output;
}
The eval method calculates the square value and outputs the result, but the performance is poor. In this example, the string output = (input * input) is used as the eval method parameter and cannot be precompiled using JavaScript.
Listing 2 shows a better way to complete this task.
Listing 2. Eval method using functions as parameters
Function square (input ){
Var output;
Eval (new function () {output = (input * input )});
Return output;
}
Use functions instead of strings as parameters to ensure that the code in the new method can be optimized by the JavaScript compiler.
Function Scope
Each scope in the JavaScript function scope chain contains several variables. It is important to understand the scope chain so that it can be used. Listing 3 shows a function scope example.
Listing 3. Function Scope
Function test (){
Var localVar = "test ";
Test1 (this. localVar );
Var pageName = document. getElementById ("pageName ");
}
Figure 1 shows the scope chain structure.
Figure 1. Scope chain structure
Using local variables is much faster than using global variables, because the longer the scope chain, the slower the resolution.
If the Code contains a with or try-catch statement, the scope chain is more complex. Figure 2 shows the scope chain of the try-catch statement.
Figure 2. Try-catch scope chain structure
String Functions
The most undesirable function in JavaScript is a string connection. I usually use the + number for connection. Listing 4 shows an example of this type.
Listing 4. String connection
Var txt = "hello" + "" + "world ";
This statement creates several intermediate strings containing the connection results. In this way, consecutive creation and destruction of strings in the background results in extremely low string connection performance. Early browsers were not optimized for such operations. We recommend that you create a StringBuffer class for implementation, as shown in listing 5.
Listing 5. StringBuffer object
Function StringBuffer (){
This. buffer = [];
}
StringBuffer. prototype. append = function append (val ){
This. buffer. push (val );
Return this;
}
StringBuffer. prototype. toString = function toString (){
Return this. buffer. join ("");
}
All attributes and methods are defined for string objects rather than values. When you reference a string value attribute or method, the ECMAScript engine implicitly creates a new String object with the same value before the method is executed. This object is only used for specific requests and is re-created when the string value method is used at the moment.
In this case, use the new statement for strings that will be called multiple times for those methods.
An example of a New String object is shown in Listing 6.
Listing 6. Example of creating a new String object
Var str = new String ("hello ");
StringObject. indexOf is faster than StringObject. match. When searching for simple string matching, try to use indexOf instead of regular expression matching.
Try to avoid matching in long strings (10 kb or above) unless you have no choice. If you are sure to match only a specific part of the string, use a substring instead of the entire string for comparison.
DOM Performance
This chapter briefly introduces some content that can be adjusted to improve DOM performance.
Repaint)
When the previously invisible content becomes visible, the DOM will be re-painted, and the opposite will be the same. Re-painting is also called re-painting. This action does not change the document layout. If you do not change the element size, shape, or position, only changing the appearance will trigger re-painting.
For example, adding a border to an element or changing the background color triggers re-painting. Repainting has a high performance cost; it requires the engine to search for all elements to determine which are visible and which must be displayed.
Reflow)
Reflux is a more significant change in proportion painting. In reflux:
Operate the DOM tree.
The layout style will change.
The className attribute of the element is changed.
The size of the browser window is changed.
The engine returns the relevant elements to determine where each part is displayed. Sub-elements are also returned to reflect the new layout of the parent element. The elements after the elements in the DOM will also be reflux to calculate the new layout, because they may be moved during initial reflux. The ancestor element is also reflux due to the variation of the child element size. Finally, all content is repainted.
Each time an element is added to a document, the browser returns to the page to calculate how all content is located and displayed. The more things are added, the more reflux times. If you can reduce the number of times to add elements separately, the browser returns less and runs faster.
CSS Performance
Place the Cascading Style Sheet (CSS) on the top. If the style sheet is placed at the bottom, it is finally loaded. In the last few seconds, the page was blank, and the browser waited for the style sheet to be loaded before other things on the page could be rendered-or even static text.
In Internet Explorer, @ import works the same as using <link> at the bottom. We recommend that you do not use it.
Abbreviation attribute
Use the abbreviation attribute to set several attributes at a time in a declaration, instead of using a declaration for each attribute. You can use the abbreviated attribute to reduce the file size and maintenance workload.
For example, you can set the background, border, border color, border style, border side (top border, right border, bottom border, Left Border), Border width, Font, margin, outline, fill attribute.
CSS Selector
The CSS selector moves from right to left to match. As shown in listing 7, the browser must traverse Each anchor element on the page to determine whether its parent element ID is an aElement.
Listing 7. The selector matches from right to left.
# AElement> {
Font-size: 18px;
}
If you remove> from the code, as shown in listing 8, the performance is worse. The browser needs to check all the anchors in the entire document. In this way, we will not only check the parent element of the anchor, but look up the ancestor element with the ID as aElement in the document tree. If the element being checked is not the child of aElement, the browser will search along the tree of the ancestor element until the root of the document.
Listing 8. worse performance if not>
# AElement {
Font-size: 18px;
}
Best practices
This chapter outlines best practices that can help you improve web application performance.
Reduce the number of HTTP requests
Each HTTP request has an overhead, including querying DNS, creating a connection, and waiting for a response. Therefore, reducing unnecessary requests can reduce unnecessary overhead. To reduce the number of requests:
Merge files. Merge scripts that are always used at the same time into the same file, without reducing the total size, but reducing the number of requests. You can also merge CSS files and images in the same way. Files can be merged automatically:
In the build phase. Use the <concat> flag to merge files by running Ant.
In the runtime stage. Enable the mod_concat module. If httpServer is Apache, pack: Tag is used as the JSP Tag library to merge JavaScript and style sheet files. (Pack: Tag is a JSP-Taglib that can reduce, compress, and merge resources, such as JavaScript and CSS, and cache them in content or common files .)
Use CSS Sprites. Combine the background image into an image and use the CSS background-image and background-position attributes to display the required image. You can also use Inline images to reduce the number of requests.
Post-load component
Only the required components are displayed; the remaining components can be waited. It is best not to present too many components at a time.
In some cases, you can use Post loading. Because components outside the visible area of the browser can be loaded in the rear, the initial rendering will become invalid shortly after these components enter the visible area.
Some JavaScript can be loaded after the onload event, such as dragging an element after the initial rendering in JavaScript.
Preload Components
You can use the browser's free time to request components (such as images, styles, and scripts) that will be used in the future through the pre-loading component ). When a user accesses the next page, if most components are loaded in the cache, the page loading will be much faster.
There are two types of pre-loading:
Unconditional: When onload is triggered, additional components are obtained.
Conditional: based on the user's actions, the user's next direction is estimated and the corresponding pre-loading is performed.
Put the script at the bottom
Scripts may cause problems because they may impede parallel download. When the script is downloaded, the browser does not start other downloads-even if those are on different hosts. Place scripts, such as style sheets, at the bottom to ensure that they are downloaded after other downloads are completed.
You can also use a latency script, which is only supported by Internet Explorer. The DEFER attribute indicates that the script does not contain document. write (). This tells the browser that they can continue rendering.
Use the cookieless domain component
When a browser sends a request for a static image and then sends a cookie, the server does not use those cookies. Because these cookies only cause unnecessary network traffic, make sure that no request is sent to the static component. Then, use the subdomain and host to save these static components.
Place JavaScript and CSS outside
In the real world, using external files usually makes the page run faster, because JavaScript and CSS files are cached by the browser. The JavaScript and CSS inline in the HTML document will be downloaded every time the HTML document is requested. This reduces the number of HTTP requests, but increases the size of HTML documents. On the other hand, if JavaScript and CSS are in the external files cached by the browser, the HTML document size will be reduced without increasing the number of requests.
RIA widget Performance
Mainstream RIA Ajax frameworks, such as ExtJS, YUI, Dojo, and others, all provide sophisticated widget libraries to enhance user experience. Compared with other frameworks, Dojo is more powerful in enterprise development because:
Object-oriented programming (OOP) Encoding
Cross-platform
Offline Dojo API support for local data storage
DataGrid, 2D, and 3D graphics (Chart components provide a simpler way to display reports in a browser)
Dojo is widely used in many websites. We will use the Dojo example to analyze the performance of the RIA widget. You can use the Dojo widget adjustment tool as needed, including Page Speed, Rock Star Optimizer, and Jiffy. We strongly recommend that you use YSlow and Firebug.
YSlow
YSlow analyzes web page performance by checking all components on the page, including those created by JavaScript, based on a set of high-performance web page rules. YSlow is a Firefox plug-in integrated with Firebug web development tools. It provides recommendations for improving page performance, summarizes component performance, displays page statistics, and provides tools for performance analysis.
Figure 3 shows the information on the YSlow Grade tab.
Figure 3. YSlow Grade Tab
YSlow web pages are built on 22 testable rules, which are listed below by importance and effect. According to the following rules, the response time of web pages can be increased by 25% to 50%:
Minimize the number of HTTP requests.
Use content publishing network (CDN ).
Add the Expires or Cache-Control header.
Use Gzip to compress the content.
Place the style sheet on the top.
Put the script at the bottom.
Avoid using CSS expressions.
Place JavaScript and CSS outside ..
Reduce DNS search.
Simplified JavaScript and CSS.
Avoid using redirection.
Delete duplicate scripts.
Configure ETags.
Make Ajax cacheable.
Use GET for Ajax requests.
Reduce the number of DOM elements.
Eliminate Error 404.
Reduce the cookie size.
Use a cookie-free domain for the component.
Avoid using filters.
Do not measure the image size in HTML.
Make favicon. ico as small as possible and can be cached.
In Figure 4, YSlow Statistics compares the page size of users who access the blank cache with those who previously visited the page.
Figure 4. YSlow Statistics Tab
The Components tab displays each component and related performance information. For example, if the component is compressed by gzip or the ETag has content (if any), you can see it. The component size and expiration time are also displayed on the Components tab, as shown in Figure 5.
Figure 5. YSlow Components Tab
Firebug
Firebug is integrated with Mozilla Firefox, allowing you to use a large number of development tools when Browsing your website. Allows you to instantly edit, debug, and monitor CSS, HTML, and JavaScript on web pages.
You can use the Firebug Net panel, as shown in figure 6, to monitor HTTP traffic generated by web pages. It shows you all the collected and calculated information. Each entry indicates a back-and-forth request/response to the page.
Figure 6. Firebug Net
The Firebug Console Panel provides two methods to monitor code performance, as shown in figure 7.
Figure 7. Firebug Console panel
Profile
Use Profiler for a specific function. JavaScript Profiler is a Firebug feature that can be used to measure the execution time of each JavaScript code. Use JavaScript Profiler to improve code performance, or to see why a function runs too long. It is similar to console. time (); but JavaScript Profiler can provide more internal process details.
Console. time ()
For specific code segments, use console. time (). The console displays the result of the command you entered into the command line. You can use the console. time (timeName) function to measure the execution time of a specific code or function. This feature is very useful for improving the performance of JavaScript code. Listing 9 shows an example.
Listing 9. console. time () Example
Var timeName = 'measuringtime ';
Console. time (timeName); // start of the timer
For (var I = 0; I <1000; I ++ ){
// Do something
Console. timeEnd (timeName); // end of the timer
MeasuringTime: xxms is displayed on the console.
Dojo widget Performance
This chapter will explore ways to improve the performance of Dojo widgets.
Loading costs
As shown in "Improving performance of Dojo-based web applications" (E. Lazutkin, Feb 2007), the first impression most Dojo users have on it is that it is huge. For example, dojo-release-1.5.0-src.zip is 19 M, and even compressed dojo-release-1.5.0.zip has 4.5 M. Most files in the lite version are redundant and never used. All Dojo versions have all copies of Dojo files and custom dojo. js files that combine all common files. The best way to reduce the load cost is to use the appropriate Dojo version.
Dojo. js can activate the Dojo objects and dynamically load other modules, unless they have been loaded by optional parts of dojo. js. When the browser loads the dojo. js file for the first time, it uploads and installs the following files:
Dojo Bootstrap code
Dojo Loader
Common modules
To reduce the loading time, consider which version is the most suitable for your application. Otherwise, you need to customize a Dojo version. For more information about the Dojo documentation, see the reference chapter.
Resolution cost
To minimize the parsing cost of the Dojo widget, use the following two methods to optimize initialization:
Tag instantiation
You can create a Dojo widget with HTML tags by adding the dojoType attribute, as shown in listing 10. The premise for running this method is that dojo. parser is included in dojo. require ("dojo. parser"); and djConfig = "parseOnLoad: true ". This is a simple method to declare components in lightweight code. All labels with the dojoType attribute on the page will be automatically parsed after the document is loaded. This method is very convenient for small applications, but it will significantly increase the startup time of web applications with a large number of HTML. The parser will access all elements and check whether Parsing is required. Use the configuration file, as provided in Firebug.
If you find that you spend too much time on dj_load_init (), modulesLoaded (), or other content similar to the initial load, consider the part initialization.
Listing 10. Create the dojoType Dojo widget
Id = "errorDialog" dojoType = "dijit. dijit" title = 'dialog1 'class = "pop-window"/>
Code instantiation
Dojo is an OOP framework, so you can use new to create widgets. To create a widget, you must enter two parameters: a json object with attributes and the element to be parsed. Each widget requires at least two statements. Listing 11 is an example.
Listing 11. Create a Dojo widget with JavaScript new
New dijit. dijit ({"title": "dialog1"}, dojo. byId ("dialog1 "));
Improved resolution Performance
To optimize the code structure and performance, you can consider improving resolution when creating widgets. Disable Automatic resolution by setting parseWidgets to false, and create an array to set the element ID, as shown in listing 12. You can also dynamically Add the ID of the new element at runtime. When a document is loaded, use dojo. forEach () to parse all elements in the array.
Listing 12. parse widgets through iterative searchIds
<Head>
....
<Script> djConfig = {parseWidgets: false, searchIds: [...]}; </script>
....
</Head>
<Body onload = 'dojo. forEach (djConfig. searchIds,
Function (id) {dojo. parser. parse (dojo. byId (id) ;}); '>
........
</Body>
Solution
The performance of the Dojo grid is mainly related to input/output operations, a large amount of data access, and browser data presentation. You can improve the performance of Dojo grid widgets by using the mechanism of merging grid features.
Review the usage of the cache mechanism. When the data is loaded from the database to the local machine, the data is stored in the memory for a period of time. This is a good way to reduce the response time of the server request data. In this way, the request is sent only when the user updates or modifies the data in the grid. The Caching mechanism is generally implemented through the Dojo mesh, but when you perform some operations on the mesh, the problem will occur. The following scenarios reveal these problems:
Sort a grid
In most cases, the grid can be correctly ordered because the grid itself implements a sorting function on the data storage layer. However, data storage affects caching. For example, if a grid column is of the Chinese character type, the sorting result may be incorrect, and the grid performance may be severely degraded due to some uncertainties.
The solution is to redefine the sorting function at the data storage layer. Listing 13 and 14 below demonstrate how to: rewrite the sorting Logic Based on the onHeaderCellMouseDown function, present data, and update the header title view of the grid.
Listing 13. Redefinition of sorting Logic
Grid. onHeaderCellMouseDown = function (event ){
Var items = DataStore. data. items;
// Sort the "Name" column which might contain characters like Chinese and so on
If (event. cellIndex = 1 ){
SortAscending =! SortAscending;
// Change the string to localestring before comparison with localeCompare method
If (sortAscending ){
Items. sort (function (m, n ){
Return m ["name"]. toString ().
LocaleCompare (n ["name"]. toString ());
});
} Else {
Items. sort (function (m, n ){
Return n ["name"]. toString ().
LocaleCompare (m ["name"]. toString ());
});
}
} Else
// Sort the "Date" column
If (event. cellIndex = 2 ){
SortAscending =! SortAscending;
// Compare the date with milliseconds computed from 1970/07/01
If (sortAscending ){
Items. sort (function (m, n ){
Return Date. parse (m ["date"]. toString ())
-Date. parse (n ["date"]. toString ());
});
} Else {
Items. sort (function (m, n ){
Return Date. parse (n ["date"]. toString ())
-Date. parse (m ["date"]. toString ());
});
}
}
}
Listing 14. Present data and update the mesh header View
// "Sorcosponx" is the index of the column to be sorted
UpdateGridAfterSort: function (sortColIdx ){
// Render the data of the grid
Var store = new dojo. data. ItemFileWriteStore (DataStore. data );
Grid. setStore (store, null, null );
Grid. update ();
// Update the header view of the gird
Var headerNodeObjs = document. getElementsByTagName ("th ");
For (var I = 0; I <2; ++ I ){
// "GridLayout" is a global array defining the layout of the grid
Var headerNodeObjName = gridLayout [0]. cells [0] [I]. name;
Var headerNodeHtml = ['<div class = "dojoxgridsortnode'];
If (I = sortcow.x ){
HeaderNodeHtml = headerNodeHtml. concat (['', (sortAscending =
True )? 'Dojoxgridsup up': 'dojoxgridsdown low', '"> <div
Class = "dojoxGridArrowButtonChar"> ', (sortAscending = true )?
'▲': 'Running',' </div> <div class = "dojoxGridArrowButtonNode"
> </Div> ']);
HeaderNodeHtml = headerNodeHtml. concat ([headerNodeObjName,
'</Div>']);
HeaderNodeObjs [I]. innerHTML = headerNodeHtml. join ("");
Break;
}
}
}
Search in the grid
When the grid has a large amount of data, the query function will lead to poor performance, especially when the grid supports real-time search and fuzzy match. The solution is to cache data with extra storage space before they are converted into JSON objects used in data storage; this will avoid a large number of function calls, such as the getItem of data storage. Listing 15 shows an example.
Listing 15. cache the data retrieved from the database to the array and find
// Fetch data from database
GetData: function (){
Function callback (ResultSet ){
// ResultSet is an array of records fetched from database and make variable
// RawData refer to it to cache it in memory
GlobalVaribles. rawData = ResultSet;
// Convert the raw data ResultSet to JSON for data store use
GlobalVaribles. dataJSON = JSONUtil. convert2JSON (ResultSet );
}
DBUtil. fetchAll (callback );
}
// Search functionality
Search: function (col, value ){
If (value = null | value = ""){
Return;
}
// Used here
Var rawData = GlobalVaribles. rawData;
Var newResults = [];
For (var I = 0; I <rawData. length; I ++ ){
Var result = rawData [I];
// Fuzzy match
If (result [col]. toLowerCase (). indexOf (value. toLowerCase ())! =-1 ){
NewResults [newResults. length] = result;
}
}
// Render the new results
GridManager. renderNewResults (newResults );
}
Delayed Loading Mechanism
The Dojo mesh originally supports the delayed loading mechanism, which can improve performance and provide a better user experience. Delayed loading in the Dojo mesh means that some data is presented in the data storage, but not all the grids display the rest of the data when you drag the scroll bar.
By default, the grid does not enable the delayed loading mechanism. It must be started explicitly. Listing 16 demonstrates how to start the service in two ways. RowsPerPage and keepRows attributes are key components.
Listing 16. Start the demo Loading Mechanism
// The programmatic way
Var grid = new dojox. grid. DataGrid ({
Store: store, // data store
Structure: gridLayout,
RowsPerPage: 10, // Render 10 rows every time
KeepRows: 50, // Keep 50 rows in rendering cache
}, "Grid ");
// The declarative way using HTML label
<Table
DojoType = "dojox. grid. DataGrid"
Id = "grid" store = "store" structure = "gridLayout"
Query = "{id :'*'}"
RowsPerPage = "10" keepRows = "50">
<! -- Other definitions -->
</Table>
Pre-Loading Mechanism
The pre-loading mechanism can load the remaining data in advance, even if the user is only temporarily used. For a Dojo grid, there may be a lot of data in data storage; you may drag the scroll bar to view other data. If a page contains a lot of data, it is inconvenient to view a Specific Row. Using the front-end mechanism and paging technology, you can view it more easily (similar to Google's paging bar) and get better performance.
Listing 17 shows the basic implementation of paging technology. First, construct some JSON objects for data storage to use for initial paging, and then add some new JSON objects when you click the last page of the paging bar.
Listing 17. building some JSON objects at the beginning and switching as needed
// Fetch data from database and convert them to JSON objects
GetData: function (){
Function callback (ResultSet ){
GlobalVaribles. rawData = ResultSet;
// "Convert2JSONs" method convert the raw data to several JSON objects
// Stored in Global array "dataJSONs ".
GlobalVaribles. dataJSONs = JSONUtil. convert2JSONs (ResultSet );
}
DBUtil. fetchAll (callback );
}
// Initial status
Var dataStore = new dojo. data. ItemFileWriteStore ({data: GlobalVaribles. dataJSONs [0]});
Var grid = new dojox. grid. DataGrid ({
Store: dataStore,
Structure: gridLayout,
}, "Grid ");
Grid. startup ();
// Assuming that the user clicks the I-th item in the paging bar, we just update the data
// Store for the grid simply and the performance is still very good.
DataStore = new dojo. data. ItemFileWriteStore ({data: GlobalVaribles. dataJSONs [I-1]});
Grid. setStore (dataStore, null, null );
Grid. update ();
Conclusion
In this article, you learned how to identify problems or bottlenecks in your web applications. You can now learn some tools, tips, and tips to adjust and improve your performance.
Author: ERDP Technical Architecture"