Various implementation schemes of tab

Posted by supinum on Fri, 11 Feb 2022 09:39:10 +0100

1. First build structure and style

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>tab </title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        ul {
            list-style: none;
        }
        .box {
            width: 500px;
            margin: 20px auto;
        }
        .box .tab {
            display: flex;
            justify-content: flex-start;
            align-items: center;
            position: relative;
            top: 1px;
        }
        .box .tab li {
            box-sizing: border-box;
            padding: 0 25px;
            margin-right: 15px;
            height: 35px;
            line-height: 33px;
            border: 1px solid #ccc;
            background: #ddd;
            color: #555;
            font-size: 14px;
            cursor: pointer;
        }
        .box .tab li.active {
            background: #fff;
            font-weight: 700;
            border-bottom-color: #fff;
        }
        .box .content div {
            display: none;
            box-sizing: border-box;
            padding: 15px;
            height: 200px;
            border: 1px solid #ccc;
        }
        .box .content div.active {
            display: block;
        }
    </style>
</head>

<body>
    <div class="box" id="box">
        <ul class="tab">
            <li index="0" class="active">music</li>
            <li index="1">film</li>
            <li index="2">comic</li>
        </ul>
        <div class="content">
            <div class="active">Endless Adventure</div>
            <div>my sister</div>
            <div>Evil master</div>
        </div>
    </div>
</body>
</html>
<script>
    // Gets the DOM object to operate on
    var box = document.querySelector('#box'),
        tabList = box.querySelectorAll('.tab li'),
        conList = box.querySelectorAll('.content div');
</script>
  • The idea of realizing the following functions: record the page card information clicked before, and judge whether the currently clicked page card is the last page card. If it is the last page card, nothing will be executed; If the current page card is not the previous page card, clear the style of the previous page card, add a style to the current page card, and transfer the current page card information to the next previous page card information variable.
  • Reason why "i" cannot be written directly (i declared by var): click LI to execute the bound function. In the formed private context, the variable "i" encountered is not private, but its parent context "EC(G) global"; At this time, the global "i" is already the final result 3 of the cycle
  • Why is the final result 3: because JS is a single thread, the browser only allocates one thread to render and parse JS code;
    • Most of JS are synchronous, and the for loop is synchronous;
    • To realize asynchronous programming, you need to use other threads of the browser, and the browser provides two queues for JS to achieve asynchronous effect: WebAPI and EventQueue [queue features: first in first out]
    • WebAPI queue: put the asynchronous task into the task listening queue to listen whether the task meets the executable conditions;
    • EventQueue queue: classify the tasks that meet the executable conditions in the WebAPI queue according to micro tasks and macro tasks;
  • After the top-down execution of the synchronization code in the JS code is completed, the main thread is idle, and then based on the EventLoop event loop mechanism, Go to the EventQueue queue in turn to fetch the executable tasks (fetch order: fetch from the micro task queue first, but not from the micro task; then fetch from the macro task; if there are neither, wait until there are members to be executed in the EventQueue), Enter the stack for execution, and exit the stack after execution.

Method 1: add a custom attribute in the DOM object (add it to the heap memory address) [Recommendation Index: 4 stars]

  • For each cycle, in the current execution context, set an attribute in the current element to store the i value at this time; When the asynchronous condition is triggered, take out the value of i stored by itself
    var preIndex = 0;
    for (var i = 0; i < tabList.length; i++) {
        var item = tabList[i];
        item.myIndex = i;
        item.onclick = function () {
            var curIndex = this.myIndex;
            if (curIndex === preIndex) return;
            tabList[curIndex].className = conList[curIndex].className = 'active';
            tabList[preIndex].className = conList[preIndex].className = '';
            preIndex = curIndex;
        };
    }

Method 2: closure class

  • Using the closure mechanism to solve the recommendation index**
  • The self executing function forms a new private execution context [parameter privatization, not affected by external variables]. There is a created heap memory address in the function body, which is occupied by external methods, so this heap and its context will not be destroyed

1. The loop is directly self executing function

  • The parameter value passed by the self executing function is the value of the current i; Stored in the formed closure
    var prevIndex = 0;
    for (var i = 0; i < tabList.length; i++) {
        (function (i) {
            var item = tabList[i];
            item.onclick = function () {
                if (i === prevIndex) return;
                tabList[i].className = conList[i].className = 'active';
                tabList[prevIndex].className = conList[prevIndex].className = '';
                prevIndex = i;
            };
        })(i);
    } 

2. Closure type II

  • Bind the self executing function for the click event (the passed parameter value is the value of the current i), and the return value is the execution method of its click operation
    var prevIndex = 0;
    for (var i = 0; i < tabList.length; i++) {
        var item = tabList[i];
        item.onclick = (function (i) {
            return function () {
                if (i === prevIndex) return;
                tabList[i].className = conList[i].className = 'active';
                tabList[prevIndex].className = conList[prevIndex].className = '';
                prevIndex = i;
            };
        })(i);
    }

3. The third method of closure: use forEach method

    var prevIndex = 0;
    tabList.forEach(function (item, i) {
        item.onclick = function () {
            if (i === prevIndex) return;
            tabList[i].className = conList[i].className = 'active';
            tabList[prevIndex].className = conList[prevIndex].className = '';
            prevIndex = i;
        };
    }); 

4. The fourth type of closure: LET is also based on the closure solution [Recommendation Index: three stars]

    let prevIndex = 0;
    for (let i = 0; i < tabList.length; i++) {
        let item = tabList[i];
        item.onclick = function () {
            if (i === prevIndex) return;
            tabList[i].className = conList[i].className = 'active';
            tabList[prevIndex].className = conList[prevIndex].className = '';
            prevIndex = i;
        };
    } 

Method 3: ultimate solution: event entrustment [Recommendation Index: five stars]

  • Event delegation: trigger the event of the parent element by clicking the child element [clicking the child element will trigger the click event of the parent element]
  • ev event object: stores the information of the current operation
  • ev.target is the clicked element, which is called the event source
    // Only one heap and one stack are generated
    let prevIndex = 0;
    box.onclick = function (ev) {
        let target = ev.target; 
        if (target.tagName === "LI") {
            // Click LI: get the custom attribute "its index" on the html structure based on getAttribute, and the result is string type
            let index = +target.getAttribute('index'); //Convert character format to numeric type
            if (index === prevIndex) return;
            tabList[index].className = conList[index].className = 'active';
            tabList[prevIndex].className = conList[prevIndex].className = '';
            prevIndex = index;
        }
    };

Topics: Javascript Front-end css queue