preface

In the last article we talked about how to implement a Promise, this time we’ll use a JS animation to see what an EventLoop is.

A JS animation

  • The animation that needs to be implemented this time is an animation that adds text and styles to the front page through JS. The final result is shown below:

  • The calling code

  • Based on the results of the implementation and the calling code, the following implementation ideas can be thought of:

    • Output the front end of the data line by line through a chain call.
    • Mainly throughtextandstyleBoth methods output to the front end
    • Each line of output can be time-controlled
    • throughsetTimeoutOutput text to the forward end

The basic idea has already had, below begins the code ~~

1. Create an Animate class

  • You first need to create an Animate method that initializes the information when called
  • When you instantiate, you need to pass in two parameters,headThe Element tag andbodyThe element of the label
  • The initialization information includes the creation of three HTML tags, respectivelyOutput information to the box.Print the style boxandUpdate the style tag for the style
    class Animate {
        constructor(headElement, bodyElement) {
            this.element = {
                headElement,
                bodyElement,
                contentElement: null.// Box to output content
                styleElement: null.// Update the style tag for the style
                styleContent: null.// Print style box
            }
            
            this.init(); // Initialize when instantiating
        }
        
        init = (a)= > {
            const styleElement = createElement('style');
            const contentElement = createElement('div', {class: 'text_content'});
            const styleContent = createElement('div', {class : 'style_content'});
            
            // Insert the node forward
            this.element.headElement.appendChild(styleElement);
            this.element.bodyElement.appendChild(contentElement);
            this.element.bodyElement.appendChild(styleContent);
    
            this.element.contentElement = contentElement;
            this.element.styleElement = styleElement;
            this.element.styleContent = styleContent; }}// Encapsulate the create Element method
    function createElement(elementName, elementObj = {}, styleObj = {}) {
        const element = document.createElement(elementName);
        
        for (const option of Object.keys(elementObj)) {
            element.setAttribute(option, elementObj[option]);
        }
        
        for (const styleName of Object.keys(styleObj)) {
            element.style[styleName] = styleObj[styleName];
        }
        
        return element;
    }
    
    / / call
    const head = document.querySelector('head');
    const body = document.querySelector('body');
    const animate = new Animate(head, body);
Copy the code

Now that the nodes we need to display and style are all created, we need to implement adding information to text_content

2. Add information to text

  • As it says, we need itsetIntervalAdd information to the content box and setThe total outputTime is the user output time, it is necessary to calculate the output of each word interval time, the formula is also very simpleFont output interval = Total output time/total text length.

Add a text method to the Animate class, a public method that outputs text, and a method that outputs font interval time

    /** * Add text * @param {Element} appendNode Insert text node * @param {string} elName tag name * @param {Object} elOption tag setting * @param {Object} styleObj Inline style * @param {Object} text Output text * @param {number} during output total time */
    text = (elName, elOption, styleObj, text, during) = > {
        const element = createElement(elName, elOption, styleObj);
        this.element.contentElement.appendChild(element);
        this.printText(element, text, during); // Output text
    }
    
    @param {Element} Element output tag @param {string} text output text * @param {number} during output text total time */
    printText = (element, text, during) = > {
        const len = text.length; // Total length of text
        const time = this.getTextDuring(len, during); // Output time per word
        let index = 0;
        let timer = null;
        timer = setInterval((a)= > {
            if (index < len) {
                element.innerHTML += text[index];
                index++;
            } else {
                clearInterval(timer);
                resolve();
            }
        }, time);
    }
    
    * @param {string} textLen * @param {number} during */
    getTextDuring(textLen, during) {
        return (during / textLen).toFixed(4);
    }
    
    / / test
    const head = document.querySelector('head');
    const body = document.querySelector('body');
    const animate = new Animate(head, body);
    animate.text('p', {}, {color: 'red'}, 'Hello World'.2000);
Copy the code

After calling animate. Text, you can see that it is ready to output:

3.EventLoop

Our Animate is written here, and the most basic output text function has been implemented. The next thing you need to do is to print each piece of text sequentially. To print sequentially you need to execute each output method sequentially. But JavaScript doesn’t make our implementation easy.

    / / test
    const head = document.querySelector('head');
    const body = document.querySelector('body');
    const animate = new Animate(head, body);
    animate.text('p', {}, {color: 'red'}, 'Hello World1'.2000);
    animate.text('p', {}, {color: 'red'}, 'Hello World2'.2000);
    animate.text('p', {}, {color: 'red'}, 'Hello World3'.2000);
Copy the code

Output:

text
Single thread
Execution queue (fifO)
Perform a task



