There are several stages of loading, execution, rendering, and refactoring, from establishing an HTTP connection to displaying the page in the browser. I’m going to share my own experience and the best experiences of others.
Load and execute
The browser is a friendly client, and there is a limit to the number of concurrent requests to the same domain name. In the past, the browser is generally 2, and H5 support is generally 6. And the server can close the request. Some friends do not understand, why not the more the better? For example: millions of PV, the number of concurrent will cause what consequences? Thus, all optimizations extend from this point and single thread. Therefore, front-end resource loading optimization has two directions
- Open source domain name increase since the same domain name can not be too much, so many domain names; Simply speaking is CDN, can be a third party, you can also get a few secondary domain name
- Throttle resource compression, load files in the same domain name as needed to fully compress, for example: originally 2M resources, if compressed to less than 1M (excluding Spaces, gzip, etc.) speed is increased by 50%; Furthermore, spa now compresses and packages files after merging them. If the files are not large in total, performance will not be greatly affected. Once more UI libraries or third-party plug-ins are introduced during development, the total file size is not small; There you have it: load on demand, delay load. For example, adding a single CSS or JS from the template HTML at webPack time; There are also webpack-HTTP-require libraries. Of course, the image also needs to do a lot of processing
- CSS implementation effects (buttons, shadows, etc.)
- Compress dimensions and size
- Sprite merger
- SVG and toff font diagram
- Canvas drawing big picture (map related)
Obstructive optimization
Should the js file be executed immediately after loading? Does immediate execution affect page rendering? In the past, browsers blocked when loading and executing JS files, that is, one by one according to the stack principle; So, the original requirement is to put js files at the bottom of the HTML code, modern browsers to some extent solve the problem of parallel loading, can also be preloaded, but will the page be rearranged after execution? So be flexible application DNS – prefetch, preload and defer | async, defer and async, of course, not all browsers to take effect, its core is not effective.
<! DOCTYPE html> <html> <head> <meta charset="utf-8">
<title>Demo</title>
<link rel="dns-prefetch" href="//cdn.com/">
<link rel="preload" href="//js.cdn.com/currentPage-part1.js" as="script">
<link rel="preload" href="//js.cdn.com/currentPage-part2.js" as="script">
<link rel="preload" href="//js.cdn.com/currentPage-part3.js" as="script">
<link rel="prefetch" href="//js.cdn.com/prefetch.js"> </head> <body> <! -- html code --> <scripttype="text/javascript" src="//js.cdn.com/currentPage-part1.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part2.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part3.js" defer></script>
</body>
</html>
Copy the code
Js execution optimization
- Scope optimization, variable level should not be too deep or too much nested, preferably this level; When you look at frameworks or libraries, you often see this:
(function(w,d){})(window,document)function check(){
var d = document, t = document.getElementById('t'), l = t.children;
for(leti=0; i<l; i++){ //code } }Copy the code
- Loop Optimization loop is the most common structure in programming, and optimization loop is an important part of performance optimization process. The basic optimization steps for a loop are as follows:
Devalued iterations — Most loops use an iterator that starts at 0 and increments to a specific value. In many cases, an iterator that starts at the maximum value and continues to decrement through the loop is more efficient. Simplify the termination condition — Since the termination condition is evaluated during each loop, it must be as fast as possible, avoiding attribute lookups or other O(n) operations. Simplify the loop body – The loop body is the one that executes the most, so make sure it is optimized to the maximum. Make sure there are no dense computations that can be easily moved out of the loop. Use post-test loops – The most common for and while loops are pre-test loops, while loops such as do-while can avoid the calculation of the initial termination condition because it is faster.
for(var i = 0; i < values.length; i++) {
process(values[i]);
}
Copy the code
Optimization 1: Simplify termination conditions
for(var i = 0, len = values.length; i < len; i++) {
process(values[i]);
}
Copy the code
Optimization 2: Use a post-test loop (note: Use a post-test loop to make sure there is at least one value to process)
var i values.length - 1;
if(i > -1) {
do {
process(values[i]);
}while(--i >= 0);
}
Copy the code
- Loop unrolling
When the number of loops is uncertain, the Duff Service can be used to optimize. The basic concept is to expand a loop into a series of statements by calculating whether the number of iterations is a multiple of 8. As follows:
// Jeff Greenberg for JS implementation of DuffValue. Length > 0 function process(v) {alert(v); } the var values =,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 [1]; var iterations = Math.ceil(values.length / 8); var startAt = values.length % 8; var i = 0; do { switch(startAt) { case 0 : process(values[i++]); case 7 : process(values[i++]); case 6 : process(values[i++]); case 5 : process(values[i++]); case 4 : process(values[i++]); case 3 : process(values[i++]); case 2 : process(values[i++]); case 1 : process(values[i++]); } startAt = 0; }while(--iterations > 0);Copy the code
Expanding the loop above can improve the processing speed of large data sets. Next, a faster Duff device technique is presented to split the do-while loop into two separate loops. (Note: This approach is almost 40% faster than the original Duff device implementation.)
// Speed Up Your Site(New Riders, 2003)
functionprocess(v) { alert(v); } the var values =,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17 [1]; var iterations = Math.floor(values.length / 8); var leftover = values.length % 8; var i = 0;if(leftover > 0) {
do {
process(values[i++]);
}while(--leftover > 0);
}
do {
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
}while(--iterations > 0);
Copy the code
Using unrolled loops for large data sets can save a lot of time, but for small data sets the extra overhead may not be worth it.
- Avoid double interpretation
There is a double interpretation penalty when JS code attempts to parse JS code, which occurs when using the eval() or Function constructor and when using setTimeout() to pass a string. The following
eval("alert('hello world');"); Var sayHi = new Function("alert('hello world');"); / / to avoidsetTimeout("alert('hello world');", 100); / / to avoidCopy the code
The above code is contained in the string, that is, the JS code must be run at the same time a new parser to parse the new code. The overhead of instantiating a new parser is not negligible, so this code is slower than direct parsing. Here are a few examples that should be avoided except in rare cases where eval is necessary. For the Function constructor, just write it as a normal Function. For setTimeout you can pass in a function as the first argument. As follows:
alert('hello world');
var sayHi = function() {
alert('hello world');
};
setTimeout(function() {
alert('hello world');
}, 100);
Copy the code
In summary, to improve code performance, avoid code that needs to be interpreted in JS whenever possible.
-
Other performance considerations Native methods are faster – whenever possible, use native methods instead of rewriting them in JS yourself. Native methods are written in compiled languages such as C/C++ and are much faster than JS. Many people think that custom sorting is faster than Sortby, and that fact comparison is better than the native method. Switch statements are faster — if you have a series of complex if-else statements, you can get faster code by converting them into a single switch statement, and you can further optimize by organizing case statements in the most likely to least likely order. Bitwise faster – When performing math operations, bitwise operations are faster than any Boolean or arithmetic operations. Selective substitution of bitwise operations for arithmetic operations can greatly improve the performance of complex computations such as modulo, logic and sum logic or consider bitwise operations for substitution.
-
Minimizing the number of statements the number of statements in your JS code also affects the speed of the operations performed; a single statement that does multiple operations is faster than multiple blocks of statements that do a single operation. Look for statements that can be grouped together to reduce the overall execution time. Here are several patterns
1. Multiple variable declarations
// Avoid var I = 1; var j ="hello"; Var arr = [1, 2, 3]; var now = new Date(); Var I = 1, j = 1"hello",
arr = [1,2,3],
now = new Date();
Copy the code
2. Insert the iteration value
Var name = values[I]; i++; Var name = values[i++];Copy the code
3. Use Array and Object literals, avoid using constructors Array(),Object()
Var a = new Array(); a[0] = 1; a[1] ="hello";
a[2] = 45;
var o = new Obejct();
o.name = "bill"; o.age = 13; Var a = [1,"hello", 45];
var o = {
name : "bill",
age : 13
};
Copy the code
DOM is definitely the slowest part of JS. DOM manipulation and interaction can take a lot of time because they often require the re-rendering of an entire page or part of a script, so understanding how to optimize the interaction with DOM can greatly improve the speed of script completion. This will be explained later
An array of storage
There is a classic problem in computer science: optimal read and write performance is achieved by changing the location of data storage, which is related to the speed at which data can be retrieved during code execution. In JS this problem is relatively simple because there are only four options.
- Literals literals represent themselves and are not stored in a specific location. JS literals include strings, numbers, booleans, objects, arrays, functions, regular expressions, and the special null, undefined
- Local variables use data storage units defined by VAR
- Array elements are stored inside the JS object, indexed by numbers
- Object members are stored inside JS objects, indexed by strings. Each data storage location has different read and write costs. In most cases the same, arrays and objects are slightly more expensive, depending on the performance of the browser and the JS interpreter. Use literals and local variables to minimize the use of array items and object members.
scope
Understanding the concept of scope is a core key to JS and functionality, not just from a performance perspective. Simply put: the range in effect, which variables can be accessed by functions, the assignment of this, the context conversion. You can’t talk about scope without going around the scope chain. You understand scope chains and identifiers.
Scope chain and identifier resolution
Each Function is an instance of a Function object, which, like any other object, has properties that can be accessed programmatically, as well as a set of internal properties that cannot be accessed by code but are accessed only by the JS engine. One of the internal attributes is [[Scope]], as defined by ecMA-262, 3rd edition
The inner attribute [[Scope]] contains the collection of objects in the Scope in which the function was created. This set is called the function’s scope chain, and it determines which data can be accessed by the function. Each object in the function’s scope is called a mutable object, and each mutable object exists in the form of a key-value pair. When a function is created, its scope chain is populated with data objects accessible from the scope in which the function was created. Such as:
function fn(a,b){
return res = a*b;
}
Copy the code
When fn is created, an object variable is inserted into its scope chain. This global object represents all variables defined in the global scope. This global object contains window, Navigator, Document, and so on. The scope is used when fn executes and creates the execution environment, also known as the execution context. It defines the environment in which a function is executed. Even if the same function is executed, a new environment is created each time, and the environment is destroyed after the function is executed. Each environment resolves parameters and variables according to scope and scope chain. A Scope chain can be thought of as a stack, with the top being the current active object (the set of objects in the function [[Scope]] property when the environment was created) or, in most cases, as a local variable defined inside the function.
Closures, on the other hand, allow functions to access data outside the local scope based on JS, although this can cause performance problems because the execution environment destroys the activated object, so variables can be cached instead of global objects. apply
object
Properties and methods, both members of objects, refer to functions as methods, and non-functions as properties. Why is object access slow? Because of the prototype chain.
Prototype and prototype chain
Look directly at the code
function fun(name,age){
this.name = name+' ';
this.age = age
}
fun.prototype.getName = function() {return this.name;
}
var fn = new fun();
true = (fn instanceof fun) //true
true__proto__ = null hasOwnProperty = (fn instanceof Object) __proto__ = fun.prototypefunction)
isPrototypeOf = (function)
propertyIsEnumerable = (function)
toLocaleString = (function)
toString = (function)
valueOf = (function) * /Copy the code
The same goes for ordinary variables until there is no such variable or attribute or method at the root (window).
DOM programming
DOM manipulation is expensive, which is the most common performance bottleneck of Web Applications. The Document Oject Module(DOM) is a language-independent programming interface for manipulating XML and HTML documents, and is implemented in browsers through JS. Browser rendering and JS interpretation engines vary from company to company. V8, as we all know, is a JS engine. But Chrome renders WebCore. Each browser has two sets of interpreters and is relatively independent. This means that every operation requires (V8<=>WebCore)==>Browser. Both interpreters require connection and communication costs. Reducing communication between the two interpreters and reducing the frequency of page changes is the direction of optimization.
Redraw repaint and rearrange reflow
Each node in the DOM tree that needs to be displayed has at least one corresponding node in the rendering tree, but the DOM element hidden (display: None) does not. The render tree nodes are called frames and boxes. Once the DOM and render tree are built, the browser begins to paint the page elements.
When does heavy painting happen? When the geometry of a page changes and the existing document flow is affected, the page layout needs to be rearranged. A few examples:
- Add or remove visible DOM elements;
- DOM element position changes;
- DOM element size changes: container padding, border, margin, etc.
- Changes in the contents of the container lead to changes in width and height: more (less) lines of text, image collapse, image replacement with another larger image
- After the browser window is initialized and resized, it needs to be redrawn. Therefore, the rearrangement should be avoided as much as possible. In order to avoid or minimize redrawing and rearrangement, some variables should be accessed as little as possible:
ScrollTop, scrollLeft, scrollWidth, scrollHeight ClientTop, clientLeft, clientWidth, clientHeight getComputedStyle() (currentStylein IE)
function scroller(){
var H = document.body.offsetHeight || scrollHeight
return function(){
var args = arguments,ct = this;
// your code
}
}
Copy the code
In order to minimize and minimize the impact of redraw and rearrangement, DOM changes should be minimized and properties that affect rearrangement accessed. If you must make changes, try to follow three steps: 1. Element out of the document stream 2. Apply multiple changes at once 3. The first and third steps of restoring to the document flow are rearranged, so the core is still step 2. Now virtual DOM big 🔥, we know a little about the basic approach. There are several ways to update once: String or an array. Join (“) innerHTML, last the appendChild createElement method, document. CreateDocumentFragment, cloneNode node to the cache node needs to change, change after the replacement. Also, animation needs to be redrawn and rearranged as little as possible, for example, moving diagonally from the top left to the bottom right
function move2RB(){
var dom = document.getElementById('id'),curent = dom.style.top;
while(curent<500){
curent++
dom.style.cssText = 'left:'+curent+'px; top:'+curent+'px'; Left = (dom.style.left)+1)+ (dom.style.left)+ (dom.style.left,10)+1)+'px'And this way, you can just change the className.Copy the code
It can be summarized in a few words: less access to the DOM, processing in JS after the calculation of one-time modification, use caching and native API; Use the existing three frameworks (angular, react, vue) and you don’t have to worry about that 🙂
Algorithm and process control
The overall structure of the code is one of the main factors affecting speed, and the number is not necessarily proportional to speed. Organizational structure, ideas and execution efficiency are the core!! JS belongs to the category of ECMA and is a scripting language. Many process control and engineering ideas are borrowed from Java, C and other languages. Therefore, knowing the coding and engineering of back-end languages is helpful for us to deepen our understanding.
The Loop cycle
- for
for(var i=0; i<10; i++){ // code }Copy the code
Reverse order can improve a little efficiency in large data volume, I <obj. Length; I — 2. While preloop
var i=0;
while(i<10){
// code
i++;
}
Copy the code
The rear cycle
var i=0;
do{
// code
}while(i++<10)
Copy the code
- for – in
for(var prop in object){
// code
}
Copy the code
Except for the for-in loop, there are only two points where you can improve efficiency
- Transactions processed per iteration
- Number of iterations The number of iterations in an array loop is as follows: 1. Find an attribute array.length in the control condition 2. Perform a numeric comparison in the control condition (I
conditional
The higher the number of if-else vs switch conditions, the higher the iteration efficiency of switch; Legibility is better when there are only two choices or simply if-else. In actual coding, if you only have a choice, in some cases can even without the if – else, USES three unary: result = (true | | false)? condition0:condition1; In addition, the most likely conditions should be written into “if ()” to reduce the number of judgments, and by extension, the possibility of if-elseif judgment should be from large to small. You can even use dichotomy:
//-- if the value of a parameter is either positive or negative, or query the binary tree, or query the mobile phone number of different SPif(parse>0){
if(parse>10){
//code
}else if(parse<5&&parse>1){
//code
}else{}}else{//code negative processing}Copy the code
Of course, this is a simple chestnut, but there are many other ways to introduce algorithms into your code to improve efficiency, such as the day of the week output
function getweek(){
var w = ['day'.'一'.'二'.'三'.'four'.'five'.'å…'],
now = new Date(),
d = now.getDay();
return 'week'+w[d];
}
Copy the code
You can store strings, variables, methods in arrays or objects. Because it’s a quote, it’s also very fast
recursive
1. The recursion
/ / - factorialfunction facttail(n){
if(n==0){
return 1;
}else{
returnn*facttail(n-1); }} //-- powerfunction fn(s,n){
if(n==0){
return 1;
}else{
returns*fn(s,n-1); }}Copy the code
But recursion if the end condition is not clear can result in running all the time, the page does not respond for a long time. In suspended animation!! Also, each browser’s “call stack” is capped. You can experiment on your own if you’re interested. To avoid this problem, in addition to clear end conditions, we can also use “tail recursion” 2. Tail recursion
/ / - factorialfunction facttail(n,res){
if(n<0){
return 0;
}else if(n==0){
return 1;
}else if(n==1){
return res;
}else{
returnfacttail(n-1, n*res); }} //-- powerfunction fn(s,n){
if(n==0){
return 1;
}else{
returns*fn(s,n-1); }}Copy the code
Cache memory
Closures allow a method to store computed data or variables internally, such as factorial function overrides
function memfacttail(n){
if(! memfacttail.cache){ memfacttail.cache = {"0": 1,"1":1
};
}
if(! memfacttail.cache.hasOwnProperty(n)){ memfacttail.cache.n = n * memfacttail(n-1); }return memfacttail.cache.n;
}
Copy the code
Strings and re
*? + This part also needs quite a long space.
Responsive pages
Cliche content, if let the page seconds open; What are the points that can be optimized? Server rendering, home page optimization, component lazy loading, Bigpipe, performance monitoring and targeted optimization, etc.
Browser thread
First dig a hole, I will share a little of their own experience in a special article
event loop
Ruan Yifeng teacher said it better, please go to This link
Web Workers
The Worker Pool API was proposed from The Google Gears plugin, which was the “prototype” of Web Workers. It was originally intended to enhance browser features such as offline browsing (offline access to cached pages and offline actions submitted after relaunching), but (2015/11) has been deprecated. HTML5 began to separate the Web Workers API into a separate specification. From there, we can put computation, codec, true asynchronous requests, and so on into Web Workers.
- Self also provides a series of interfaces, including self.JSON, self.Math, self.console, etc. The most obvious difference is that the document object is missing. Location (readonly), navigator still exists; So DOM access doesn’t exist either. It can only be enabled by creating a separate JS file and calling it as follows
Var worker = new worker ('worker.js') // or from the main page of the JS file, such as main.js //-- the main pageif (window.Worker) {
var worker = new Worker('worker.js');
var data = {a: 1, b: [1, 2, 3], c: 'string'};
worker.postMessage(data);
worker.onmessage = function(e) {
console.log('main thread received data'); console.log(e.data); // Terminate worker.terminate(); // Terminate worker.terminate(); // Terminate does not receive subsequent messages, but post does not report errors // worker.postmessage (1); } worker.onerror =function(err) {
console.log('main thread received err'); console.log(err.message); // Prevent err.preventdefault (); }}Copy the code
worker.js
// importScripts()'lib.js');
// importScripts('a.js'.'b.js');
onmessage = function(e) { console.log(self); Var data = e.data; console.log('worker received data');
console.log(data);
var res = data;
res.resolved = true;
postMessage(res);
setTimeout(function() {
throw new Error('error occurs'); // terminate() // close(); }, 100); };Copy the code
- The communication main thread and worker thread send and receive messages in the same way (postMessage sends messages, onMessage/onError receives messages, and data is taken from the data attribute of MessageEvent). PS: value copy is passed between threads, rather than shared reference
- ImportScripts can import other JS files. Global variables in the external file will be pasted onto self, which can be referenced directly in worker. ImportScripts are synchronized and the next line is executed after downloading and executing, so you need to be aware of obstructive issues. Application scope:
- Audio/video decoding If tried audioContext decodeAudioData operation, such as we urgently need a can “heavy work” background thread
- Image preprocessing, such as image cropping before uploading, or even adding watermarks, splicing and Mosaic, can avoid a lot of temporary file transfers if it can be done on the client
- Data processing algorithms such as sorting reduce the pressure on the server. In case of large data or methods that cannot be processed over 200ms, JSON objects with large data exceed the allowed time
//---main
var worker = new Worker('worker.js'EvaluateData (jsonData)} worker.postMessage (jsonText) {worker.postmessage(jsonText) {worker.postmessage(jsonText); //-- worker self.onmessage = (e){var jsonData = e.ata // main; var jsonData = json.parse (jsonText) // self.postMessage(jsonData) }Copy the code
- If the client template, such as Markdown, or the server returns JSON, and the client takes it and gives it to a background thread to parse and apply the template HTML to generate the page, the client does all of this, and there is less to transfer
- Shared workers must be cognate! Must be homologous! Must be homologous!
//---main
var sWorker = new SharedWorker('worker.js')
sWorker.port.start()
//---first
first.onchange = function() {
sWorker.port.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
//---first
second.onchange = function() {
sWorker.port.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
sWorker.port.onmessage = function(e) {
result1.textContent = e.data;
console.log('Message received from worker');
}
Copy the code
In this way the front end is built to “resemble” multithreading
tool
The debug tools list
- YUI
- Firebug
- IE
- Safari
- Chrome
Optimization tools, specific search, improper porters.
- page speed
- Fiddler
- YSlow
- dynaTrace