蓝桥杯备赛
css
https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
flex布局
文字的水平居中
text-align: center;省模拟赛 三行情书 CSS 中的单行、多行文本溢出
span {
font-size: 20px;
color: #837362;
/* TODO:补充下面的代码 */
display: block;
overflow: hidden; /* 隐藏溢出内容 */
white-space: nowrap; /* 禁止文本换行 */
text-overflow: ellipsis; /* 溢出时显示省略号 */
}
p {
color: #837362;
/* TODO:补充下面的代码 */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}text-overflow: ellipsis作用:当文本溢出容器时,显示省略号(
...)。前提条件:
必须设置
overflow: hidden必须设置
white-space: nowrap(单行)或限制高度的多行布局
overflow: hidden- 隐藏超出容器尺寸的内容,与
text-overflow配合实现截断效果。
- 隐藏超出容器尺寸的内容,与
display: -webkit-box; /* 使用旧版弹性盒模型(多行截断) */
-webkit-box-orient: vertical; /* 设置弹性盒子元素垂直排列 */
-webkit-line-clamp: 3; /* 限制显示 3 行文本 */
overflow: hidden; /* 隐藏溢出内容 */关键知识点
display: -webkit-box作用:启用旧版弹性盒模型(Flexbox 的前身),用于实现多行文本布局。
注意:这是 WebKit 私有属性,现代浏览器推荐使用
display: flex,但多行截断仍需此属性。
-webkit-box-orient: vertical- 作用:设置弹性盒子元素在垂直方向排列,为多行截断提供方向基础。
-webkit-line-clamp: 3作用:限制文本最多显示 3 行,超出行数部分隐藏并显示省略号。
兼容性:需配合
display: -webkit-box和overflow: hidden使用。
省模拟赛 日历格绘 grid
/* 日 */
#days {
/* TODO_1: 请补充代码 */
display: grid;
grid-template-columns: repeat(7, 1fr);
/* TODO_1: END */
gap: 10px;
text-align: center;
font-size: 20px;
font-weight: 700;
color: rgb(85, 85, 85);
}
/* TODO_2: 请补充代码 */
#days .day:first-child {
grid-column: 3;
}
/* TODO_2: END */2023 电影院排座位 flex
正解代码:
/* TODO:待补充代码 */
.seat-area {
margin-top: 50px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.seat-area .seat:nth-child(8n + 2) {
margin-right: 20px;
}
.seat-area .seat:nth-child(8n + 6) {
margin-right: 20px;
}知识点
:nth-child(n) 选择器是 CSS 里一个强大且灵活的选择器,它可以根据元素在父元素中的索引来选择特定的元素。 它的核心是 n,可以用数字、关键字或数学公式来控制选中的元素。
① 选中特定的第 n 个元素
.seat-area .seat:nth-child(3) {
background-color: red;
}作用:选中 第 3 个 .seat,背景变红。
选中所有偶数/奇数的元素
/* 选中偶数(2、4、6...) */
.seat-area .seat:nth-child(even) {
background-color: blue;
}
/* 选中奇数(1、3、5...) */
.seat-area .seat:nth-child(odd) {
background-color: green;
}使用 n 表达式
:nth-child(n) 可以使用数学表达式来匹配一组元素,比如 3n+1、5n+2 等。
n 的基本作用
n 代表 从 0 开始的递增整数(0,1,2,3...),所以 3n+1 这样写时,可以代入不同的 n 值。
选中“每 3 个中的第 1 个”
.seat-area .seat:nth-child(3n + 1) {
background-color: yellow;
}作用:选中 第 1、4、7、10... 个 .seat
选中“每 5 个中的第 2 个”
.seat-area .seat:nth-child(5n + 2) {
background-color: pink;
}作用:选中 第 2、7、12、17... 个 .seat
nth-child() VS nth-of-type()
:nth-child(n)→ 选中所有类型的元素,不管是<div>还是<p>。:nth-of-type(n)→ 只选中特定类型的元素,例如div:nth-of-type(2)只会选中第二个<div>,忽略其他类型的元素。
<div>我是 div 1</div>
<p>我是 p 1</p>
<div>我是 div 2</div>
<p>我是 p 2</p>
<div>我是 div 3</div>
<p>我是 p 3</p>div:nth-child(2) {
color: red; /* 这里会选中 `<p>`,因为它是整个父级的第 2 个子元素 */
}
div:nth-of-type(2) {
color: blue; /* 这里只选中第二个 `<div>` */
}选中前 n 个元素
.seat-area .seat:nth-child(-n + 5) {
background-color: orange;
}作用:选中 前 5 个元素。
排除前 n 个元素
.seat-area .seat:nth-child(n + 6) {
background-color: purple;
}作用:选中 第 6 个及以后的元素。
2023 图片水印生成 结合js
// TODO: 根据输入参数创建文字水印
for (let i = 0; i < count; i++) {
const span = document.createElement("span");
span.innerHTML = text;
span.style.color = color;
span.style.transform = `rotate(${deg}deg)`;
span.style.opacity = opacity;
container.appendChild(span);
}transform: rotate()
rotate(deg)让元素绕自身中心点旋转。deg是角度,正数是顺时针旋转,负数是逆时针旋转。
示例
.box {
transform: rotate(45deg); /* 顺时针旋转 45° */
}
.box2 {
transform: rotate(-30deg); /* 逆时针旋转 30° */
}扩展:transform 还能做什么?
transform 不止可以旋转,还能缩放、移动、倾斜:
element.style.transform = `rotate(30deg) scale(1.5) translateX(50px)`;2022 展开你的扇子 transform
/*TODO:请补充 CSS 代码*/
#box:hover #item1 {
transform: rotate(-60deg);
}
#box:hover #item2 {
transform: rotate(-50deg);
}
#box:hover #item3 {
transform: rotate(-40deg);
}
#box:hover #item4 {
transform: rotate(-30deg);
}
#box:hover #item5 {
transform: rotate(-20deg);
}
#box:hover #item6 {
transform: rotate(-10deg);
}
#box:hover #item7 {
transform: rotate(10deg);
}
#box:hover #item8 {
transform: rotate(20deg);
}
#box:hover #item9 {
transform: rotate(30deg);
}
#box:hover #item10 {
transform: rotate(40deg);
}
#box:hover #item11 {
transform: rotate(50deg);
}
#box:hover #item12 {
transform: rotate(60deg);
}20022 蓝桥知识网 纯静态
/*
TODO:请补充代码
*/
.top {
height: 440px;
width: 100%;
background-color: #a6b1e1;
}
.center {
border-bottom: 2px solid #e6e8ed;
height: 380px;
width: 100%;
}
.bottom {
height: 80px;
width: 100%;
}
.body {
width: 1024px;
margin: 0 auto;
}
.topContent {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.nav {
display: flex;
width: 100%;
height: 46px;
justify-content: space-between;
align-items: center;
}
.navLeft {
font-size: 18px;
color: white;
}
.navRight {
display: flex;
gap: 16px;
font-size: 16px;
color: white;
}
.course {
font-size: 45px;
color: black;
margin-top: 30px;
}
.skill {
font-size: 21px;
font-weight: 200;
color: white;
margin-top: 62px;
}
.button {
color: #efbfbf;
border-radius: 2px;
font-size: 18px;
box-shadow: inset 0 0 0 2px #efbfbf;
margin-top: 36px;
height: 38px;
width: 100px;
text-align: center;
padding-top: 8px;
}
.centerContent {
display: flex;
width: 100%;
flex-wrap: wrap;
column-gap: 20px;
row-gap: 10px;
padding-top: 74px;
}
.centerChild {
display: flex;
flex-direction: column;
height: 144px;
width: 502px;
align-content: space-between;
}
.title {
font-size: 30px;
font-weight: 200;
color: black;
margin-bottom: 20px;
}
.text {
font-size: 18px;
color: #aaa;
line-height: 1.4em;
white-space:;
}
.bottomContent {
display: flex;
flex-direction: column;
width: 100%;
justify-content: center;
align-items: center;
padding-top: 30px;
font-size: 14px;
color: #aaa;
}
.bottomContent div:nth-child(1) {
margin-bottom: 10px;
}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>蓝桥知识网</title>
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<!--TODO:请补充代码-->
<div class="container">
<div class="top">
<div class="body">
<div class="topContent">
<div class="nav">
<div class="navLeft">蓝桥知识网</div>
<div class="navRight">
<div>首页</div>
<div>热门技术</div>
<div>使用手册</div>
<div>知识库</div>
<div>练习题</div>
<div>联系我们</div>
<div>更多</div>
</div>
</div>
<div class="course">蓝桥云课</div>
<div class="skill">随时随地丰富你的技术栈!</div>
<div class="button">加入我们</div>
</div>
</div>
</div>
<div class="center">
<div class="body">
<div class="centerContent">
<div class="centerChild">
<div class="title">人工智能</div>
<div class="text">
人工智能亦称智械、机器智能,指由人制造出来的机器所表现出来的智能。通常人工智能是指通过普通计算机程序来呈现人类智能的技术。
</div>
</div>
<div class="centerChild">
<div class="title">前端开发</div>
<div class="text">
前端开发是创建 WEB 页面或 APP 等前端界面呈现给用户的过程,通过 HTML,CSS 及
JavaScript 以及衍生出来的各种技术、框架、解决方案,来实现互联网产品的用户界面交互。
</div>
</div>
<div class="centerChild">
<div class="title">后端开发</div>
<div class="text">
后端开发是实现页面的交互逻辑,通过使用后端语言来实现页面的操作功能,例如登录、注册等。
</div>
</div>
<div class="centerChild">
<div class="title">信息安全</div>
<div class="text">
ISO(国际标准化组织)的定义为:为数据处理系统建立和采用的技术、管理上的安全保护,为的是保护计算机硬件、软件、数据不因偶然和恶意的原因而遭到破坏、更改和泄露。
</div>
</div>
</div>
</div>
</div>
<div class="bottom">
<div class="body">
<div class="bottomContent">
<div>© 蓝桥云课 2022</div>
<div>京公网安备 11010102005690 号 | 京 ICP 备 2021029920 号</div>
</div>
</div>
</div>
</div>
</body>
</html>2023 自适应页面 媒体查询响应式
/* TODO: 考生需要完成以下内容 */
@media (max-width: 800px) {
.menu {
height: 54px;
}
.collapse {
display: none;
}
label.menu-btn {
display: block;
color: white;
margin-left: 20px;
line-height: 54px;
}
input.menu-btn:checked ~ .collapse {
display: block;
background-color: #252525; /*这里不要忘记背景色*/
}
.collapse li {
width: 100%;
z-index: 10;
}
#tutorials .row img {
margin: 0;
}
#tutorials .row {
grid-template-columns: 1fr;
}
}~ 和 + 的区别,~ 是后续所有兄弟元素,而 + 是直接下一个兄弟元素。
grid布局
https://ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html
html
flex布局复习一下
https://www.runoob.com/git/git-branch.html
js
景深效果,好难
https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter-function/blur mdn
模板字符串应用,基础中的基础
布局切换 添加移除类名实现效果 2024
知识点参考文档:
https://developer.mozilla.org/zh-CN/docs/Web/API/Element/classList
// 经典模式,浏览模式,工具模式点击事件
option.addEventListener('click', function () {
// TODO:待补充代码
document.querySelectorAll('.layout-option').forEach(item=>
item.classList.remove('active')
)
this.classList.add('active')
// TODO:END
// 以下代码无需修改产品360度展示 难难难 promise reduce 2024
实现思路
使用
reduce迭代sequence数组:reduce可以依次执行sequence中的所有函数,并传递上一个函数的结果作为下一个函数的输入。reduce初始值是Promise.resolve(initialValue),确保整个流程从一个Promise开始,统一处理同步和异步情况。
使用
Promise.resolve()处理可能的同步函数:Promise.resolve(value):确保即使某个sequence函数是同步的,也能正确处理。
错误处理:
- 在
reduce过程中捕获可能的错误,避免整个流程崩溃。
- 在
const pipeline = (initialValue, sequence) => {
return sequence.reduce((prevPromise, currentFunc) => {
return prevPromise.then(currentFunc); // 确保上一步完成后执行下一步
}, Promise.resolve(initialValue)); // 初始值包装成 Promise,确保管道统一
};
// 测试示例
const step1 = (input) => new Promise((resolve) => setTimeout(() => resolve(input + 1), 500));
const step2 = (input) => new Promise((resolve) => setTimeout(() => resolve(input * 2), 500));
const step3 = (input) => input - 3; // 同步函数
pipeline(5, [step1, step2, step3])
.then((result) => console.log("最终结果:", result)) // 5 -> 6 -> 12 -> 9
.catch((err) => console.error("管道错误:", err));
// 检测需要,请勿删除
try {
module.exports = { pipeline };
} catch {}// 官方解法:
const pipeline = async (initialValue, sequence) => {
let res = initialValue;
for (const fn of sequence) {
res = await fn(res);
}
return res;
};代码解析
初始化
Promise.resolve(initialValue):- 确保
initialValue被包装成Promise,以支持同步/异步混合处理。
- 确保
reduce顺序执行:prevPromise.then(currentFunc)让每个函数的返回值作为下一个函数的输入,形成串行执行。
支持同步 & 异步:
step3是一个同步函数,但Promise.then(currentFunc)仍能正确传递结果。
执行流程示例
假设 initialValue = 5,sequence = [step1, step2, step3],执行顺序如下:
step1(5)→ 结果6(Promise延迟 500ms)step2(6)→ 结果12(Promise延迟 500ms)step3(12)→ 结果9(同步)
最终 console.log("最终结果:", result) 会输出:
最终结果: 9
总结
顺序执行:无论 sequence 中的函数是同步还是异步,都会按顺序执行。 自动传递数据:每个步骤的输出自动作为下一个步骤的输入。 错误处理:如果 sequence 中的某个 Promise 失败,整个 pipeline 会进入 catch 处理。 返回 Promise:支持 await,适用于 async/await 编程风格。
复习promise和reduce谢谢
promise复习
同步和异步
我们编写的JavaScript代码实际上是单线程的程序,那就决定了我们的代码是一行一行的顺序执行的,处理一些简短、快速的运算操作时主线程就够了,如果在同步程序中发送了网络请求,如果超时了,下面的代码依赖于网络请求,那么整个网页将会失去响应。
而异步的概念则与同步恰恰相反,一个异步过程的执行将不再与原有的序列有顺序关系,特别是对发送网络请求,不确保响应时间时候,异步是最优选择,网络请求无论多长时间,程序将不会在此等待,直接走下面的代码,等异步的请求有了响应,主线程几乎不用关心异步任务的状态了,自己完成回调后续的操作,程序间互不影响。
简单来理解:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
为什么要使用Promise
"回调地狱"这个词,不知道大家听过没有,就是异步调用获取到结果后,为下一个异步函数提供参数,所以就会一层一层的出现回调里面嵌入回调,导致层次很深,代码维护起来特别的复杂,看一下下面的小案例大家就知道什么意思了。
下面的举例就以uniapp里面的网络请求uni.request()为例了,如果你做的是微信小程序wx.request()也是一样的,还有jQuery的ajax(),这些都是异步请求,通过success回调函数获取数据的,那有童鞋会问为什么不适用vue的axios那,因为axios网络请求已经封装了promise了。
getData(){//获取分类列表id
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/navlist.php",
success:res=>{let id=res.data[0].id
// 根据分类id获取该分类下的所有文章
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/newslist.php",
data:{
cid:id
},
success:res2=>{//获取到一篇文章的id,根据文章id找到该文章下的评论let id=res2.data[0].id;
uni.request({
url:"https://ku.qingnian8.com/dataApi/news/comment.php",
data:{
aid:id
},
success:res3=>{//找到该文章下所有的评论
console.log(res3)}})}})}})}回调地狱,其实说白了就是上一步的结果是下一步的开始,一层套一层,当需求很多很复杂很细的话,代码会一堆一堆的,很难看懂,也很难维护。
大家看到上的代码了没有,数一数有几层嵌套,出现了几个success回调,这个案例的嵌套还算是少的那,还有比这更夸张的,这就是所谓的回调地狱,层层嵌套,要是维护起这样的代码来,直接会把新手劝退的,自己写过的代码要是不加注释也不知道这到底是干嘛的。
在没有ES6的promise时候原来是怎么优化的那,把每一个request请求封装出一个函数将结果进行返回,这就是原来常用的回调函数方案,将上述代码可以改造成如下代码:
使用回调函数的方法:(本质还是回调地狱,可读性好一点而已)
什么是promise
promise是解决异步的方法,本质上是一个构造函数,可以用它实例化一个对象。对象身上有resolve、reject、all,原型上有then、catch方法。promise对象有三种状态:pending(初识状态/进行中)、resolved或fulfilled(成功)、rejected(失败)
- pending。它的意思是 "待定的,将发生的",相当于是一个初始状态。创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
- resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法
- rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。
new Promise((resolve, reject) => {...}):创建一个新的 Promise 对象,表示一个异步操作。
uni.request({...}):uni-app 发送 HTTP 请求。
success 回调:
- 请求成功时,调用
resolve(res),将res传递给then方法处理。
fail 回调:
- 请求失败时,调用
reject(err),将err传递给catch方法处理。
.then(res => {...}):
resolve(res)触发时执行,打印请求返回的res数据(成功请求的响应)。
.catch(err => {...}):
reject(err)触发时执行,打印err错误信息(请求失败的错误)。
原型(Prototype)
原型是 JavaScript 实现继承的一种机制,每个对象都可以从它的 原型 继承属性和方法。
代码中的原型体现
let p = new Promise((resolve, reject) => {
uni.request({
url: "https://ku.qingnian8.com/dataApi/news/navlist.php2",
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
},
});
});这里 Promise 是 JavaScript 原生的构造函数,它的原型链如下:
Promise.prototype;Promise本质上是一个对象,它的 原型上有.then()和.catch()方法,所以p.then(...)和p.catch(...)其实是Promise继承自Promise.prototype的方法。
原型的作用
代码复用:所有
Promise实例都会继承.then()和.catch(),但这些方法只在Promise.prototype中存储一份,而不是每个实例都拷贝一份,节省内存。原型链查找:当
p.then(...)被调用时,JavaScript 会在p对象上找.then()方法,如果找不到,就会沿着p.__proto__继续查找,最终在Promise.prototype上找到。
实例化(Instantiation)
实例化指的是通过构造函数(Constructor Function)创建对象的过程。
代码中的实例化
let p = new Promise((resolve, reject) => {...});
let p = new Promise((resolve, reject) => {...});这里 Promise 是一个 构造函数,而 p 是用 new 关键字实例化出的一个 Promise 实例。
实例化的作用
创建一个新的对象
pp继承Promise.prototype上的方法独立的 Promise 实例,可以拥有不同的状态(pending、fulfilled、rejected)
将回调函数修改为promise方案
.then 的链式调用函数
函数返回promise对象
promise.all 异步请求同步化 页面加载
async await 异步同步化
await坑点
其他解法
分享点滴 2024
很好的做法:
/**
* @param {string} url 目标 URL
* @param {object} params 参数对象
* @return {string} 拼接后的 url
*/
function appendParamsToURL(url, params) {
// TODO: 待补充代码
let newStr = url;
if (!url.includes("?")) {
newStr += "?";
for (let key in params) {
newStr += `${key}=${params[key]}&`;
}
newStr = newStr.slice(0, -1);
console.log(newStr);
} else {
for (let key in params) {
newStr += `&${key}=${params[key]}`;
}
}
return newStr;
}点击分析按钮显示这样也是对的
function appendParamsToURL(url, params) {
// 如果 params 为空对象,直接返回原 URL
if (!params || Object.keys(params).length === 0) {
return url;
}
// 将 params 对象转换为 key=value 的字符串形式,并用 & 符号连接
const paramString = Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join("&");
// 判断 URL 是否已经包含 ?
const separator = url.includes("?") ? "&" : "?";
// 返回拼接后的 URL
return url + separator + paramString;
}
// 测试用例
console.log(appendParamsToURL("https://example.com", { key1: "value1", key2: "value2" }));
// 输出: "https://example.com?key1=value1&key2=value2"
console.log(appendParamsToURL("https://example.com?existing=123", { key1: "value1" }));
// 输出: "https://example.com?existing=123&key1=value1"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
Object.keys(params).length === 0 这里会返回一个数组,所以判断数组的长度,就可以知道有没有参数内容
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
Object.entries(params) 把 params 对象传入
会形成一个数组,每个元素也是一个数组,元素的数组是键值对
比如:
const obj = { foo: "bar", baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ].map(([key, value]) => ...):
遍历数组,把
key和value转换成key=value形式的字符串。encodeURIComponent(...)用于编码,防止 URL 特殊字符(如?、&、=)被误解析。例如:
encodeURIComponent("a b"); // 结果是 "a%20b".join('&'):
- 将转换后的键值对数组合并成字符串,用
&连接,例如:
"key1=value1&key2=value2";5. 找回连接的奇幻之旅 2024
function resetableOnce(fn) {
// TODO: 待补充代码
let result;
let done = false;
function runOnce() {
if (!done) {
result = fn(...arguments);
done = true;
}
return result;
}
function reset() {
done = false;
}
// TODO: END
return { runOnce, reset };
}悠然画境 2024
根据用户输入的 selectedText(选中的文本),从 artDataArray 中筛选出最佳匹配的图片,并按照匹配程度排序,返回指定数量的图片。
/**
* @param {*} imageCount 生成的图片数量
* @param {*} selectedText 用户输入的文本
*/
function generateAndDisplayImages(imageCount, selectedText) {
let imgAry = []; // 定义最佳匹配的图片数组
// TODO:待补充代码
console.log(imageCount, selectedText);
artDataArray.forEach((item) => {
arr = item.tags.split("、");
console.log(arr);
item.count = 0;
for (let i = 0; i < arr.length; i++) {
if (selectedText.includes(arr[i])) item.count++;
}
});
artDataArray.sort((a, b) => {
return b.count - a.count;
});
console.log(artDataArray);
imgAry = artDataArray.slice(0, imageCount);
//TODO:END
return imgAry;
}实现代码:
function generateAndDisplayImages(imageCount, selectedText) {
let imgAry = []; // 定义最佳匹配的图片数组
// TODO:待补充代码
// 补充一个权重字段,后续可以用来做排序
// 对数组数据做一个映射,用map ...item把本来有的继续拥有
imgAry = artDataArray
.map((item) => {
// tags原先是顿号分隔的字符串,用顿号分割成数组,然后进行筛选,计算数组长度赋值给权重
let weight = item.tags.split("、").filter((s) => selectedText.includes(s)).length;
return { ...item, weight };
})
.sort((a, b) => b.weight - a.weight);
// 然后降序排序sort
// 再进行切片,从头开始切到用户想要的数量
imgAry = imgAry.slice(0, imageCount); //slice不改变原数组,所以要重新赋值
//TODO:END
return imgAry;
}map 方法用于遍历数组并返回一个新数组,它的核心作用是:
item.tags.split("、")tags是一个包含多个关键字的字符串(用顿号、分隔),先用split("、")转换为数组,例如:
javascript"风景、日落、山川" => ["风景", "日落", "山川"].filter(s => selectedText.includes(s))过滤出
tags里存在于selectedText的关键词。selectedText.includes(s)检查selectedText是否包含该tag,返回true的会被保留。例如:
javascriptselectedText = "今天的日落很美"[("风景", "日落", "山川")].filter((s) => "今天的日落很美".includes(s), ); // 结果: ["日落"] => 匹配到1个tag
{ ...item, weight }:使用 扩展运算符 ...,保留原 item 的所有属性,并添加 weight 字段。
重点:
按 weight 降序排序
.sort((a, b) => b.weight - a.weight)- 数组排序
sort(callback)这里的排序方式是降序(从大到小)。
b.weight - a.weight:b.weight > a.weight时,b排在a前面(降序)。b.weight < a.weight时,a继续排在前面。
这样可以保证匹配度高的图片排在前面。
slice(start, end):
slice(0, imageCount)取出前imageCount个元素(即匹配度最高的图片)。slice不会修改原数组,所以必须重新赋值imgAry。
商品浏览足迹 2024 难难难
代码一
/**
* 将同一天浏览的相同商品去重并作为数组返回
* @param {Array} defaultData json 文件中读取到的原始数据
* @returns 去重后的数据,数据结构与 defaultData 相同
*/
const removeDuplicates = defaultData => {
console.log(defaultData)
let uniqueMap = new Map(); // 用于存储去重后的数据
let newData = []; // 最终的去重数据
defaultData.forEach(item => {
let date = item.viewed_on.split('T')[0]; // 提取 YYYY-MM-DD
let key = `${date}-${item.id}`; // 唯一键:日期+商品ID
if (!uniqueMap.has(key)) {
uniqueMap.set(key, item); // 存入 Map,确保唯一性
newData.push(item); // 加入结果数组
}
});知识点:
split('T')[0]:字符串分割
let date = item.viewed_on.split('T')[0];
split('T')方法会将"2024-03-23T10:00:00"按 "T" 进行分割,得到:["2024-03-23", "10:00:00"]取索引
0的部分,即"2024-03-23",用于日期去重。
拓展
split可以根据任意分隔符来拆分字符串,例如:"apple,banana,orange".split(",") // ["apple", "banana", "orange"]若
split()传入的是 不存在的分隔符,则返回整个字符串:"hello world".split("|") // ["hello world"]
Map 是 ES6 新增的数据结构,类似于对象,但键可以是任何类型(如对象、数组等)。
- 主要方法:
为什么用 Map 而不是对象 {}?
Map允许使用复杂类型作为键(如数组、对象)。Map的has()查找性能是 O(1),而对象的in操作符可能需要 O(n)(在原型链上查找)。
if (!uniqueMap.has(key)):去重
if (!uniqueMap.has(key)) {
uniqueMap.set(key, item);
newData.push(item);
}逻辑
has(key)判断Map中是否已存在相同key(即日期+商品ID)。如果不存在,则存入
Map,并将item添加到newData结果数组。这样确保了同一天不会有重复的相同商品。
代码二
/**
* 将去重后的数据根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
* @param {*} defaultData 去重后的数据
* @returns 根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
*/
const sortByDate = (defaultData) => {
let newData = [];
// TODO:待补充代码
// 将数据按 viewed_on 的日期部分提取出来,并进行排序
newData = defaultData.sort((a, b) => {
let dateA = a.viewed_on.split("T")[0];
let dateB = b.viewed_on.split("T")[0];
return dateB.localeCompare(dateA); // 降序排序
});
return newData;
};使用 sort() 进行降序排序:
return dateB.localeCompare(dateA);localeCompare()按照字符串的字典顺序比较dateB - dateA,保证最新日期排前面。
代码三
/**
* 将去重排序后的所有商品数据,作为一个对象存储并返回,
* 该对象的所有 `key` 为浏览商品的当天日期(即,字段 viewed_on 格式化为 YYYY-MM-DD),
* `value` 为当天浏览的所有商品(以数组形式表现)。
* @param {Array} defaultData 重后的所有商品数据
* @returns
*/
const transformStructure = (defaultData) => {
let newData = {};
// TODO:待补充代码
defaultData.forEach((item) => {
// 提取 viewed_on 的日期部分 YYYY-MM-DD
let date = item.viewed_on.split("T")[0];
// 如果该日期已存在于 newData,则追加商品,否则创建新数组
if (!newData[date]) {
newData[date] = [];
}
newData[date].push(item);
});
return newData;
};简易jsx解析器
创建一个 DOM 元素,并根据传入的 config 对象为其设置内容、属性、样式以及事件。
const jsx = (type, config) => {
/* TODO: 待补充代码 */
//type: 传入的 HTML 标签名称,例如 "div", "span", "button" 等。
//config: 一个对象,包含:
//children: 可以是单个文本/元素,也可以是一个数组(多个子元素)。
//id: (可选)元素的 id 属性。
//style: (可选)一个对象,包含 CSS 样式键值对。
//onclick: (可选)点击事件处理函数。
// 判断config.children是否为数组
//**如果 children 不是数组**,表示只有一个子元素(可能是字符串,也可能是一个 DOM 节点)。
if (!Array.isArray(config.children)) {
let n = document.createElement(`${type}`);
n.innerHTML = config.children;
//**如果 children 不是数组**,直接把它作为 innerHTML 插入到创建的元素 n 内部。
//第二问添加属性和样式
//判断config.id是否存在,若存在则添加
config.id && n.setAttribute("id", config.id);
let result = "";
if (config.style) {
//config.style 是一个对象,例如:
//{
//color: "red",
//fontSize: "20px",
// backgroundColor: "blue"
//}
//目标是将其转换为:
//color: red; font-size: 20px; background-color: blue;
// 遍历style属性
for (let k in config.style) {
// 声明一个待拼接的字符串 遍历 config.style 对象的所有键,例如 "color", "fontSize", "backgroundColor"。
let r = "";
//处理驼峰命名的 CSS 属性
// 遍历字符串 color c o l o r
// CSS 里 fontSize 需要转换成 font-size,即大写字母前加 -,并转换为小写。
for (let item of k) {
// 字符串若为大写则前面加上一个"-"
if (isUpperCase(item)) {
item = "-" + item.toLowerCase();
r += item;
} else {
r += item;
}
}
result += `${r}:${config.style[k]};`;
//生成类似 "color:red;" 的 CSS 样式字符串
}
// 去掉最后一个分号 不过一般可以忽略,因为多个 ; 也不会影响解析)
result = result.slice(0, result.length - 1);
n.setAttribute("style", result);
}
// 第三问
n.onclick = config.onclick;
// 如果 config 里有 onclick,就直接赋值给 n.onclick,让它在点击时执行。
return n;
} else {
let div = document.createElement(`${type}`);
// 遍历数组元素
for (let item of config.children) {
// 若此元素为节点,appendChild父元素内
if (item.nodeType === 1) {
// item.nodeType === 1:检查 item 是否是 DOM 节点。
//如果是 DOM 节点,就用 appendChild 添加到 div 里。
//否则,把 item 作为文本插入。
div.appendChild(item);
} else {
div.innerHTML += item;
}
}
return div;
}
//如果 children 不是数组,返回 n(单个元素)。
//如果 children 是数组,返回 div(包含多个子元素的父元素)。
};相关知识点
1. document.createElement(tagName)
2. Array.isArray()
3. setAttribute(name, value)
4. && 短路逻辑
2023 收集帛书碎片 数组去重
function collectPuzzle(...puzzles) {
// TODO:在这里写入具体的实现逻辑
// 对所有的拼图进行收集,获取不同拼图类型的结果,并返回
console.log(puzzles);
let arr = [];
puzzles.forEach((ietm) => {
ietm.forEach((element) => {
if (!arr.includes(element)) {
arr.push(element);
}
});
});
console.log(arr);
return arr;
}简单解法:
return [...new Set(puzzles.flat())];去重并展平一个二维数组 puzzles,返回去重后的元素数组。
puzzles.flat():展平数组
puzzles是一个二维数组(即数组中的元素仍然是数组)。Array.prototype.flat()用于 展平数组,默认展开一层,即把二维数组变成一维数组。
const puzzles = [
[1, 2, 3],
[3, 4, 5],
[5, 6],
];
console.log(puzzles.flat()); // [1, 2, 3, 3, 4, 5, 5, 6]
arr.flat([depth]);
//depth:指定要展平的深度,默认值为 1
const nestedArr = [1, [2, [3, [4, 5]]]];
console.log(nestedArr.flat()); // [1, 2, [3, [4, 5]]] (默认只展开一层)
console.log(nestedArr.flat(2)); // [1, 2, 3, [4, 5]] (展开两层)
console.log(nestedArr.flat(Infinity)); // [1, 2, 3, 4, 5] (全部展开)Set 是 ES6 引入的集合数据结构,它存储的值 不允许重复。
通过 new Set(array),我们可以移除 array 中的重复值。
const arr = [1, 2, 3, 3, 4, 5, 5, 6];
const uniqueSet = new Set(arr);
console.log(uniqueSet); // Set {1, 2, 3, 4, 5, 6}
// 详解 set
const mySet = new Set(iterable);
//iterable(可迭代对象,如数组、字符串等)会被转换为 Set,并自动去重
const mySet = new Set([1, 2, 3, 3, 4]);
console.log(mySet); // Set {1, 2, 3, 4}
// 常用方法:
const s = new Set([1, 2, 3]);
s.add(4); // 添加元素
s.delete(2); // 删除元素
console.log(s.has(3)); // true (判断是否存在)
console.log(s.size); // 3 (获取元素个数)
s.clear(); // 清空所有元素[...new Set(...)]:把 Set 转回数组
Set类型不能像数组一样用索引访问,所以我们使用 展开运算符 (...) 把它转换为数组。
const uniqueArr = [...new Set([1, 2, 3, 3, 4, 5, 5, 6])];
console.log(uniqueArr); // [1, 2, 3, 4, 5, 6]
// 展开运算符
// 在 Set、Array 等可迭代对象前加 ...,可以将元素展开。
// Set 本身不是数组,但可以用 ... 将其转换为数组。
const mySet = new Set([1, 2, 3]);
const myArray = [...mySet];
console.log(myArray); // [1, 2, 3]2023视频弹幕 难啊难
function renderBullet(bulletConfig, videoEle, isCreate = false) {
const spanEle = document.createElement("SPAN");
spanEle.classList.add(`bullet${index}`);
if (isCreate) {
spanEle.classList.add("create-bullet");
}
// TODO:控制弹幕的显示颜色和移动,每隔 bulletConfig.time 时间,弹幕移动的距离 bulletConfig.speed
// 弹幕元素的宽和高
// 写入弹幕内容
spanEle.innerHTML = bulletConfig.value;
// 设置弹幕内容随机
spanEle.style.color = "#" + getRandomNum(999999);
// 插入元素
videoEle.appendChild(spanEle);
// 获取父元素样式
const videoRect = getEleStyle(videoEle);
// 得到父宽度
let left = videoRect.width;
spanEle.style.left = left + "px";
const top = getRandomNum(videoRect.height);
spanEle.style.top = top + "px";
// 获取子元素弹幕的样式
const spanRect = getEleStyle(spanEle);
// 定时器控制弹幕移动
let timer = setInterval(() => {
left -= bulletConfig.speed;
spanEle.style.left = left + "px";
// spanEle.style.top = top+"px"
// 控制弹幕是否隐藏 要写在定时器里,实时控制
// if(bulletConfig.isHide){
// spanEle.style.display = "none"
// }else{
// spanEle.style.display = "inline"
// }
// 弹幕移出屏幕后,移除掉span元素,且停止定时器
// 弹幕的本身宽度加上left值小于等于0时,说明已移出屏幕
if (left + spanRect.width <= 0) {
videoEle.removeChild(spanEle);
clearInterval(timer);
}
}, bulletConfig.time);
}
document.querySelector("#sendBulletBtn").addEventListener("click", () => {
// TODO:点击发送按钮,输入框中的文字出现在弹幕中
bulletConfig.value = document.querySelector("#bulletContent").value;
renderBullet(bulletConfig, videoEle, true);
});2023年度明星项目
// 保存翻译文件数据的变量
let translation = {};
let data = []; //数据量
let page = 0; ///当前页码
// 记录当前语言
let currLang = "zh-cn";
// TODO: 请在此补充代码实现项目数据文件和翻译数据文件的请求功能
window.onload = async () => {
const res = await fetch("./js/all-data.json");
data = await res.json();
const res2 = await fetch("./js/translation.json");
translation = await res2.json();
// 第一次初始化前十五个
data.slice(0, 15).forEach((element) =>
$(".list > ul").append(
createProjectItem(
// 保持原有属性,并增加一个属性
{
...element,
description: currLang == "zh-cn" ? element.descriptionCN : element.descriptionEN,
},
),
),
);
};
// TODO-END
// TODO: 请修改以下代码实现项目数据展示的功能
// 以下代码(13-23行)为 createProjectItem 函数使用示例
// Mock一个项目的数据
// const item = {
// icon: "bun.svg",
// description:
// "Incredibly fast JavaScript runtime, bundler, transpiler and package manager...",
// name: "Bun",
// stars: 37087,
// tags: ["runtime", "bun"],
// };
// 添加至页面的项目列表中,查看页面可以看到有一行 bun 的项目数据
// $(".list > ul").append(createProjectItem(item));
// 计算翻页逻辑
document.querySelector(".load-more").addEventListener("click", function () {
//page ==0 第一轮
data.slice((page + 1) * 15, (page + 2) * 15).forEach((element) =>
$(".list > ul").append(
createProjectItem(
// 保持原有属性,并增加一个属性
{
...element,
description: currLang == "zh-cn" ? element.descriptionCN : element.descriptionEN,
},
),
),
);
//一共60条,四页,当第三页page==2的加载更多按下时,应该要隐藏加载更多的按钮
if (page == Math.ceil(data.length / 15) - 2) {
this.style.display = "none";
}
page++;
});
// TODO-END
// 用户点击切换语言的回调
$(".lang").click(() => {
// 切换页面文字的中英文
if (currLang === "en") {
$(".lang").text("English");
currLang = "zh-cn";
} else {
$(".lang").text("中文");
currLang = "en";
}
$("body")
.find("*")
.each(function () {
const text = $(this).text().trim();
if (translation[text]) {
$(this).text(translation[text]);
}
});
// TODO: 请在此补充代码实现项目描述的语言切换
// 拿到所有description节点
document.querySelectorAll("body > div > div.list > ul > li > div.desc > p").forEach((item) => {
item.textContent =
currLang == "zh-cn"
? data.find((i) => i.descriptionEN == item.textContent).descriptionCN
: data.find((i) => i.descriptionCN == item.textContent).descriptionEN;
});
});
// 生成列表DOM元素的函数,将该元素的返回值append至列表中即可生成一行项目数据
/**
* @param {string} name - 项目名称
* @param {string} description - 项目描述
* @param {string[]} tags - 项目标签
* @param {number} stars - 项目star数量
* @param {string} icon - 项目icon路径
*/
function createProjectItem({ name, description, tags, stars, icon }) {
return `
<li class="item">
<img src="images/${icon}" alt="">
<div class="desc">
<h3>${name}</h3>
<p>${description}</p>
<ul class="labels">
${tags.map((tag) => `<li>${tag}</li>`).join("")}
</ul>
</div>
<div class="stars">
+${stars}
</div>
</li>
`;
}知识点:
document.querySelectorAll() 选择符合 **CSS 选择器** 的所有元素,并返回一个 NodeList(**类似数组的对象**)。
**.forEach(item => { ... })**
//遍历 NodeList,对每个 p 元素执行回调函数。
**item.textContent = currLang == "zh-cn" ? ... : ...**
//currLang 是一个变量,存储当前的语言:
//如果 currLang == "zh-cn",则把 p 里面的文本替换成 **中文**。
//否则,把 p 里面的文本替换成 **英文**。
**data.find(i => i.descriptionEN == item.textContent).descriptionCN**
// data 是一个包含**中英文对照**的数组,可能是这样的:
// const data = [
// { descriptionEN: "Hello", descriptionCN: "你好" },
// { descriptionEN: "Goodbye", descriptionCN: "再见" }
// ];
// find() 方法查找 data 里面符合条件的对象:
// data.find(i => i.descriptionEN == item.textContent) 在 data 里查找 **当前 p 的文本** 是否匹配 descriptionEN(即当前文本是英文)。
//找到后,返回 descriptionCN,**用中文替换英文**。
// find 用法
// data.find(i => i.descriptionEN == item.textContent).descriptionCN
// data.find(i => i.descriptionEN == item.textContent):
//遍历 data,找到 descriptionEN 等于 item.textContent 的对象。
//如果找到了,返回该对象。 对象用.运算符取到属性 .descriptionCNconst res = await fetch("./js/all-data.json");
data = await res.json(); // 背下来补充:fetch使用
async function fetchData() {
try {
const response = await fetch("https://api.example.com/users", {
method: "GET", // 默认可省略
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`HTTP 错误! 状态码: ${response.status}`);
}
const data = await response.json();
console.log("获取的数据:", data);
return data;
} catch (error) {
console.error("请求失败:", error);
throw error; // 向上传递错误
}
}async function createUser(userData) {
try {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer your_token",
},
body: JSON.stringify(userData),
});
if (response.status === 201) {
return await response.json(); // 返回创建的资源
}
throw new Error(`创建失败,状态码: ${response.status}`);
} catch (error) {
console.error("创建用户失败:", error);
throw error;
}
}
// 使用示例
const newUser = {
name: "张三",
email: "zhangsan@example.com",
};
createUser(newUser).then((user) => {
console.log("新用户ID:", user.id);
});async function updateUser(userId, updatedData) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedData),
});
if (response.status === 200) {
return await response.json();
}
throw new Error(`更新失败,状态码: ${response.status}`);
} catch (error) {
console.error("更新用户失败:", error);
throw error;
}
}
// 使用示例
updateUser(123, { email: "new@example.com" });async function deleteUser(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "DELETE",
});
if (response.status === 204) {
console.log("删除成功");
return true;
}
throw new Error(`删除失败,状态码: ${response.status}`);
} catch (error) {
console.error("删除操作失败:", error);
throw error;
}
}
// 使用示例
deleteUser(123);async function partialUpdate(postId, updates) {
try {
const response = await fetch(`https://api.example.com/posts/${postId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updates),
});
if (response.ok) {
return await response.json();
}
throw new Error(`局部更新失败: ${response.status}`);
} catch (error) {
console.error("更新失败:", error);
throw error;
}
}
// 使用示例
partialUpdate(456, { views: 1000 });正则 2023 ISBN转换与生成
很巧妙的一个题解做法
// 将用户输入的带分隔符的 isbn 字符串转换只有纯数字和大写 X 字母的字符串
// 入参 str 为转换为包含任意字符的字符串
/** @params {String} str */
function getNumbers(str) {
// TODO: 待补充代码
// 正则全局除掉数字和X外的其他内容
return str.replace(/[^\dX]/g, "");
}
// 验证当前 ISBN10 字符串是否有效
// 入参 str 为待判断的只有纯数字和大写 X 字母的字符串
function validISBN10(str) {
// TODO: 待补充代码
//1.判断格式
if (!/^\d{9}[\dX]$/.test(str)) {
return false;
}
// 2.计算校验码正误
let sum = str
.slice(0, 9)
.split("")
.map(Number)
.reduce((acc, cur, idx) => {
return acc + cur * (idx + 1);
});
let check = sum % 11;
return (check == 10 ? "X" : String(check)) == str[str.length - 1];
}
// 将用户输入的 ISBN-10 字符串转化为 ISBN-13 字符串
// 入参 isbn 为有效的 ISBN-10 字符串
function ISBN10To13(isbn) {
// TODO: 待补充代码
let isbn12 = "978" + isbn.slice(0, 9);
let sum =
isbn12
.split("")
.map(Number)
.filter((_, i) => i % 2 == 0)
.reduce((acc, cur) => acc + cur, 0) +
isbn12
.split("")
.map(Number)
.filter((_, i) => i % 2 !== 0)
.reduce((acc, cur) => acc + 3 * cur, 0);
let check = 10 - (sum % 10);
return isbn12 + String(check);
}
// 测试用例
console.log(getNumbers("7-5600-3879-4")); // 7560038794
console.log(getNumbers("7 5600 3879 4")); // 7560038794
console.log(validISBN10("7560038794")); // true
console.log(validISBN10("7560038793")); // false
console.log(validISBN10("756003879")); // false
console.log(validISBN10("756003879004")); // false
console.log(ISBN10To13("7560038794")); // 9787560038797
console.log(ISBN10To13("3598215088")); // 9783598215087知识点:
正则表达式:/[^\dX]/g
\d匹配数字0-9X匹配字母 X(ISBN-10 校验位可能是 X)^取反,即匹配 非数字且非 X 的字符g全局匹配,即替换所有符合条件的字符
正则表达式:/^\d{9}[\dX]$/
^\d{9}:前 9 位必须是数字[\dX]$:最后一位必须是数字或者 X^...$:确保整个字符串匹配
字符串截取:str.slice(0,9)
- 取出 前 9 位数字
字符串转数字:.split("").map(Number)
split(""):把字符串拆成数组map(Number):把字符数组转换成数字数组
加权求和:
ISBN-10 校验码计算公式:
S = 1×d1 + 2×d2 + ... + 9×d9计算
S % 11,余数即校验位(如果是10,用X代替)
.split("") — 拆分字符串
split("") 会按照每个字符拆分字符串,返回一个数组。
示例
let str = "12345";
console.log(str.split(""));
// 结果: ["1", "2", "3", "4", "5"] (字符串数组).map(Number) — 把字符串转换为数字
.map(Number)作用是 把数组中的每个元素都转换成数字。Number()是 JavaScript 的 内置构造函数,用于显式转换为数字。
示例
let arr = ["1", "2", "3", "4", "5"];
console.log(arr.map(Number));
// 结果: [1, 2, 3, 4, 5] (数字数组)如果输入字符串可能包含 -、空格等无效字符,可以使用 正则表达式 过滤:
function safeConvert(str) {
return str.replace(/\D/g, "").split("").map(Number);
}
console.log(safeConvert("1-234-567"));
// 结果: [1, 2, 3, 4, 5, 6, 7]\D匹配所有非数字字符,用""替换掉。保证输出不会出现
NaN。
2022 课程列表
let pageNum = 1; // 当前页码,默认页码1
let maxPage; // 最大页数
// TODO:待补充代码
let data = [];
// 获取 .list-group 容器
const listGroup = document.querySelector(".list-group");
// 获取页码容器
const pagination = document.getElementById("pagination");
window.onload = async () => {
const res = await axios.get("js/carlist.json");
console.log(res.data);
data = res.data;
// 计算最大页码
maxPage = Math.ceil(data.length / 5);
console.log("最大页码", maxPage);
const pageContainer = document.createElement("div");
const pageContent = `<div>共${maxPage}页,当前${pageNum}页</div>`;
pageContainer.innerHTML = pageContent;
pagination.appendChild(pageContainer);
data.slice(0, 5).forEach((element) => {
const name = element.name;
const price = element.price;
const description = element.description;
// 创建一个新的 list group 项目
const listItem = document.createElement("a");
listItem.href = "#";
listItem.classList.add("list-group-item", "list-group-item-action");
// 设置 list 项目内容
const itemContent = `
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">${name}</h5>
<small>${price}</small>
</div>
<p class="mb-1">${description}</p>
`;
listItem.innerHTML = itemContent;
// 将新项添加到 list-group 容器中
listGroup.appendChild(listItem);
});
};
// 点击上一页
let prev = document.getElementById("prev");
prev.onclick = function () {
// TODO:待补充代码
if (pageNum == 1) {
prev.classList.add("disabled");
}
};
// 点击下一页
let next = document.getElementById("next");
next.onclick = function () {
// TODO:待补充代码
// 选取6-10条
data.slice(pageNum * 5, (pageNum + 1) * 5).forEach((element) => {
const name = element.name;
const price = element.price;
const description = element.description;
// 创建一个新的 list group 项目
const listItem = document.createElement("a");
listItem.href = "#";
listItem.classList.add("list-group-item", "list-group-item-action");
// 设置 list 项目内容
const itemContent = `
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">${name}</h5>
<small>${price}</small>
</div>
<p class="mb-1">${description}</p>
`;
listItem.innerHTML = itemContent;
// 将新项添加到 list-group 容器中
listGroup.appendChild(listItem);
// 判断页码为最后一页
if (pageNum === maxPage - 1) {
console.log("当前页码", pageNum);
next.classList.add("disabled");
}
});
// 使页码加1
pageNum++;
console.log("当前页码", pageNum);
// 更新页码信息
const pageContainer = pagination.querySelector("div");
pageContainer.innerHTML = `<div>共${maxPage}页,当前${pageNum}页</div>`;
};2022 灯的颜色变化
const white = document.getElementById("defaultlight");
const redlight = document.getElementById("redlight");
const greenlight = document.getElementById("greenlight");
// TODO:完善此函数 显示红色颜色的灯
function red() {
redlight.style.display = "inline-block";
white.style.display = "none";
}
// TODO:完善此函数 显示绿色颜色的灯
function green() {
redlight.style.display = "none";
greenlight.style.display = "inline-block";
white.style.display = "none";
}
// TODO:完善此函数
function trafficlights() {
setTimeout(() => {
red();
}, 3000);
setTimeout(() => {
green();
}, 6000);
}
trafficlights();2022 冬奥大抽奖
let rollTime; // 定义定时器变量用来清除定时器
let time = 0; // 转动次数
let speed = 300; // 转动时间间隔
let times; // 总转动次数
// 开始按钮点击事件后开始抽奖
$("#start").on("click", function () {
$("#award").text(""); //清空中奖信息
times = parseInt(Math.random() * (20 - 30 + 1) + 20, 10); // 定义总转动次数,随机20-30次
rolling();
});
// TODO:请完善此函数
function rolling() {
time++; // 转动次数加1
// 转到第几个
const index = time % 8 || 8;
const li = document.querySelector(`.li${index}`);
li.classList.add("active");
clearTimeout(rollTime);
rollTime = setTimeout(() => {
window.requestAnimationFrame(rolling); // 进行递归动画
li.classList.remove("active");
}, speed);
// time > times 转动停止
if (time > times) {
clearInterval(rollTime);
// 抽中的文字显示在框里
document.querySelector("#award").innerHTML = li.innerHTML;
time = 0;
return;
}
}2022 寻找小狼人
Array.prototype.myarray = function (cb) {
// 使用 reduce 方法遍历当前数组
return this.reduce((acc, cur) => {
// 判断当前元素是否符合条件
if (cb(cur)) {
// 如果符合条件,就将其加入到 accumulator (acc) 数组中
acc.push(cur);
}
// 返回 accumulator,供下次调用
return acc;
}, []); // 初始化 accumulator 为一个空数组
};
// 调用:
let newcardList = cardList.myarray((item) => item.category == "werewolf");
// 返回条件为真的新数组
Array.prototype.myarray = function (cb) {
// TODO:待补充代码
console.log(cb);
console.log(this);
const arr = [];
for (let i = 0; i < this.length; i++) {
if (cb(this[i])) {
arr.push(this[i]);
}
}
console.log(arr);
return arr;
};关键知识点解释:
1. Array.prototype:
Array.prototype是所有数组实例共享的方法和属性的原型对象。在这里,myarray被添加到Array.prototype上,意味着所有数组实例都可以使用这个方法。通过这种方式,你可以扩展 JavaScript 数组的功能,创建自己的自定义方法(类似于
map、filter等内置方法)。
2. this:
在 JavaScript 中,
this关键字通常指向当前执行上下文的对象。在这里,this是指调用myarray方法的数组实例。例如,调用
[1, 2, 3].myarray()时,this就是[1, 2, 3]数组。
3. reduce() 方法:
reduce()是数组的一个内置方法,它接收一个回调函数作为参数。这个回调函数有两个参数:acc: 累积值,通常用来存储最终结果。在这里,acc是一个数组,用来存储符合条件的元素。cur: 当前数组元素,表示正在遍历的当前项。
reduce()方法会遍历数组中的每一个元素,将它们与累积值一起传递给回调函数,最终返回累积值(此例中是一个新数组)。
例如,以下代码使用 reduce() 计算数组元素的和:
const sum = [1, 2, 3].reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 输出: 6在你的代码中,reduce() 被用来遍历数组,并根据回调函数 cb 的条件筛选元素。
4. 回调函数 cb:
cb是传递给myarray方法的回调函数,它接收当前数组元素(cur)作为参数,并返回一个布尔值(true或false),表示该元素是否满足条件。在
myarray方法中,cb(cur)被用于判断当前元素是否符合某个条件。如果返回true,则将当前元素添加到累积数组acc中。
例如,回调函数可以是以下形式:
const isEven = (num) => num % 2 === 0; // 判断是否为偶数然后调用 myarray 方法时:
[1, 2, 3, 4].myarray(isEven); // 结果: [2, 4]5. push() 方法:
push()是数组的一个内置方法,它用于将一个或多个元素添加到数组的末尾,并返回新的数组长度。在这个代码中,当cb(cur)返回true时,当前元素cur被加入到累积数组acc中。
6. 初始化累积器 []:
reduce()方法的第二个参数是初始化累积值的默认值。在这里,传入的是一个空数组[],表示初始时acc是空数组。
总结:
这段代码为
Array类的所有数组实例添加了一个自定义方法myarray,该方法接受一个回调函数cb,并通过reduce()遍历数组,返回一个新数组,其中包含满足回调函数条件的元素。它类似于
Array.prototype.filter()方法,但是使用了reduce()来实现。
例子:
假设你有一个数组 [1, 2, 3, 4, 5],并且你想要筛选出所有大于 2 的元素,你可以这样做:
const numbers = [1, 2, 3, 4, 5];
const result = numbers.myarray((num) => num > 2);
console.log(result); // 输出: [3, 4, 5]2022健身大调查
function formSubmit() {
// TODO:待补充代码
//收集用户填写的表单信息
const height = document.getElementById("height").value;
console.log(height); //已拿到身高
const weight = document.getElementById("weight").value;
console.log(weight); //已拿到体重
const male = document.querySelector('[type ="radio"]:checked').value;
console.log(male); //拿到性别对应的数字
const gender = male == 0 ? "男" : "女";
console.log(gender);
const place = document.querySelector('[type ="checkbox"]:checked').value;
let sportPlace = ""; //这里注意不要用const定义,不能对常量进行赋值
if (place == 1) {
sportPlace = "公园";
} else if (place == 2) {
sportPlace = "健身房";
} else {
sportPlace = "户外";
}
console.log(sportPlace);
console.log(place); //拿到地点所对应的数字
//点击提交,隐藏表单部分和提交按钮
const myForm = document.getElementById("quescontent");
myForm.style.display = "none";
//document.getElementById('result').innerHTML = `
//问卷提交完毕,感谢您的参与!
//<br>
//身高 ${height}cm,体重 ${weight}kg,性别${gender},会在${places}锻炼
//`
//提交信息显示
const result = document.getElementById("result");
// 进行字符串拼接
//let content = ''
// content = `<br>身高${height}cm,体重${weight}kg,性别 ${gender},会在${sportPlace}健身锻炼。`
result.innerHTML = `
问卷提交完毕,感谢您的参与!
<br>
身高 ${height}cm,体重 ${weight}kg,性别${gender},会在${sportPlace}健身锻炼。
`;
result.style.display = "block";
}
// 不通过正确题解:
function formSubmit() {
// TODO:待补充代码
const height = document.getElementById("height").value;
const weight = document.getElementById("weight").value;
const gender = document.getElementById("male").checked ? "男" : "女";
const places = [];
const checkboxes = document.getElementsByName("place");
checkboxes.forEach((item) => {
if (item.checked) {
switch (item.value) {
case "1":
places.push("公园");
break;
case "2":
places.push("健身房");
break;
case "3":
places.push("户外");
break;
default:
break;
}
}
});
document.getElementById("quescontent").style.display = "none";
document.getElementById("result").innerHTML = `
问卷提交完毕,感谢您的参与!
<br>
身高 ${height}cm,体重 ${weight}kg,性别${gender},会在${places}锻炼
`;
document.getElementById("result").style.display = "block";
}省模拟赛 记词小能手
/**
* @description 待完成单词复习,跳到下一个单词
*/
function waitReview() {
// TODO: 请补充代码
if (index < words.length) {
index++;
console.log(index);
} else {
index = 1;
}
renderData(index);
}
/**
* @description 完成单词复习,删除当前单词,跳到下一个单词
*/
function completeReview() {
// TODO: 请补充代码
words.splice(index - 1, 1);
if (words.length === 0) {
wordBox.innerHTML = `<p class="success">今日复习已完成</p>`;
} else {
// 当删除最后一个元素后,index自动重置为1
if (index > words.length) index = 1;
renderData(index);
}
// TODO: END
waitTaskBox.innerText = `还需复习:${words.length}`;
}呵呵,filter,splice 傻傻分不清
头像合成器 DOM、事件
function onLoad() {
//TODO:待补充代码
const allbox = document.querySelectorAll(".app-condidate-filter");
allbox.forEach((box) => {
box.addEventListener("click", function () {
const url = box.style.backgroundImage.replace('url("', "");
const urlRes = url.replace('")', "");
console.log(urlRes);
const img = document.querySelector("#avatar-filter");
console.log(img);
img.src = urlRes;
allbox.forEach((item) => {
item.classList.remove("checked");
});
box.classList.add("checked");
});
});
}
window.onload = onLoad;vue2
外卖给好评
<template>
<div class="block">
<span class="demonstration">请为外卖评分: </span>
<ul class="rate-list">
<li>
<!-- TODO 补全 el-rate 属性 -->
送餐速度:<el-rate show-score v-model="speed" @change="change"></el-rate>
</li>
<li>
<!-- TODO 补全 el-rate 属性 -->
外卖口味:<el-rate show-score v-model="flavour" @change="change"></el-rate>
</li>
<li>
<!-- TODO 补全 el-rate 属性 -->
外卖包装:<el-rate show-score v-model="pack" @change="change"></el-rate>
</li>
</ul>
</div>
</template>
<style>
.block {
border: 1px solid #c7c5c5;
padding: 10px;
}
.rate-list {
list-style: none;
padding-inline-start: 20px;
margin-block-start: 10px;
margin-block-end: 10px;
}
.el-rate {
display: inline-block;
}
</style>
<script>
module.exports = {
data() {
return {
speed: 0, // 送餐速度
flavour: 0, // 外卖口味
pack: 0, // 外卖包装
};
},
/* TODO: 考生需要完成以下内容 */
methods: {
change() {
if (this.speed && this.flavour && this.pack) {
this.$emit("change", {
speed: this.speed,
flavour: this.flavour,
pack: this.pack,
});
}
},
},
};
</script>知识点:
事件传递 $emit
在 Vue 组件中,子组件可以使用 $emit 触发事件,通知父组件数据变化:
this.$emit("change", { speed: this.speed, flavour: this.flavour, pack: this.pack });父组件可以监听这个 change 事件:
<RateComponent @change="handleRateChange"></RateComponent>在 methods 里定义 handleRateChange 处理接收的数据。
vue3
6. 多表单校验 2024
切记切记
// TODO:待补充代码
const validateChineseName = (rule, value, callback) => {
const reg = /^[一-龥]+$/; // 匹配 **全部为汉字** 的字符串
if (!value) {
return callback(new Error("请输入姓名"));
} else if (!reg.test(value)) {
return callback(new Error("只能输入汉字"));
} else {
callback(); // 校验通过
}
};
const rules = reactive({
name: [
{ required: true, message: "请输入姓名,只能输入汉字", trigger: "blur" },
{ validator: validateChineseName, trigger: "blur" },
],
sex: [{ required: true, message: "请选择性别", trigger: "change" }],
age: [{ required: true, message: "请输入年龄", trigger: "blur" }],
isCompetition: [{ required: true, message: "请选择是否参加过编程比赛", trigger: "change" }],
isEntrepreneurship: [{ required: true, message: "请选择是否有过创业经历", trigger: "change" }],
});注意顺序
小蓝驿站 2024
注意渲染的层级问题 这里应该用user了
这样写是不对的,会遍历每一个进行追加
会造成这种结果
因为遍历数组每一项,如果if不执行,else会重复执行
错误解析:
正确解法:
const addContact = () => {
let firstLetter = newContact.value[0].toUpperCase();
let found = false; // 标记是否已找到该字母分组
contacts.value.forEach((item) => {
if (item.letter === firstLetter) {
item.contacts.push({ name: newContact.value });
found = true;
}
});
// 如果没有找到该字母分组,则创建一个新的
if (!found) {
contacts.value.push({
letter: firstLetter,
contacts: [{ name: newContact.value }],
});
}
};新冠疫情统计
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>全球新冠疫情数据统计</title>
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<script src="js/axios.js"></script>
<script src="js/vue.js" type="text/javascript" charset="utf-8"></script>
<script src="js/echarts.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<header>
<div>全球新冠疫情数据统计</div>
</header>
<main>
<!-- TODO: 请修改以下代码实现不同国家的数据展示功能 -->
<div class="title">
<h2>{{Country?Country:'请选择国家'}}</h2>
</div>
<div class="boxes">
<div class="box1">
<h3>确诊</h3>
<div class="number">
<span class="font-bold">新增:</span>
{{NewConfirmed}}
</div>
<div class="number">
<span class="font-bold">总计:</span>
{{TotalConfirmed}}
</div>
</div>
<div class="box2">
<h3>死亡</h3>
<div class="number">
<span class="font-bold">新增:</span>
{{NewDeaths}}
</div>
<div class="number">
<span class="font-bold">总计:</span>
{{TotalDeaths}}
</div>
</div>
</div>
<select v-model="selectedCountry" @change="change">
<option value="">Select Country</option>
<option v-for="item in options " :value="item">{{item.Country}}</option>
<!-- 请在此渲染所有国家选项 -->
</select>
<div id="chart" style="width: 100%; height: 50vh;"></div>
</main>
</div>
</body>
<script>
var vm = new Vue({
el: "#app",
data() {
return {
options: [], //所有数据
selectedCountry: "", // 选中的国家对象
Country: "", //国家名称
CountryCode: "", //国家简称
countryList: [], //国家简称列表
confirmedList: [], //累计确诊列表
NewConfirmed: 0, //新增确诊
TotalConfirmed: 0, //累计确诊
NewDeaths: 0, //新增死亡
TotalDeaths: 0, //累计死亡
};
},
methods: {
// TODO: 请修改该函数代码实现题目要求
initChart() {
// 初始化图表
this.chart = echarts.init(document.getElementById("chart"));
this.chartOptions = {
title: {
text: "全球感染人数前30国家累计确诊人数统计",
x: "center",
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
label: {
show: true,
},
},
},
// 设置x轴数据
xAxis: {
// 这里需要显示国家名称缩写,因为有些国家的全称太长,会导致界面不美观
data: this.countryList,
// [
// "US",
// "IN",
// "FR",
// "DE",
// "BR",
// "JP",
// "KR",
// ],
axisLabel: {
show: true,
interval: 0,
},
},
yAxis: {
type: "value",
name: "确诊数量",
},
// 设置y轴数据
series: [
{
data: this.confirmedList,
// [
// 100856162, 44680355, 39560482, 37446795, 36362366, 29489769, 29299166,],
type: "bar",
itemStyle: {
normal: {
color: "#a90000",
},
},
},
],
};
// 调用此方法设置 echarts 数据
this.chart.setOption(this.chartOptions);
},
// 改变select渲染数据
change() {
console.log("选中的国家数据:", this.selectedCountry); // 打印选中的国家对象及其数据
if (this.selectedCountry) {
this.NewConfirmed = this.selectedCountry.NewConfirmed;
this.TotalConfirmed = this.selectedCountry.TotalConfirmed;
this.NewDeaths = this.selectedCountry.NewDeaths;
this.TotalDeaths = this.selectedCountry.TotalDeaths;
this.Country = this.selectedCountry.Country;
} else {
this.TotalConfirmed = 0;
this.NewDeaths = 0;
this.TotalDeaths = 0;
this.NewConfirmed = 0;
this.Country = "请选择国家";
}
},
},
// TODO: 请在此添加代码实现组件加载时数据请求的功能
async mounted() {
const res = await axios.get("./js/covid-data.json");
// 得到数据进行赋值
this.options = res.data;
console.log(this.options);
this.options.forEach((element) => {
const countryCode = element.CountryCode;
const totalConfirmed = element.TotalConfirmed;
this.countryList.push(countryCode);
this.confirmedList.push(totalConfirmed);
});
console.log(this.countryList);
console.log(this.confirmedList);
this.initChart();
},
});
</script>
</html>错误点:会没有默认值
axios
组课神器 2023 第一问
/**
* @description 模拟 ajax 请求,拿到树型组件的数据 treeData
* @param {string} url 请求地址
* @param {string} method 请求方式,必填,默认为 get
* @param {string} data 请求体数据,可选参数
* @return {Array}
* */
async function ajax({ url, method = "get", data }) {
let result;
// TODO:根据请求方式 method 不同,拿到树型组件的数据
// 当method === "get" 时,localStorage 存在数据从 localStorage 中获取,不存在则从 /js/data.json 中获取
if (method === "get") {
const res = localStorage.getItem("data");
if (res) {
result = JSON.parse(res);
} else {
const ress = await axios("./js/data.json");
result = ress.data.data;
}
} else {
localStorage.setItem("data", JSON.stringify(data));
}
// 当method === "post" 时,将数据保存到localStorage 中,key 命名为 data
return result;
}基本用法
// 获取数据
axios
.get("https://api.example.com/data")
.then((response) => {
console.log("数据获取成功:", response.data);
})
.catch((error) => {
console.error("请求失败:", error);
});
// 带参数
axios.get("/api/users", {
params: {
page: 2,
limit: 10,
},
});axios
.post(
"/api/login",
{
username: "admin",
password: "secret",
},
{
headers: {
"Content-Type": "application/json",
},
},
)
.then((response) => {
localStorage.setItem("token", response.data.token);
});const formData = new FormData();
formData.append("avatar", fileInput.files[0]);
axios.post("/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度: ${percent}%`);
},
});echarts
github明星项目统计
不要再打错单词了谢谢 以及map使用前提是数组,注意上一步slice已经处理完返回了数组,就不要再.value操作,不然会undefined
正确代码:
const changeHandle = () => {
let data;
// TODO:待补充代码
if (language.value === "All") {
data = chartData.value.slice(pageStart.value - 1, pageEnd.value);
} else {
const filterData = chartData.value.filter((item) => item.language === language.value);
data = filterData.slice(pageStart.value - 1, pageEnd.value);
}
xData.value = data.map((item) => item.name);
yData.value = data.map((item) => item.stars);
console.log(language.value);
console.log(pageStart.value);
console.log(pageEnd.value);
initChart();
};列表渲染记得动态绑定value
2022 和手机相处的时光
/*TODO:ECharts 的配置中存在错误,请改正*/
var option = {
title: {
text: "一周的手机使用时长",
},
xAxis: {
type: "category",
data: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
},
yAxis: {
type: "value",
},
series: [
{
data: [2.5, 2, 2.6, 3.2, 4, 6, 5],
type: "line",
},
],
};天气趋势
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>天气趋势</title>
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<script src="./js/axios.js"></script>
<script src="js/vue.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/echarts.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
<div class="top-bar">2022年 Y 城全年温度统计图</div>
<!-- 主体 -->
<div class="container">
<!-- 月份 -->
<div class="month">
<ul>
<!-- TODO:待补充代码 在下面的 li 标签中完成 12个月份 (即 monthList) 的渲染 -->
<li
:class="value === showMonth ? 'active' : ''"
v-for="(value, key) in monthList"
@click="changeMonth(value)"
>
{{ value }}
</li>
</ul>
</div>
<div class="chart">
<!-- TODO:待补充代码 -->
<!-- currentMonth 未来七天和本月 tab 切换,只有当前月才显示 -->
<div id="currentMonth" v-if="showIndex === new Date().getMonth() + 1">
<div class="title">
<h3>{{!isCurentMonth ? '未来7天天气' : '本月天气'}}</h3>
<div class="type">
<span id="seven" :class="!isCurentMonth ? 'active' : ''" @click="changeSeven(true)"
>未来7天</span
>
<span
id="current"
:class="isCurentMonth ? 'active' : ''"
@click="changeSeven(false)"
>本月</span
>
</div>
</div>
</div>
<div id="chart"></div>
</div>
</div>
</div>
</body>
</html>
<script>
// TODO:待补充代码
var vm = new Vue({
el: "#app",
data: {
chart: null, // 图表
chartOptions: null, // 图表配置项
typeTitle: "本月天气",
monthList: {
January: "1月",
February: "2月",
March: "3月",
April: "4月",
May: "5月",
June: "6月",
July: "7月",
August: "8月",
September: "9月",
October: "10月",
November: "11月",
December: "12月",
},
list: [],
// showMonth: (new Date().getMonth() + 1).toString() + '月',
// showIndex: new Date().getMonth() + 1,
showIndex: 1,
showMonth: "1月",
isCurentMonth: true,
sevenXaixs: [],
sevenData: [],
},
mounted: function () {
// 先进行axios请求,然后初始化 echarts
axios.get("./js/weather.json").then((res) => {
this.list = res.data.map((item) => {
return Object.values(item)[0];
});
this.$nextTick(() => {
this.initChart();
});
});
},
methods: {
initChart() {
// 初始化图表
this.chart = echarts.init(document.getElementById("chart"));
// 配置项
this.chartOptions = {
grid: {
top: 35,
bottom: 5,
left: 10,
right: 10,
containLabel: true,
},
tooltip: {
trigger: "axis",
axisPointer: {
lineStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "rgba(255,255,255,0)",
},
{
offset: 0.5,
color: "rgba(255,255,255,1)",
},
{
offset: 1,
color: "rgba(255,255,255,1)",
},
],
global: false,
},
},
},
},
xAxis: [
{
type: "category",
boundaryGap: false,
axisLabel: {
formatter: "{value}",
fontSize: 12,
margin: 20,
textStyle: {
color: "#bfbfbf",
},
},
axisLine: {
lineStyle: {
color: "#e9e9e9",
},
},
splitLine: {
show: true,
lineStyle: {
color: "#f7f7f7",
},
},
axisTick: {
show: false,
},
// x 轴显示的数据,日期
data: !this.isCurentMonth
? this.sevenXaixs
: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31,
],
},
],
yAxis: [
{
boundaryGap: false,
type: "value",
axisLabel: {
textStyle: {
color: "#bfbfbf",
},
formatter: `{value}℃`,
},
nameTextStyle: {
color: "#fff",
fontSize: 12,
lineHeight: 40,
},
splitLine: {
lineStyle: {
color: "#f7f7f7",
},
},
axisLine: {
show: true,
lineStyle: {
color: "#e9e9e9",
},
},
axisTick: {
show: false,
},
},
],
series: [
{
name: "天气",
type: "line",
smooth: false,
showSymbol: false,
symbolSize: 0,
zlevel: 3,
itemStyle: {
color: "#ff6600",
borderColor: "#a3c8d8",
},
lineStyle: {
normal: {
width: 3,
color: "#ff6600",
},
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(
0,
0,
0,
1,
[
{
offset: 0,
color: "#ff6600",
},
{
offset: 0.8,
color: "#ff9900",
},
],
false,
),
},
},
// Y 轴显示的数据,即温度数据
data: !this.isCurentMonth ? this.sevenData : this.list[this.showIndex - 1],
},
],
};
// 调用此方法设置 echarts 数据
this.chart.setOption(this.chartOptions);
},
changeMonth(value) {
this.showMonth = value;
let charArr = value.split("");
charArr.pop();
this.showIndex = parseInt(charArr.join(""));
this.isCurentMonth = true;
this.$nextTick(() => {
this.initChart();
});
},
changeSeven(flag) {
this.isCurentMonth = !flag;
// 处理七天数据和七天轴
if (this.isCurentMonth === false) {
let sevenData = [];
let sevenXaixs = [];
let count = 0;
let month = new Date().getMonth() + 1;
let date = new Date().getDate();
for (let i = date; i < this.list[month - 1].length; i++) {
sevenData.push(this.list[month - 1][i - 1]);
sevenXaixs.push(month.toString() + "/" + i.toString());
}
console.log(sevenData);
console.log(sevenXaixs);
if (sevenData.length > 7) {
sevenData = sevenData.splice(0, 7);
sevenXaixs = sevenXaixs.splice(0, 7);
} else {
while (sevenData.length < 7) {
sevenData.push(this.list[month][count++]);
sevenXaixs.push((month + 1).toString() + "/" + count.toString());
}
}
this.sevenXaixs = sevenXaixs;
this.sevenData = sevenData;
console.log(sevenData);
console.log(sevenXaixs);
}
this.$nextTick(() => {
this.initChart();
});
},
},
});
</script>v-for 的语法是:(value, key, index) in object。这里的 value 是对象属性的值,key 是属性的名称,index 是当前遍历的索引
element-plus
node.js
tree 命令助手
拿到对应的路径,遍历路径下的文件和文件夹,格式化之后生成:
├── app.js
├── css
│ ├── index.css
│ └── style.css
├── index.html
└── js
├── demo.js
└── index.js不要通过是否带点来判断是否是文件夹
正确代码
// 引入 Node.js 相关模块
const fs = require("fs"); // 负责文件操作
const path = require("path"); // 负责路径操作
// 生成文件树的函数
function generateTree(dirPath) {
// 读取当前目录下的所有文件和文件夹
const files = fs.readdirSync(dirPath);
// 用于存储当前目录的文件树结构
const tree = [];
console.log(files); // 输出当前目录的所有文件和文件夹
// 遍历每一个文件或文件夹
files.forEach((file) => {
const filePath = path.join(dirPath, file); // 获取当前文件/文件夹的完整路径
const isDirectory = fs.statSync(filePath).isDirectory(); // 判断是否为文件夹
if (isDirectory) {
// 递归调用 generateTree 获取子目录的结构
const subtree = generateTree(filePath);
tree.push({ name: file, children: subtree }); // 存入当前文件夹的子结构
} else {
// 直接存入文件名
tree.push({ name: file });
}
});
return tree; // 返回当前目录的文件树
}知识点
fs模块
fs(File System)是 Node.js 内置的文件系统模块,用于执行文件操作,如读写文件、创建目录、删除文件等。
fs.readdirSync(dirPath)作用:同步读取指定目录下的所有文件和子目录,并返回一个文件名数组。
示例:
console.log(fs.readdirSync("./test"));
// 假设 test 目录下有 "a.txt"、"b.txt"、"subdir" 三个文件/文件夹
// 输出:['a.txt', 'b.txt', 'subdir']fs.statSync(filePath).isDirectory()作用:获取文件或文件夹的状态信息,并判断它是否是文件夹。
示例:
console.log(fs.statSync("./test").isDirectory()); // true or falsepath 模块(路径处理)
path 模块用于处理文件和目录路径。
path.join(dirPath, file)作用:拼接路径,保证跨平台兼容(Windows 用
\,Linux/macOS 用/)。示例:
console.log(path.join("folder", "file.txt"));
// 在 Windows: 'folder\file.txt'
// 在 macOS/Linux: 'folder/file.txt'递归(Recursion)
递归是指函数内部调用自身,适用于分层结构(如文件树)。
- 在本代码中,如果遇到子目录,就再次调用
generateTree(filePath),直到所有子目录都被遍历完。
解法二: 深度优先 栈
const fs = require("fs");
const path = require("path");
// 生成目录树结构的核心函数
function generateTree(rootDir) {
// 使用栈数据结构实现深度优先遍历
const stack = [
{
// 初始化根节点:包含目录名和子节点容器
node: {
name: path.basename(rootDir), // 提取目录名(如 '/user/project' -> 'project')
children: [], // 子节点容器(文件/子目录)
},
dirPath: rootDir, // 当前处理的绝对路径
},
];
// 保存根节点引用(即栈中第一个元素的node)
const rootNode = stack[0].node;
// 深度优先遍历开始
while (stack.length > 0) {
// 弹出栈顶元素:包含当前节点和对应路径
const { node, dirPath } = stack.pop();
// 读取当前目录下的所有条目(文件和子目录)
const items = fs.readdirSync(dirPath);
// 处理每个条目
items.forEach((item) => {
// 构建完整路径(如 '/project' + 'src' -> '/project/src')
const fullPath = path.join(dirPath, item);
// 判断是否为目录(true=目录,false=文件)
const isDirectory = fs.statSync(fullPath).isDirectory();
// 创建新节点:包含名称和可能的子节点容器
const newNode = {
name: item,
// 如果是目录则初始化空数组,文件则设为 undefined
children: isDirectory ? [] : undefined,
};
// 如果是目录,将新节点压入栈(后续处理其子项)
if (isDirectory) {
stack.push({
node: newNode, // 新目录对应的节点
dirPath: fullPath, // 新目录的绝对路径
});
}
// 将新节点添加到父节点的 children 数组中
node.children.push(newNode);
});
}
// 返回根目录的直接子节点数组(而非整个根节点对象)
return rootNode.children; // 重要设计:使 generateDirectoryStructure 可以直接使用
}箭头函数等号和大于号中间不能有空格
markdown文档解析 2023
class Parser {
constructor() {
this.heading = /^(#{1,6}\s+)/;
this.blockQuote = /^(\>\s+)/;
this.unorderedList = /^((\*|-){1}\s+)/;
this.image = /\!\[(.*?)\]\((.*?)\)/g;
this.strongText = /\*{2}(.*?)\*{2}/g;
this.codeLine = /\`{1}(.*?)\`{1}/g;
// TODO: 补充分割符正则
this.hr = /^\-{3,}/;
//这里容易错,写成/^-{3,}/
//短横线记得转义
}
// 获取单行内容
parseLineText(lineText) {
this.lineText = lineText;
}
// 是否是空行
isEmptyLine() {
return this.lineText === "";
}
// 是否为符合标题规范
isHeading() {
return this.heading.test(this.lineText);
}
isHr() {
return this.hr.test(this.lineText);
}
isBlockQuote() {
return this.blockQuote.test(this.lineText);
}
isList() {
return this.unorderedList.test(this.lineText);
}
isImage() {
return this.image.test(this.lineText);
}
isNomalLine() {
return true;
}
isStrong() {
return this.strongText.test(this.lineText);
}
isCode() {
return this.codeLine.test(this.lineText);
}
// 解析标题
parseHeading() {
const temp = this.lineText.split(" ");
const headingLevel = temp[0].length;
const title = temp[1].trim();
return `<h${headingLevel}>${title}</h${headingLevel}>`;
}
parseHr() {
return `<hr>`;
}
parseBlockQuote() {
const temp = this.lineText.split(" ");
const content = temp[1].trim();
return `<p>${content}</p>`;
}
parseList() {
const temp = this.lineText.split(" ");
const content = temp[1].trim();
return `<li>${content}</li>`;
}
parseImage() {
const myReg = /^\!\[(\S+)\]\((\S+)\)/;
const str = this.lineText;
myReg.test(str);
return `<img src="${RegExp.$2}" alt="${RegExp.$1}">`;
}
parseStrong() {
return this.lineText.replace(this.strongText, `<b>${RegExp.$1}</b>`);
}
parseCodeLine() {
return this.lineText.replace(this.codeLine, `<code>${RegExp.$1}</code>`);
}
/**
* TODO: 请完成剩余各种语法的解析
* 1. 完成对分隔符的解析
* 2. 完成对引用区块的解析
* 3. 完成对图片,和文字效果的解析
* 4. 完成对无序列表的解析
*/
}
class Reader {
constructor(text) {
//获取全部原始文本
this.text = text;
this.lines = this.getLines();
this.parser = new Parser();
}
runParser() {
let currentLine = 0;
let hasParsed = [];
while (!this.reachToEndLine(currentLine)) {
// 获取行文本
this.parser.parseLineText(this.getLineText(currentLine));
// 判断空白行
if (this.parser.isEmptyLine()) {
currentLine++;
continue;
}
if (this.parser.isHeading()) {
hasParsed.push(this.parser.parseHeading());
currentLine++;
continue;
}
// TODO: 请完成剩余各种语法的解析
if (this.parser.isHr()) {
hasParsed.push(this.parser.parseHr());
currentLine++;
continue;
}
if (this.parser.isBlockQuote()) {
const preLine = this.getLineText(currentLine - 1);
const nextLine = this.getLineText(currentLine + 1);
if (!this.parser.blockQuote.test(preLine)) {
hasParsed.push(`<blockquote>`);
}
hasParsed.push(this.parser.parseBlockQuote());
currentLine++;
if (!this.parser.blockQuote.test(nextLine)) {
hasParsed.push(`</blockquote>`);
}
continue;
}
if (this.parser.isList()) {
const preLine = this.getLineText(currentLine - 1);
const nextLine = this.getLineText(currentLine + 1);
if (!this.parser.unorderedList.test(preLine)) {
hasParsed.push(`<ul>`);
}
hasParsed.push(this.parser.parseList());
currentLine++;
if (!this.parser.unorderedList.test(nextLine)) {
hasParsed.push(`</ul>`);
}
continue;
}
if (this.parser.isImage()) {
hasParsed.push(this.parser.parseImage());
currentLine++;
continue;
}
if (this.parser.isNomalLine()) {
let lineText = this.getLineText(currentLine);
if (this.parser.isStrong()) {
lineText = lineText.replace(this.parser.strongText, "<b>$1</b>");
}
if (this.parser.isCode()) {
lineText = lineText.replace(this.parser.codeLine, "<code>$1</code>");
}
hasParsed.push(lineText);
currentLine++;
continue;
}
currentLine++;
}
return hasParsed.join("");
}
getLineText(lineNum) {
return this.lines[lineNum];
}
getLines() {
this.lines = this.text.split("\n");
return this.lines;
}
reachToEndLine(line) {
return line >= this.lines.length;
}
}
module.exports = function parseMarkdown(markdownContent) {
return new Reader(markdownContent).runParser();
};有注释版本:
class Parser {
constructor() {
// 正则表达式定义
this.heading = /^(#{1,6}\s+)/; // 匹配标题(# 前缀)
this.blockQuote = /^(\>\s+)/; // 匹配引用块(> 前缀)
this.unorderedList = /^((\*|-){1}\s+)/; // 匹配无序列表(* 或 - 开头)
this.image = /\!\[(.*?)\]\((.*?)\)/g; // 匹配图片语法()
this.strongText = /\*{2}(.*?)\*{2}/g; // 匹配加粗文本(**text**)
this.codeLine = /\`{1}(.*?)\`{1}/g; // 匹配行内代码(`code`)
this.hr = /^\-{3,}/; // 匹配水平分割线(---)
}
// 存储当前行文本
parseLineText(lineText) {
this.lineText = lineText;
}
// 判断是否为空行
isEmptyLine() {
return this.lineText === "";
}
// 检测是否是标题
isHeading() {
return this.heading.test(this.lineText);
}
// 检测是否是水平分割线
isHr() {
return this.hr.test(this.lineText);
}
// 检测是否是引用块
isBlockQuote() {
return this.blockQuote.test(this.lineText);
}
// 检测是否是无序列表
isList() {
return this.unorderedList.test(this.lineText);
}
// 检测是否是图片
isImage() {
return this.image.test(this.lineText);
}
// 默认普通行(未被其他规则匹配的行)
isNomalLine() {
return true;
}
// 检测是否包含加粗文本
isStrong() {
return this.strongText.test(this.lineText);
}
// 检测是否包含行内代码
isCode() {
return this.codeLine.test(this.lineText);
}
// 解析标题为 HTML
parseHeading() {
const temp = this.lineText.split(" ");
const headingLevel = temp[0].length; // # 的数量决定标题级别
const title = temp[1].trim();
return `<h${headingLevel}>${title}</h${headingLevel}>`;
}
// 解析水平分割线为 <hr>
parseHr() {
return `<hr>`;
}
// 解析引用块(当前实现不完整,仅处理单行)
parseBlockQuote() {
const temp = this.lineText.split(" ");
const content = temp[1].trim();
return `<p>${content}</p>`; // 问题点:应使用 <blockquote> 标签
}
// 解析列表项为 <li>
parseList() {
const temp = this.lineText.split(" "); // 用空格分割当前行文本
const content = temp[1].trim(); // 取分割后的第二部分内容
return `<li>${content}</li>`;
}
// 解析图片为 <img> 标签
parseImage() {
const myReg = /^\!\[(\S+)\]\((\S+)\)/;
const str = this.lineText;
myReg.test(str);
return `<img src="${RegExp.$2}" alt="${RegExp.$1}">`; // 使用正则捕获组提取 alt 和 src
}
// 将 **text** 替换为 <b> 标签
parseStrong() {
return this.lineText.replace(this.strongText, `<b>${RegExp.$1}</b>`);
}
// 将 `code` 替换为 <code> 标签
parseCodeLine() {
return this.lineText.replace(this.codeLine, `<code>${RegExp.$1}</code>`);
}
}class Reader {
constructor(text) {
this.text = text; // 原始 Markdown 文本
this.lines = this.getLines(); // 按行分割后的数组
this.parser = new Parser(); // 语法解析器实例
}
// 核心解析流程
runParser() {
let currentLine = 0;
let hasParsed = []; // 存储解析后的 HTML 片段
while (!this.reachToEndLine(currentLine)) {
// 获取当前行文本并传递给解析器
this.parser.parseLineText(this.getLineText(currentLine));
// 跳过空行
if (this.parser.isEmptyLine()) {
currentLine++;
continue;
}
// 优先级判断逻辑
// 1. 处理标题
if (this.parser.isHeading()) {
hasParsed.push(this.parser.parseHeading());
currentLine++;
continue;
}
// 2. 处理水平分割线
if (this.parser.isHr()) {
hasParsed.push(this.parser.parseHr());
currentLine++;
continue;
}
// 3. 处理引用块(带嵌套检测)
if (this.parser.isBlockQuote()) {
// 检查前后行是否为引用块
const preLine = this.getLineText(currentLine - 1);
const nextLine = this.getLineText(currentLine + 1);
// 前一行不是引用块时添加 <blockquote> 开始标签
if (!this.parser.blockQuote.test(preLine)) {
hasParsed.push(`<blockquote>`);
}
hasParsed.push(this.parser.parseBlockQuote());
currentLine++;
// 下一行不是引用块时闭合标签
if (!this.parser.blockQuote.test(nextLine)) {
hasParsed.push(`</blockquote>`);
}
continue;
}
// 4. 处理无序列表(带嵌套检测)
if (this.parser.isList()) {
const preLine = this.getLineText(currentLine - 1);
const nextLine = this.getLineText(currentLine + 1);
// 前一行不是列表时添加 <ul> 开始标签
if (!this.parser.unorderedList.test(preLine)) {
hasParsed.push(`<ul>`);
}
hasParsed.push(this.parser.parseList());
currentLine++;
// 下一行不是列表时闭合标签
if (!this.parser.unorderedList.test(nextLine)) {
hasParsed.push(`</ul>`);
}
continue;
}
// 5. 处理图片
if (this.parser.isImage()) {
hasParsed.push(this.parser.parseImage());
currentLine++;
continue;
}
// 6. 处理普通文本(含内联样式)
if (this.parser.isNomalLine()) {
let lineText = this.getLineText(currentLine);
// 处理加粗
if (this.parser.isStrong()) {
lineText = lineText.replace(this.parser.strongText, "<b>$1</b>");
}
// 处理行内代码
if (this.parser.isCode()) {
lineText = lineText.replace(this.parser.codeLine, "<code>$1</code>");
}
hasParsed.push(lineText);
currentLine++;
continue;
}
currentLine++;
}
return hasParsed.join(""); // 合并所有 HTML 片段
}
// 辅助方法
getLineText(lineNum) {
return this.lines[lineNum];
} // 获取指定行文本
getLines() {
return this.text.split("\n");
} // 按换行符分割文本
reachToEndLine(line) {
return line >= this.lines.length;
} // 检测是否到文件末尾
}注意:const myReg = /^\!\[(\S+)\]\((\S+)\)/
^匹配输入字符串的开头,确保从行首开始匹配。\!匹配字面量的感叹号!(\!中的\是转义符)。\[匹配左方括号[(\[是转义后的写法)。(\S+)\S+:匹配 1 个或多个非空白字符(包括字母、数字、符号,但不含空格、换行等)。第一个捕获组:捕获
![...]中的内容(即图片的替代文本alt)。
\]匹配右方括号](转义后的写法)。\(匹配左括号((转义后的写法)。(\S+)- 第二个捕获组:捕获
(...)中的内容(即图片的 URL 或路径)。
- 第二个捕获组:捕获
\)匹配右括号)(转义后的写法)。
加粗文本正则 /\*{2}(.*?)\*{2}/g
原理解析:
\*{2}匹配 两个连续的星号**。\*转义表示字面量星号,{2}表示精确匹配两次。
(.*?)非贪婪匹配任意字符(除换行符),捕获加粗内容。.*?会匹配尽可能少的字符,直到遇到下一个**。
\*{2}匹配闭合的**。
