Event mechanism

The event triggers three phases

Event triggering has three phases

  • windowPropagated to event triggers, which are triggered when a registered capture event is encountered
  • Event that triggers registration when propagated to event trigger
  • From the event triggerwindowPropagation, which is triggered when a bubbling event is encountered

Event firing is generally performed in this order, but there are exceptions where both bubbling and capturing events are registered to a target node, and event firing is performed in the order in which they were registered.

// The following will print bubbles and then capture
node.addEventListener('click',(event) =>{
	console.log('bubble')},false);
node.addEventListener('click',(event) =>{
	console.log('capture')},true)
Copy the code

Register event

Usually we register events using addEventListener, and the third argument to this function can be a Boolean or an object. For the Boolean useCapture parameter, this parameter defaults to false. UseCapture determines whether a registered event is a capture event or a bubble event. For object parameters, you can use the following attributes

  • capture, Boolean values, anduseCaptureRole as
  • once, Boolean value, value istrueIndicates that the callback is invoked only once, after which the listener is removed
  • passive, a Boolean value indicating that it will never be calledpreventDefault

In general, we only want events to fire on the target, and stopPropagation can be used to prevent further propagation of events. StopPropagation is usually thought of as preventing events from bubbling, but it also prevents capturing events. StopImmediatePropagation can also be implemented to block events, but it can also prevent the event target from executing other registered events.

node.addEventListener('click',(event) =>{
	event.stopImmediatePropagation()
	console.log('bubble')},false);
// Clicking Node will only execute the above function, which will not be executed
node.addEventListener('click',(event) => {
	console.log('capture')},true)
Copy the code

The event agent

If the children of a node are dynamically generated, the children should register events with their parents

<ul id="ul">
	<li>1</li>
    <li>2</li>
	<li>3</li>
	<li>4</li>
	<li>5</li>
</ul>
<script>
	let ul = document.querySelector('#ul')
	ul.addEventListener('click', (event) => {
		console.log(event.target);
	})
</script>
Copy the code

The event broker approach has the following advantages over registering events directly to the target

  • Save memory
  • There is no need to unregister events for child nodes

Cross domain

Because browsers have the same origin policy for security reasons. That is, if a protocol, domain name, or port difference is cross-domain, the Ajax request will fail.

There are several common ways to solve cross-domain problems

JSONP

The principle behind JSONP is simple: it exploits the fact that

<script src="http://domain/api? param1=a&param2=b&callback=jsonp"></script> <script> function jsonp(data) { console.log(data) } </script>Copy the code

JSONP is simple to use and compatible, but is limited to GET requests.

In development, you may encounter multiple JSONP requests with the same callback function name. In this case, you need to wrap a JSONP

function jsonp(url, jsonpCallback, success) {
  let script = document.createElement("script");
  script.src = url;
  script.async = true;
  script.type = "text/javascript";
  window[jsonpCallback] = function(data) {
    success && success(data);
  };
  document.body.appendChild(script);
}
jsonp(
  "http://xxx"."callback".function(value) {
    console.log(value); });Copy the code

CORS

CORS requires both browser and backend support. Internet Explorer 8 and 9 need to be implemented through XDomainRequest.

The browser will automatically carry out CORS communication, the key to achieve CORS communication is the back end. As long as the backend implements CORS, cross-domain is achieved.

To enable CORS, set access-Control-allow-Origin on the server. This attribute indicates which domain names can access resources. If a wildcard is set, all websites can access resources.

document.domain

This mode can be used only when the secondary domain names are the same. For example, a.test.com and b.test.com are used in this mode.

Just add document.domain = ‘test.com’ to the page to indicate that the secondary domain is the same

postMessage

This approach is often used to retrieve third-party page data embedded in a page. One page sends the message, and the other determines the source and receives the message

// Send the message
window.parent.postMessage('message'.'http://test.com');
// Receive the message
var mc = new MessageChannel();
mc.addEventListener('message', (event) => {
    var origin = event.origin || event.originalEvent.origin; 
    if (origin === 'http://test.com') {
        console.log('Verified')}});Copy the code

