[toc]
# 题1、使用原生 JS 的 DOM 实现:1. 弹出一个框,可以输入一个数字 n 。 2. 然后在页面中制作 n*n 个的格子 3.每个格子鼠标放上时背景色变为红色,鼠标挪开时颜色恢复 4. 当点击一个格子时,颜色变成绿色并锁定这个颜色(鼠标放上、挪开不改变颜色) 5.点击一个已经被锁定的元素时,取消锁定颜色恢复,继承可以鼠标放上、挪开时改变颜色。(格子大小可以使用 CSS 控制,背景色的改变通过 JS 控制)

## 实现01-先根据 N 生成 table

## 实现02-鼠标放上、挪开时改变背景色
说明:因为为每个 td 绑定事件,比较浪费资源(td太多),所以我们可以使用 `事件委托` 技术只在 table 上绑定一个事件即可:

## 实现03-标记一个格子被锁定了
为了标记至少有两种实现思路:
思路一、创建一个 Map 数据类型,保存每个格子是否被锁定的状态。
let locked = new Map()
// 当点击一个格子时
locked.set(格子对象, true) // 被锁定了
思路二、直接在鼠标上自定义一个属性来标记它的状态
<td data-locked="1"></td>
<td data-locked="0"></td>
使用时直接 对象.dataSet 即可
以下采用第二种 dataset 的方案

## 实现04-如果锁定就不允许修改
在鼠标放上和挪开时判断是否被锁定:

## 实现05-再次点击时解锁

## 扩展:面向对象的写法

# 題2、弹出两个框,一个框输入一个拥有总金额数量(整数),另一个框可以输入多个商品的价格(多个数字之间用空格隔开),计算得出,这些总金额最多能购买多少件商品(每件商品只能购物一次)。
比如:输入
总金额:100
多个商品价格:10 40 49 60 70 80 100 300
得出的结果:
最多能买:3 个
哪些价格的商品:10 40 49
花费多少钱:99
实际思路:
1. 先把价格的字符串转成数组(split 通过空格转)
2. 对数组排序(sort((a,b)=>a-b))
3. 循环数组从第1个(最便宜的)开始循环累加商品金额,直到累加的金额大于,总金额为止
~~~
// 总金额
let totalPrice = prompt("请输入总金额:")
// 商品价格列表
let goodsPrice = prompt("请输入商品价格(多个用空格隔开):")
// 商品价格列表转数组
let goodsArr = goodsPrice.split(/\s+/) // 根据至少一个连续的空格转数组
// 让数组升序排列
goodsArr.sort((a,b)=>a-b)
// 从数组的第一个商品开始购买
let sum = 0 // 已购买的商品的总价
let buy = [] // 已购买的商品的单价
let price
for(let i=0; i<goodsArr.length; i++) {
// 价格转成数字(+、Number、parseInt、parseFloat)
price = Number(goodsArr[i])
// 如果没有超出就继续
if( (price + sum) <= totalPrice) {
sum += price
buy.push(price)
} else {
// 超出了就退出
break
}
}
// 结果输出
console.log("拥有的总钱数:"+totalPrice)
console.log("可以购买的商品列表:"+goodsPrice)
console.log("最多能购买:"+buy.length+" 个商品")
console.log("能够购买的商品价格为:"+buy.toString())
console.log("购买这件商品共花费:"+sum)
~~~
运行结果:

# 题3、随机颜色的 99 乘法表

代码实现:

# 题4、JS 原生购物车
数据:
~~~
const data = [
{
"image": "https://img13.360buyimg.com/babel/s1180x940_jfs/t1/112612/27/8633/100927/5ed0ffe3Ee5006a06/142195ec551409e6.jpg.webp",
"goods_name": "小米移动电源10000mAh",
"price": "135",
"comment": "1.4万"
},
{
"image": "https://img10.360buyimg.com/pop/s1180x940_jfs/t1/133282/15/506/78667/5ece44afEd0d8193e/89395514aa661a69.jpg.webp",
"goods_name": "小米电源 高配版",
"price": "135",
"comment": "1.4万"
},
{
"https://img10.360buyimg.com/da/s1180x940_jfs/t1/120568/26/3467/101836/5ed0fda0E49973841/e1801a3d7e067ce7.jpg.webp",
"goods_name": "小米活塞耳机",
"price": "135",
"comment": "1.4万"
},
{
"https://imgcps.jd.com/ling/100008348542/5omL5py66LSt5a6e5oOg/5aSH6LSn6LaF5YC8/p-5bd8253082acdd181d02fa33/28403921/590x470.jpg",
"goods_name": "小米耳机",
"price": "135",
"comment": "1.4万"
}
]
~~~

