The essentials and extensions of high-performance JavaScript (I.)

Source: Internet
Author: User
Tags script tag

A few days ago, I received the book "High-Performance JavaScript" from HTML5 China and intended to take it as a holiday pastime and write an article documenting some of the key points in the book.

Personally think this book is worth the low-level other front-end friends to read, there will be many unexpected gains.

Chapter I load and execute

Based on the logic of the UI single thread, the loading of a regular script blocks subsequent page scripts and even the loading of the DOM. The following code will error:

<!DOCTYPE HTML><HTMLLang= "en"><Head>  <MetaCharSet= "UTF-8">  <title></title>  <Scriptsrc= "Http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></Script></Head><Body>  <Script>Console.log ($); Document.queryselector ('Div'). InnerText='Happy mid-autumn'; </Script>  <Div>9999999999999</Div></Body></HTML>

The reason is that after the div is placed in the script, it has not yet been parsed by the page to execute the script (which is, of course, the most basic knowledge point).

It is mentioned in the book that using the Defer property can delay the script until the DOM loading is complete before execution.

We usually like to put the script at the end of the page, and wrap the domcontentloaded event, in fact, only need to add defer attribute to the script tag is simpler and better than the former (as long as there is no compatibility problem), after all, even The event bindings for domcontentloaded are bypassed first.

The book does not mention the Async attribute, and its load execution does not affect the loading of the page, compared with defer, it does not wait until the DOM is loaded to execute, but the script itself is loaded to execute (but the execution is asynchronous, does not block the page, the script and DOM loading completed without an absolute order).

Chapter II Data storage

This chapter mentions the scope chain at the outset, telling the reader that "for a browser, the deeper the identifier (variable) is, the slower it will read and write (the higher the performance cost)".

We know a lot of libraries like this. Encapsulation:

