【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

🧑‍💼 个人简介:一个不甘平庸的平凡人🍬
🖥️ 蓝桥杯专栏:蓝桥杯题解/感悟
🖥️ TS知识总结:十万字TS知识点总结
👉 你的一键三连是我更新的最大动力❤️!
📢 欢迎私信博主加入前端交流群🌹

📑 目录

; 🔽 前言

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解
新一期的蓝桥杯大赛开始报名已经有一段时间了,最近博主的粉丝朋友们有很多都已经在积极备考了,也有很多朋友私信我说让我多发发题解,于是我就去蓝桥杯官网碰碰运气,看能不能找到好的题目(因为今年是蓝桥杯开放Web应用开发方向的第二年,官网上的备赛题目比较少),正巧发现蓝桥杯正在举行线上模拟赛,我便花了一些时间做题、总结、写作,于是这篇文章就诞生了。

如标题所见,这是 Web 应用开发模拟赛 1 期大学组 的题解,关于蓝桥杯更多的题解博主会在之后的文章中陆续更新,欢迎大家关注订阅!

话不多说,开撕!

本篇只会大概提出题目要求,关于题目的更多细节可自行去模拟赛主页查询:Web 应用开发模拟赛 1 期大学组

1️⃣ 数据类型检测

这一题大致的意思就是写一个能够 判断参数数据类型的函数,咋一看感觉非常的简单,可再仔细一看题目要求,足足需要判断 14种类型!如果你的基础知识不牢固是有很大机率通不过这题的,从题目的通过率就可以看出:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

作为第一题,虽然只有5分,并且挑战人数是10道题中最多的,但 通过率确是10道题中最低的,这恰恰反应了大家的基础核心知识不牢固,所以还是简易大家多补补基础核心知识。

要求:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

虽然有14种类型需要判断,但其实只需要一行代码就能通过这题:


function getType(data) {

 return Object.prototype.toString.call(data).slice(8,-1)
}

JavaScript中判断数据类型主要有以下几种方式:

  • typeof 可以用来区分除了 null类型以外的 原始数据类型undefinednumberstringsymbolboolean)和对象类型中的函数,针对其它类型时 typeof一律返回 object类型。
  • instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,所以它 不能用来判断原始数据类型的数据

    并且 instanceof的结果并不一定是可靠,因为在 ES7的规范中可以通过自定义 Symbol.hasInstance方法来覆盖 insanceof的默认行为

  • Object.prototype.toString.call 能够满足大部分场景下的需求,但它 无法区分数字类型和数字对象类型(同理还有字符串类型和字符串对象类型等,)
Object.prototype.toString.call(2).slice(8,-1)

Object.prototype.toString.call(new Number(2)).slice(8,-1)

ES7规范中可以使用 Symbol.toStringTag自定义 Object.prototype.toString方法的行为,所以该方法判断数据类型也不一定是完全可靠的。
* Array.isArray 用来判断是否是数组

所以这题对于知道 Object.prototype.toString.call方法的同学来说,几乎就是秒解!

如果非要深究 性能的话,可以 结合使用 typeofObject.prototype.toString.call,两者运行时间比较如下:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

结合使用:

function getType(data) {

  if(typeof data !== "object"){
    return typeof data;
  }

  return Object.prototype.toString.call(data).slice(8,-1);
}

2️⃣ 渐变色背景生成器

这题所实现的渐变色背景生成器还是非常有意思的,推荐大家去看一下它完整的源码(代码非常少):

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

主要考察的就是通过JavaScript来修改自定义的 CSS 变量,题中主要的代码如下(并不是全部代码,不需要我们了解与实现的代码这里就没有贴出,原题可自行查阅蓝桥官网)

HTML:

 <div class="controls">
   <input id="color1" type="color" name="color1" value="#00dbde" />
   <input id="color2" type="color" name="color2" value="#fc00ff" />
 div>

CSS:


:root {
  --color1: #00dbde;
  --color2: #fc00ff;
}
const inputs = document.querySelectorAll(".controls input");

 inputs.forEach((item)=>{
    item.addEventListener("change",function (e) {
        document.querySelector("html").style.setProperty(--${e.target.id}, e.target.value);
    })
 })

遍历获取到的两个取色盘元素( input.color),分别对其添加 change事件,然后通过 document.querySelector("html").style.setProperty方法修改 html元素(根元素,也即是 :root)上css变量的值即可。

