[toc]
# 题1、使用原生 JS 的 DOM 实现:1. 弹出一个框,可以输入一个数字 n 。 2. 然后在页面中制作 n*n 个的格子 3.每个格子鼠标放上时背景色变为红色,鼠标挪开时颜色恢复 4. 当点击一个格子时,颜色变成绿色并锁定这个颜色(鼠标放上、挪开不改变颜色) 5.点击一个已经被锁定的元素时,取消锁定颜色恢复,继承可以鼠标放上、挪开时改变颜色。(格子大小可以使用 CSS 控制,背景色的改变通过 JS 控制)
![](https://img.kancloud.cn/f6/16/f616720f304963e011625f112d4c3a05_846x544.png)
## 实现01-先根据 N 生成 table
![](https://img.kancloud.cn/4f/f9/4ff91a5cc09f58c8f23753e885e6933b_1530x1514.png)
## 实现02-鼠标放上、挪开时改变背景色
说明:因为为每个 td 绑定事件,比较浪费资源(td太多),所以我们可以使用 `事件委托` 技术只在 table 上绑定一个事件即可:
![](https://img.kancloud.cn/91/c9/91c91647154a79cf57c4aa64bb7b7898_1028x916.png)
## 实现03-标记一个格子被锁定了
为了标记至少有两种实现思路:
思路一、创建一个 Map 数据类型,保存每个格子是否被锁定的状态。
let locked = new Map()
// 当点击一个格子时
locked.set(格子对象, true) // 被锁定了
思路二、直接在鼠标上自定义一个属性来标记它的状态
<td data-locked="1"></td>
<td data-locked="0"></td>
使用时直接 对象.dataSet 即可
以下采用第二种 dataset 的方案
![](https://img.kancloud.cn/44/de/44defba9e2679163ac0852c21123f195_2018x980.png)
## 实现04-如果锁定就不允许修改
在鼠标放上和挪开时判断是否被锁定:
![](https://img.kancloud.cn/07/c4/07c4bd19cdd4f4c9fab03eec58db522e_1248x1122.png)
## 实现05-再次点击时解锁
![](https://img.kancloud.cn/f2/96/f296d2d71cafb1e27b3311885c7e405d_1216x892.png)
## 扩展:面向对象的写法
![](https://img.kancloud.cn/cf/08/cf08bbdb5a79d697f545861a1b79676d_1040x1460.png)
# 題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)
~~~
运行结果:
![](https://img.kancloud.cn/66/70/6670a865cb61c48f9d3355fc87ee7824_748x270.png)
# 题3、随机颜色的 99 乘法表
![](https://img.kancloud.cn/72/6b/726b784857b099accb0a5511d11c3f8a_1598x1006.png)
代码实现:
![](https://img.kancloud.cn/c7/6b/c76be00f5b5429a9620e278951002b04_850x1364.png)
# 题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万"
}
]
~~~
![](https://img.kancloud.cn/30/28/3028ccdc3f5a8bae17551cd79000d5a4_1998x1072.png)
OOP代码实现:
1. html 和 CSS
![](https://img.kancloud.cn/7c/2d/7c2d63a64413043ec34210d5332ba39f_400x1506.png)
2. 购物车类
![](https://img.kancloud.cn/32/74/3274de939c1f4c81864e323fd515fd6a_566x644.png)
3. 为类添加渲染数据的方法
![](https://img.kancloud.cn/72/65/72657a7290cf37aed7a542ca5270ef1d_618x1110.png)
4. 渲染购物车表格
![](https://img.kancloud.cn/21/bf/21bf2d4c5e9fc4b03046b09dbf663535_610x782.png)
5. 加入购物车
![](https://img.kancloud.cn/b3/35/b3357cc50d4d2ae492959eeee7c70c00_814x1082.png)
6. 删除
![](https://img.kancloud.cn/34/87/34878ea9316b1f91c01ca265f3bff196_676x672.png)
7. 使用
![](https://img.kancloud.cn/4b/63/4b63d92f6618c90c319e2ca2822092b7_214x142.png)
# 题5、表单验证
![](https://img.kancloud.cn/50/57/5057e22396dc1d68eb96aacbe8ed5639_810x674.png)
# 题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
制作完之后的效果:
![](https://img.kancloud.cn/a4/7a/a47a853c292076636b2aed76397435df_748x626.png)
代码实现:
HTML 和 CSS
![](https://img.kancloud.cn/06/1c/061c91bc2e6980c6cb35cf73c8928a15_754x1182.png)
封装原生 AJAX 为Promise 对象
![](https://img.kancloud.cn/ee/31/ee31c43f9e852fbeb0586b3a146a1597_1110x564.png)
两个辅导函数
![](https://img.kancloud.cn/e2/2e/e22ec42fcc278fb579676a20438e7019_648x258.png)
点击搜索时调用接口获取某一页的数据
![](https://img.kancloud.cn/3f/56/3f56e016d70e14c5e5027472c06be110_1878x1172.png)
根据返回的数据渲染翻页按钮和数据列表
![](https://img.kancloud.cn/61/dc/61dc57e9b9881f0c8c3a8324a3bb7eea_676x1292.png)
搜索联想功能
![](https://img.kancloud.cn/2b/e3/2be3eb5110f3115849db8a4721dd7b7f_1034x1162.png)
点击翻页按钮时重新调用接口
![](https://img.kancloud.cn/2b/12/2b122ddcb46bc20da989d3a16945568a_710x378.png)
# 题8、现有三个接口:登录-注册 、获取短验证码、令牌过期刷新,请使用前端技术实现登录功能?
功能描述:
![](https://img.kancloud.cn/7d/d3/7dd3b1200a88c83262283d192b358a27_1208x850.png)
代码实现(使用 OOP 的语法)
1. 静态页面
![](https://img.kancloud.cn/46/ae/46ae09387e4599c1dd81ff3b6757b17f_778x1402.png)
2. 封装 AJAX 对象
![](https://img.kancloud.cn/a2/6a/a26ab5e9ff6269372268c9cb9f8cd3da_798x994.png)
3. 创建一个辅助类
![](https://img.kancloud.cn/d6/69/d6692858914e5d244df08090c95958f6_430x282.png)
4. 创建实现业务逻辑的类
![](https://img.kancloud.cn/d7/ea/d7eafa5b247941861646304dd92f0e8b_546x532.png)
类中有三个方法
![](https://img.kancloud.cn/80/a5/80a54905e4ec78e87de09f3b89858f17_856x780.png)
![](https://img.kancloud.cn/44/42/444285eee12d5f634b5653ed0b4133cf_1082x1340.png)
5. 在页面中使用这个类实现功能
![](https://img.kancloud.cn/82/6f/826f1121a94091d5a3a3c732945da9b5_618x244.png)
# 题9、以第题8的基础上,能够获取并修改头像?
接口一、获取用户信息的接口
地址:http://ttapi.research.itcast.cn/app/v1_0/user/profile
方式:GET
请求参数:
![](https://img.kancloud.cn/71/f3/71f3f89fc7f0a8a86a8341c468d3751a_1246x570.png)
接口二、修改头像
地址:http://ttapi.research.itcast.cn/app/v1_0/user/photo
方式:PATCH
请求参数:
![](https://img.kancloud.cn/8d/cb/8dcb7cad20e3bbfd553cb327bacc1912_1092x1156.png)
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)
}
~~~