(function(win, doc, undefined) {  //  TODO}) (window, document, undefined) 

To form a local scope in the form of Iife, one of the advantages of this approach is of course to avoid the variables that pollute the global scope, but notice that we also pass the top-level scope objects, such as window, document, and undefined, into the scope of the seal. This allows the browser to retrieve only when the layer scope correctly obtains the corresponding top-level object, reducing the performance cost of the layer-up retrieval of objects, which is an important optimization point for a script library like JQuery, which calls global variables thousands of at a time.

We are routinely told to try to avoid using with to change the scope of the current function, the P22 page of this book describes the reason, here is a simple example:

function A () {   var foo = 123;     with (document) {       var bd = body;        + foo)   }}

Within the scope block of the with, the scope chain of the execution Environment (context) is pointed to document, so the browser can read the various properties of the document more quickly in the with code block (the scope chain object that the browser retrieves first becomes document).

But when we need to get the local variable foo, the browser will retrieve the document first, not retrieve the previous scope chain to retrieve function A to obtain the correct Foo, which will increase the browser to retrieve the scope object overhead.

The book mentions the processing of try-catch that also alters the scope of the chain, but I do not think it is very useful:

Try {    catch  (ex) {    handleError (ex)  // note here }

The book means that you want to use a separate method handleError in catch to handle errors and reduce access to local variables outside of Catch (the first level of scope within a catch code block becomes the ex scope layer).

Let's take an example:

    (function() {        var t = date.now ();         function HandleError (ex) {            alert (t + ': ' +ex.message)        }        try  {             //todo:sth        catch  (ex) {            handleError (ex);        }    }) ()

The reason I don't feel good is that when HandleError is executed, the first layer of its scope chain points to the execution environment within the HANDLEERROR code block, and the second layer's scope chain contains the variable T.

So when you retrieve T in HandleError, the browser is still flipping through a chain of scopes (of course, the speed of retrieving the layer will be faster than retrieving the ex layer, after all, ex has some extra properties by default).

The subsequent reference to the prototype chain is also a very important link, whether it is the book or "Senior" a book is very detailed introduction, this article does not repeat, but you can remember this:

the internal prototype of an object __proto__ always points to the prototype prototype of its constructed object, which is retrieved in the following order when the script engine reads the object properties:

object instance properties → object prototype→ object __proto__ point to the previous layer prototype→ .... → Top Floor (object.prototype)

To learn more about the prototype chain ecology, you can check out this article I've been collecting for a long time.

The idea of "avoiding multiple reads of the same object property" in the last chapter is also common in the JQ source code:

This approach can greatly reduce the size of the file when the script is finally built, and can improve the reading speed of these object properties, stone.

Chapter Three DOM programming

Many of the knowledge points mentioned in this chapter are examples of descriptions or extensions in other books. As in the opening of the WebKit Core Technology Insider (page 18th), it is mentioned that the JS engine is separate from the DOM engine, causing the script to access the DOM tree in a very expensive way; the JavaScript design pattern Also mentioned in the Book of High-volume DOM node operation should be done throttling to reduce performance costs, interested friends can buy these two books to see.

This chapter recommends using the native Dom method of Document.queryselectorall to get a list of elements in the selector API, mentioning a very important point of knowledge-returning only one NodeList rather than the HTML collection, so these The returned set of nodes does not correspond to the real-time document structure , which can be used with confidence when traversing nodes.

This chapter Reflow redraw introduction can refer to Nanyi Teacher's "Web page performance management detailed" article, this chapter many points mentioned in the article is also mentioned in Mr. Ruan.

One thing we need to keep in mind is that when we call the following properties/methods, the browser "has to" flush the render queue and trigger the Reflow to return the correct value:

Offsettop/offsetleft/offsetwidth/offsetheightscrolltop/scrollleft/scrollwidth/scrollheightclienttop/clientleft /clientwidth/clientheightgetcomputedstyle ()

Therefore, if some calculations require frequent access to these offset values, it is recommended that you cache them in a variable, and the next time you read directly from the variable, you can effectively reduce the redundant reflow redraw.

In this chapter, when we introduce the bulk modification of the DOM to reduce reflow redrawing, there are three scenarios for leaving elements out of the document flow, which are worth documenting:

Scheme ⑴: Hide the Element (Display:none) First, batch processing finished and then displayed (for most cases);

Scenario ⑵: Create a document fragment (Document.createdocumentfragment), Save the batch new node to the document fragment and then insert it into the node to be modified (performance is optimal, applicable to the new node situation);

Scenario ⑶: Clone the node to be modified by CloneNode and replace the old node with the ReplaceChild method after modifying it.

Here is an extension, that is, Dom large-volume operation throttling, refers to when we need to do a large number of repeated DOM operations in a time unit, we should actively reduce the number of DOM manipulation processing.

For example, in the Hand Q Guild Hall home using the Iscroll, for the page scrolling can be real-time adsorption navigation bar, the approximate code is as follows:

    var New Iscroll ("wrapper",            {                onscrollmove:dealnavbar,                onscrollend:dealnavbar            }    );

The Dealnavbar method is used to process the navigation bar so that it remains adsorbed on top of the viewport.

This approach causes a very serious lag in page scrolling, because every time iscroll scrolls it performs a very multiple calculation of the Dealnavbar method (and of course we also need to get the scrolltop of the container to calculate the position of the navigation bar's adsorption, causing the re-plotting to continue, This is even more tragic).

There is a viable solution to this problem-throttling, which is willing to perform a dealnavbar in a certain time unit (such as 300ms) when the Iscroll container scrolls:

    varThrottle =function(FN, delay) {varTimer =NULL; return function () {            varContext = This, args =arguments;            Cleartimeout (timer); Timer= SetTimeout (function() {fn.apply (context, args);        }, delay);    };    }; varMyscroll =NewIscroll ("wrapper", {onScrollMove:throttle.bind ( This, Dealnavbar, 300)            }    );

Of course, this method will lead to the top of the navigation bar adsorption is not so real-time solid, will blink and look uncomfortable, the individual is inclined to only in the onscrollend to deal with it.

So when do you need to throttle it?

General in the event of frequent triggering of callbacks we recommend the use of throttling, such as Window.onscroll, Window.onresize, and other in the "design mode" in the book mentioned a scene-the need to insert a large number of content on the page, when it is inserted in one breath, it may be a few times the throttle (for example, up to 80 inserts per second) to complete the operation.

The fourth chapter algorithm and Process Control

This chapter mainly introduces some loops and iterations of the algorithm optimization, suitable for careful reading, feel there is no superfluous to explain or expand the place, but this chapter refers to the "Call stack/call stack", think of my interview encountered a and call stack related issues, here is a digression.

The original question was, if the call to a function went wrong, how do I know who called the function? Note Only debugging in Chrome is allowed, and code modification is not allowed.

The answer is simply to set a breakpoint on the called function, and then view the call Stack area information on the Sources tab:

In addition, the last mention of the memoization algorithm in this chapter, actually belongs to a proxy mode, each time the computation cache, the next time bypass the calculation directly to the cache, this is the optimization of performance is very helpful, this idea is not only used in the algorithm, such as in my The caching concept is used in the Smartcomplete component, which caches the response data from the server each time, and the next time the same request parameter responds directly from the cache, reducing redundant server requests and speeding up response times.

Chapter fifth string and regular expressions

The method of "adding content through a loop to the end of a string" to build the final string is poorly performing in some browsers, and it is recommended to use arrays in these browsers to build strings.

It is important to note that in mainstream browsers, the form of adding content to the end of a string by looping has been greatly optimized, and performance is better than the form of array-building strings.

Then the article mentions the string construction principle is worth knowing:

var str = ""// does not produce a temporary string str + = "B" + "C";  // generated a temporary string! / **/= "D" + str + "E"  // generates a temporary string! 

The creation of a "temporary string" affects the performance of the string build process, increases the memory overhead, and whether the "temporary string" is allocated or "basic string", if the "base string" is the string variable itself (the stack memory has allocated space for it), Then the process of string building does not produce extra "temporary strings", which improves performance.

For example, let's look at the "basic string" for each line:

var str = ""+ = "a";  // "Basic string" is strstr + = "B" + "C";  // " Basic string" is "B" /*  */= "D" + str + "E"  //"base string" is "D"

In the last line, for example, the browser allocates a temporary memory to hold the temporary string "B", then copies the value of STR, "E" to the right of "B" from left to right (the browser will also attempt to allocate more memory to the underlying string in order to extend the content).

As for the "bad build string in some browsers" mentioned earlier, we can look at how the "Senior Year" book (P33) describes the "bad" reason:

var // Open a space in memory to store "Java"lang = lang + "script";  // Create a space that can hold up to 10 characters, // Copy the string "Java" and "script" (note that both strings also open up memory space) to this space, // then destroy the original "Java" and "script" strings

Let's continue to extend a basic point--how is the string method called?

We know that a string is a primitive type, and it is not an object why can we call string property methods such as Concat, substring, and so on?

Do not forget that all things are objects, in the previous we mentioned the prototype chain also mentions that the topmost layer is object.prototype, and each string, in fact, belong to a wrapper object.

We analyze the following example, what happens throughout the process:

var s1 = "some text"; var s2 = s1.substring (2= "Red"; alert (s1.color);

Every time the S1 property method is called, the background always does one thing silently before this-executes the s1=new string (' some text ') , which allows us to invoke the properties of the string object along the prototype chain (for example, the second row calls the SUBSTRING).

After the call is complete, the background silently destroys the previously created wrapper object. This causes the object to be destroyed immediately after the third row we have added a property color to the wrapper object, and the last line again creates the wrapper object without the color attribute, thus alert undefined.

This is described in the book "Senior Year":

The main difference between a reference type and a basic wrapper type is the lifetime of the object. An instance of a reference type created with the new operator is kept in memory until the execution flow leaves the current scope. Objects that are automatically created by the basic wrapper type exist only in the execution of a single line of code and are immediately destroyed. This means that we cannot add properties and methods to the base type values at run time. ”

The regular section mentions "backtracking", which is described in Wikipedia:

The backtracking method uses the thought of trial and error, and it tries to solve a problem by step. When it comes to solving a problem in steps, when it tries to discover that the existing step answer is not valid, it will cancel the previous or even previous steps, and try again to find the answer to the question again through the other possible steps. Backtracking is usually done with the simplest recursive method, and there are two possible scenarios after repeating the steps above:1. Find the right answer that may exist 2. After trying all possible step methods to announce that the problem has no answer in the worst case, Backtracking results in an exponential time calculation of the complexity. 

General we should minimize regular backtracking to improve matching performance:

var str = "<p>123</p><p>456</p>"; var r1 =/<p>.*<\/p>/i.test (str);  // greedy matching can lead to more backtracking var r2 =/<p>.*? <\/p>/i.test (str);   // recommended, lazy matching reduces backtracking

For the part of the book suggesting the optimization of regular matching, I have summed up some of the more important points and supplemented the corresponding examples:

1. Let the match fail faster end

The most time-consuming part of a regular match is often not a match, but a match failure, which can effectively reduce the matching time if the process of matching failure is terminated earlier:

var str = ' eabc21323ab213 ',    =/\bab/.test (str),   // matching failed process longer    r2 =/^ Ab/.test (str);    // The process of matching failures is short

2. Reduced conditional branching + materialized quantifiers

The former refers to avoiding conditional branching as much as possible, such as (. | \r|\n) can be replaced by equivalent [\s\s];

The quantifier is embodied in order to make the regular more accurate match to the content, such as the use of specific characters to replace the abstract quantifier.

Both of these methods can effectively reduce backtracking. Here's an example:

var str = ' cat 1990 ';  // 19XX years of birth of a cat or a bat var r1 =/(Cat|bat) \s\d{4}/.test (str);  // Not recommended var r1 =/[bc]at\s19\d{2}/.test (str);  // Recommended

3. Using non-capturing groups

Capturing a group consumes time and memory to record a reverse reference, so when we don't need a reverse reference, we can avoid these costs by using a non-capturing group:

var str = ' teacher Vajoy '; var r1 =/(Teacher|student) \s (\w+)/.exec (str) [2];  // Not recommended var r2 =/(?: teacher|student) \s (\w+)/.exec (str) [1];  // Recommended

4. Capture only the content of interest to reduce post-processing

Many times you can use a group to directly get the parts we need to reduce the subsequent processing:

var str = ' He says ' I do like the book "'; var r1 = Str.match (/"[^"]* "/). ToString (). Replace (/"/g, ");  // Not recommended var r2 = str.replace (/^.*? ") ([^ "]*)"/, ' $ ');  // Recommended var r3 =/"([^"]*) "/.exec (str) [1];  // Recommended

5. Complex expressions can be properly disassembled

There may be a misconception that it is always better to match the results in a single regular expression than to match multiple points.

This chapter tells readers to "avoid handling too many tasks in a regular expression." Complex search problems require conditional logic, and splitting into two or more regular expressions is easier to solve and often more efficient. "

Here is not a complex example, directly using the book to remove the string two examples of the first and last blank:

// trim1 function () {  returnthis. replace (/^\s+/, ""). Replace (/\s+$/, "")}//  TRIM2function() {  returnthis. replace (/^\s+|\s+$/, "") }

In fact, trim2 is slower than trim1, because TRIM1 simply retrieves the original string once and retrieves the string that removes the whitespace from the head again. and trim2 needs to retrieve the original string two times.

It is also necessary to disassemble the usual complex regular expressions with many conditional branches, which are mainly due to the backtracking problems caused by conditional branching.

Of course, if the conditional branch is removed, a single regular match result is a preferred choice, for example, the proposed scheme for trim in the book is:

function () {  returnthis. replace (/^\s* ([\s\s]*\s)? \s*$/, "$")}

The first half of the book is summed up here, mutual encouragement ~

The essentials and extensions of high-performance JavaScript (I.)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.