By “slow” here I mean the slow run-time, not the load time of resources.
In the past one year and a half, I have been working at Bosch Software Innovations, Robert Bosch, where our frontend technology stack heavily relies on ExtJS. I had chances to develop Visual Rules Web Modeler and helped with several other ExtJS-based apps and I did experience a lot of performance issues with ExtJS apps in common.
In this article, I’m gonna share with you the key bottlenecks that might cause an ExtJS app to run slow, and also point out the mistakes that rookie ExtJS devs most likely make.
ExtJS in this article refers to the version 3.3.x and below.
1. Over-declaration of Ext.Panel
The most used ExtJS component, IMO, would be Ext.Panel. Declaring a panel with ExtJS is so simple that developers could easily over-declare them. Below is a typical declaration of a panel with nested sub-panels.
var panel = new Ext.Panel({ // Level-1 title: 'Multi Column, Nested Layouts and Anchoring', bodyStyle:'padding:5px 5px 0', width: 600, items: [{ // Level-2 layout:'column', items:[{ // Level-3 columnWidth:.5, layout: 'vbox', items: [{ // Level-4 html: 'This is some text' }, { html: 'This is another text' }] },{ columnWidth:.5, layout: 'form', items: [{ xtype:'textfield', fieldLabel: 'Last Name', name: 'last', anchor:'95%' },{ xtype:'textfield', fieldLabel: 'Email', name: 'email', vtype:'email', anchor:'95%' }] }] }] });
Any problem with this config ? NooO !? The problem is that this panel has four nested levels of panels, but it actually needs two in order to work and look as desired.
- This is an example of over-declaration which leads to seriously deep nested level of HTMLElements created, and severely affects the initialization/rendering time and run time of the component.
- I’ve seen ExtJS developers who just like to declare nested panels because it’s convenient for them to put some CSS styles, text or images inside.. or sometimes just because adding another level of panel makes the form look as desired !?!
- The rule of thumb is: use as few panels and nested levels of components/panels as possible.
To achieve this you need to wisely make use of Ext layouts and CSS styles when declaring a complex component.
2. Delay the HTMLElement creation whenever possible
Interacting (Read/Write) with the DOM is always not cheap, especially on IE 6, reading the DOM also causes re-flow.
So the rule of thumb is: try to delay the creation of HTMLElements whenever possible. This could be achieved by:
- Component Lazy instantiation. This is possible with xtype.
- Try to defer expensive operations till afterrender.
- Avoid unnecessarily instantiating/rendering components in the constructor or initComponent of another component.
Example 1: A simple example is do not create Ext.Tooltip and render it hiddenly in the DOM when the button is first rendered. The tooltip usually should be rendered at the first time users hover mouse over the button. If tooltip is rendered right after the button, and users never hover mouse over the button. We have already created a wasted, never-used tooltip which adds up to the performance issue.
Example 2: Another example is careless use of renderTo:
var window = new Ext.Window({ renderTo: document.body, title: 'The Untitled' }); window.show(); // the window has been rendered before this line
With the above declaration, the window will be rendered immediately and hidden to the <body> tag. In many cases, we don’t need the renderTo, the window should be rendered only when it is displayed the first time as following:
var window = new Ext.Window({ title: 'The Untitled' }); window.show(); // the window is to be rendered and displayed at this line
3. Use the delegate pattern whenever possible:
I’m not gonna dig into the details of delegate pattern here, but will rephrase it in ExtJS language instead.
Example: An example of delegate pattern is that you have an Ext.Toolbar with 10 Ext.Button, and you need to assign Ext.Tooltip to every button so that when users hover mouse over a button, a Tooltip is displayed with an explanation text.
If you create 10 Ext.Tooltip and assign them to 10 Ext.Button then it is not the most optimized solution. You can create only 1 Ext.Tooltip and assign it to the parent element, which is Ext.Toolbar, of those 10 Ext.Button.
When users hover mouse over the Ext.Toolbar, you will display the same Ext.Tooltip but with different explanation text according to the target element ( actual Ext.Button ) being mouse over. (See getTarget for how to get target element)
With this technique, only 1 Ext.Tooltip is created, and 1 event listener is attached to the Ext.Toolbar.
- Saves you a lot of memory usage and run time of your app but still achieves the same result.
You might want to look at delegate config property of Ext.Tooltip for the sample implementation.
4. Component destruction – How to destroy things properly
This is one of the main bottlenecks I found in the slow ExtJS apps that I have helped improving the performance. The idea is when a component is not in use any more, we should clean up :
- The HTMLElements existing in the DOM
- Dettach all event listeners to avoid memory leaks.
- Destroy all descendant components in the same recursive way
These above are handled in the method destroy of an Ext.Component. There are cases you need to call #destroy method during run time as leaving unused components in the DOM would lead to severe slow performance.
Example 1: if you happen to create a custom Context Menu (Ext.menu.Menu) inside a panel (Ext.Panel), remember to override the destroy method of the panel, and destroy the context menu in there. Later if the panel gets destroyed, your context menu gets destroyed too.
Your.Component.Klass = Ext.extend(Ext.Component, { initComponent: function(){ // Initialize your custom stuff this.contextMenu = new Ext.menu.Menu({ renderTo: document.body // .. }); }, destroy: function(){ // Destroy your custom stuff this.contextMenu.destroy(); this.contextMenu = null; // Destroy the component Your.Component.Klass.superclass.destroy.call(this); } });
Example 2: One common mistake is the misuse of config option closeAction of Ext.Window.
If you configure closeAction as ‘hide‘, the window will be hidden when users hit the ‘Close’ button. However, there is a chance that, the window will never be displayed again through out the whole run time. So make sure if yosu really want to do the show/hide, otherwise always configure closeAction as ‘close‘ for the .destroy method will be called on the window after it is hidden to destroy it properly.
var window = new Ext.Window({ closeAction: 'hide', title: 'The Untitled' }); window.show(); // render and display the window window.hide(); // the window is not destroyed but only hidden in the DOM.
I hope this article would give you some clues fighting with performance issues of your ExtJS-based applications. If you have other experience regarding performance issues specific to ExtJS apps, feel free to share here ! :D
Cheers,
Totti