这一题题目参考信息中给出了如何使用CSS变量的介绍,根据这个提示来做这题还是非常简单的。

3️⃣ 水果叠叠乐

这简直就是简易般的羊了个羊:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

这一题有个坑,就是点击元素时是需要将这个被点击的元素克隆一份添加到下方的栏中,而不是直接将被点击的元素移动到下方的栏中。

具体实现代码如下

HTML:

<ul id="card">
  <li data-id="1" id="fruit-one">
    <img src="./images/pineapple.svg" alt="" />
  li>

ul>

<div class="fixed">
  <div class="gradient-border" id="box">div>
div>

JavaScript:

$("#card li").on("click", function (e) {

  if($("#box li").length === 7) return;

  $("#box").append($(this).clone());

  $(this).hide();

  const list = $(#box li[data-id=${this.getAttribute('data-id')}]);

  if (list.length >= 3) {

    list.each((i,item) => {

      item.remove()
    })
  }
});

我们知道 DOM的事件处理函数中的 this指的是触发事件的 DOM元素,我这里使用 $()包裹 this$(this))的目的是使 this变成 JQ对象,从而能够使用 jQuery提供的方法,如 clone克隆元素。

4️⃣ element-ui 组件二次封装

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

需要封装 element-ui 的表格组件,实现点击表格组件左侧 radio时选中该行,题目要求说了一大堆,其实通过这题只需修改两行代码就行:


<template slot-scope="scope">
   <el-radio v-model="currentRow" :label="scope.$index">el-radio>
template>

这样做虽然能通过检测,但它存在以下 bug (说明 题目答案检测不严格):

  1. 点击选中第二行时,第二行前面的 radio并没有被选中
  2. 点击取消选中时已经选中的 radio并没有被取消

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

并且在查看代码时会发现题中给了我们一个 setCurrent方法用来 设置当前选中行,并且题目中也明确提示我们 redio有一个 change方法:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解
  1. 所以严谨来说当 radio状态改变时是应该调用 setCurrent方法的

综上所述,最终的代码应该这样写:


<template slot-scope="scope">

    <el-radio v-model="currentRow" :label="scope.$index" @change="setCurrent(scope.row)">el-radio>
template>
methods: {
  setCurrent(row) {
    this.currentRow = this.tableData.indexOf(row)
    this.$refs.singleTable.setCurrentRow(row);
  },
},

表格的数据是在 propstableData中, setCurrent方法接收的 row代表的是当前行的数据,当调用 setCurrent方法时在 tableData中查找 row的下标赋值给 currentRow即可(因为 el-radio v-model绑定的是 currentRow),这样就解决了上面说的BUG。

5️⃣ http 模块应用

要求就是使用原生 http模块,搭建起来一个简单的 Node服务器,并返回 &#x201C;hello world&#x201D; ,考察的就是基础的原生知识,如果你对 Node还不了解,可以订阅我的
Node.js从入门到精通专栏(私信进群能够享返现活动)。


const http = require("http");

const app = http.createServer();

app.on("request",function (req,res) {
    res.end("hello world")
})

app.listen(8080)

6️⃣ 新课上线啦

就是按照官方给的最终效果图,去实现下面这个页面:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

没啥技术含量,全靠堆HTML和CSS,这里就不放代码了。

但这个题是我认为是整场模拟赛里最坑人的题,特别废时间,我建议这个题要么 放到最后再写(因为完成度50%以上就能得到分,其它题不行),要么完成差不多后就直接去做下面的题,别死扣细节,不然吃亏的都是你!

; 7️⃣ 成语学习

这个题也是非常的有意思,不过需要我们写的代码并不多,大多都是官方已经给出了。

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

getSingleWord(val) {
  for (let i = 0; i < this.idiom.length; i++) {
    if (!this.idiom[i]) {
      this.idiom[i] = val
      this.$set(this.idiom, i, val)
      return
    }
  }
}

confirm() {
  const target = this.arr.find(item => item.tip === this.tip);
  this.result = target.word === this.idiom.join('');
}

这里使用的是 vue2,需要注意的一点就是当我们直接修改 data里的数组中的元素时,视图并不会响应式更新,如果你了解 vue2的响应式原理,应该明白这是 vue2响应式的一个缺陷所在,我们必须使用 $set来修改触发从而引发视图更新。

其实题中原有的代码已经显示了这一点,如 clear函数:

