一、window对象
BOM属于window对象
1.BOM(浏览器对象模型)
bom里面包含着dom,只不过bom我们平时用得比较少,我们经常使用的是dom操作,因为我们页面中的这些标签都是在dom中取的,所以我们操作dom多一点。
window对象里面dom是核心(document object model),当然window对象里面除了dom对象之外还有很多其它很多对象(如navigator(显示你用的什么浏览器的),location(location有很多的方法,比如说我们的可以通过js的形式跳转页面,下载xx的时候不用写个a,写一个普通盒子就行了,因为我们js也可以跳转页面,实现下载的功能的,通过js的方式跳转页面。因为我们有的情况下是让它自动跳转页面,比如说我注册成功了,我让它5s之后自动跳转到首页,是自动跳转,不需要a链接来点击),history(历史对象),screen(屏幕)。
最大的是window,其次是document。
alert其实是window.alert(),console.log其实是window.console.log()。基本上我们所有的对象都是基于window的,所以大多数情况下可以省略。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>bom</title>
</head><body><script>// document.querySelector()// 按理说这个更合适,因为一层一层的去找,但是我们都知道window是最大的,没有比它更大的了,所以window是可以省略的 // window.document.querySelector()// 验证一下console.log(document === window.document)// 我们声明的这些函数或者全局变量,其实都挂在window上,我们调用的时候其实是window.fn()function fn() {console.log(11)}window.fn()// 我们全局声明的变量也是如此// var声明的是挂在window上,不是const,const不行// const num = 10// 所以var或者函数声明的这一些,默认都挂在window下,const和let就不是,因为它们是挂在自己的作用域内var num = 10console.log(window.num)</script>
</body></html>
2.定时器—延时函数
这个定时器和之前我们学的定时器interval不同,这个定时器只执行一次,(比如说弹出的一个小广告,显示几秒之后自动关闭,关闭之后就不再出来了),而之前学的定时器是一直执行的,(比如说轮播图)。第一个定时器是间歇定时器,间隔多少秒执行一次,而延时是多少秒之后执行一次,然后就不再执行了。和前面间歇函数的写法一模一样,只是名字和功能不一样而已。
定时器有两种,间歇(setInterval)和延时(setTimeout)
就执行一次为什么要清除?因为延时函数后面会有一些特殊的使用方法,它可以借助一种函数,叫递归函数,自己调自己可以来模拟定时器,但是自己调自己很容易把自己调晕,隔多少秒之后就来执行一次,执行完之后再回来调一次自己,又开始了。所以在特殊情况下我们需要清除。
平时我们只用一次的就不用去清除了,但是在某些特殊情况下,比如说setTimeout自己调自己,用到递归函数的时候我们就必须要清除了。这个后面会讲。
按道理,定时器都能开启和清除。setInteval返回的值是数字型的id(1、2…是第几个定时器),setTimeout返回的也是一个id(等待时间之后再回来执行回调函数)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>延时函数</title>
</head><body><script>// 到了2s之后打印1次之后就不会再继续打印了setTimeout(function () {console.log('时间到了')}, 2000)</script>
</body></html>
案例
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>5秒自动关闭的广告</title><style>img {position: fixed;left: 0;bottom: 0;}</style>
</head><body><img src="./images/ad.png" alt=""><script>// 1.获取元素 将图片获取过来const img = document.querySelector('img')// 2.开启定时器setTimeout(function () {img.style.display = 'none'}, 3000)</script>
</body></html>
3.JS执行机制
JS的执行机制:JS到底是怎么执行的。首先JS代码是从上往下执行的,第二,我们知道JS要解析,JS有JS解析器,浏览器有2个引擎,第一个引擎叫渲染引擎,可以渲染html,css,第2个引擎是用来解析JS的,叫JS解析器。在谷歌浏览器里面内置了一个V8引擎,是我们浏览器的一个,这个V8引擎是专门用来解析我们的JS的。
2个的结果都是1111,3333,2222
单线程有一个特点,比如说有一个元素,要进行删除操作,必须要先添加再删除,不能还没有就删除,不能同时进行添加和删除操作,这就是js的特点,从上往下依次执行,只能单线程不能多线程。单线程意味着我们所有的任务需要排队,前面一个任务结束后面一个任务才会进行,如果JS的执行时间过长,这样就会造成页面的渲染不连贯。
如果遇到上面那种耗时的操作,造成页面的阻塞,js有解决这个问题,利用了多核CPU的计算能力。JS本来是单线程的,但是我们为了解决这个问题,我们可以创建多个线程,所以这里JS出现了同步和异步。
定时器是异步任务,因为它是耗时的,哪怕是耗0s也是耗时的。耗时的任务都是异步任务,异步任务放在任务队列中的。(需要注意的是,我们JS是没有队列和栈的,只是借助其他语言的队列和栈来更好的理解我们这门语言)
什么是事件循环?(去面试一般都会问到)
就是整个执行栈里面执行完了之后,就去任务队列里面看,把任务队列里面的拿过来再执行,执行栈里面的这个异步任务执行完之后就又去任务队列里面拿,如果再有就再拿过来执行,反复这样转圈,就是一个循环。这个我们称之为事件循环。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事件循环</title>
</head><body><script>console.log(1)console.log(2)setTimeout(function () {console.log(3)})console.log(4)</script>
</body></html>
JS是单线程的,没有办法处理多线程,但是浏览器可以处理多线程。
先把1放入执行栈,再把2放入执行栈,因为同步任务是立马就执行完的,所以先打印1,再打印2。这个点击和定时器都耗时,是异步任务,所以JS就不处理了,提交给浏览器处理,将这两个异步事件放到浏览器,浏览器实时监测是先点击还是时间先到,假如是时间先到,就提交给任务队列,执行栈在执行完同步任务之后就一直在等待,所以当在任务队列里面看到时间之后就先执行时间,如果一直没点击就不执行点击事件(1234)。如果在执行完1、2之后,发现先点击了,就先把点击推过去,然后执行栈再取过来执行,之后时间到了才是时间(1243)
4. location对象
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>location</title>
</head><body><script>// console.log(window.location)// console.log(location)// console.log(location.href)// 不一定点链接让页面跳转,页面也可以自动跳转// 1. href 经常用href 利用js的方法跳转页面// 既然是属性,那我们就可以赋值// 最典型的一个使用场景是,当我注册成功之后,会提示5s之后自动跳转回首页(不需要点击,自动跳转,这是location.href的使用)location.href = 'http://www.baidu.com'</script>
</body></html>
这个经常见于注册一个账号,注册成功后几秒后自动跳转回首页,或者支付完之后多少秒跳转回首页。
我们应该用setInterval而不是setTimeout,因为里面的数字在变每隔1s就会换一次。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>5秒钟之后自动跳转页面</title><style>span {color: red;}</style>
</head><body><a href="http://www.itcast.cn">支付成功<span>5</span>秒钟之后跳转到首页</a><script>// 1. 获取元素const a = document.querySelector('a')// 2. 开启定时器// 3. 声明倒计时变量let num = 5// timerId里面存的是一个序号 let timerId = setInterval(function () {num--// 上来之后会立即变成4吗?不会,因为定时器1s之后才会调用这些代码a.innerHTML = `支付成功<span>${num}</span>秒钟之后跳转到首页`// 倒计时成了0就应该跳转了,并且关闭定时器// timeId里面存的是序号// 如果num === 0 则停止定时器,并且完成跳转功能if (num === 0) {// 没有名字清除不了clearInterval(timerId)// 4. 跳转 location.hreflocation.href = 'http://www.itcast.cn'}}, 1000)</script>
</body></html>
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>location属性和方法</title>
</head><body><!-- 表单一定要有name,不然提交不了数据 --><form action=""><input type="text" name="username"><input type="password" name="pwd"><button>提交</button></form><!-- 只有那一块变了,后面vue会学到 点击之后是my就把my显示过来,是friend就把friend显示过来 --><!-- 用哈希拿到#后面的值 知道是点的哪一个 --><a href="#/my">我的</a><a href="#/friend">关注</a><a href="#/download">下载</a><button class="reload">刷新</button><!--location 除了那些属性外,还有一个方法 --><script>const reload = document.querySelector('.reload')reload.addEventListener('click', function () {// 类似于按下F5 刷新页面// location.reload()// 强制刷新 ctrl+f5location.reload(true)})</script>
</body></html>
spa单页面应用程序,有很多都是一个页面,它只是其中一块变了,别的没变,上面没刷新。这种应用后面会学,因为它是一种非常好用的应用,因为它不需要刷新整个页面,加载资源就会比较少,这种应用页面没有变,它没有跳转页面就可以实现页面跳转,它的实现原理是它是通过#后面的值来发生变化来发生判断的,所以我们在获取的时候一定要拿到#后面的值,后面还会讲。我们可以通过哈希来拿到这个值。
true强制执行,比如说有些网页上面的小图标,favicon已经引进来了,但是图标一直不出来,刷新也不管用,这时候可以ctrl+f5强制刷新,所谓强制刷新就是不从本地里面去取数据了,而是在线重新渲染重新拉下来这些数据
5. navigator对象
比如京东的页面,PC端打开就是PC端,移动端打开就是移动端。
这个代码不需要我们写,正则都出来了,不用去背,要用的时候直接cv就行。
如果是手机端跳转到http://m.itcast.cn上去,如果是pc端就不跳转。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>自动跳转到移动端</title><!-- 拿过来之后直接放在最上面就行了 --><!-- 这是立即函数加了一个! --><script>// 检测 userAgent(浏览器信息)!(function () {const userAgent = navigator.userAgent// 验证是否为Android或iPhoneconst android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)// 如果是Android或iPhone,则跳转至移动站点if (android || iphone) {// 只需要把它换成自己想要的地址就可以了location.href = 'http://m.itcast.cn'}})()// 写在上面也可以// 最后一个要加;// (function () { })(); 这个前面加不加!无所谓// 还有人喜欢这么写:这样写是错误的,因为JS不会认为这是一个整体,它会认为是调用的一个function函数// function(){}()// 但是如果加一个!或者+或者~等等,只要是一个算术表达式都能的,它就会把前面当做一个整体了// !function () { }()</script>
</head><body>这是PC端的页面<!-- <script>// (function () { })()</script> -->
</body></html>
6. history对象
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>history对象</title>
</head><body><button>后退</button><button>前进</button><script>const back = document.querySelector('button:first-child')// back的下一个兄弟const forward = back.nextElementSibling// 回调函数back.addEventListener('click', function () {// 后退一步// 怎样实现后退?复制有前进后退按钮的地址到一个页面中打开即可// history.back()// 单词难记,推出了更好记的方法history.go(-1)})forward.addEventListener('click', function () {// 前进一步// 要是前进不了是因为没有再打开一个新的页面,在前进后退按钮页面再打开另外一个地址// history.forward()history.go(1)// location.reload可以刷新页面})</script>
</body></html>
二、本地存储
1. 本地存储介绍
本地存储是一个仓库,用于存放数据,你可以理解为“数据库”,但是这个数据库比较特殊,是放在浏览器里面的。以前我们也有一个小仓库存放数据,叫变量,变量是放在内存的,内存有一个特点,一刷新就没了,但是有的数据我们不想丢失,所以我们在浏览器里面添加了一个小仓库,用于存放数据,只要浏览器在它就在,数据不会丢失。
本地存储一共有两个,sessionStorage和localStorage,每个都约5M左右。https://todomvc.com/examples/vanilla-es6/
2. 本地存储分类
大多数时候是可以共享的,当然也有不能的情况,当我们讲ajax的时候,它如果涉及到跨域的问题,是不能访问的,后面会讲。
另外打开一个页面,跟刚才的页面没有任何关系,发现有storage但是发现没有这些数据,原因是我们没有学过跨域,比如说京东和百度的数据肯定是不能共享的,他有不同的域名,前面的域名不相同是不能取的。当我们打开history对象这个代码的页面,F12点击storage(我们还没有讲跨域,讲了跨域之后就懂了)
我们发现打印出的18是字符型的。由此可知,本地存储只能存储字符串数据类型,不管什么类型都给你转换成字符串类型。这个跟变量不一样。
localStorage关闭浏览器不会消失,sessionStorage关闭浏览器就消失。和变量的区别是sessionStorage也是存到仓库里面去的,只不过它关闭浏览器就会消失。sessionStorage和localStorage的用法一致(存,获取,删除,改)。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>localStorage存储</title>
</head><body><script>// 要存储一个名字 uname// 键值对要加引号,不加引号就当成变量来看了,是个字符串 //属性名 值localStorage.setItem('uname', 'pink老师')// 2. 获取方式 都加引号console.log(localStorage.getItem('uname'))// 3. 删除本地存储 只删除名字 (手动删除)// 能打印:因为是先存再打印最后再删除的// localStorage.removeItem('uname')// 4. 改 (如果原来有这个键,则是改,如果没有这个键则是增)localStorage.setItem('uname', 'black')// 我要存一个年龄 18不用加引号,因为是数字型localStorage.setItem('age', 18)// 除了数字型,其他的都要加引号// 实在记不住,全部都加上引号也行// 我们发现打印出的18是字符型的 由此可知,本地存储只能存储字符串数据类型,不管什么类型都给你转换成字符串类型console.log(localStorage.getItem('age'))// sessionStorage和localStorage的用法一致</script>
</body></html>
3. 存储复杂数据类型
一个一个地存太麻烦,考虑存对象或者数组的形式
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>本地存储复杂数据类型</title>
</head><body><script>const obj = {uname: 'pink老师',age: 18,gender: '女'}/* //存储 复杂数据类型// 第一个obj是键 第二个obj是值(因为是变量了所以就不加引号了)localStorage.setItem('obj', obj) //直接采取这种格式我们得到的是[obj][obj]这种格式,为解决这个问题,我们把复杂数据类型转换成JSON字符串/*//这样也可,只不过上面那种更好localStorage.setItem('obj', {uname: 'pink老师',age: 18,gender: '女'})*/// 取 */// console.log(localStorage.getItem('obj'))// 不能像上面那么写 我们应该转换完了再存// 1. 复杂数据类型存储必须转换为JSON字符串存储// 因为要转换所以JSON.stringfy()是个方法// 这个代码会把对象转换为字符串存进去,也可以直接把obj里面的内容直接复制到方法的括号里面去localStorage.setItem('obj', JSON.stringify(obj))// 在本地存储里面只能存储字符串// 取// 打印出来是黑色的 所以是字符串而不是对象// console.log(localStorage.getItem('obj'))// 也可以检测一下是不是字符串console.log(typeof localStorage.getItem('obj'))// JSON对象 特点:属性和值有引号,而且引号统一是双引号// {"uname":"pink老师","age":18,"gender":"女"} //看着像对象但它已经不是对象了,它是字符串// 我们取过来的是字符串,不能直接用,这样就拿不到数据(uname,age这些)了,那么反过来,我们把这个字符串转化成对象,以后就可以拿过来数据直接渲染了// 2. 把JSON字符串转换为对象// 先获取再转换console.log(JSON.parse(localStorage.getItem('obj')))/* // 如果觉得看起来挺费劲,可以这样const str = localStorage.getItem('obj')console.log(JSON.parse(str)) */</script>
</body></html>
三、综合案例
对象数组,因为数据有很多条,所以我们要把对象放到数组里面去
(2). 如果没有数据,则用 空数组来代替 的具体实施:
逻辑或中断,如果有数据就把数据存进去,返回的就是数组,数组不为空,那就是真的,根据逻辑或中断的原则,如果前面为真,则后面就不执行了,但是如果把本地存储里面的数据清空,那它里面就没有内容,没有内容就为空,空就是假,取出一个空字符串,根据逻辑或中断的原则,前面为假则执行后面的,那么就把后面的空数组返回过来了。
我们可以把存储数据注释起来看看效果。
根据本地存储的原则,即使已经注释起来了,但是刚才已经存过了,所以也有,我们可以把它清空,本地存储就没有数据了,重新刷新一下页面,再打开控制台,就会发现返回的是一个空数组,那说明我们写的程序是正确的,因为一会儿我们渲染页面,如果数组里面有数据,就根据数组里面的数据渲染页面,如果数组是空的,那么渲染的页面也就是空的页面。
前面我们已经拿到了数据,接下来我们根据数据渲染页面,比如说我有几条数据就可以在下面渲染几行
以前我们用字符串拼接str+=这种效率并不高,而我们以后的开发中利用的是map和join这两种数组方法来实现字符串拼接,这种的效率会更高一些,那么就意味着从今天开始以后所有的渲染页面拼接字符串统一使用数组的这两个方法来实现。因为这个方法需要以前拼接字符串str方式的铺垫,前面的学习了后面再学习这个会更简单。
1.map的语法格式:map里面有一个回调函数,回调函数里面有2个参数,这个写法和for each很像,ele(element)是数组元素,index是数组索引号,只不过map在使用的时候可以添加下面这一句话:return ele + ‘颜色’,它的意思是让数组的每个元素进行添加颜色,每循环一次就把当前的结果返回过来,一共有3个数组元素所以循环了3次,那拼接完,map最大的特点是有返回值,返回新的数组,现在我们通过new arr把数组拿过来了,返回的就是把数据处理之后的结果,并且放到了一个数组里面。
2.map不仅可以遍历数组还可以处理数据,比如说可以让数组里的每一个数据拼接字符串,第一次循环让red加上颜色,第二次循环让blue加上颜色…每次循环都有一个返回值返回到了数组。它的本质是这样子:只要用到了map,它的返回值就是数组,先准备一个空数组,第一次循环就可以把red颜色返回过来,第二次循环就是blue颜色…,这样的话整个map执行完就会生成一个新的数组,然后我们就可以拿到这个数组了。
分析:我们前面已经能够拿到数据了 ,数组里面放着相关对象,然后我们可以根据里面有几条数据生成几个tr。我们可以这么去做,可以用map去遍历数组,看看有几个对象,然后根据对象去处理数据,也就是说可以生成tr,在生成tr的同时我们就就可以把拿到的数据的值放到相应的位置,也就是说我在生成tr的时候,把数据填充进去。第一次循环就生成一个tr,但是如果数组里面有多条数据,那么每次循环都有一个返回值放到数组里面去再生成第二个tr,所以map方法最大的特点就是可以生成对应数据的tr,然后再把数据填充进去。我们知道map方法最大的特点是有一个数组返回给我们,等到map全部遍历完了就根据你有几条数据返回对应的数组,所以map有一个数组返回给我们了,但是虽然里面有相应的tr了,它毕竟是个数组,我们没有办法把数组直接追加给tbody,所以我们还需要做一步,通过join的方法把数组转换成字符串,也就是说我们要的不是数组是字符串。我有2个tr全都连在一起就形成了一个字符串,我们最后就可以把这个字符串追加给tbody,有了这些tr页面之中就可以把它渲染出来了。这个就是我们说的通过map和join方法实现渲染页面的一个思路了。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>数组map方法和join方法</title>
</head><body><script>const arr = ['red', 'blue', 'pink']// 1.数组map方法 处理数据并且返回一个数组// 不要用map方法来遍历数组,不然就违背了我们设计map方法的初衷了。map方法设计的主要原则是我想要得到一个数组返回一个数组,就用map。在进行map遍历的时候,可以让它处理一些数据,然后再把处理的结果返回给我,这才是map的正确处理方法。const newArr = arr.map(function (ele, index) {// console.log(ele) // 数组元素// console.log(index) // 索引号return ele + '颜色'})console.log(newArr)// 2. 数组join方法 把数组转换为字符串// 小括号为空则默认逗号分隔,因为我们都知道数组默认逗号分隔console.log(newArr.join()) // red颜色,blue颜色,pink颜色// 小括号是空字符串,则元素之间没有分隔符console.log(newArr.join('')) // red颜色blue颜色pink颜色console.log(newArr.join('|')) // red颜色|blue颜色|pink颜色</script>
</body></html>
步骤:
因为我们要做渲染,所以我们还是封装成一个渲染函数叫做render,因为我们后面不管是增加也好还是删除也好需要重新渲染页面,所以我们封装成一个函数。
把字符串赋值给tbody之后,tbody就可以把tr显示出来了。
前面已经把数据存到本地存储里面,本地存储有数据,有数据我们就可以通过map的方法来遍历arr数组,在遍历的同时就可以return返回tr,返回字符串,在返回的同时把里面的相关数据换成了活的数据,map最大的特点是返回的是一个数组,那么就把return后面的东西返回给了数组装起来了,简单的来说我们有一个数组,return就把tr拿过来放到数组里面,这样我们就可以拿到数组了,经过刚才的验证我们发现数组里面确实存的是一个tr的字符串。
先把render函数封装一下,然后通过map方法拿到返回值的数组,接下来只需要把数组转换成字符串就可以追加给tbody了。在上面先把tbody获取一下。
共有数据几条:是根据数组的长度得来的,数组里面有几条数据就变成几,所以我们只需要把盒子拿过来,把它的值改成数组的长度就行了。
点击添加没有实现跳转,说明阻止默认行为成功了,接下来做非空判断,如果前3个为空就不允许增加数据。终止程序,并且返回一个提示框。首先把相关的数据获取过来,在提交事件里面。
当我们新增一条数据的时候一定要把它存储到本地存储里面,不然页面一刷新数据就丢失了。当它存储完毕之后,就可以拿到本地存储的最新数据,再去渲染我们的页面,这样页面就会多一条数据,又因为本地存储的数据不会丢失,所以再刷新,页面中的数据就不会消失了 。
页面一刷新,代码从上往下去执行,我们的初始值已经没有用到了,刚开始的本地存储一定要注释起来,要不然我们刚才录入的数据就会被覆盖掉了,页面就只剩初始的那一条数据了。这句话只是为了测试用的。(localStorage.setItem(‘data’, JSON.stringify(initData)))
这句话一读到的时候,我就要去本地存储里面读最新的data数据,我们前面已经把那两条数据存到data里面去了,那arr取过来的就是最新的那两条数据,有数据,所以后面的空数组不执行。然后代码从上往下继续,渲染函数刚开始不执行,但是下面有一个渲染函数,所以就回头调用,一回头调用我们就拿arr里面的两条数据来做渲染,所以只要往本地存储里面去存储了,那本地存储里面存的就是最新的数据,然后每次刷新页面,我就要从本地存储里面取最新的数据然后赋值给arr,那arr就可以来进行遍历渲染了,这就是它的执行过程,所以我们在做提交事件的时候,千万不要忘了最后一条:把最新的arr存到data里面,新增业务完成。
删除业务
为什么给tbody注册点击事件呢?因为我们会有新的数据产生,如果直接给按钮注册点击事件,我们用原先传统的方式是注册不上的,而 事件委托可以给新增的这些数据添加点击事件,又因为按钮有行都会变化的,而tbody是不变的,所以我们给他们的父亲tbody注册点击事件。这么多按钮,如何知道点了哪一条数据呢?知道当前按钮的索引号就可以了。map里面除了有一个ele参数以外,还有一个index可以得到数组元素的索引号,所以在遍历的时候就可以得到每一条数据的索引号了,现在只需给a加上一个自定义属性就可以了
没有数据arr.length就是0,0当假来看,则执行表达式2的那一个值,如果里面有2条数据,数字型的2当真看,则执行表达式1的结果,把结果返回过来再赋值给stuId。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><title>学生就业统计表</title><link rel="stylesheet" href="./iconfont/iconfont.css"><link rel="stylesheet" href="css/index.css" />
</head><body><h1>学生就业统计表</h1><form class="info" autocomplete="off"><input type="text" class="uname" name="uname" placeholder="姓名" /><input type="text" class="age" name="age" placeholder="年龄" /><input type="text" class="salary" name="salary" placeholder="薪资" /><select name="gender" class="gender"><option value="男">男</option><option value="女">女</option></select><select name="city" class="city"><option value="北京">北京</option><option value="上海">上海</option><option value="广州">广州</option><option value="深圳">深圳</option><option value="曹县">曹县</option></select><button class="add"><i class="iconfont icon-tianjia"></i>添加</button></form><div class="title">共有数据<span>0</span>条</div><table><thead><tr><th>ID</th><th>姓名</th><th>年龄</th><th>性别</th><th>薪资</th><th>就业城市</th><th>录入时间</th><th>操作</th></tr></thead><tbody><!-- <tr><td>1</td><td>迪丽热巴</td><td>23</td><td>女</td><td>12000</td><td>北京</td><td>2099/9/9 08:08:08</td><td><a href="javascript:"><i class="iconfont icon-shanchu"></i>删除</a></td></tr> --></tbody></table><script>// 参考数据const initData = [{stuId: 1,uname: '迪丽热巴',age: 22,salary: '12000',gender: '女',city: '北京',time: '2099/9/9 08:08:08'}]// 这里是为了测试,先存了一下,我们看到我们正确的拿到了数据,但是建议把这一行一定要把它注释起来,因为我们页面第一次打开肯定是没有数据的,所以我们拿个空数组去代替是没有问题的,后面做增加等业务的时候就可以把最新的数据放到本地存储里面,这样再打开页面的时候就可以从本地存储里面取真正的数据了。// 页面一打开从上往下依次去执行,这里有一个对象数组,下面一句话把这个数据存到本地存储里面,那就意味着我们每次打开页面都会重新存储一下这个data数据,把它存储到本地存储里面去,如果后面的数据发生了变化,我们一打开就又给我们覆盖掉了,所以一定要把下面这句话注释起来,因为本地存储已经存到浏览器里面去了,本地存储(aplication)一直都是存在的,所以我们没有必要打开页面又重新去填写了// localStorage.setItem('data', JSON.stringify(initData)) // 把这个解开之后就可以往本地存储里面去存这个数组了,本地存储有了数据我们就可以来遍历这条数据了// 1. 渲染业务// 1.1 先读取本地存储的数据// (1). 本地存储有数据则记得转换为对象然后存储到变量里面,后期用于渲染页面// 我们数组本质上也属于对象// (2). 如果没有数据,则用 空数组来代替 const arr = JSON.parse(localStorage.getItem('data')) || []console.log(arr)// 1.2 利用map和join方法来渲染页面const tbody = document.querySelector('tbody')// 渲染页面需要封装一个函数function render() {// (1). 利用map遍历数组,返回对应tr的数组// 每循环一次就返回一个tr,在返回的同时把相关的数据填充进去// map里面的第一个参数是ele(数组元素), 里面的每一条数组元素,也就是数组对象// map在遍历数组,结果发现只有一条数据它就会这么做,map一旦遍历就会有一个空数组等着map的返回值,一运行的时候就发现有一个return,把tr返回,在把tr返回的同时把里面的数据更换,所以旧表整个tr装到了数组里面,因为只有一条数据,所以只遍历一次就结束了,那我们就可以得到一个新的数组,那我们就可以把这个数组拿到了// map的另一个参数:index,可以得到数组元素的索引号,所以我在遍历的时候就可以拿到每一条数据的索引号了,现在只需要给a加一个自定义属性,data-id = 索引号const trArr = arr.map(function (ele, index) {return `<tr><td>${ele.stuId}</td><td>${ele.uname}</td><td>${ele.age}</td><td>${ele.gender}</td><td>${ele.salary}</td><td>${ele.city}</td><td>${ele.time}</td><td><a href="javascript:" data-id="${index}"><i class="iconfont icon-shanchu"></i>删除</a></td></tr>`})console.log(trArr)// (2). 把数组转换为字符串 (join)// (3). 把生成的字符串换追加给tbody// 把多个tr转换成一个字符串tbody.innerHTML = trArr.join('')// 显示共有几条数据document.querySelector('.title span').innerHTML = arr.length}// 在打印的时候千万别忘了调用一下render函数render()// 到浏览器里面看看能不能拿到数组里面存着的tr,发现输出的是两个空数组,可能有一个情况是本地存储里面没有拿到数据,我没有遍历到,所以没有办法 往里面去存数据。到应用里面看看,果然本地存储里面没有数据,因为得到的是一个空数组,所以当然本地存储里面存不进去了,空数组没有办法遍历,所以没有办法加tr// 2. 新增业务// 2.1 form表单注册提交事件,阻止默认行为const info = document.querySelector('.info')const uname = document.querySelector('.uname')const age = document.querySelector('.age')const salary = document.querySelector('.salary')const gender = document.querySelector('.gender')const city = document.querySelector('.city')info.addEventListener('submit', function (e) {// 因为要阻止默认行为(跳转之类的),我们先不让它提交,所以要写个事件对象,然后去调用事件对象里面的方法e.preventDefault()// 2.2 非空判断if (!uname.value || !age.value || !salary.value) {return alert('输入内容不能为空')}// 2.3 给 arr 数组追加对象,里面存储表单获取过来的数据arr.push({// 新增一条新的数据,所以ID号就是原来的数组的长度+1,因为push是后追加// 这种做法不可取,因为如删除第一条数据之后再录入会出现ID号相同的情况// stuId: arr.length + 1,// 处理 stuId: 数组最后一条数据的stuId + 1// 还需考虑数据全部删除的情况// 全部删除之后再添加数据就报错了,因为我们数组里面没有数据,我们再用最后一条数据获取它的stuId就报错了,所以我们要加一个判断条件,如果没有数据,就直接让它赋值为1就行了 arr.length判断数组有没有长度stuId: arr.length ? arr[arr.length - 1].stuId + 1 : 1,uname: uname.value,age: age.value,salary: salary.value,gender: gender.value,city: city.value,// 用户录入了一条信息,它就自动获取当前的时间time: new Date().toLocaleString()})// 2.4 渲染页面和重置表单 (reset() 方法)render()// form表单是info,其实就是我们的this,因为this指向我们函数的调用者,也就是info。写this和info都可以this.reset() // 重置表单(把表单输入框里面的值自动重置,恢复到原来的样子,清空内容)// 2.5 把数组重新存入本地存储里面,记得转换为JSON字符串存储// 因为我们已经有data这个数据了,一旦存了新的就把以前的覆盖掉了,实现了替换,这样就保证了data里面存的是最新的数据localStorage.setItem('data', JSON.stringify(arr))})// 3. 删除业务// 3.1 采用事件委托形式,给tbody注册点击事件tbody.addEventListener('click', function (e) {// 判断是否点击的是删除按钮 A链接if (e.target.tagName === 'A') {// alert(11)// 3.2 得到当前点击链接的索引号,渲染数据的时候,动态给a链接添加自定义属性例如 data-id="0"// 点击的那个对象有一个自定义属性叫data-id,这样可以拿到当前的索引号console.log(e.target.dataset.id)// 确认框确认是否要真的删除// confirm会返回两个值,如果点击确定就返回true,true就表示我要执行里面的代码,不用做删除,如果用户点击的是取消就返回false,就不执行大括号里面的代码if (confirm('你确定要删除这条数据吗?')) {// 3.3 根据索引号,利用 splice 删除数组这条数据arr.splice(e.target.dataset.id, 1)// 3.4 重新渲染页面render()// 3.5 把最新 arr 数组存入本地存储// 不然删了之后本地存储里面也还是有数据localStorage.setItem('data', JSON.stringify(arr))}}})</script>
</body></html>
下面这个案例未找到