Mobile performance optimization

Introduction to mobile performance optimization

What is performance

  • Page response speed

Page response speed

  • Open the page to the actual use of the time

    • Network request time
    • Page load and render time
  • How smoothly you interact with the page

    • The execution speed of Javascript scripts

Load the first screen of a page faster, without thinking about the entire page

Why optimize performance on mobile

  • Improve user experience
  • Compared with the PC, the network speed of the mobile terminal is slow
  • The performance of mobile devices is lower than that of PCS

Learn what

  • Performance optimization points at each stage
  • Specific performance optimization policies

Optimization points in network request process

Chrome Dev Tool timeline represents the meaning of each stage

The amount of time before a request can be sent. Contains the time used to process the agent.

In addition, if there are already established connections, this time also includes the time it takes to wait for the established connections to be reused, which follows Chrome’s rule of up to 6 TCP connections from the same source.

Proxy Negotiation

  • The time of negotiation with the proxy server connection.

DNS Lookup

  • Time used to perform DNS lookup. Each new field on the page requires a full round-trip DNS lookup.

Initial Connection / Connecting

  • Time spent establishing links, including TCP and multi-attempt handshakes, and processing SSL.

SSL

  • Time when the SSL handshake is complete.

Request Sent / Sending

  • The time when the request was initiated

Waiting (TTFB)

  • Time To First Byte after a request is sent To the First Byte of the response received

Content Download / Downloading

  • Time used to download the response

Multiple resources are distributed on different domains to reduce the waiting time of the request queue

  • Browsers allocate a limited number of concurrent channels per domain name
  • Multiple domains mean more DNS query time, and it is usually appropriate to split domain names into three to five

Reduce the DNS query time by using dns-prefetch

  • Try resolving the domain name before requesting the resource

  • This is valid only for cross-domain DNS lookup
  • Do not add dns-prefetch to a resolved domain name

Reduce the number of HTTP requests

  • Merge resources (merge CSS, JS files)
  • Static resource caching
    • Static resources are suffixed with hash to calculate the hash based on the file content
    • If the file content does not change, the hash and URL will not change
    • If the URL and file are unchanged, the HTTP cache mechanism is automatically triggered and 304 is returned
  • Inline the first screen related code
  • Use caching (browser caching, localStorage, etc.)
  • Use CDN to make resources load faster
<! -- <link rel="stylesheet" href="./css/reset.css" />
<link rel="stylesheet" href="./css/base.css" />
<link rel="stylesheet" href="./css/index.css"/ > -- > <! -- <link rel="stylesheet" href="./css/index.css"/ > -- > <! --1. The combined resource cannot be too large --> <! --2<link rel="stylesheet" href="./ CSS /common.css" />
<link rel="stylesheet" href="./css/index.css" />
Copy the code
/ / use the < scriptsrc="https://cdn.bootcss.com/zepto/1.0rc1/zepto.min.js"></script>
Copy the code

Reduce the size of the requested resource

  • Resource compression (HTML, CSS compression and JS compression and obfuscation)
  • Enable Gzip compression
  • Reduce Cookie size

Using HTTP2

  • Multiplexing: The most valuable advantage is that it solves the problem of line header blocking and can send N requests in parallel in the browser.
  • Bow compression: smaller load volume.
  • New binary format: HTTP1. x is for text format transport and HTTP2 is for binary format transport.
  • Server push: The server can actively push resources to the client.

Optimization points during page loading and rendering

  • CSS is typically introduced in head
  • JavaScript is usually introduced at the end of the body

  • Reduce Reflow/Relayout and Repaint
    • When the size, position, hiding, and other attributes of an element change, the browsing needs to be recalculated, which is called backflow
    • When the appearance, style and other attributes of an element change, the browser only needs to redraw, which is called redraw
    • Backflow always causes redrawing, and redrawing does not necessarily cause backflow

Optimization points in JavaScript scripts

  • DOM manipulation optimization
  • Events to optimize
  • Lazy loading and preloading of images

Image optimization

Reduce the number of HTTP requests

  • Use CSS for drawing (animation) instead of simple images (www.webhek.com/post/40-css…)
  • Merge small ICONS (CSS Sprites)
  • Embed small ICONS in HTML (Base64 images)

Reduce the size of the requested resource

  • Use icon fonts instead of simple ICONS
  • The compressed image
  • Choose the right image size
  • Choose the right type of picture

Image type

  • jpg
    • Lossy compression, high compression rate, does not support transparency
    • Suitable for scenes with rich colors, gradients and no need for transparent images
  • png
    • Png-8 256 color + supports transparency
    • Png-24 2^24 color + does not support transparency
    • Png-32 2^24 color + support transparency
    • Suitable for most scenarios where transparent images are required
  • webp
    • Compared with PNG and JPG, the image is smaller under the same visual experience
    • Support lossy compression, lossless compression, transparency and animation
    • In theory, it can completely replace PNG, JPG, GIF image format
    • There are some compatibility issues