clear(i) {
  this.idiom[i] = ""
  this.$set(this.idiom, i, "")
},

8️⃣ 学海无涯

这一题稍微有点复杂,但只要自己理清楚思路,还是能够做出来的,先看一下效果:

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

题中使用了Echarts,了解Echarts的朋友知道 Echarts 的表格有x轴和y轴的数据(都是数组形式),所以我们知道只要将题中给我们的数据转换成对应的格式就行了。

复杂的地方在于数据处理,原数据如下:

"data": {
 "2月": [
   30, 40, 30, 20, 10, 20, 30, 69, 86, 12, 32, 12, 23, 40, 50, 61, 39, 28,
   20, 35, 20, 38, 43, 52, 30, 39, 52, 70
 ],
 "3月": [
   36, 48, 52, 30, 39, 52, 20, 18, 25, 33, 21, 36, 44, 63, 32, 89, 98, 23,
   25, 36, 29, 31, 42, 23, 45, 56, 98, 83, 25, 28, 48
 ]
}

我的思路就是将原数据转换成两个对象,一个代表周的x轴y轴数据,一个代表月的x轴y轴数据,所以两个对象的结构是一模一样,转成后的两个对象如下:


weekData = {
  x: ['2月第1周', '2月第2周', '2月第3周', '2月第4周', '3月第1周', '3月第2周', '3月第3周', '3月第4周', '3月第5周'],
  y: [180, 274, 253, 324, 277, 240, 332, 378, 101]
}

monthData = {
  x: ['2月', '3月'],
  y: [1031, 1328]
}

现在我们根据代码来一点点了解我是如何转换的:


let weekData ={
  x:[],
  y:[]
}, monthData ={
  x:[],
  y:[]
};

function mySetOption(data) {

  option.xAxis.data = data.x;

  option.series[0].data = data.y;

  myChart.setOption(option);
}

document.querySelector(".tabs").addEventListener("click",function (e) {
  if (e.target.id === "week") {
    mySetOption(weekData);
  }else if(e.target.id === "month") {
    mySetOption(monthData);
  }
})

axios.get('./data.json').then(res=>{
  const data = res.data.data;

  for (const key in data) {

    let weekCount = monthCount = 0,
        weekNum = 1;

    for (let i = 0; i < data[key].length; i++) {

      weekCount += data[key][i];
      monthCount += data[key][i];

      if ((i+1) % 7 === 0 || ((data[key].length - i  7) && (i === data[key].length - 1))) {
        weekData.x.push(${key}第${weekNum++}周);
        weekData.y.push(weekCount);

        weekCount = 0;
      }
    }
    monthData.x.push(key);
    monthData.y.push(monthCount);
  }

  mySetOption(weekData);
})

代码不算难,相信大家根据代码里的注释都能够理解,着重说一下上面我使用的 if 表达式:

(i+1) % 7 === 0 || ((data[key].length - i  7) && (i === data[key].length - 1))

要明白我们求每一周的总数居时需要在第二周开始前将 weekCount归零,我选择了在每周的 最后一天(且已经 push了数据)重置 weekCount,而不是在每周的第一天(计算数据之前)重置 weekCount,是因为我感觉这样做判断比较好写。

首先 (i+1) % 7 === 0判断了是每星期的最后一天,如:第一周的星期日,在数组中下标为6,加上1对7取余就得0。