Event loop

JS is known as a non-blocking single-threaded language because it was originally created to interact with browsers. If JS is a multithreaded language, we may have problems processing the DOM in multiple threads (adding nodes in one thread and removing nodes in the other), of course we can introduce read and write locks to solve this problem.

JS generates execution environments during execution, and these execution environments are sequentially added to the execution stack. If asynchronous code is encountered, it is suspended and added to the Task queue (there are multiple tasks). Once the execution stack is empty, the Event Loop takes the code that needs to be executed from the Task queue and puts it into the execution stack for execution, so it is essentially asynchronous or synchronous behavior in JS.

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

console.log('script end');
Copy the code

The above code is asynchronous even though the setTimeout delay is 0. This is because the HTML5 standard states that the second argument to this function must be at least 4 milliseconds, which increases automatically. So setTimeout will still print after script end.

Different Task sources are assigned to different Task queues. Task sources can be divided into microtasks and macrotasks. In the ES6 specification, microtasks are called Jobs and MacroTasks are called tasks.

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

new Promise((resolve) = > {
    console.log('Promise')
    resolve()
}).then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout
Copy the code

Although setTimeout is written before Promise in the above code, because Promise belongs to micro task and setTimeout belongs to macro task, there will be the above printing.

Microtasks include Process. nextTick, Promise, Object.observe, and MutationObserver

Macro tasks include Script, setTimeout, setInterval, setImmediate, I/O, and UI Rendering

A lot of people have a misconception that microtasks are faster than macro tasks, but that’s not true. Because a macro task includes script, the browser executes a macro task first, followed by a microtask if there is asynchronous code.

So the correct sequence for an Event loop is this

  1. Execute synchronized code, which is a macro task
  2. If the execution stack is empty, check whether microtasks need to be executed
  3. Perform all microtasks
  4. Render the UI if necessary
  5. Then start the next Event loop, which executes the asynchronous code in the macro task

As we can see from the Event loop sequence above, if the asynchronous code in the macro task has a lot of computation and needs to manipulate the DOM, we can put the DOM into the micro task for faster interface response.

Event loop in Node

The Event loop in Node is different from the Event loop in the browser.

The Node Event loop is divided into six stages, which are repeated in sequence

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ connections ─ ─ ─ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ├ ──┤ close callbacks ────── our r companyCopy the code

timer

The timers phase executes setTimeout and setInterval

The time specified by a timer is not the exact time, but the callback is executed as soon as possible after this time, which may be delayed because the system is executing another transaction.

The lower limit time has a range of [1, 2147483647]. If the specified time is not in this range, it will be set to 1.

I/O

The I/O phase performs all but the close event, timer, and setImmediate callbacks

idle, prepare

Idle, internal implementation in the prepare phase

poll

The poll phase is important, and in this phase, the system does two things

  1. The timer of the point-to-point is executed
  2. Execute events in the poll queue

And when there is no timer in poll, the following two things can be found

  • If the poll queue is not empty, the callback queue is traversed and executed synchronously until the queue is empty or system limited
  • If the poll queue is empty, two things happen
    • If you havesetImmediateThe poll phase stops and the check phase is executedsetImmediate
    • If there is nosetImmediateIf needed, it waits for the callback to be queued and executes it immediately

If another timer needs to be executed, the callback is returned to the timer phase.

check

The Check phase performs setImmediate

close callbacks

Close The Close event is executed during the Callbacks phase

In Node, timer execution is random in some cases

setTimeout((a)= > {
    console.log('setTimeout');
}, 0);
setImmediate((a)= > {
    console.log('setImmediate');
})
SetTimeout, setImmediate
// May also reverse the output, depending on performance
// setImmediate is executed because it may take less than 1 millisecond to enter the Event loop
// Otherwise setTimeout will be executed
Copy the code