MacroTasksandMicrotasks
Perform macro tasks first and then micro tasks

  • The macro tasks include: Script, setInterval, setTimeout, I/O, requestAnimationFrame, setImmediate (Node.js)
  • Microtasks include promise.then, MutationObserver, process.nexttick (node.js)

Now that you know about the execution queue, look at the test example you just wrote. When animate. Text is called, setInterval is inserted into the task queue instead of being executed first, as shown in the following figure

When the three animate. Text methods are completed, three setInterval macro tasks are added to the task queue. When the script method is completed, the first inserted setInterval executes and outputs H. Then add the insert macro task to the end of the task queue at the specified time (setInterval is a cyclic method that executes at intervals until the timer is cleared).

Now that we know about the JS execution queue, back to the animation code, we need to know how to implement the sequential execution of one text method at a time. The previous article implemented promises using two run queues (resolveArr, rejectArr) to hold the methods to execute when the state changes. You can do the same here by adding an array of functions to the constructor of the class, adding all of the executing methods to the array at the time the script macro task executes, and creating a new macro task to execute the array’s methods one by one. Next up:

  • Add an array of functions to the constructor
  • Add an array of execution functionsrunmethods
  • Add a macro task to executerun
  • willtextThe code executed in the method is placed in an array of functions and is called each time it completes executionrunMethod to execute the next method in the array of functions
  • Modify theprintTextMethod is the Promise method
    constructor() {
        this.runArr = []; // Array of functions! [](https://user-gold-cdn.xitu.io/2019/11/30/16eb9e73100327d0? w=368&h=240&f=gif&s=364207)
    }
    
    // The method to execute the function
    run = (a)= > {
        this.runArr.length ? this.runArr.splice(0.1) [0] () :0;
    }
    
    /** * Add text * @param {Element} appendNode Insert text node * @param {string} elName tag name * @param {Object} elOption tag setting * @param {Object} styleObj Inline style * @param {Object} text Output text * @param {number} during output total time */
    text = (elName, elOption, styleObj, text, during) = > {
        this.runArr.push(async() = > {const element = createElement(elName, elOption, styleObj);
            this.element.contentElement.appendChild(element);
            await this.printText(element, text, during);
            this.run(); })}/** ** @param {Element} Element output tag * @param {string} text Output text * @param {number} during output text total time */
    printText = (element, text, during) = > {
        return new Promise(resolve= > {
            const len = text.length;
            const time = this.getTextDuring(len, during);
            let index = 0;
            let timer = null;
            timer = setInterval((a)= > {
                if (index < len) {
                    element.innerHTML += text[index];
                    index++;
                } else{ clearInterval(timer); resolve(); } }, time); })}Copy the code

After that, we’ll call the test example

    / / test
    const head = document.querySelector('head');
    const body = document.querySelector('body');
    const animate = new Animate(head, body);
    animate.text('p', {}, {color: 'red'}, 'Hello World1'.2000);
    animate.text('p', {}, {color: 'red'}, 'Hello World2'.2000);
    animate.text('p', {}, {color: 'red'}, 'Hello World3'.2000);
Copy the code

Success! Now take a look at its execution queue diagram:

4. Finally add style and chain calls

Here our approach has been achieved in order to output the text interface, the need to do is to add style and chain calls, add style implementation method and add text is roughly the same, the chain call is actually return after each method was implemented the object itself is ok, there is not much explanation, the final code:

    'use strict';

class Animate {
    constructor(headElement, bodyElement) {
        this.element = {
            headElement,
            bodyElement,
            styleElement: null.contentElement: null.styleContent: null};this.runArr = []; // Perform a combination of functions
        this.init();

        setTimeout((a)= > { console.log(this.runArr); this.run(); }, 0);
    }

    // Create a text interface
    init = (a)= > {
        const styleElement = createElement('style');
        const contentElement = createElement('div', {class: 'text_content'});
        const styleContent = createElement('div', {class : 'style_content'});

        this.element.headElement.appendChild(styleElement);
        this.element.bodyElement.appendChild(contentElement);
        this.element.bodyElement.appendChild(styleContent);

        this.element.contentElement = contentElement;
        this.element.styleElement = styleElement;
        this.element.styleContent = styleContent;
    }

    run = (a)= > {
        this.runArr.length ? this.runArr.splice(0.1) [0] () :0;
    }

    /** * Add text * @param {Element} appendNode Insert text node * @param {string} elName tag name * @param {Object} elOption tag setting * @param {Object} styleObj Inline style * @param {Object} text Output text * @param {number} during output total time */
    text = (elName, elOption, styleObj, text, during) = > {
        this.runArr.push(async() = > {const element = createElement(elName, elOption, styleObj);
            this.element.contentElement.appendChild(element);
            await this.printText(element, text, during);
            this.run();
        })
        return this;
    }

