选项卡的多种实现方案

本文探讨了实现前端选项卡的多种方法,包括在DOM对象中添加自定义属性、闭包以及事件委托。强调了在for循环中使用闭包和let的异步编程原理,详细解释了WebAPI和EventQueue队列的角色以及事件委托作为终极解决方案的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.先搭结构和样式

浏览器页面呈现结果

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>选项卡</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">音乐</li>
            <li index="1">电影</li>
            <li index="2">动漫</li>
        </ul>
        <div class="content">
            <div class="active">无尽的冒险</div>
            <div>我的姐姐</div>
            <div>魔道祖师</div>
        </div>
    </div>
</body>
</html>
<script>
    // 获取要操作的DOM对象
    var box = document.querySelector('#box'),
        tabList = box.querySelectorAll('.tab li'),
        conList = box.querySelectorAll('.content div');
</script>
  • 下面功能实现的思路:记录之前点击的页卡信息,判断当前点击的页卡是否为上次的页卡,如果是上次的页卡,什么都不执行;如果当前页卡不是之前的页卡,将之前页卡的样式清除,为当前页卡加上样式,并将当前页卡信息传给下次的之前页卡信息变量。
  • 不能直接写“i”的原因(var声明的i):点击LI把绑定的函数执行,在形成的私有上下文中,遇到的变量“i”不是私有的,而是其上级上下文「EC(G)全局」的;而此时全局的“i”,已经是循环最后的结果3
  • 为什么是最后结果3:因为JS是单线程,浏览器只分配了一个线程去渲染、解析JS代码;
    • JS中大部分都是同步的,而且for循环是同步的;
    • 想要实现异步编程,需要借助浏览器的其他线程,并且浏览器为JS实现异步效果,提供了两个队列:WebAPI和EventQueue【队列特点:先进先出】
    • WebAPI队列:将异步任务放到这个任务监听队列中,监听任务是否达到可执行条件;
    • EventQueue队列:将WebAPI队列中监听到达到可执行条件的任务,按照微任务与宏任务进行分类;
  • 等到JS代码中同步代码自上而下执行完成后,主线程空闲下来了,再基于EventLoop事件循环机制,依次去EventQueue队列取出可执行的任务(取出顺序:先去微任务队列取,微任务中没有;再去宏任务中去取;如果两个都没有,就等待,直到EventQueue队列中有成员待执行再去取;),进栈执行,执行完毕出栈。

方法一:在DOM对象中添加自定义属性(添加到堆内存地址中)【推荐指数 :4星】

  • 每次循环,在当前执行上下文中,在当前元素中设置一个属性将此时的i值存储起来;当异步条件触发,取出其自身存储的i的值
    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;
        };
    }

方法二:闭包类

  • 利用闭包的机制去解决 推荐指数 **
  • 自执行函数,形成全新私有执行上下文【参数私有化,不受外界变量影响】,函数体内有一个创建的堆内存地址,被外面的方法占用,所以此堆和其上下文不被销毁

1.闭包第一种

  • 循环内直接为自执行函数
  • 自执行函数传递的参数值为当前i的值;在形成的闭包中存储起来
    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.闭包第二种

  • 为点击事件绑定自执行函数(传递的参数值为当前i的值),返回值为其点击操作的执行方法
    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.闭包第三种:使用forEach方法

    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.闭包第四种:LET也是基于闭包方案解决的 【推荐指数:三颗星】

  • 遇见{},并且for条件内有let声明变量,形成私有块级上下文,在此上下文中,开辟的堆内存地址被外界事件占用,不被释放,所以每次循环生成一个不被销毁的私有作用域,for循环每次执行,都将每次i的值存进当前形成的栈内存中
    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;
        };
    } 

方法三:终极方案:事件委托 【推荐指数:五颗星】

  • 事件委托:通过点击子级元素来触发父级元素的事件【点击子元素,一定会触发父元素的点击事件】
  • ev 事件对象:存储了当前操作的信息
  • ev.target 是点击的元素,称为事件源
    // 只产生一个堆,一个栈
    let prevIndex = 0;
    box.onclick = function (ev) {
        let target = ev.target; 
        if (target.tagName === "LI") {
            // 点击的是LI:基于getAttribute获取html结构上的自定义属性「它的索引」,获取结果为字符串类型
            let index = +target.getAttribute('index'); //字符格式转换为数字类型
            if (index === prevIndex) return;
            tabList[index].className = conList[index].className = 'active';
            tabList[prevIndex].className = conList[prevIndex].className = '';
            prevIndex = index;
        }
    };

最终实现结果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值