This article describes how to improve the efficiency of JavaScript code execution at the data access level. In general, there are the following principles:
- In a function, reading and writing local variables is always the fastest, while reading global variables is the slowest;
- Use the with statement as little as possible because it will increase the access cost of data other than the with statement;
- Although the closure is powerful, it cannot be abused; otherwise, it will affect the execution speed and memory;
- Nested object members will significantly affect performance and should be used as little as possible;
- Avoid Multiple accesses to global variables of object members or functions, and assign them to local variables as much as possible to cache them.
Such a few words seem simple, but to have a deep understanding of the truth, it involves JS identifier parsing, scope chain, runtime context (also known as the execution environment) A series of concepts such as prototype chain and closure. I have read a JavaScript closure for online translation. I have explained these things in this article, but I still seem to understand it several times. However, this book is illustrated and well understood. You can't help but feel it. The Ox is the ox ~
Scope chain and identifier Parsing
Every JS function is represented as an object. This object has an internal attribute [[Scope], which contains a set of objects in the Scope created by a function, this set is called the Scope chain of a function. It determines which data can be accessed by the function. Each object in the function scope is called a variable object ). After a function is created, its scope chain will be filled with accessible data objects in the scope of the function.
For example, the following global function:
function add(num1, num2) { var sum = num1 + num2; return sum;}
Because this function is created under the global scope, the scope chain of the add () function is only filled with a single variable object-Global Object:
When this function is executed, an internal object called "execution context" is created, which defines the environment in which the function is executed. The runtime context corresponding to each function execution is unique. Therefore, multiple calls to the same function may create multiple runtime contexts. After the function is executed, the execution context is destroyed.
What is the relationship between the function scope chain and the runtime context we just talked about? Yes. Each runtime context has its own scope chain for identifier parsing, the Scope chain references the Scope chain pointed to by the internal object [[Scope] of the function. In addition, an "Activation object" is created, which contains all the local variables, named parameters, parameter sets, and this of the function, when a function is executed, it is treated as a variable object, which is the same as the previous one. Then the object will be pushed to the front-end of the scope chain. In this way, the runtime context will be created. When the runtime context is destroyed, the activity object will also be destroyed (except the closure, which will be discussed later ).
For example, the runtime context and scope chain corresponding to the previous add () function runtime:
During function execution, an identifier parsing process is performed every time a variable is encountered. This process searches for the scope chain from the beginning to the end, from the activity object of the currently running function to the global object, the identifier with the same name is found. If it is not found, it is considered as undefined.
Identifier resolution Performance
Identifier resolution is costly. In the context chain at run time, the deeper the location of an identifier, the slower its read/write speed. Obviously, it is the fastest way to read and write local variables, because its object is at the frontend of the action chain. A good rule of thumb is that if a cross-scope value is referenced more than once in a function, it is stored in a local variable.
In addition, temporary changes to the scope chain during code execution will also affect the performance of identifier parsing. There are two statements that will cause this situation-with and catch, when these two statements are executed, a new variable object (with is the object specified in the statement, catch is the exception object) is pushed to the header of the scope chain, in this way, the original accessible objects are pushed back to a level, which makes their access cost higher. Therefore, it is best to avoid using with statements. If a catch statement is used, you can define a function to handle errors to reduce the number of statements in the catch statement.
This is an example of a changed scope chain in the with statement:
Closure, scope, and memory
After understanding the scope chain, the closure will be easy to understand. Closure is one of the most powerful features of JavaScript. It allows functions to access data outside the local scope. However, there is a performance issue related to closures. Think about the following code:
function assignEvents() { var id = 'xdi9592'; document.getElementById('save-btn').onclick = function(event) { saveDocument(id); }}
The onclick event processor inside a function is a closure. To allow the closure to access data in the function assignEvents (), when the closure is created, its [[Scope] attribute is initialized as an object in the Scope chain of its external function runtime, that is, the [[Scope] attribute of the closure contains references of objects with the same Scope as the runtime context.
It is the scope chain and closure of the context of the function assignEvents () runtime:
Generally, function activity objects are destroyed along with the runtime context. However, when a closure is introduced, because the reference still exists in the [[Scope] attribute of the closure, the activation object cannot be destroyed. This means that the closure in the script requires more memory overhead than the non-closure function.
When a closure is executed, an active object will be created for the closure and placed at the frontend of the scope chain:
In this case, the overhead for access to data outside the closure (such as id and saveDocument) is higher, because they are pushed to a level at the location of the scope chain. This is the main performance focus of using closures: You need to frequently access a large number of cross-scope identifiers, and each access will cause performance loss.
Object member parsing, prototype, prototype chain
Object members refer to the attributes or methods of objects. When we want to access TestObj. when an object member such as abc is located, the TestObj object is first found during the identifier parsing process, and then the object member parsing process is required to access the abc attribute (or method.
Objects in JavaScript are prototype based and prototype is the basis of other objects. It defines and implements a list of members that a new object must contain. The object is bound to its prototype through an internal property _ proto _ (this property is visible to developers in Firefox, Safari, and Chrome.
An object can have two types of members: instance members and prototype members. Instance members exist in the object instance, and prototype members are inherited from the object prototype. Once an instance of a built-in Object (such as Object and Array) is created, it automatically owns an Object instance as the prototype, as shown in the following code:
var book = { title : 'High Performance JavaScript', publisher : 'Yahoo! Press'}alert(book.toString()); //"[object Object]"
In this example, the book does not define the toString () method, but it can be executed smoothly because the toString () method is a prototype member inherited from the object book:
Note that the _ proto _ attribute also exists in the book prototype. As mentioned earlier, JavaScript objects are prototype-based. Since each object has a prototype, this naturally forms a "chain", which we call a prototype chain. The prototype chain ends on the object whose prototype is null. The parsing of object members is actually the process of traversing the prototype chain, from instance members to finding prototype members.
You can also define and use the constructor to create another type of prototype. In this way, a new defined prototype object is inserted into the prototype chain. Consider the following example:
function Book(title, publisher) { this.title = title; this.publisher = publisher;}Book.prototype.sayTitle = function() { alert(this.title);}var book1 = new Book('High Performance JavaScript', 'Yahoo! Press');var book2 = new Book('JavaScript: The Good Parts', 'Yahoo! Press');
Here, a prototype is manually created for the Book object and a method sayTitle () is defined. For instance book1, the prototype chain is like this: book1 prototype-> Book. prototype, Book. prototype-> Object, Object prototype-> null.
When you want to access a member in book1, the process of retrieving the prototype chain is as follows: first, search for the instance Member (title, publisher) of book1 ), if not, search for the book1 prototype Book. the prototype member (sayTitle). If the prototype has not been reached, continue to search for the Book. prototype: prototype Object prototype member (toString, valueOf ...), if no prototype is found, the system continues searching for the prototype of the Object. If the prototype of the Object is null, the query ends. At this time, the member is undefined, if any query is found in this process, stop the query and return. Relationship between prototype chains
Performance of object member Parsing
Like identifier parsing, object member parsing also has overhead. During the traversal of the prototype chain, each layer will increase performance loss, the deeper the object exists in the prototype chain, the slower it is to find it.
In addition, because the object Member may contain other Members, such as window. location. href: each time a vertex operator is encountered, this nested member will lead to the JavaScript engine to search for all object members. Obviously, the deeper the object member nesting, the slower the access speed. Therefore, try to use it as little as possible, for example, execute location. href is always better than window. location. href must be fast.
Obviously, it is better to cache object members with variables when they are frequently accessed.