In this case, of course, the order of execution is the same

var fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout((a)= > {
        console.log('timeout');
    }, 0);
    setImmediate((a)= > {
        console.log('immediate');
    });
});
// Because the readFile callback is executed in poll
// setImmediate detects that there is no setImmediate, so it immediately jumps to the Check phase to perform the callback
// Execute setTimeout in the timer phase
// So the above output must be setImmediate
Copy the code

All of the above are macroTask executions, which are executed immediately after each of the above phases.

setTimeout((a)= >{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')})},0)

setTimeout((a)= >{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')})},0)

// The above code prints differently in the browser and node
// The browser must print timer1, promise1, timer2, promise2
// Node may print timer1, timer2, promise1, promise2
It is also possible to print timer1, promise1, timer2, promise2
Copy the code

Process. nextTick is executed before other microtasks in Node.

setTimeout((a)= > {
 console.log("timer1");

 Promise.resolve().then(function() {
   console.log("promise1");
 });
}, 0);

process.nextTick((a)= > {
 console.log("nextTick");
});
// nextTick, timer1, promise1
Copy the code

storage

Cookie, localStorage, sessionStorage, indexDB

features cookie localStorage sessionStorage indexDB
Data life cycle Generally, it is generated by the server. You can set the expiration time Unless it’s cleaned up, it’s always there Clean up when the page is closed Unless it’s cleaned up, it’s always there
Data store size 4K 5M 5M infinite
Communicates with the server Each time it is carried in the header, it has an impact on request performance Don’t participate in Don’t participate in Don’t participate in

As you can see from the table above, cookies are no longer recommended for storage. LocalStorage and sessionStorage can be used if you don’t have extensive data storage requirements. For data that does not change much, try to use localStorage, otherwise use sessionStorage.

With cookies, we also need to be careful about security.

attribute role
value If the value is used to save the user login status, the value should be encrypted and the user id cannot be in plain text
http-only Cookies cannot be accessed through JS, reducing XSS attacks
secure This parameter can be carried only in HTTPS requests
same-site The browser cannot carry cookies in cross-domain requests to reduce CSRF attacks

Service Worker

Service Workers essentially act as a proxy server between the Web application and the browser, and can also act as a proxy between the browser and the network when the network is available. They are designed, among other things, to enable the creation of an effective offline experience, intercept network requests and take appropriate action based on whether the network is available and whether updated resources reside on the server. They also allow access to push notifications and background synchronization apis.

At present, this technology is usually used to cache files and improve the speed of the first screen. You can try to achieve this function.

// index.js
if (navigator.serviceWorker) {
  navigator.serviceWorker
    .register("sw.js")
    .then(function(registration) {
      console.log("Service worker registered successfully");
    })
    .catch(function(err) {
      console.log("Servcie worker registration failed");
    });
}
// sw.js
// Listen for the install event and cache the required files in the callback
self.addEventListener("install", e => {
  e.waitUntil(
    caches.open("my-cache").then(function(cache) {
      return cache.addAll(["./index.html"."./index.js"]); })); });// Intercept all requested events
// If the requested data is already in the cache, use the cache directly, otherwise request the data
self.addEventListener("fetch", e => {
  e.respondWith(
    caches.match(e.request).then(function(response) {
      if (response) {
        return response;
      }
      console.log("fetch source"); })); });Copy the code

Open the page and you can see from Application in the developer tools that the Service Worker has started

In the Cache, we can also find that the required files have been cached

When we refresh the page we can see that our cached data is read from the Service Worker

Apply colours to a drawing mechanism

The browser rendering mechanism is generally divided into the following steps

  1. Process THE HTML and build the DOM tree.
  2. Process CSS to build CSSOM trees.
  3. Merge DOM and CSSOM into a render tree.
  4. According to the layout of the render tree, calculate the position of each node.
  5. Call THE GPU to draw, synthesize the layer and display it on the screen.

When the CSSOM tree is built, rendering is blocked until the CSSOM tree is built. Building a CSSOM tree is a very performance-intensive process, so you should try to keep the hierarchy flat and reduce excessive layering. The more specific the CSS selector, the slower the execution.

Building the DOM is paused when the HTML is parsed to a Script tag, and then resumed from where it was paused. That is, if you want the first screen to render as fast as possible, the less you should load JS files on the first screen. CSS also affects THE execution of JS, which is executed only after the stylesheet has been parsed, so it is safe to assume that CSS will also pause DOM building in this case.

Load and DOMContentLoaded

The Load event is triggered to indicate that the DOM, CSS, JS and images in the page have all been loaded.

The DOMContentLoaded event is triggered to represent that the original HTML is fully loaded and parsed, without waiting for CSS, JS, and images to load.

The layer

In general, you can think of a normal document stream as a layer. Specific properties generate a new layer. The rendering of different layers does not affect each other, so it is recommended to generate a new layer for frequent rendering to improve performance. However, you should not create too many layers, which will cause the reaction.

New layers can be generated using the following common properties

  • 3 d transform:translate3d,translateZ
  • will-change
  • video,iframeThe label
  • Through animationopacityThe animation transformation
  • position: fixed

Repaint and Reflow

Redraw and backflow are a small part of the rendering step, but these two steps have a significant impact on performance.

  • Redraw is when a node needs to change its appearance without affecting the layout, such as changescolorIt’s called redrawing
  • Backflow is when layout or geometry properties need to be changed.

Backflow must occur redraw, redraw does not necessarily cause backflow. The cost of backflow is much higher, and changing the underlying node is likely to result in a series of backflows from the parent node.

So the following actions can cause performance problems:

  • Change window size
  • Change the font
  • Add or remove styles
  • Text change
  • Position or float
  • The box model

What a lot of people don’t know is that redraw and reflux are actually related to Event loops.

  1. When the Event Loop completes Microtasks, it determines whether the document needs to be updated. Since the browser has a refresh rate of 60Hz, it only updates every 16ms.
  2. And then determine if there isresizeorscrollIf there is, it will trigger the event, soresizescrollEvents are triggered at least once in 16ms and have throttling functions.
  3. Determines whether a Media Query is triggered
  4. Update the animation and send the event
  5. Check whether a full-screen operation event exists
  6. performrequestAnimationFrameThe callback
  7. performIntersectionObserverCallback, a method used to determine whether an element is visible, can be used on lazy loading, but is not compatible
  8. Update the interface
  9. So that’s what you might do in one frame. If there is free time in a frame, it will be executedrequestIdleCallbackThe callback.

The above content is from an HTML document

Reduce redrawing and reflow

  • Use translate instead of top

    <div class="test"></div>
    <style>
    	.test {
    		position: absolute;
    		top: 10px;
    		width: 100px;
    		height: 100px;
    		background: red;
    	}
    </style>
    <script>
    	setTimeout((a)= > {
            // cause backflow
    		document.querySelector('.test').style.top = '100px'
    	}, 1000)
    </script>
    Copy the code
  • Use visibility instead of display: None, because the former will only cause redraw and the latter will cause backflow (changing the layout)

  • Take the DOM offline and modify it, for example: first give the DOM to display: None (once Reflow), then you modify it 100 times before displaying it

  • Do not place DOM node property values in a loop as variables in the loop

    for(let i = 0; i < 1000; i++) {
        // Fetching offsetTop causes backflow because the correct value needs to be fetched
        console.log(document.querySelector('.test').style.offsetTop)
    }
    Copy the code
  • Do not use the table layout, it is possible that a small change will cause the entire table to be rearranged

  • Select the speed of the animation implementation, the faster the animation, the more backflows, you can also choose to use requestAnimationFrame

  • CSS selectors match from right to left to avoid DOM depth

  • Turn frequently running animations into layers that prevent backflow from affecting other elements. For the video TAB, for example, the browser automatically turns the node into a layer.