    /** * Add style file * @param {string} selector name * @param {*} styleObject * @param {*} during */
    style = (selector, styleObject, during) = > {
        this.runArr.push(async() = > {const parentElement = createElement('ul', {class: 'style_row'});
            this.element.styleContent.appendChild(parentElement);
            await this.printStyle(parentElement, selector, styleObject, during);
            this.run();
        })
        return this;
    }

    /** ** @param {Element} Element output tag * @param {string} text Output text * @param {number} during output text total time */
    printText = (element, text, during) = > {
        return new Promise(resolve= > {
            const len = text.length;
            const time = this.getTextDuring(len, during);
            let index = 0;
            let timer = null;
            timer = setInterval((a)= > {
                if (index < len) {
                    element.innerHTML += text[index];
                    index++;
                } else{ clearInterval(timer); resolve(); } }, time); })}@param {Element} parentElement type of the parentElement * @param {string} selectorName selector text * @param {Object} StyleObject * @param {number} during Output total time */
    printStyle = (parentElement, selectorName, styleObject, during) = > {
        return new Promise(async resolve => {
            const styleStr = JSON.stringify(styleObject).length ; 
            const textLen = selectorName.length + styleStr + 2; // Add 2 to add parentheses
            const time = this.getTextDuring(textLen, during);

            const list = createElement('li', {class: 'selector'}); / / < li > < / li > list
            const selector = createElement('span', {class: 'selector'}); //  CSS selector
            const bracketsLeft = createElement('span', {class: 'style_brackets'}); // { open curly brace
            const bracketsRight = createElement('span', {class: 'style_brackets'}); // { close curly brace
            list.appendChild(selector);
            list.appendChild(bracketsLeft);
            parentElement.appendChild(list);
            
            await this.printText(selector, selectorName, time * selectorName.length);
            await this.printText(bracketsLeft, '{', time * 3);
            this.element.styleElement.innerHTML += `${selectorName} {\n`;
            for (const style of Object.keys(styleObject)) {
                const el = this.createStyleElement(list);
                await this.printText(el.styleName, style, time * style.length);
                await this.printText(el.colon, ':', time * 2);
                await this.printText(el.style, `${styleObject[style]}; \n`, time * styleObject[style].length);
                this.element.styleElement.innerHTML += `${style} : ${styleObject[style]}; \n`;
            }
            list.appendChild(bracketsRight);
            await this.printText(bracketsRight, '} ', time);
            this.element.styleElement.innerHTML += `} \n`; resolve(); })}/** * Create the style Element */
    createStyleElement = (list) = > {
        const p = createElement('p', {class: 'style_row'});
        const style = createElement('span', {class: 'style'}); < / span > < / span > style
        const styleName = createElement('span', {class: 'style_name'}); //  The style name
        const colon = createElement('span', {class: 'style_colon'}); < / span > < / span > colon
        p.appendChild(styleName);
        p.appendChild(colon);
        p.appendChild(style);
        list.appendChild(p);
        return {
            style,
            styleName,
            colon,
        }
    }

    * @param {string} textLen * @param {number} during */
    getTextDuring(textLen, during) {
        return (during / textLen).toFixed(4); }}/ / create the Element
function createElement(elementName, elementObj = {}, styleObj = {}) {
    const element = document.createElement(elementName);

    for (const option of Object.keys(elementObj)) {
        element.setAttribute(option, elementObj[option]);
    }

    for (const styleName of Object.keys(styleObj)) {
        element.style[styleName] = styleObj[styleName];
    }
    
    return element;
}
Copy the code

Testing:

    'use strict';

    const head = document.querySelector('head');
    const body = document.querySelector('body');
    const animate = new Animate(head, body);
    
    animate
    .text('p', {class: 'text'}, {}, 'hello! '.200)
    .text('p', {class: 'text_yellow'}, {}, 'I want to be yellow.'.500)
    .style('.text_yellow', {'color': 'yellow'}, 1000)
    .text('p', {class: 'text'}, {}, 'Success! '.1000)
Copy the code

summary

  • The source address
  • We used an animated example to see how the JS event loop works and how the code executes in a browser/Node.
  • Macro tasks: Script, setInterval, setTimeout, I/O, requestAnimationFrame, setImmediate (Node.js)
  • Microtasks: Promise.then, MutationObserver, process.nexttick (node.js)
  • Perform macro tasks first, then micro tasks
  • Finally, through this small animation example we can use the code to make a fun thing for yourself, such as: automatic display resume 😁