Skip to content

蓝桥杯备赛

作者:青见春山
发表于:2026-07-27
字数统计:19826 字
预计阅读67分钟

css

https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

flex布局

文字的水平居中

css
text-align: center;

Image

省模拟赛 三行情书 CSS 中的单行、多行文本溢出

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;
}
  1. text-overflow: ellipsis

    • 作用:当文本溢出容器时,显示省略号(...)。

    • 前提条件:

      • 必须设置 overflow: hidden

      • 必须设置 white-space: nowrap(单行)或限制高度的多行布局

  2. overflow: hidden

    • 隐藏超出容器尺寸的内容,与 text-overflow 配合实现截断效果。
css
display: -webkit-box; /* 使用旧版弹性盒模型(多行截断) */
-webkit-box-orient: vertical; /* 设置弹性盒子元素垂直排列 */
-webkit-line-clamp: 3; /* 限制显示 3 行文本 */
overflow: hidden; /* 隐藏溢出内容 */

关键知识点

  1. display: -webkit-box

    • 作用:启用旧版弹性盒模型(Flexbox 的前身),用于实现多行文本布局。

    • 注意:这是 WebKit 私有属性,现代浏览器推荐使用 display: flex,但多行截断仍需此属性。

  2. -webkit-box-orient: vertical

    • 作用:设置弹性盒子元素在垂直方向排列,为多行截断提供方向基础。
  3. -webkit-line-clamp: 3

    • 作用:限制文本最多显示 3 行,超出行数部分隐藏并显示省略号。

    • 兼容性:需配合 display: -webkit-boxoverflow: hidden 使用。

Image

省模拟赛 日历格绘 grid

css
/* 日 */
#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

Image

正解代码:

css
/* 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 个元素

css
.seat-area .seat:nth-child(3) {
  background-color: red;
}

作用:选中 第 3 个 .seat,背景变红。

选中所有偶数/奇数的元素

css
/* 选中偶数(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+15n+2 等。

n 的基本作用

n 代表 从 0 开始的递增整数(0,1,2,3...),所以 3n+1 这样写时,可以代入不同的 n 值。

选中“每 3 个中的第 1 个”

css
.seat-area .seat:nth-child(3n + 1) {
  background-color: yellow;
}

作用:选中 第 1、4、7、10....seat

选中“每 5 个中的第 2 个”

css
.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>,忽略其他类型的元素。

html
<div>我是 div 1</div>
<p>我是 p 1</p>
<div>我是 div 2</div>
<p>我是 p 2</p>
<div>我是 div 3</div>
<p>我是 p 3</p>
css
div:nth-child(2) {
  color: red; /* 这里会选中 `<p>`,因为它是整个父级的第 2 个子元素 */
}

div:nth-of-type(2) {
  color: blue; /* 这里只选中第二个 `<div>` */
}

选中前 n 个元素

css
.seat-area .seat:nth-child(-n + 5) {
  background-color: orange;
}

作用:选中 前 5 个元素

排除前 n 个元素

css
.seat-area .seat:nth-child(n + 6) {
  background-color: purple;
}

作用:选中 第 6 个及以后的元素

2023 图片水印生成 结合js

javascript
// 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 是角度,正数是顺时针旋转,负数是逆时针旋转

示例

css
.box {
  transform: rotate(45deg); /* 顺时针旋转 45° */
}
.box2 {
  transform: rotate(-30deg); /* 逆时针旋转 30° */
}

扩展:transform 还能做什么?

transform 不止可以旋转,还能缩放、移动、倾斜

javascript
element.style.transform = `rotate(30deg) scale(1.5) translateX(50px)`;

2022 展开你的扇子 transform