Animation optimization

  • CSS3 transitions and animations are preferred
  • Exercise with Translate3D is preferred
  • Use requestAnimationFrame when you must animate in JavaScript
<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Animation optimization</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      .mask {
        position: fixed;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0.0.0.0.5);
        opacity: 1;
        0.5 s / * transition: opacity; * /
      }
    </style>
  </head>
  <body>
    <div id="mask" class="mask"></div>

    <script>
   		 // Use Js with CSS3 to animate the disappearing element
        const $mask = document.getElementById('mask');



        $mask.addEventListener(
            'click'.function () {
                $mask.style.opacity = 0;
            },
            false
        );
        $mask.addEventListener(
            'transitionend'.function () {
                $mask.style.display = 'none';
            },
            false
        );
    </script>
  </body>
</html>

Copy the code
  • Use JS requestAnimationFrame to do this
    • window.requestAnimationFrame()You need to pass in as an argument a callback function that will be executed once before the browser’s next redraw.
$mask.addEventListener(
    "click".function () {
        // setTimeout(fadeOut, 20);
        requestAnimationFrame(fadeOut);
    },
    false
);

let opacity = 1;

function fadeOut() {
    opacity -= 0.05;

    if (opacity <= 0) {
        opacity = 0;
        $mask.style.display = "none";
    } else {
        requestAnimationFrame(fadeOut);
    }
    $mask.style.opacity = opacity;
}
Copy the code

CSS optimization

Selector optimization

  • Don’t use too many nested and complex selectors. Keep it simple and choose directly from styles. Don’t overdo it.
  • Avoid too many wildcard selectors
  • Removes unmatched styles
/* The worst way */
ul li a {
    text-decoration: none;
}
ul.list li.list-item a.list-link {
    text-decoration: none;
} 
/* A better way */
.list-link {
    text-decoration: none;
}

/* Avoid excessive wildcard selectors */
/* A small amount can */
* {
    padding: 0;
    margin: 0;
} 

/* 1.4. remove unmatched styles */
.list{}Copy the code

Other optimization

  • Extract common part
  • Avoid using CSs@import to import CSS
/* Extract the common part */
/* The worst way */
ol {
    padding: 0;
    margin: 0;
}
p {
    padding: 0;
    margin: 0;
}
/* A better way */
ol.p {
    padding: 0;
    margin: 0;
}

<syule>
/* Avoid using CSS @import to send extra HTTP requests (@import in less sass can be used because it does not send extra requests as opposed to copying code to the current page)*/
 @import "./reset.css";
</style>
Copy the code

Short for CSS color property value

