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>
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") {
let index = +target.getAttribute('index');
if (index === prevIndex) return;
tabList[index].className = conList[index].className = 'active';
tabList[prevIndex].className = conList[prevIndex].className = '';
prevIndex = index;
}
};