css
/*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 蓝桥知识网 纯静态

css
/*
 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;
}
html
<!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 自适应页面 媒体查询响应式

css
/* 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

景深效果,好难

Image

Image

https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter-function/blur mdn

Image

Image

模板字符串应用,基础中的基础

Image

布局切换 添加移除类名实现效果 2024

Image

知识点参考文档:

https://developer.mozilla.org/zh-CN/docs/Web/API/Element/classList

javascript
// 经典模式,浏览模式,工具模式点击事件
    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

Image

实现思路

  1. 使用 reduce 迭代 sequence 数组

    • reduce 可以依次执行 sequence 中的所有函数,并传递上一个函数的结果作为下一个函数的输入。

    • reduce 初始值是 Promise.resolve(initialValue),确保整个流程从一个 Promise 开始,统一处理同步和异步情况。

  2. 使用 Promise.resolve() 处理可能的同步函数

    • Promise.resolve(value):确保即使某个 sequence 函数是同步的,也能正确处理。
  3. 错误处理

    • reduce 过程中捕获可能的错误,避免整个流程崩溃。
javascript
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 {}
javascript
// 官方解法:
const pipeline = async (initialValue, sequence) => {
  let res = initialValue;
  for (const fn of sequence) {
    res = await fn(res);
  }
  return res;
};

代码解析

  1. 初始化 Promise.resolve(initialValue)

    • 确保 initialValue 被包装成 Promise,以支持同步/异步混合处理。
  2. reduce 顺序执行

    • prevPromise.then(currentFunc) 让每个函数的返回值作为下一个函数的输入,形成串行执行。
  3. 支持同步 & 异步

    • step3 是一个同步函数,但 Promise.then(currentFunc) 仍能正确传递结果。

执行流程示例

假设 initialValue = 5sequence = [step1, step2, step3],执行顺序如下:

  1. step1(5) → 结果 6Promise 延迟 500ms)

  2. step2(6) → 结果 12Promise 延迟 500ms)

  3. step3(12) → 结果 9(同步)

最终 console.log("最终结果:", result) 会输出:

最终结果: 9


总结

顺序执行:无论 sequence 中的函数是同步还是异步,都会按顺序执行。 自动传递数据:每个步骤的输出自动作为下一个步骤的输入。 错误处理:如果 sequence 中的某个 Promise 失败,整个 pipeline 会进入 catch 处理。 返回 Promise:支持 await,适用于 async/await 编程风格。

复习promise和reduce谢谢

promise复习

同步和异步

我们编写的JavaScript代码实际上是单线程的程序,那就决定了我们的代码是一行一行的顺序执行的,处理一些简短、快速的运算操作时主线程就够了,如果在同步程序中发送了网络请求,如果超时了,下面的代码依赖于网络请求,那么整个网页将会失去响应。

而异步的概念则与同步恰恰相反,一个异步过程的执行将不再与原有的序列有顺序关系,特别是对发送网络请求,不确保响应时间时候,异步是最优选择,网络请求无论多长时间,程序将不会在此等待,直接走下面的代码,等异步的请求有了响应,主线程几乎不用关心异步任务的状态了,自己完成回调后续的操作,程序间互不影响。

简单来理解:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。

Image

为什么要使用Promise

"回调地狱"这个词,不知道大家听过没有,就是异步调用获取到结果后,为下一个异步函数提供参数,所以就会一层一层的出现回调里面嵌入回调,导致层次很深,代码维护起来特别的复杂,看一下下面的小案例大家就知道什么意思了。

下面的举例就以uniapp里面的网络请求uni.request()为例了,如果你做的是微信小程序wx.request()也是一样的,还有jQuery的ajax(),这些都是异步请求,通过success回调函数获取数据的,那有童鞋会问为什么不适用vue的axios那,因为axios网络请求已经封装了promise了。

javascript
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请求封装出一个函数将结果进行返回,这就是原来常用的回调函数方案,将上述代码可以改造成如下代码:

使用回调函数的方法:(本质还是回调地狱,可读性好一点而已)

Image

Image

什么是promise

promise是解决异步的方法,本质上是一个构造函数,可以用它实例化一个对象。对象身上有resolve、reject、all,原型上有then、catch方法。promise对象有三种状态:pending(初识状态/进行中)、resolved或fulfilled(成功)、rejected(失败)

  1. pending。它的意思是 "待定的,将发生的",相当于是一个初始状态。创建Promise对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。

Image

  1. resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法

Image

  1. rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。

Image

Image

Image

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 实现继承的一种机制,每个对象都可以从它的 原型 继承属性和方法。

代码中的原型体现
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 原生的构造函数,它的原型链如下:

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)创建对象的过程

代码中的实例化
javascript
let p = new Promise((resolve, reject) => {...});
let p = new Promise((resolve, reject) => {...});

这里 Promise 是一个 构造函数,而 p 是用 new 关键字实例化出的一个 Promise 实例

实例化的作用
  • 创建一个新的对象 p

  • p 继承 Promise.prototype 上的方法

  • 独立的 Promise 实例,可以拥有不同的状态(pending、fulfilled、rejected)

将回调函数修改为promise方案

.then 的链式调用函数

Image

函数返回promise对象

Image

promise.all 异步请求同步化 页面加载

Image

Image

async await 异步同步化

Image

await坑点

Image

Image

Image

其他解法

Image

分享点滴 2024

很好的做法:

javascript
/**
 * @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;
}

Image

点击分析按钮显示这样也是对的

Image

javascript
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 这里会返回一个数组,所以判断数组的长度,就可以知道有没有参数内容

Image

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/entries

Object.entries(params) 把 params 对象传入

会形成一个数组,每个元素也是一个数组,元素的数组是键值对

比如:

javascript
const obj = { foo: "bar", baz: 42 };
console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

Image

.map(([key, value]) => ...)

  • 遍历数组,把 keyvalue 转换成 key=value 形式的字符串。

  • encodeURIComponent(...) 用于编码,防止 URL 特殊字符(如 ?&=)被误解析。例如:

javascript
encodeURIComponent("a b"); // 结果是 "a%20b"

.join('&')

  • 将转换后的键值对数组合并成字符串,用 & 连接,例如:
javascript
"key1=value1&key2=value2";

5. 找回连接的奇幻之旅 2024

Image

javascript
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 };
}

Image

Image

悠然画境 2024

根据用户输入的 selectedText(选中的文本),从 artDataArray 中筛选出最佳匹配的图片,并按照匹配程度排序,返回指定数量的图片

javascript
/**
 * @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;
}

实现代码:

javascript
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 方法用于遍历数组并返回一个新数组,它的核心作用是:

  1. item.tags.split("、")

    • tags 是一个包含多个关键字的字符串(用顿号 分隔),先用 split("、") 转换为数组,例如:
    javascript
    "风景、日落、山川" => ["风景", "日落", "山川"]
  2. .filter(s => selectedText.includes(s))

    • 过滤出 tags存在于 selectedText 的关键词。

    • selectedText.includes(s) 检查 selectedText 是否包含该 tag,返回 true 的会被保留。

    • 例如:

    javascript
    selectedText = "今天的日落很美"[("风景", "日落", "山川")].filter((s) =>
      "今天的日落很美".includes(s),
    );
    // 结果: ["日落"]  =>  匹配到1个tag

{ ...item, weight }:使用 扩展运算符 ...,保留原 item 的所有属性,并添加 weight 字段。

重点:

weight 降序排序

javascript
.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 难难难

代码一

javascript
/**
* 将同一天浏览的相同商品去重并作为数组返回
* @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 允许使用复杂类型作为键(如 数组、对象)。

  • Maphas() 查找性能是 O(1),而对象的 in 操作符可能需要 O(n)(在原型链上查找)。

if (!uniqueMap.has(key)):去重

javascript
if (!uniqueMap.has(key)) {
  uniqueMap.set(key, item);
  newData.push(item);
}
逻辑
  • has(key) 判断 Map 中是否已存在相同 key(即 日期+商品ID)。

  • 如果不存在,则存入 Map,并将 item 添加到 newData 结果数组

  • 这样确保了同一天不会有重复的相同商品

代码二

javascript
/**
 * 将去重后的数据根据字段 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() 进行降序排序

javascript
return dateB.localeCompare(dateA);
  • localeCompare() 按照字符串的字典顺序比较 dateB - dateA,保证最新日期排前面。

代码三

javascript
/**
 * 将去重排序后的所有商品数据,作为一个对象存储并返回,
 * 该对象的所有 `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;
};

Image

简易jsx解析器

创建一个 DOM 元素,并根据传入的 config 对象为其设置内容、属性、样式以及事件。

typescript
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)

Image

Image

2. Array.isArray()

Image

3. setAttribute(name, value)

Image

Image

Image

4. && 短路逻辑

Image

Image

2023 收集帛书碎片 数组去重

javascript
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;
}

简单解法:

javascript
return [...new Set(puzzles.flat())];

去重并展平一个二维数组 puzzles,返回去重后的元素数组

puzzles.flat():展平数组

  • puzzles 是一个二维数组(即数组中的元素仍然是数组)。

  • Array.prototype.flat() 用于 展平数组,默认展开一层,即把二维数组变成一维数组。

javascript
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] (全部展开)

SetES6 引入的集合数据结构,它存储的值 不允许重复

通过 new Set(array),我们可以移除 array 中的重复值。

javascript
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 类型不能像数组一样用索引访问,所以我们使用 展开运算符 (...) 把它转换为数组。
javascript
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视频弹幕 难啊难

javascript
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年度明星项目

javascript
// 保存翻译文件数据的变量
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>
  `;
}

知识点:

javascript
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 的对象。
//如果找到了,返回该对象。 对象用.运算符取到属性 .descriptionCN
javascript
const res = await fetch("./js/all-data.json");
data = await res.json(); // 背下来

补充:fetch使用

javascript
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; // 向上传递错误
  }
}
javascript
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);
});
javascript
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" });
javascript
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);
javascript
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转换与生成

很巧妙的一个题解做法

Image

javascript
// 将用户输入的带分隔符的 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-9

  • X 匹配字母 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("") 会按照每个字符拆分字符串,返回一个数组。

示例
javascript
let str = "12345";
console.log(str.split(""));
// 结果: ["1", "2", "3", "4", "5"] (字符串数组)

.map(Number) — 把字符串转换为数字

  • .map(Number) 作用是 把数组中的每个元素都转换成数字

  • Number() 是 JavaScript 的 内置构造函数,用于显式转换为数字。

示例
javascript
let arr = ["1", "2", "3", "4", "5"];
console.log(arr.map(Number));
// 结果: [1, 2, 3, 4, 5] (数字数组)

如果输入字符串可能包含 -、空格等无效字符,可以使用 正则表达式 过滤:

javascript
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

Image

2022 课程列表

javascript
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 灯的颜色变化

javascript
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 冬奥大抽奖

javascript
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 寻找小狼人

javascript
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 数组的功能,创建自己的自定义方法(类似于 mapfilter 等内置方法)。

2. this:

  • 在 JavaScript 中,this 关键字通常指向当前执行上下文的对象。在这里,this 是指调用 myarray 方法的数组实例。

  • 例如,调用 [1, 2, 3].myarray() 时,this 就是 [1, 2, 3] 数组。

3. reduce() 方法:

  • reduce() 是数组的一个内置方法,它接收一个回调函数作为参数。这个回调函数有两个参数:

    • acc: 累积值,通常用来存储最终结果。在这里,acc 是一个数组,用来存储符合条件的元素。

    • cur: 当前数组元素,表示正在遍历的当前项。

  • reduce() 方法会遍历数组中的每一个元素,将它们与累积值一起传递给回调函数,最终返回累积值(此例中是一个新数组)。

例如,以下代码使用 reduce() 计算数组元素的和:

javascript
const sum = [1, 2, 3].reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 输出: 6

在你的代码中,reduce() 被用来遍历数组,并根据回调函数 cb 的条件筛选元素。

4. 回调函数 cb:

  • cb 是传递给 myarray 方法的回调函数,它接收当前数组元素(cur)作为参数,并返回一个布尔值(truefalse),表示该元素是否满足条件。

  • myarray 方法中,cb(cur) 被用于判断当前元素是否符合某个条件。如果返回 true,则将当前元素添加到累积数组 acc 中。

例如,回调函数可以是以下形式:

javascript
const isEven = (num) => num % 2 === 0; // 判断是否为偶数

然后调用 myarray 方法时:

javascript
[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 的元素,你可以这样做:

javascript
const numbers = [1, 2, 3, 4, 5];
const result = numbers.myarray((num) => num > 2);
console.log(result); // 输出: [3, 4, 5]

2022健身大调查

javascript
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";
}
// 不通过

正确题解:

javascript
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";
}

省模拟赛 记词小能手

javascript
/**
 * @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、事件

javascript
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

Image

外卖给好评

html
<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 触发事件,通知父组件数据变化:

javascript
this.$emit("change", { speed: this.speed, flavour: this.flavour, pack: this.pack });

父组件可以监听这个 change 事件:

html
<RateComponent @change="handleRateChange"></RateComponent>

methods 里定义 handleRateChange 处理接收的数据。

vue3

6. 多表单校验 2024

Image

切记切记

Image

javascript
// 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" }],
});

注意顺序

Image

小蓝驿站 2024

注意渲染的层级问题 这里应该用user了

Image

这样写是不对的,会遍历每一个进行追加

Image

会造成这种结果

因为遍历数组每一项,如果if不执行,else会重复执行

Image

错误解析:

Image

Image

正确解法:

javascript
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 }],
    });
  }
};

新冠疫情统计

html
<!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>

错误点:会没有默认值

Image

axios

组课神器 2023 第一问

javascript
/**
 * @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;
}

基本用法

javascript
// 获取数据
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,
  },
});
javascript
axios
  .post(
    "/api/login",
    {
      username: "admin",
      password: "secret",
    },
    {
      headers: {
        "Content-Type": "application/json",
      },
    },
  )
  .then((response) => {
    localStorage.setItem("token", response.data.token);
  });
javascript
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明星项目统计

Image

不要再打错单词了谢谢 以及map使用前提是数组,注意上一步slice已经处理完返回了数组,就不要再.value操作,不然会undefined

正确代码:

javascript
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

Image

2022 和手机相处的时光

javascript
/*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",
    },
  ],
};

天气趋势

html
<!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

不要通过是否带点来判断是否是文件夹

正确代码

javascript
// 引入 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)
    • 作用:同步读取指定目录下的所有文件和子目录,并返回一个文件名数组

    • 示例

javascript
console.log(fs.readdirSync("./test"));
// 假设 test 目录下有 "a.txt"、"b.txt"、"subdir" 三个文件/文件夹
// 输出:['a.txt', 'b.txt', 'subdir']
  • fs.statSync(filePath).isDirectory()
    • 作用:获取文件或文件夹的状态信息,并判断它是否是文件夹

    • 示例

javascript
console.log(fs.statSync("./test").isDirectory()); // true or false

path 模块(路径处理)

path 模块用于处理文件和目录路径。

  • path.join(dirPath, file)
    • 作用:拼接路径,保证跨平台兼容(Windows 用 \,Linux/macOS 用 /)。

    • 示例

javascript
console.log(path.join("folder", "file.txt"));
// 在 Windows: 'folder\file.txt'
// 在 macOS/Linux: 'folder/file.txt'

递归(Recursion)

递归是指函数内部调用自身,适用于分层结构(如文件树)。

  • 在本代码中,如果遇到子目录,就再次调用 generateTree(filePath),直到所有子目录都被遍历完。

解法二: 深度优先 栈

javascript
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

javascript
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();
};

有注释版本:

javascript
class Parser {
  constructor() {
    // 正则表达式定义
    this.heading = /^(#{1,6}\s+)/; // 匹配标题(# 前缀)
    this.blockQuote = /^(\>\s+)/; // 匹配引用块(> 前缀)
    this.unorderedList = /^((\*|-){1}\s+)/; // 匹配无序列表(* 或 - 开头)
    this.image = /\!\[(.*?)\]\((.*?)\)/g; // 匹配图片语法(![alt](url))
    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>`);
  }
}
javascript
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+)\)/

  1. ^ 匹配输入字符串的开头,确保从行首开始匹配。

  2. \! 匹配字面量的感叹号 !\! 中的 \ 是转义符)。

  3. \[ 匹配左方括号 [\[ 是转义后的写法)。

  4. (\S+)

    • \S+:匹配 1 个或多个非空白字符(包括字母、数字、符号,但不含空格、换行等)。

    • 第一个捕获组:捕获 ![...] 中的内容(即图片的替代文本 alt)。

  5. \] 匹配右方括号 ](转义后的写法)。

  6. \( 匹配左括号 ((转义后的写法)。

  7. (\S+)

    • 第二个捕获组:捕获 (...) 中的内容(即图片的 URL 或路径)。
  8. \) 匹配右括号 )(转义后的写法)。

加粗文本正则 /\*{2}(.*?)\*{2}/g

原理解析:

  • \*{2} 匹配 两个连续的星号 **

    • \* 转义表示字面量星号,{2} 表示精确匹配两次。
  • (.*?) 非贪婪匹配任意字符(除换行符),捕获加粗内容。

    • .*? 会匹配尽可能少的字符,直到遇到下一个 **
  • \*{2} 匹配闭合的 **