/* not recommended */
.box{ color:# 000000; background-color:#ddeeff; }
Recommend * / / *
.box{ color:# 000; background-color:#def; }
Copy the code

Delete the CSS unit whose property value is 0

0 is 0, you don’t need any units, as long as the first number is 0, you can get rid of all the units.

/* not recommended */
.box{ margin:0px; padding:0px; }Recommend * / / *
.box{ margin:0; padding:0; }Copy the code

DOM optimization

Rendering optimization

  • Reduce the number of DOM elements and nesting levels

  • Try to avoid using a table layout and use other labels instead

  • The table is parsed as a whole, and only small changes can be displayed until the whole table is parsed, which may cause the whole table to be rearranged

    • table {
      	width: 100%;
      	border-collapse: collapse;
      }
      th,
      td {
      	border: 1px solid #ccc;
      	text-align: center;
      }
      
      <table>
          <tr>
              <th>The name</th>
              <th>age</th>
              <th>gender</th>
          </tr>
          <tr>
              <td>Zhang SAN</td>
              <td>18</td>
              <td>male</td>
          </tr>
          <tr>
              <td>Li si</td>
              <td>20</td>
              <td>female</td>
          </tr>
      </table>
      Copy the code

JS selector optimization

  • Ids are preferred for retrieving individual elements

    • console.log(document.getElementById('box')); / / recommend
      console.log(document.querySelector('#box')); / / do not recommend
      Copy the code
  • When you get multiple elements, try to get them directly from the className of the element itself

    • console.log(document.querySelectorAll('ul.list li.item')); / / do not recommend
      console.log(document.getElementsByClassName('item')); / / recommend
      console.log(document.querySelectorAll('.item')); / / recommend
      Copy the code

Reduce DOM operations

  • Always cache the result of a selector’s selection

    • // Always cache the result of the selector
      const $list = document.getElementById('list');
      Copy the code
  • Avoid using innerHTML multiple times in the loop. Use it once after the loop ends

    • const $list = document.getElementById('list');
      const todoDatas = ['Laundry'.'cooking'.'Write code'];
      // Error
      for (const item of todoDatas) {
          $list.innerHTML += `<li class="item">${item}</li>`;
      }
      
      The correct way to write it is to avoid using innerHTML multiple times in the loop, but only once after the loop is complete
      let html = ' ';
      for (const item of todoDatas) {
          html += `<li class="item">${item}</li>`;
      }
      $list.innerHTML = html;
      Copy the code
  • A newly created element that is added to the page after the necessary operations are completed

    • for (const item of todoDatas) {
          const $li = document.createElement("li");
      
          // The newly created element is added to the page after the necessary operations are completed
          $li.className = "item";
          $li.innerHTML = item;
          $li.style.color = "pink";
      
          $list.appendChild($li);
      }
      Copy the code
  • Multiple appendChild optimizations using DocumentFragment

    • // Optimize multiple appendChild with DocumentFragment
      const $liFragment = document.createDocumentFragment();
      
      for (const item of todoDatas) {
          const $li = document.createElement('li');
      
          $li.className = 'item';
          $li.innerHTML = item;
          $li.style.color = "pink";
      
          $liFragment.appendChild($li);
      }
      $list.appendChild($liFragment);
      Copy the code
  • Do not change the style of an element directly with JS. Change the style of an element by adding and removing classes

    • <style>
          .box {
              width: 100px;
              height: 100px;
              background-color: pink;
          }
          .active {
              width: 200px;
              height: 200px;
              background-color: green;
          }
      </style>
      
       const $box = document.getElementById("box");
       // let active = false;
      $box.addEventListener(
          "click".() = > {
              if(! active) { active =true;
                  // $box.style.width = '200px';
                  // $box.style.height = '200px';
                  // $box.style.backgroundColor = 'green';
      
                  $box.classList.add("active");
              } else {
                  active = false;
                  // $box.style.width = '100px';
                  // $box.style.height = '100px';
                  // $box.style.backgroundColor = 'pink';
                  $box.classList.remove("active");
              }
      
              $box.classList.toggle("active");
          },
          false
      );
      Copy the code
  • Attention to forced reflux

    • When the obtained attribute values include but are not limited to “global attributes” such as offsetTop, offsetLeft, scrollTop, and clientTop, the layout and style of other elements on the page need to be up to date, which will cause multiple reflows and redraws. Such an operation is called forced backflow
    • Gist.github.com/paulirish/5…
    • The results can be cached and updated as needed
<! DOCTYPEhtml>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <style>
            .backtop {
                position: fixed;
                right: 20px;
                bottom: 20px;
                width: 90px;
                height: 90px;
                line-height: 90px;
                text-align: center;
                background-color: rgba(0.0.0.0.6);
                border-radius: 50%;
                color: #fff;
                font-size: 60px;
                text-decoration: none;
                -webkit-tap-highlight-color: transparent;
            }
            .none {
                display: none;
            }

        </style>
    </head>
    <body style="height: 5000px;">
        <a href="#" id="backtop" class="backtop">&uarr;</a>

        <script>
            // Pay attention to forced backflow
            const $backtop = document.getElementById("backtop");
            // Recalculate when the cache window changes
            let winHeight = window.innerHeight;

            window.addEventListener(
                "resize".() = > {
                    winHeight = window.innerHeight;
                },
                false
            );

            window.addEventListener("scroll", scrollHandler, false);

            function scrollHandler() {
                // console.log('scroll');
                // It is not recommended that both attributes cause forced backflow
                // We can handle window.innerheight
					// if(document.documentElement.scrollTop > window.innerHeight)
                if (document.documentElement.scrollTop >= winHeight) {
                    $backtop.classList.remove("none");
                } else {
                    $backtop.classList.add("none"); }}</script>
    </body>
</html>
Copy the code

The event agent

What is an event broker implementation

What is event broker

  • Also called event delegate, delegates events that are listened for on child elements to the parent element, allowing the parent element listener to take advantage of event bubbling

Implementation of the event broker

<! DOCTYPEhtml>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>The event agent</title>
        <style>
            * {
                box-sizing: border-box;
            }
            body {
              background-color: #f5f5f5;
          }
          .input {
              width: 100%;
              height: 40px;
              border: 1px solid #ccc;
              margin-bottom: 20px;
              font-size: 20px;
          }
          .list {
              padding: 0;
              margin: 0;
            }
            .item {
                display: flex;
                justify-content: space-between;
                padding: 0 10px;
                margin-bottom: 10px;

                background-color: #fff;

                font-size: 40px;
            }

            .del {
                text-decoration: none;
            }
        </style>
    </head>
    <body>
        <input type="text" id="input" class="input" placeholder="Please enter your to-do list" />
        <ul class="list" id="list">
            <li class="item">Wash the clothes<a href="javascript:;" class="del">x</a></li>
            <li class="item">Cook a meal<a href="javascript:;" class="del">x</a></li>
            <li class="item">Write the code<a href="javascript:;" class="del">x</a></li>
        </ul>

        <script>


            const $input = document.getElementById('input');
            const $list = document.getElementById('list');

            // Use event delegate to parent element performance cost principle: bubble
            $list.addEventListener(
                'click'.evt= > {
                    // console.log('click');
                    // console.log(evt.target);
                    if (evt.target.classList.contains('del')) { $list.removeChild(evt.target.parentNode); }},false
            );

            $input.addEventListener(
                'keypress'.evt= > {
                    // console.log(evt);
                    if (evt.keyCode === 13) {
                        / / return
                        if(! $input.value)return;

                        const $item = document.createElement('li');
                        const $del = document.createElement('a');
                        $item.className = 'item';
                        $del.className = 'del';
                        $del.href = 'javascript:; ';

                        $item.innerHTML = $input.value;
                        $del.innerHTML = 'x';
								
                        
                        // Each creation consumes the performance of the a tag binding event function
                        // $del.addEventListener(
                        // 'click',
                        //   () => {
                        // $list.removeChild($item);
                        / /},
                        // false
                        // );

                        $item.appendChild($del);
                        $list.appendChild($item);

                        $input.value = ' '; }},false
            );
        </script>
    </body>
</html>
Copy the code

Event dilution

What is event dilution

  • Event dilution is reducing the frequency of events that fire multiple times over a period of time

Method of event dilution

  • Image stabilization

    • Execute the callback n seconds after the event is triggered, and if it is triggered again within n seconds, re-time (cast time)
  • The throttle

    • Only execute the function once in a while (cooldown)

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Event dilution</title>
    <style>
      .backtop {
        position: fixed;
        right: 20px;
        bottom: 20px;
        width: 90px;
        height: 90px;
        line-height: 90px;
        text-align: center;
        background-color: rgba(0.0.0.0.6);
        border-radius: 50%;
        color: #fff;
        font-size: 60px;
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
      }
      .none {
        display: none;
      }
    </style>
  </head>
  <body style="height: 2000px">
    <a href="#" id="backtop" class="backtop none">&uarr;</a>
		
    <script>
      // What event needs to be diluted
      // Scroll resize mousemove TouchMove etc
      // window.addEventListener('scroll', handler, false);
      // window.addEventListener('resize', handler, false);
      // window.addEventListener('mousemove', handler, false);
      // window.addEventListener('touchmove', handler, false);
      // function handler(evt) {
      // console.log(evt.type);
      // }

      // window.addEventListener('scroll', debounce(scrollHandler), false);
      window.addEventListener("scroll", throttle(scrollHandler), false);

      const $backtop = document.getElementById("backtop");
      let winHeight = window.innerHeight;

      window.addEventListener(
        "resize",
        debounce(() = > {
          winHeight = window.innerHeight;
          console.log(winHeight);
        }),
        false
      );

      function scrollHandler() {
        console.log("scroll");

        if (document.documentElement.scrollTop >= winHeight) {
          $backtop.classList.remove("none");
        } else {
          $backtop.classList.add("none"); }}/ / stabilization debounce
      function debounce(fn, miliseconds = 250, context) {
        let timer = null;
		  // call debounce to generate function a(name optional)
        return function (. args) {
          const self = context || this;

          if (timer) {
            clearTimeout(timer);
          }

          timer = setTimeout(() = > {
            fn.apply(self, args);
            timer = null;
          }, miliseconds);
        };
      }

      / / throttling throttle
      function throttle(fn, miliseconds = 250, context) {
        let lastEventTimestamp = null;
			
        return function (. args) {
           // Specify that this is either passed in or this of the current return function
          const self = context || this;
           // Record the current timestamp
          const now = Date.now();
			 // If the timestamp is false or the current time - previous time >= specified time, the statement execution code is entered
          if(! lastEventTimestamp || now - lastEventTimestamp >= miliseconds) {// Update the previous time value
            lastEventTimestamp = now;
             // Execute the incoming function binding this to pass in argsfn.apply(self, args); }}; }</script>
  </body>
</html>
Copy the code
// Simulate an Ajax request
function ajax(content) {
    console.log('ajax request ' + content)
}

let inputc = document.getElementById('throttle')

let throttleAjax = throttle(ajax, 1000)
// let debounceAjax = debounce(ajax, 500)

inputc.addEventListener('keyup'.e= > {
    throttleAjax(e.target.value)
    //debounceAjax(e.target.value)
})

Copy the code

Review the previous return to the top show and hide implementation

The scrollHandler function takes no arguments and does not use this, so when we call debounce, we pass only one argument.

Now let’s rewrite the scrollHandler function using arguments and this

We replaced the show and hide threshold with the parameter threshold and $backtop with this.

So we want to call debounce to pass threshold to scrollHandler and set this to $backtop.

Here we call debounce with a third argument, context, specifying that this in the scrollHandler is $backtop.

Debounce returns a function called a, which calls bind() and returns a function. The second argument to addEventListener requires a function, so calling bind is ok.

As the first argument to bind(), we can specify that function A points to this. Since we passed context ($backtop), we can pass null here.

The bind() method starts with the second argument and can pass arguments to function A, which args receives. Here we pass winHeight (the current viewport height) as the threshold.

So far, we have demonstrated the use of args and context. The throttling function is similar to the anti – shake function.

Lazy loading of images

What is picture lazy loading

  • Lazy image loading is also called lazy image loading (on demand)
  • Load images as needed
  • Better load the first screen of the page without considering the entire page

Lazy image loading implementation

<! DOCTYPEhtml>
<html lang="en">
  <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
      <title>Lazy loading of images</title>
      <style>
          * {
              padding: 0;
              margin: 0;
          }

          img {
              width: 100%;
              height: 183px;
          }
      </style>
    </head>
    <body>

    <div class="img-container">
      <img
        src="./images/loading.gif"
        alt=""
        data-src="./images/1.jpg"
        class="lazyload"
      />
    </div>
    <div class="img-container">
      <img
        src="./images/loading.gif"
        alt=""
        data-src="./images/2.jpg"
        class="lazyload"
      />
    </div>
    <div class="img-container">
      <img
        src="./images/loading.gif"
        alt=""
        data-src="./images/3.jpg"
        class="lazyload"
      />
    </div>
    <div class="img-container">
      <img
        src="./images/loading.gif"
        alt=""
        data-src="./images/4.jpg"
        class="lazyload"
      />
    </div>
    <div class="img-container">
      <img
        src="./images/loading.gif"
        alt=""
        data-src="./images/5.jpg"
        class="lazyload"
      />
    </div>

    <script>
      // The height of the image should be set
      const imgs = [...document.querySelectorAll(".lazyload")];

      lazyload();

      window.addEventListener('scroll', lazyload, false);
     


      function lazyload() {
        for (let i = 0; i < imgs.length; i++) {
          const $img = imgs[i];

          if (isInVisibleArea($img)) {
            $img.src = $img.dataset.src;
            imgs.splice(i, 1); i--; }}}// Whether the DOM element is visible
      function isInVisibleArea($el) {
        const rect = $el.getBoundingClientRect();
        // console.log(rect);

        return (
          rect.bottom > 0 &&
          rect.top < window.innerHeight &&
          rect.right > 0 &&
          rect.left < window.innerWidth
        );
      }
     
    </script>
  </body>
</html>

Copy the code
  • Add anti-shake and throttling to optimize performance

 window.addEventListener("scroll", debounce(lazyload), false);

/ / not applicable
// window.addEventListener('scroll', throttle(lazyload), false);

/ / stabilization debounce
// The event handler is executed only once during a certain time period
function debounce(fn, miliseconds = 250, context) {
    let timer = null;

    return function (. args) {
        const self = context || this;

        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(() = > {
            fn.apply(self, args);
            timer = null;
        }, miliseconds);
    };
}

/ / throttling throttle
// The event handler stops working for a certain period of time after it executes once
function throttle(fn, miliseconds = 250, context) {
    let lastEventTimestamp = null;

    return function (. args) {
        const self = context || this;
        const now = Date.now();

        if (!lastEventTimestamp || now - lastEventTimestamp >= miliseconds) {
            lastEventTimestamp = now;
            fn.apply(self, args);
        }
    };
}
Copy the code

Image preloading

What is image preloading

  • Preload images that may be used in the future

Image preloading implementation

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Image preloading</title>
    <style>
      .img-container {
        display: flex;
        align-items: center;
        height: 100vh;
        background-color: rgba(0.0.0.0.5);
      }
      img {
        width: 100%; {} *margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div class="img-container">
      <img src="./images/1.jpg" alt="Image" id="img" />
    </div>

    <script>

      const imgs = [
        './images/2.jpg'.'./images/3.jpg'.'./images/4.jpg'.'./images/5.jpg'
      ];
      let i = 0;
      const $img = document.getElementById('img');
		
      // The page starts with a call to preload to load the first element of the array
      preload(imgs[i])
        .then(data= > {})
        .catch(() = > {});

      // Click toggle
      $img.addEventListener(
        'click'.() = > {
          // When the index is less than array length
          if (i < imgs.length) {
            // Assign the SRC of the array element to the page element
            $img.src = imgs[i];
            // the next click of I +1 increments the second element of the array
            i++;
			   // When the index is less than array length
            if (i < imgs.length) {
              // Preload the next imagepreload(imgs[i]); }}else {
            // If the index is the same as the array length, there are no elements in the array
            console.log('It's the last one! '); }},false
      );

      / / preload
      function preload(src) {
        // Promise is wrapped
        return new Promise((resolve, reject) = > {
          // Create a new image label
          const image = new Image();
			
          // The image is successfully loaded
          image.addEventListener('load'.() = > resolve(image), false);
          // Image loading failed call failed state
          image.addEventListener('error'.() = > reject, false);
			// Assign the passed SRC value to the new image
          image.src = src;
        });
      }
    </script>
  </body>
</html>

Copy the code

Extension: Why is opening the page faster the second time

  • The first time the page is loaded, some data is cached, and later loads are fetched directly from the cache without requesting the server, so it is faster and less stressful for the server

  • The network cache is divided into three parts: DNS cache, HTTP cache (browser cache), and CDN cache

  • Local storage and offline storage can improve the loading speed of the first screen

DNS cache

  • When the page is displayed, the system performs DNS query, finds the IP address of the server corresponding to the domain name, and sends a request
  • DNS domain name lookup is performed on the client firstRecursive query

  • Finding at any step ends the lookup process, and the client only issues a query once during the whole process
  • If the forwarder configured on the DNS server is not configured, the forwarder is forwarded to the DNS server13Initiate a parsing request, as shown hereIterative queryAs shown in figure

13 root: There are 13 root domain server IP addresses in the world, not 13 servers! Because these IP mirrors can be set up around the world with anycast technology, access is not unique to one host

  • Obviously, multiple query requests are made throughout the process

  • After entering the page for the first time, the DNS resolved address record will be cached in the client, then at least there is no need to launch the subsequent iteration query, so that the speed is faster

HTTP cache

  • This means that the page resources obtained by HTTP requests are stored locally and then loaded directly from the cache instead of the request server, resulting in faster response. Look at the picture first:

Strong cache

On the first request, the server tells the browser the expiration date of the resource through the Expires and cache-control fields in the response header, and then asks the browser to evaluate the resource. If the resource does not expire, the server will directly use it without making a request to the server. This is a strong Cache

Expires

  • Used to specify the absolute expiration time of a resource, added to the response header when the server responds.
expires: Wed, 22 Nov 2021 08:41:00 GMT
Copy the code

Ps: If the time on the server is inconsistent with that on the browser, it may fail. For example, if the current time is August 1, the expires time is August 2, and the client changes the computer time to August 3, then the cache won’t be used

Cache-Control

  • Specify the resource expiration time in seconds, as follows, indicating that the resource will be available within 300 seconds after this request is returned correctly, otherwise it will expire
cache-control:max-age=300
Copy the code

Why do you need two fields to specify cache expiration time?

  • Because some browsers only recognize cache-Control and some don’t, they look for Expires if they don’t

The difference between Expires and Cache-Control

  • Expires isHTTP / 1.0Cache-control isHTTP / 1.1The;
  • Expires is meant to be compatible, not supportedHTTP / 1.1That would have an effect
  • Cache-control has a higher priority than Expires if both exist.

Cache-ControlRequest headerCommon properties

Ps: How many seconds is custom, I write here is easy to understand

Cache-ControlResponse headersCommon properties

Disadvantages of strong caching

  • After the cache expires, a request is made to retrieve the resource regardless of whether the resource has changed
  • What we want is to continue to use the old resource without renewing the resource file, even if it expires
  • So the negotiation cache it comes, in the case of strong cache expiration, then go through the negotiation cache process, determine whether the file has been updated
Negotiate the cache
  • The first time a resource is requested, the server returns the expiration date specified above to the browser and adds it to the response headerLast-ModifiedField that tells the browser when the resource was last modified
last-modified: Fri, 27 Oct 2021 08:35:57 GMT
Copy the code
  • The browser then passes the time through another field when it requests it againIf-Modified-Since, sent to the server
if-modified-since: Fri, 27 Oct 2021 08:35:57 GMT
Copy the code
  • The server then compares the two fields. If they are the same, it means that the file has not been updated, and returns the status code 304 and the empty response body to the browser. The browser simply takes the expired resource and continues to use it
  • If the comparison is different, the resource is updated, and the status code 200 and the new resource are returned

So last-modified/if-modified-since are paired to compare file modification times

disadvantages

  • If the cache file is opened locally, it will cause a problem even if the file is not modifiedLast-ModifiedThe server cannot match the cache and sends the same resource
  • becauseLast-ModifiedIt can only be timed in seconds. If a file is modified in an imperceptible period of time, the server will consider it a hit and cannot return the correct resource
  • If the resource is periodically changed, for example, after a resource is modified, it is changed back to its original appearance within a period, we think that the cache before this period can be used, butLast-ModifiedDon’t think so

Because of these shortcomings, there is another pair of ETag/ if-none-match to compare file contents

ETag/If-None-Match

The first time a resource is requested, the server returns Expires, Cache-Control, last-Modified, and the Etag field, a unique identifier for the current resource file, in addition to the response header.

This identifier is generated by the server based on the encoding of the file content. It can accurately sense the changes in the file and regenerates the ETag whenever the file content is different

etag: W/"132489-1627839023000"
Copy the code
  • The browser then passes the file through another field when it requests it againIf-None-Match, sent to the server
if-none-match: W/"132489-1627839023000"
Copy the code
  • The server then compares the time of the two fields. If the two fields are the same, it indicates that the file has not been updated, and returns the status code 304 and the empty response body to the browser. The browser directly takes the expired resource and continues to use it
  • If the comparison is different, the resource is updated, and the status code 200 and the new resource are returned

Difference between Last-Modified and ETag

  • EtagPerceived file accuracy is higher thanLast-Modified
  • When both servers are used, the server verifies the priorityEtag/If-None-Match
  • Last-ModifiedPerformance is better thanEtagBecause theEtagIt is not a complete substitute for the overhead incurred by the server during the generation process, which affects server-side performanceLast-Modified, can only be used as a supplement and reinforcement
Strong cache vs. negotiated cache
  • The strong cache is searched first, and the negotiated cache is searched again if there is no match
  • The strong cache does not send requests to the server, so sometimes the browser does not know that the resource has been updated, but the negotiation cache will send requests to the server, and the server must know if the resource has been updated
  • Most projects currently use cached copywriting
    • Negotiation cache general storage:HTML
    • Strong cache general storage:css.image.js, file name withhash

Heuristic cache

This is when there is no Expires, cache-Control: max-age or cache-Control :s-maxage in the response and last-Modified is set

By default, browsers use a heuristic algorithm, called heuristic caching, to calculate the cache expiration date

The cache time is usually 10% of the last-Modified value minus the Date field in the response header (the time the message was created)

max(0, (Date - Last-Modified)) % 10
Copy the code

Cache actual usage policy

For frequently changing resources
  • useThe cache-control: no - CacheMake the browser request data each time and then cooperateEtagorLast-ModifiedWhile not saving the number of requests, it can significantly reduce the size of the response data
For resources that don’t change very often:
  • You can give it to themCache-ControlConfigure a very largemax-age=31536000(one year), so that the browser later requests for the same URL will hit the strong cache, which will need to be added to the file name (or path) to solve the update problemhash, version number, etc., and then change the dynamic character to change the reference URL, invalidating the previous strong cache (actually not immediately invalidating, just no longer used).
Cache location, and read priority

Priority is in the following order

1. Service Worker

2. Memory Cache

The resource is stored in memory and read directly from memory for the next access. When a page is refreshed, for example, much of the data comes from the in-memory cache. Generally store scripts, fonts, and pictures.

The advantage is fast reading speed; Disadvantages Because once the Tab Tab is closed, the cache in memory is released, so the capacity and storage time is poor

3. Disk Cache

A resource is stored in a hard disk and read from the hard disk the next time it is accessed. Based on the fields in the request header, it determines which resources need to be cached, which resources can be used without being requested, and which resources have expired and need to be re-requested. And even in the case of cross-domain sites, resources with the same address once cached by the hard disk will not be requested again.

Advantages are cached in hard disk, large capacity, and longer storage timeliness; The disadvantage is that the reading speed is slower

4. Push Cache

This is a push cache, which is HTTP/2 content, and it’s used when all three of these caches fail. It only exists in the Session and is released once the Session ends, so the Cache time is short and the Cache in the Push Cache can only be used once

CDN cache

  • When we send a request and the browser’s local cache is invalidated, the CDN helps us figure out where the path to get the content is short and fast.
  • For example, a request to a server in Guangzhou is much faster than a request to a server in Xinjiang, and then requests data to the nearest CDN node
  • CDN will determine whether the cached data is expired, if not, it will directly return the cached data to the client, thus speeding up the response speed. If the CDN determines that the cache has expired, it will issue a back source request to the server, pull the latest data from the server, update the local cache, and return the latest data to the client.
  • CDN not only solves the problem of cross-carrier and cross-region access, greatly reduces the access delay, but also plays a role of distribution, reducing the load of the source server

Several refresh and carriage return differences

Three refresh operations
  • Normal operations: Enter url, forward link, and backward link in the address bar to force the cache to work, and negotiate the cache to work
  • Manual refresh: F5, click refresh button, right click menu refresh to force cache invalidation and negotiate cache validity
  • Force refresh: CTRL + F5 force cache invalidation, negotiation cache invalidation

The local store

Cookie

Local storage, which was first proposed, carries cookies in each HTTP request to determine whether multiple requests are initiated by the same user. Its characteristics are as follows:

  • There is a security issue, if it is intercepted, it can get all the Session information, and then it forwards the Cookie to do that
  • Each domain name contains a maximum of 20 cookies and the size cannot exceed 4kb
  • Cookies are sent whenever a new page is requested
  • If the Cookie is created successfully, the name cannot be changed
  • Cookies cannot be shared across domain names

There are two ways to share cookies across domains

  • Use Nginx reverse proxy
  • After logging in to one site, write cookies to other sites. The Session on the server is stored on a node, and the Cookie is stored on a Session ID

Cookie usage scenarios

  • The most common one is to use a Cookie in conjunction with a Session, storing the Session ID in a Cookie and carrying the Session ID with each request so that the server knows who initiated the request
  • Can be used to count the number of clicks on a page

What fields do cookies have

  • Name,SizeName the size
  • Value: Saves the user login status. This value should be encrypted and cannot be used in plain text
  • Path: Path to which this Cookie can be accessed. For example, juejin.cn/editor, the path is /editor, and only those under /editor can read cookies
  • httpOnly: disables Cookie access through JS to reduce XSS attacks.
  • Secure: Can be carried only in HTTPS requests
  • SameSite: specifies that browsers cannot carry cookies in cross-domain requests to reduce CSRF attacks
  • Domain: a domain name, cross-domain, or Cookie whitelist that allows a subdomain to obtain or manipulate cookies from the parent domain, useful for single sign-on
  • Expires/Max-size: Specifies the expiration time or number of seconds. If this is not set, it will expire when the browser is closed, just like Session
LocaStorage
  • Is a new feature of H5, is to store information to the local, its storage size is much larger than Cookie, 5M, and is permanent storage, unless the initiative to clean up, or it will always exist
  • Restricted by the same origin policy, that is, the port, protocol, host address of any different cannot be accessed, and the browser set to privacy mode, can not read LocalStorage
  • It is used in a lot of scenarios, such as storing website themes, storing user information, etc., save a large amount of data or not much change of data can be used it
SessionStorage
  • SessionStorage is also a new feature of H5. It is mainly used to temporarily store data of the same window or TAB. It will not be deleted when the page is refreshed, but will be deleted after the window or TAB is closed
  • EssionStorage and LocalStorage are LocalStorage, and can not be crawled, and have the same origin policy restrictions, but SessionStorage is more strict, only in the same browser under the same window can be shared
  • Its API is the same as LocalStorage getItem, setItem, removeItem, clear
  • Its usage scenarios are generally time-sensitive, such as storing visitor login information of some websites, as well as temporary browsing history
indexDB

Is a browser native database with the following features

  • Key-value pair storage: Internal object warehouse storage data, all types of data can be directly stored, including JS objects, stored in the form of key-value pairs, each data has a corresponding primary key, the primary key is unique
  • asynchronousIndexDB operations can still be performed by the user, and the asynchronous design prevents large data reads and writes from slowing down the performance of the web page
  • Support transactions: For example, modify the entire table data, modify half of the time reported an error, at this time will be restored to the state of the unmodified level, there is no successful modification of half of the situation
  • The same-origin restrictions: Each database should create its corresponding domain name, web pages can only access the database under its own domain name
  • Large storage space: Generally not less than 250MB, or even no upper limit
  • Support binary storageSuch as ArrayBuffer and Blob objects

In addition to the above four front-end storage methods, there are WebSQL, similar to SQLite, is a real relational database, you can use SQL to operate, but with JS to convert, more trouble

The difference between the above four

Offline storage

Service Worker

A Service Worker is an independent thread running outside the main thread of JS and behind the browser, which naturally cannot access DOM. It is equivalent to a proxy server, which can intercept requests sent by users, modify requests or directly respond to users without contacting the server. Loading JS and images, for example, allows us to use web applications offline

It is used for offline cache (improving the loading speed of the first screen), message push, and network proxy. To use a Service Worker, you must use HTTPS, because the Service Worker involves request interception and requires HTTPS to ensure security

There are three steps to implementing caching with Service workers:

  • It is a registered
  • The files can then be cached after listening for the Install event
  • The next time you access it, you can return the cached data directly by intercepting the request
/ / index. Js registration
if (navigator.serviceWorker) { 
    navigator.serviceWorker.register('sw.js').then( registration= > {
        console.log('Service worker registered successfully')
    }).catch((err) = >{
        console.log('Servcie worker registration failed')})}// sw.js listens for the 'install' event and caches the required files in the callback
self.addEventListener('install'.e= > {
    // Opens the specified cache file name
    e.waitUntil(caches.open('my-cache').then( cache= > {
        // Add files to cache
        return cache.addAll(['./index.html'.'./index.css'])}})))// If there is a request in the cache, use the cache directly; otherwise, request the data
self.addEventListener('fetch'.e= > { 
    // Find the response hit in the request cache
    e.respondWith(caches.match(e.request).then( response= > {
        if (response) {
            return response
        }
        console.log('fetch source')}})))Copy the code