((data[key].length - i <= 7) && (i="==" data[key].length - 1)< code>&#x4E2D;<code>data[key].length - i <= 7< code>&#x4EE3;&#x8868;&#x5269;&#x4F59;&#x7684;&#x6570;&#x636E;&#x4E0D;&#x8DB3;7&#x6761;&#xFF0C;<code>i === data[key].length - 1</code>&#x4EE3;&#x8868;&#x5230;&#x4E86;&#x6570;&#x7EC4;&#x6700;&#x540E;&#x4E00;&#x9879;&#x3002;&#x4E4B;&#x6240;&#x4EE5;&#x505A;&#x8FD9;&#x4E48;&#x4E00;&#x4E2A;&#x5224;&#x65AD;&#xFF0C;&#x662F;&#x4E3A;&#x4E86;&#x9002;&#x914D;&#x5929;&#x6570;&#x4E0D;&#x662F;7&#x7684;&#x500D;&#x6570;&#x7684;&#x60C5;&#x51B5;&#xFF0C;&#x5982;3&#x6708;31&#x5929;&#xFF0C;&#x8BA1;&#x7B97;&#x5B8C;&#x524D;4&#x5468;&#x7684;&#x6570;&#x636E;&#x540E;&#x5269;&#x4F59;3&#x5929;&#x7684;&#x6570;&#x636E;&#xFF08;31-4x7&#xFF09;&#xFF0C;&#x8FD9;3&#x5929;&#x4E5F;&#x8981;&#x6C42;&#x548C;&#x4F5C;&#x4E3A;&#x7B2C;5&#x5468;&#x7684;&#x6570;&#x636E;&#xFF0C;&#x6240;&#x4EE5;&#x9700;&#x8981;&#x5728;&#x6700;&#x540E;&#x4E00;&#x5929;&#x7ED3;&#x7B97;&#x8FD9;&#x4E09;&#x5929;&#x3002;<!--=--></code><!--=-->

其实上面这个写的复杂了,我们可以巧妙改变 for 循环的 步长,并使用数组切片( slice)和求和( reduce)来简化代码:


let weekData ={
  x:[],
  y:[]
}, monthData ={
  x:[],
  y:[]
};

function mySetOption(data) {

  option.xAxis.data = data.x;

  option.series[0].data = data.y;

  myChart.setOption(option);
}

document.querySelector(".tabs").addEventListener("click",function (e) {
  if (e.target.id === "week") {
    mySetOption(weekData);
  }else if(e.target.id === "month") {
    mySetOption(monthData);
  }
})

axios.get('./data.json').then(res=>{
  const data = res.data.data;

  for (const key in data) {

    for (let i = 0,w = 1; i < data[key].length; i += 7,w++) {

      let weekCount = data[key].slice(i,i + 7).reduce((prev,next) => prev + next);

      weekData.x.push(${key}第${w}周);
      weekData.y.push(weekCount);
    }

    monthData.x.push(key);
    monthData.y.push(data[key].reduce((prev,next)=>prev + next));
  }

  mySetOption(weekData);
})

9️⃣ 逃离二向箔

这个题目很有意思,让我瞬间想起了三体宇宙,顿时感觉这题不简单,结果还正如我所想,题中考察的是 promise以及 JavaScript中的事件执行机制(先执行同步代码再执行异步代码),对于 promise,相信大多数人都会比较头疼,这一题考察的角度也比较刁钻,让我们一起来看一看吧!

题目的大致要求就是有一个数组,里面存放了很多需要发射的飞船(一个返回 promise对象的函数),然后给了我们一个最大数量,我们只能一次性发送这个最大数量的飞船,当有一个飞船发射成功( promise执行结束),我们才能继续发射下一个飞船。

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解
完整代码:

function createRequest(i) {
  return function () {
    return new Promise((resolve, reject) => {
      const start = Date.now();
      setTimeout(() => {
        if (Math.random() >= 0.05) {
          resolve(
            第${i + 1}艘曲率飞船达到光速,成功逃离,用时${Date.now() - start}
          );
        } else {
          reject(
            第${i + 1}艘曲率飞船出现故障,无法达到光速,用时${
              Date.now() - start
            }
          );
        }
      }, 3000 + i * 100);
    });
  };
}

class RequestControl {
  constructor({ max, el }) {
    this.max = max;
    this.requestQueue = [];
    this.el = document.querySelector(el);
    setTimeout(() => {
      this.requestQueue.length > 0 && this.run();
    });
    this.startTime = Date.now();
  }
  addRequest(request) {
    this.requestQueue.push(request);
  }
  run() {

    let promiseI = 0

    for(let i = 0;i < this.max;i++) {
      this.requestQueue[i]().then(res=>{
        this.render(res)

        sentNext()
      }).catch(res=>{

        this.render(res)
        sentNext()
      })
      promiseI++;
    }

    const sentNext = ()=> {

      if (promiseI >= this.requestQueue.length) return;
      this.requestQueue[promiseI]().then(res=>{
        this.render(res)

        sentNext()
      }).catch(res=>{
        this.render(res)
        sentNext()
      })
      promiseI++;
    }
  }
  render(context) {
    const childNode = document.createElement("li");
    childNode.innerText = context;
    this.el.appendChild(childNode);
  }
}

let requestControl = new RequestControl({ max: 10, el: "#app" });
  for (let i = 0; i < 25; i++) {
    const request = createRequest(i);
    requestControl.addRequest(request);
  }

module.exports = {
  requestControl,
};

我这里定义了一个变量 promiseI存储已经执行过的 promise的数量,同时能直接将 promiseI当作 requestQueue数组的下标,以此来调用 requestQueue数组中下一个未被执行的 promise,这里大家可以好好理解一下。

需要注意的一点就是 this.requestQueue[promiseI]函数的调用是同步代码(这个函数的调用代表 开始发射了飞船),调用它之后返回的 promise的执行才是异步代码(这个 promise的执行代表飞船的发射 过程promise执行完就代表飞船发射成功了),所以 promiseI++的执行永远都是在 this.requestQueue[promiseI]函数执行之后,这也就是 promiseI能够精确表示已发射出去飞船数量的原因。

这道题值得大家好好思考!

一开始做这道题时我秉持的是不修改原始数据(如 requestQueue),但在官方的解答中我发现可能是我想错了,给大家看看官方给出的更简单的解法:

run() {

  let reqLength = this.requestQueue.length;
  if (!reqLength) return;

  let min = Math.min(reqLength,this.max);

  for (let i = 0; i < min; i++) {

    this.max--;

    let req = this.requestQueue.shift();

    req().then((res)=>{
      this.render(res)
    }).catch((err)=>{
      this.render(err)
    }).finally(()=>{

      this.max++;

      this.run();
    })

  }
}

🔟 梅楼封的一天

要求就是实现一个脱敏函数,函数入参要求:

  • 第一个参数为字符串(任意字符串)。
  • 第二个参数为脱敏规则, 可以是字符串,也可以是数组
  • 第三个参数是字符串,表示用什么来占位脱敏文字(默认为: *)。
  • 第四个参数是:是否将手机号( 11 位数字)进行脱敏,默认为 true(规则是: 保留前三位和后三位,中间脱敏占位)。

出参要求:

  • 第一个参数不存在返回 null
  • 第一个参数存在,第二个参数不存在,返回原字符串。
  • 第一个参数和第二个参数都存在,返回脱敏后的新字符串以及被脱敏的文本位置,返回格式是一个对象( 注意:无论手机号是否脱敏处理,都不会返回手机号的被脱敏时的位置 ),格式如下:
{
  "ids": [],
  "newStr": ""
}

这题我利用了正则和字符串的 replace方法来实现,本来我使用 new Array(word.length).fill(symbol).join('')来生成与违规字符相同数量(长度)的脱敏字符( *),但最后想到字符串本身有一个repeat方法能很方便的实现这一效果:


 const toDesensitization = (str, rule, symbol = "*", dealPhone = true) => {
    if(!str) return null;
    if (str && !rule) return str;

    const obj = {
        ids: [],
        newStr: str
    }

    function getNewStr(ru) {

        const reg = new RegExp(ru,'gim');

        obj.newStr = obj.newStr.replace(reg,function (word,index) {

            obj.ids.push(index);

            return symbol.repeat(word.length)
        })
    }

    if (Array.isArray(rule)) {

        for (const ru of rule) {
            getNewStr(ru)
        }
    }else {
        getNewStr(rule)
    }

    if (dealPhone) {

        const reg2 = /(1[35789]+\d{1})\d{5}(\d{3})/g;

        obj.newStr = obj.newStr.replace(reg2,$1${symbol.repeat(5)}$2)
    }

    return obj
};

module.exports = toDesensitization;

整体的思路很清晰,有一点就是 replace函数的第二个参数是一个函数时的用法(以及上述 $1$2的用法)大家可能不太清楚,这里可以查阅:MDN的说明

🔼 结语

至此,第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组的题解就结束了,这期模拟赛整体上来说并不算很难,但考察的知识点还是比较多的,特别是对基础知识的考察(相信你在做题的过程中也能察觉到),所以博主还是建议大家在做题的过程中好好总结,好好复习,祝大家都能在正式比赛中取得满意的成绩!

因为没做什么准备,只是在寝室拿起电脑随便做了一下,最终耗时257分钟,比正式比赛要求的4小时超了17分钟,但幸好最后的成绩还不错。

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解
总结来说,比较耗时的题就是第6题了,大家可以注意一下,在正式比赛时做好规划。

如果本篇文章对你有所帮助,还请客官一件四连!❤️

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

Original: https://blog.csdn.net/m0_51969330/article/details/127813909
Author: 海底烧烤店ai
Title: 【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 | 精品题解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/652479/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球