[TOC]
# 题1、使用原生 JS 的 DOM 实现:1. 弹出一个框,可以输入一个数字 n 。 2. 然后在页面中制作 n\*n 个的格子 3.每个格子鼠标放上时背景色变为红色,鼠标挪开时颜色恢复 4. 当点击一个格子时,颜色变成绿色并锁定这个颜色(鼠标放上、挪开不改变颜色) 5.点击一个已经被锁定的元素时,取消锁定颜色恢复,继承可以鼠标放上、挪开时改变颜色。(格子大小可以使用 CSS 控制,背景色的改变通过 JS 控制)JavaScript 小案例
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfat92dqw1j30ni0f4tdn.jpg)
## 实现01-先根据 N 生成 table
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfat9r7htrj316i1621kx.jpg)
## 实现02-鼠标放上、挪开时改变背景色
说明:因为为每个 td 绑定事件,比较浪费资源(td太多),所以我们可以使用 `事件委托` 技术只在 table 上绑定一个事件即可:
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfataa4ye4j30sk0pge00.jpg)
## 实现03-标记一个格子被锁定了
为了标记至少有两种实现思路:
思路一、创建一个 Map 数据类型,保存每个格子是否被锁定的状态。 let locked = new Map() // 当点击一个格子时 locked.set(格子对象, true) // 被锁定了
思路二、直接在鼠标上自定义一个属性来标记它的状态
~~~
使用时直接 对象.dataSet 即可
~~~
以下采用第二种 dataset 的方案
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfataw64i5j31k20r84qp.jpg)
## 实现04-如果锁定就不允许修改
在鼠标放上和挪开时判断是否被锁定:
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfatbn8xvgj30yo0v6nkr.jpg)
## 实现05-再次点击时解锁
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfatcg6889j30xs0os4hh.jpg)
~~~
<!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>
td {
width: 30px;
height: 30px;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
// 先获取 box 这个 div
let box = document.getElementById('box')
let num = prompt('请输入一个数字:')
// 创建格子 table
let table = document.createElement('table')
table.border = '1'
// 循环创建 n 个 tr
for (let i = 0; i < num; i++) {
let tr = document.createElement('tr')
// 在 tr 中添加 td
for (let j = 0; j < num; j++) {
let td = document.createElement('td')
// 把创建的 td 放到 tr 中
tr.appendChild(td)
}
// 把 tr 放到 table 中
table.appendChild(tr)
}
// 把 table 放到 box 中
box.appendChild(table)
// 为 table 绑定事件(事件委托技术:将事件绑定到父元素上)
// e:事件对象
// mouseover: 鼠标放上
table.addEventListener('mouseover', function (e) {
// e.srcElement: 触发这个事件的事件源
// 如果触发事件源是 TD 标签就改变背景色
if (e.srcElement.nodeName == 'TD') {
// 判断当前格子是否被锁定,如果没锁定就修改
if (e.srcElement.dataset.locked != 1) {
e.srcElement.style.backgroundColor = 'red'
}
}
})
// mouseout: 鼠标挪开
table.addEventListener('mouseout', function (e) {
// e.srcElement: 触发这个事件的事件源
// 如果触发事件源是 TD 标签就改变背景色
if (e.srcElement.nodeName == 'TD') {
// 判断当前格子是否被锁定,如果没锁定就修改
if (e.srcElement.dataset.locked != 1) {
e.srcElement.style.backgroundColor = 'white'
}
}
})
// click: 点击
table.addEventListener('click', function (e) {
// e.srcElement: 触发这个事件的事件源
// 如果触发事件源是 TD 标签就改变背景色
if (e.srcElement.nodeName == 'TD') {
e.srcElement.style.backgroundColor = 'green'
// 在这个 td 标签上标记一下,它已经被锁定了
e.srcElement.dataset.locked = 1
}
})
</script>
</body>
</html>
~~~
## 扩展:面向对象的写法
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfatcvg54gj30sw14ke81.jpg)
# 題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)
~~~
运行结果:
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfatdjyuljj30ks07iwhx.jpg)
# 题3、随机颜色的 99 乘法表
![](http://ww1.sinaimg.cn/large/007WurYGgy1gfdyinbmsmj318e0ry4qp.jpg)
代码实现:
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg2fattij30nm11w4fe.jpg)
# 题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万"
},
{
"image": "https://imgcps.jd.com/ling/100008348542/5omL5py66LSt5a6e5oOg/5aSH6LSn6LaF5YC8/p-5bd8253082acdd181d02fa33/28403921/590x470.jpg",
"goods_name": "小米耳机",
"price": "135",
"comment": "1.4万"
}
]
~~~
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg3oe4n6j31ji0tsqd9.jpg)
OOP代码实现:
1. html 和 CSS
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg48wkhuj30b415u49q.jpg)
2. 购物车类
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg4pywpzj30fq0hw0zu.jpg)
3. 为类添加渲染数据的方法
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg5cmihyj30h60uuh3h.jpg)
4. 渲染购物车表格
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg5uf0grj30gy0lqqga.jpg)
5. 加入购物车
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg6b3zubj30mm0u2nia.jpg)
6. 删除
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg6pe5agj30is0iodq2.jpg)
7. 使用
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg72echij305y03y3z2.jpg)
# 题5、表单验证
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg8aj405j30mi0iqabo.jpg)
# 题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 制作完之后的效果:
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffg9x2edjj30ks0heacq.jpg)
代码实现: HTML 和 CSS
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgal18wxj30ky0wuqf5.jpg)
封装原生 AJAX 为Promise 对象
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgawuew3j30uu0fo10i.jpg)
两个辅导函数
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgb8nuhuj30i0076whm.jpg)
点击搜索时调用接口获取某一页的数据
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgbje5thj31g60wk4n3.jpg)
根据返回的数据渲染翻页按钮和数据列表
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgbtwahpj30is0zwtnc.jpg)
搜索联想功能
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgc6yqwuj30sq0waar8.jpg)
点击翻页按钮时重新调用接口
![](http://ww1.sinaimg.cn/large/007WurYGgy1gffgcj3c1dj30jq0ai0yq.jpg)
~~~
<!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>
img {
max-width: 10%;
}
#suggestion {
position: absolute;
background-color: #eee;
width: 400px;
}
#suggestion div {
padding: 5px;
}
#suggestion div:hover {
background-color: skyblue;
}
#page {
list-style: none;
}
#page li {
display: inline-block;
border: 1px solid skyblue;
padding: 5px 10px;
margin: 5px;
}
#page li:hover,
#page li.active {
background-color: red;
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<!-- autocomplete: 关闭浏览器显示的提示 -->
<input type="text" autocomplete="off" id="keywords" placeholder="请输入内容">
<button id="btn">搜索</button>
<!-- 搜索联想 -->
<div id="suggestion"></div>
<!-- 数据列表 -->
<ul id="list"></ul>
<!-- 翻页 -->
<ul id="page"></ul>
<script>
// 封装原生 Ajax 为 Promise 对象
const axios = {
_xhr: new XMLHttpRequest(),
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', url)
this._xhr.send()
})
}
}
// 辅导函数
function $(id) {
return document.getElementById(id)
}
function C(tag) {
return document.createElement(tag)
}
// 获取页面中的几个元素
const btn = $('btn')
const keywords = $("keywords")
const list = $('list')
const suggestion = $("suggestion")
const page = $('page')
// 当前页码
let pageNum = 1
// 搜索按钮
btn.onclick = function () {
// 清空联想框
suggestion.innerHTML = ''
// 获取输入框中的关键字并去掉左右空格
let value = keywords.value.trim()
if (value === '') {
alert('必须要输入关键字!!!')
return false
}
// 调用接口取数据
axios.get(`http://ttapi.research.itcast.cn/app/v1_0/search?q=${value}&page=${pageNum}&per_page=15`)
.then(res => {
if (res.message != 'OK') return alert('接口出错')
// 渲染数据
render(res.data.results)
// 渲染翻页
renderPage(res.data.total_count)
})
// 渲染数据
function render(data) {
// 清空原数据
list.innerHTML = ''
// 循环渲染
let li, div, img
data.forEach(v => {
li = C('li')
div = C('div')
div.innerHTML = v.title
li.appendChild(div)
v.cover.images.forEach(k => {
img = C('img')
img.src = k
li.appendChild(img)
});
// li 放到页面
list.appendChild(li)
});
}
// 渲染翻页页码
function renderPage(totalCount) {
// 清空原页码
page.innerHTML = ''
// 计算总的页数 = 向上取整(总的记录数 / 每页的条数)
let pageCount = Math.ceil(totalCount / 15)
// 循环创建按钮
let li
for (let i = 0; i < pageCount; i++) {
// 创建一个按钮
li = C('li')
li.innerText = i
// 如果是当前页就添加 active 这个类
if (pageNum == i) {
// 修改标签的 class 有两种写法: className 和 classList
// li.className = 'active'
li.classList.add('active')
}
// 放到页面中
page.appendChild(li)
}
}
}
// 当搜索框中的值改变时
keywords.onkeyup = function () {
// 获取输入框中的关键字并去掉左右空格
let value = keywords.value.trim()
if (value === '') {
return false
}
// 调用联想接口 取数据
axios.get(`http://ttapi.research.itcast.cn/app/v1_0/suggestion?q=${value}`).then(res => {
if (res.message == 'OK') {
// 渲染数据
// 先清空上次数据
suggestion.innerHTML = ''
let div
// 循环数据
res.data.options.forEach(v => {
// 创建div
div = C('div')
div.innerHTML = v
// 放进去
suggestion.appendChild(div)
})
}
})
}
// 为联想的选项设置点击事件
// 我们可以直接为所有选项的父元素设置点击事件(事件委托)
// 因为事件是会冒泡的,点击子元素时,事件会传递给父元素
// 所以可以由这个父元素统一处理所有子元素的点击事件
// e.srcElmenet 代表:具体触发点击事件的子元素(事件源)
suggestion.onclick = function (e) {
// 获取点击的 div 的内容
let content = e.srcElement.innerText
// 把内容放到搜索框中
keywords.value = content
// 隐藏联想框
suggestion.innerHTML = ''
// 直接触发搜索按钮的点击事件
btn.onclick()
}
// 为所有的翻页按钮绑定点击事件,这里我们使用事件委托,把事件绑定给父元素
page.onclick = function (e) {
// 如果点击的是一个 li 标签就执行
if (e.srcElement.nodeName === 'LI') {
// 如果点击的页码不是当前页
if (pageNum !== e.srcElement.innerText) {
// 修改当前页为点击的按钮的页码
pageNum = e.srcElement.innerText
// console.log(e.srcElement.innerText);
// 重要根据页码调用接口获取数据
btn.onclick()
}
}
}
</script>
</body>
</html>
~~~