OOP代码实现:
1. html 和 CSS

2. 购物车类

3. 为类添加渲染数据的方法

4. 渲染购物车表格

5. 加入购物车

6. 删除

7. 使用

# 题5、表单验证

# 题6、有一个数组,数组中有10件商品,每件商品的是一对象类型的数据,在页面中每次显示三条记录,并可以上下按钮翻页?
~~~
<div id="app"></div>
<button onclick="prev()">上一页</button>
<button onclick="next()">下一页</button>
/********** 1. 构建 10 件商品的数据 *******/
let data = []
// 循环生成 10 件商品
for(let i=0; i<10; i++) {
data.push({
id: i+1,
goods_name: '手机-' + i,
price: (Math.random()*100).toFixed(2)
})
}
/*********** 2. 在页面中渲染三件商品
如何截取数组?
slice:不会修改原数组
splice:从原数组中把截取的数据删除
翻页?
使用 slice 从数组中截取出三件商品,截取时的下标:
第1页 --》 0 ~ 3
第2页 --> 3 ~ 6
第3页 --> 6 ~ 9
第4页 --> 9 ~ 12
.......
第n页 --> (n-1)*3 ~ (n-1)*3+3
******/
const app = document.getElementById('app')
// 当前页
let page = 1
// 显示第 i 页的数据
function showData(page) {
// 计算截取的下标
let start = (page-1)*3
let end = start + 3
// 截取出第1页的数据
let goods = data.slice(start, end)
// 先把原数据清空
app.innerHTML = ''
goods.forEach(v=>{
let div = document.createElement('DIV')
div.innerHTML = "商品名称:"+v.goods_name + ",价格:¥"+v.price
app.appendChild(div)
})
}
showData( page )
// 下一页
function next() {
// 如果不是最后一页
if(page < 4) {
page++
showData(page)
}
}
// 上一页
function prev() {
// 如果不是第1页
if(page > 1) {
page--
showData(page)
}
}
~~~
扩展练习:有一个数组,数组中有10件商品,一次在页面中显示三件商品,有一个“换一换”,每点击一次换三件。要求:页面中始终显示三件商品,不够三件时从前面取,比如:最后只剩一件不够三件了,那么就从最前面拿2件凑够三件显示。
实现思路:1. 每次从数组中截取出前三件商品并从数组中把这三件商品删除(splice(0,3))
2. 把截取出的三件商品再合并到数组的最后(concat)
~~~
<button onclick="change()">换一换</button>
function change() {
// 取出前3件商品,并从数组中删除这三件
let goods = data.splice(0,3)
// 把这3件商品再放回数组的最后
data = data.concat(goods)
// 渲染这三件商品
app.innerHTML = ''
goods.forEach(v=>{
let div = document.createElement('DIV')
div.innerHTML = "商品名称:"+v.goods_name + ",价格:¥"+v.price
app.appendChild(div)
})
}
~~~
# 题7、实现一个文章搜索功能:制作一个搜索框和一个搜索按钮,当点击搜索按钮时就调用接口搜索相关文章,每页显示15条,并实现翻页功能?
接口文档地址:[http://ttapi.research.itcast.cn/app/v1\_0/search](http://ttapi.research.itcast.cn/app/v1_0/search)
请求方式:GET
参数:q(搜索关键字) page(当前页码) per_page (每页条数)
使用技术:原生 JS
制作完之后的效果:

代码实现:
HTML 和 CSS

封装原生 AJAX 为Promise 对象

两个辅导函数

点击搜索时调用接口获取某一页的数据

根据返回的数据渲染翻页按钮和数据列表

搜索联想功能

点击翻页按钮时重新调用接口

# 题8、现有三个接口:登录-注册 、获取短验证码、令牌过期刷新,请使用前端技术实现登录功能?
功能描述:

代码实现(使用 OOP 的语法)
1. 静态页面

2. 封装 AJAX 对象

3. 创建一个辅助类

4. 创建实现业务逻辑的类

类中有三个方法


5. 在页面中使用这个类实现功能

# 题9、以第题8的基础上,能够获取并修改头像?
接口一、获取用户信息的接口
地址:http://ttapi.research.itcast.cn/app/v1_0/user/profile
方式:GET
请求参数:

接口二、修改头像
地址:http://ttapi.research.itcast.cn/app/v1_0/user/photo
方式:PATCH
请求参数:

HTML 代码
~~~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
}
#login-layer {
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.4);
position: fixed;
left: 0;
top: 0;
display: none;
}
#login {
width: 400px;
height: 250px;
background-color: #fff;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-radius: 10px;
padding: 50px 0 0 50px;
box-sizing: border-box;
}
input {
padding: 5px;
margin-bottom: 20px;
}
#avatar {
max-width: 350px;
}
</style>
</head>
<body>
<div id="login-info"></div>
<hr>
<div><img id="avatar"></div>
<button id="btn-info">获取头像</button>
<div>
<input type="file" id="upload-file" />
<br>
<button id="upload-btn">更新头像</button>
</div>
<!-- 登录表单 -->
<div id="login-layer">
<div id="login">
<form id="login-form">
<div><input type="text" name="mobile" placeholder="请输入11位手机号码"></div>
<div>
<input type="text" name="code" placeholder="请输入短信验证码">
<button name="send-code">发送短信验证码</button>
</div>
<div>
<button name="login">登录</button>
<button name="close">关闭</button>
</div>
</form>
</div>
</div>
<script src="./index.js"></script>
<script>
new Index()
</script>
</body>
</html>
~~~
JS 代码
~~~
// Index 类(逻辑逻辑)
function Index() {
// 创建工具对象
this.tool = new Tool()
// 获取页面中的元素
this.loginInfo = this.tool.$('login-info')
this.loginLayer = this.tool.$('login-layer')
this.loginForm = this.tool.$('login-form')
this.infoBtn = this.tool.$('btn-info')
this.avatar = this.tool.$('avatar')
this.uploadFile = this.tool.$('upload-file')
this.uploadBtn = this.tool.$('upload-btn')
// 1. 检查登录信息
this.checkLogInfo()
// 2. 处理登录、退出两个按钮
this.bindLoginBtn()
// 3. 处理表单中的按钮
this.bindFormBtn()
// 4. 显示个人信息
this.getUserInfo()
// 5. 上传图片
this.bindUpload()
}
// 判断登录信息
Index.prototype.checkLogInfo = function () {
if (localStorage.getItem('token') === null) {
this.loginInfo.innerHTML = '您好,游客 <button name="login">登录</button>'
} else {
let mobile = localStorage.getItem('mobile')
this.loginInfo.innerHTML = `${mobile} <button name="logout">退出</button>`
}
}
// 为登录、退出绑定点击事件
Index.prototype.bindLoginBtn = function () {
// 为父元素绑定一个点击事件,处理这个元素中所有的按钮(事件委托)
this.loginInfo.onclick = e => {
// 当点击的是按钮时
if (e.srcElement.nodeName === 'BUTTON') {
// 点击是登录
if (e.srcElement.name === 'login') {
// 显示登录层
this.loginLayer.style.display = 'block'
}
// 点击的是退出
if (e.srcElement.name === 'logout') {
// 清空令牌
localStorage.clear()
// 重新检查登录状态
this.checkLogInfo()
}
}
}
}
// 为表单中的按钮
Index.prototype.bindFormBtn = function () {
this.loginForm.onclick = e => {
if (e.srcElement.nodeName === 'BUTTON') {
// 阻止按钮的默认行为(提交表单)
e.preventDefault()
if (e.srcElement.name === 'send-code') {
// 1. 验证手机号码并去掉左右空格
let mobile = this.loginForm.mobile.value.trim()
let RE = /^1[34578]\d{9}$/
if (!RE.test(mobile)) {
return alert("手机号码格式不正确!")
}
// 2. 发送短信(调用接口)
axios.get('/sms/codes/' + mobile).then(res => {
if (res.message === 'OK') {
// 3. 按钮 60s 倒计时
let sec = 60
// 按钮状态为禁用
e.srcElement.disabled = true
e.srcElement.innerText = '60s'
// 启动定时器
let si = setInterval(() => {
sec--
if (sec < 0) {
// 停止定时器
clearInterval(si)
e.srcElement.disabled = false
e.srcElement.innerText = '发送短信验证码'
} else {
e.srcElement.innerText = sec + 's'
}
}, 1000)
} else {
alert('发短信失败!')
}
})
} else if (e.srcElement.name === 'login') {
// 1. 表单验证
let mobile = this.loginForm.mobile.value.trim()
if (!/^1[34578]\d{9}$/.test(mobile)) {
return alert("手机号码格式不正确!")
}
let code = this.loginForm.code.value.trim()
if (!/^\d{6}$/.test(code)) {
return alert("短信验证码不正确!")
}
// 2. 调用接口
axios.post('/authorizations', { mobile, code }).then(res => {
if (res.status == 201) {
// 3. 保存令牌
localStorage.setItem('token', res.data.data.token)
localStorage.setItem('refresh_token', res.data.data.refresh_token)
// 如何把 137 8888999 9 中间 7 位变成* ---> 137*******9
// 方法一、正则: mobile.replace(/^(\d{3})(\d{7})(\d)$/, "$1******$3")
// 方法二、字符串截取 let mobile = mobile.substr(0,3) + '*******' + mobile.substr(-1)
localStorage.setItem('mobile', mobile.replace(/^(\d{3})(\d{7})(\d)$/, "$1******$3"))
// 4. 更新用户信息
this.loginLayer.style.display = 'none'
this.checkLogInfo()
} else {
alert("验证码不正确!")
}
})
} else if (e.srcElement.name === 'close') {
// 隐藏登录层
this.loginLayer.style.display = 'none'
}
}
}
}
// 获取用户信息
Index.prototype.getUserInfo = function () {
this.infoBtn.onclick = () => {
axios.get('/user/profile').then(res => {
// 设置头像到图片上
this.avatar.src = res.data.photo
})
}
}
// 上传图片
Index.prototype.bindUpload = function () {
// 将要上传的图片对象
let image
// 获取上传的图片
this.uploadFile.onchange = e => {
// 把选择的图片保存到外层的变量中
image = e.srcElement.files[0]
// 预览图片
const fr = new FileReader()
fr.readAsDataURL(e.srcElement.files[0])
// 设置读取完之后的回调函数
fr.onload = e1 => {
// 把读取的图片路径设置到头像
this.avatar.src = e1.srcElement.result
}
}
// 更新头像按钮
this.uploadBtn.onclick = e => {
// 在上传文件时一般需要使用 FormData 这个对象
let fd = new FormData()
fd.append('photo', image)
// 调用接口更新头像
axios.patch('/user/photo', fd).then(res => {
console.log(res)
})
}
}
// 封装 AJAX 的对象
const axios = {
baseURL: 'http://ttapi.research.itcast.cn/app/v1_0',
_xhr: new XMLHttpRequest(),
_addToken: function (isjson = true) {
// 获取令牌
let token = localStorage.getItem('token')
if (token !== null) {
// 把令牌添加到请求协议头上
this._xhr.setRequestHeader('Authorization', 'Bearer ' + token)
}
if (isjson) {
this._xhr.setRequestHeader('Content-Type', 'application/json')
}
},
get: function (url) {
return new Promise((ok, err) => {
this._xhr.onreadystatechange = () => {
if (this._xhr.readyState == 4 && this._xhr.status == 200) {
ok(JSON.parse(this._xhr.responseText))
}
}
this._xhr.open('GET', this.baseURL + url)
// 设置协议头
this._addToken()
this._xhr.send(null)
})
},
post: function (url, data) {
return new Promise((ok, err) => {
this._xhr.onreadystatechange = () => {
// 只要连接成功就返回数据
if (this._xhr.readyState == 4) {
// 返回状态码和数据
ok({
"status": this._xhr.status,
"data": JSON.parse(this._xhr.responseText)
})
}
}
this._xhr.open('POST', this.baseURL + url)
// 设置协议头
this._addToken()
// 发送
this._xhr.send(JSON.stringify(data))
})
},
patch: function (url, data) {
return new Promise((ok, err) => {
this._xhr.onreadystatechange = () => {
// 只要连接成功就返回数据
if (this._xhr.readyState == 4) {
// 返回状态码和数据
ok({
"status": this._xhr.status,
"data": JSON.parse(this._xhr.responseText)
})
}
}
this._xhr.open('PATCH', this.baseURL + url)
// 设置协议头(提交的不是 json)
this._addToken(false)
// 发送
this._xhr.send(data)
})
}
}
// 辅助类
function Tool() {
}
Tool.prototype.$ = function(id) {
return document.getElementById(id)
}
Tool.prototype.C = function(tag) {
return document.createElement(tag)
}
~~~