本次蓝桥杯省赛在4月13号结束,并在省赛中取得了一等奖的成绩,题目放在文末。本次记录主要是复盘一下知识点,引用官方题解,如果大佬们有更好的方法,欢迎交流学习~

我之前留意到一位UP主推荐的这本书,觉得挺吸引人的,于是入手了一本。该书出自外国作者之手并已被译成中文。阅读之后,我发现它的确颇有价值,特别是它为我提供了全新的视角去理解这门语言,尤其是在函数式编程和异步编程领域,让我收获了不一样的见解和领悟。

2024-05-28T14:24:19.png

1、智能停车系统

第一题是“签到题”,也就是证明你来参加过比赛的,
考察的是css中的

flex布局和grid布局
它们是css3新增的响应式布局,比较常用。另外还有浮动、定位、边距等实现布局的方式,在flex布局中,主要分为主轴,和侧轴,默认为主轴方向排列(flex-firection:row),而以下代码分别作用于父元素与子元素
作用于父元素:

display:flex;
justify-content:center; /*沿着轴的方向布局方式(居中)*/

flex-direction:column;   /*改变轴的方向为垂直方向:*/

align-item:center;  /*交叉轴的排列方式(居中)*/

flex-warp:warp;    /*换行*/

子元素

flex-shrink:0;  /*不允许被挤压*/

flex:flex-grow flex-shrink  flex-basis; /*缩写形式*/

order:1;  /*改变顺序*/

因此本题的答案只需要在最后三行添加代码即可

.cars {
  position: absolute;
  z-index: 2;
  display: flex;
  width: 600px;
  height: 600px;
  flex-direction: column;
 /* TODO: 请在下方编写题目代码,不要更改其他选择器里的代码 */
  flex-wrap: wrap;  
  align-content:space-between; 
  justify-content:space-between; 
}

2024-05-27T04:15:49.png
并且文档中的提示均已给出,直接使用space-around即可实现

2、布局切换

js中较为经典的题目了,同样也是签到题。“干掉他人,留下自己”是这道题的精髓,也就是当按钮被点击的时候,就取消所有样式,再给自己添加上这个样式

  option.addEventListener('click', function () {
       for (const otherOption of layoutOptions) {
            // 移除其他选项的 active 类
            otherOption.classList.remove('active');
         }
        // TODO:待补充代码
        this.classList.add('active'); // 为当前选项添加active类
  }

2024-05-27T04:17:57.png

3、产品 360 度展示

开始上点难度了,考察的是Promise以及asyncawait
Promise的出现是解决回调地狱的问题,而async则是Promise和generator结合的语法糖,使得异步编程同步化 ,在执行同步操作的时候也不会造成阻塞。
2024-05-27T04:26:41.png

const pipeline = async (initialValue, sequence) => {
  let res = initialValue;
  for (const fn of sequence) {
    res = await fn(res);
  }
  return res;
};

4、多表单验证

考察的是Elementplus的表单验证 ,对于我来说,最难受的就是做布局的题目了。
这个题我当时想着后面做,结果发现没时间了。

const validateName = (rule, value, callback) => {
  if (value === "") {
    callback(new Error("请输入姓名"));
  } else {
    const reg = /[^\u4e00-\u9fa5]/g;
    if (reg.test(value)) {
      callback(new Error("只能输入汉字"));
    }
    callback();
  }
};
// TODO-3:书写表单校验规则,并绑定到对应表单上
const rules = reactive({
  name: [
    { required: true, message: "请输入姓名", trigger: "blur" },
    { validator: validateName, 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" },
  ],
});

5、找回连接的奇幻之旅

考察了闭包函数、剩余参数、call、apply以及this的指向问题
这里给大家辨析以下callapply以及bind方法的区别,
call和apply的区别在于第二个参数前者接收的是单个参数(从第二个参数开始,你可以直接列举出需要传递给函数的参数,它们会按照顺序传递。),而后者接收的是一个数组,两个方法都会让函数能够立即执行。
bind方法第二个参数接收的方式与call相同,但是函数不会立即执行。

我理解的闭包函数特点有:
1、让内部函数访问其所在外部函数的作用域中的变量,即使外部函数已经执行完毕,只要内部函数还被引用,这些外部变量就不会被垃圾回收机制回收。
2、维持外部函数局部变量的持久性。即使外部函数执行结束,由于内部函数持有对外部变量的引用,这些变量的值可以跨函数调用被保留下来

function resetableOnce(fn) {
  let done = false; // 标记函数是否已执行
  let result; // 保存函数执行的结果

  /**
   * 保证只执行一次的函数
   * @returns {Any} 返回函数fn的执行结果
   */
  function runOnce(...ags) {
    if (!done) {
      // 如果之前没有执行过,则执行函数fn,并保存结果
      result = fn.apply(this, ags);
      done = true; // 标记为已完成,确保之后不再执行
    }
    // 返回之前执行的结果
    return result;
  }

  /**
   * 重置函数的执行状态
   */
  function reset() {
    done = false; // 重置done为false,允许函数再次执行
  }

  // 返回控制对象
  return { runOnce, reset };
}





6、tree 命令助手

考察知识点:Node.js递归.fs操作
引用官方题解

function generateTree(dirPath) {
  // 读取目录下的所有文件和文件夹
  const files = fs.readdirSync(dirPath);
  const tree = [];

  files.forEach(file => {
    const filePath = path.join(dirPath, file);
    const isDirectory = fs.statSync(filePath).isDirectory();

    if (isDirectory) {
      // 如果是目录,则递归生成子目录的文件树
      const subtree = generateTree(filePath);
      tree.push({ name: file, children: subtree });
    } else {
      // 如果是文件,则直接添加到文件树
      tree.push({ name: file });
    }
  });

  return tree;
}

7、Github 明星项目统计

vue3的双向绑定
array的原型方法,filter、map
由于JavaScript单线程的特性,为了不阻塞用户界面和其他任务的执行,它使用异步处理机制来处理耗时操作,因此大量的使用回调函数来处理问题,在js中函数是“一等公民”,也就是能够像变量一样当作参数来传递。而箭头函数与普通函数的区别则在于箭头函数不绑定自己的this值,它会捕获其所在上下文的this值作为自己的this。箭头函数其实就是其它编程语言的lambda表达式。

<select name="language" id="language" @change="changeHandle" v-model="language">
  <option v-for="language in languages" :value="language">{{language}}</option>
</select>
<script>
  setup() {
       const changeHandle = () => {
          let newData = chartData.value.filter(item => item.language === language.value);
          if (language.value === 'All') {
            newData = chartData.value;
          }
          newData = newData.slice(pageStart.value - 1, pageEnd.value);
          xData.value = newData.map(item => item.name);
          yData.value = newData.map(item => item.stars);
          initChart();
        };
  }
</script>

8、小蓝驿站

主要还是考察vue3的数据处理以及字符串的原型方法。js是一门面向对象的语言,它实现面向对象的方式与java、python、php等编程语言不同,前者是基于原型实现,后者是基于类实现,在ES6中虽然引入了class的概念,但是它本质上还是原型的语法糖。

// 目标 1
<ul class="contacts-list">
  <!-- 使用 Vue 的 v-for 指令循环渲染 sortedContacts 中的每个联系人组 -->
  <li
    class="contacts-group"
    v-for="(group, groupIndex) in sortedContacts"
    :key="groupIndex"
  >
    <!-- 如果联系人组中有联系人,则显示该组的标题 -->
    <div v-if="group.contacts.length > 0" class="contacts-group-title">
      {{ group.letter }}
    </div>
    <!-- 使用 v-for 指令循环渲染每个联系人 -->
    <ul>
      <li
        class="contact-item"
        v-for="(contact, contactIndex) in group.contacts"
        :key="contactIndex"
      >
        <!-- 显示联系人名字 -->
        <span class="contact-name">{{ contact.name }}</span>
        <button class="del-contact-button">删除</button>
      </li>
    </ul>
  </li>
</ul>
<script>
    setup() {
        // 目标 2
    const addContact = () => {
      // TODO:待补充代码 目标 2
      if (newContact.value.trim() !== "") {
        // 获取新联系人名字的首字母并转为大写
        const firstLetter = newContact.value[0].toUpperCase();
        // 查找是否有相同首字母的联系人组
        const groupIndex = contacts.value.findIndex(
          (group) => group.letter === firstLetter
        );
        if (groupIndex > -1) {
          // 如果找到了相同首字母的联系人组,就添加到该组
          contacts.value[groupIndex].contacts.push({ name: newContact.value });
        } else {
          // 如果没有找到相同首字母的联系人组,就新建一个组,并添加到联系人列表
          const newGroup = {
            letter: firstLetter,
            contacts: [{ name: newContact.value }],
          };
          contacts.value.push(newGroup);
        }
      }
      // TODO:END
      // 添加完成清空联系人输入框
      newContact.value = "";
    };
  }
</script>

9、 商品浏览足迹

考察知识点:

  1. axios
  1. 对象去重
  1. Date对象


window.onload = async ()=> {
      const res = await  axios("./js/data.json");
      const newData = getData(res.data);
      showData(newData);
};
/**
 * 将同一天浏览的相同商品去重并作为数组返回
 * @param {Array} defaultData json 文件中读取到的原始数据
 * @returns 去重后的数据,数据结构与 defaultData 相同
 */
const removeDuplicates = defaultData => {
  let newData = [];
  // TODO:在下面补充代码,最终完成该函数封装
  defaultData.forEach((defaultGoods) => {
    const defaultDate = new Date(defaultGoods.viewed_on);
    const dD = `${defaultDate.getFullYear()}-${
      defaultDate.getMonth() + 1
    }-${defaultDate.getDate()}`;
    defaultGoods.date = dD;

    let hadThisGoods = false;
    newData.forEach((tmpGoods) => {
      const tmpDate = new Date(tmpGoods.viewed_on);
      const tD = `${tmpDate.getFullYear()}-${
        tmpDate.getMonth() + 1
      }-${tmpDate.getDate()}`;
      if (tmpGoods.id == defaultGoods.id && tD == dD) {
        hadThisGoods = true;
      }
    });
    if (!hadThisGoods) {
      newData.push(defaultGoods);
    }
  });
  return newData;
}

/**
 * 将去重后的数据根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
 * @param {*} defaultData 去重后的数据
 * @returns 根据字段 viewed_on(格式化为 YYYY-MM-DD) 降序排序
 */
const sortByDate = defaultData => {
  let newData = [];
  // TODO:在下面补充代码,最终完成该函数封装
  function sortByProperty(property, asc) {
    return function (value1, value2) {
      let a = new Date(value1[property]);
      let b = new Date(value2[property]);
      // 默认升序
      if (asc == undefined) {
        return a - b;
      } else {
        return asc ? a - b : b - a;
      }
    };
  }
  newData = defaultData.sort(sortByProperty("date", false));
  return newData;
}
/**
 * 将去重排序后的所有商品数据,作为一个对象存储并返回,
 * 该对象的所有 `key` 为浏览商品的当天日期(即,字段 viewed_on 格式化为 YYYY-MM-DD),
 * `value` 为当天浏览的所有商品(以数组形式表现)。
 * @param {Array} defaultData 重后的所有商品数据
 * @returns 
 */
const transformStructure = defaultData => {
  let newData = {};
  // TODO:在下面补充代码,最终完成该函数封装
  //  月份和日期 不足10的前面补0
  defaultData.forEach(item=>{
    let date=new Date(item.date) ;
    let y = date.getFullYear()
    let m = date.getMonth()+1
    m=m<10?'0'+m:m
    let d = date.getDate();
    d=d<10?'0'+d:d
    item.date=`${y}-${m}-${d}`;   
  })
  newData = defaultData.reduce((aD, item) => {
    console.log(item);
    if (!aD[item.date]) {
      aD[item.date] = [];
      aD[item.date].push(item);
    } else {
      if (!aD[item.date].includes(item.date)) {
        aD[item.date].push(item);
      }
    }
    return aD;
  }, {});
  return newData;
}


10、NPM Download Simulator

获取数据类型、 promise.race

function myRace(iterable) {
  return new Promise((resolve, reject) => {
    if (!(Symbol.iterator in Object(iterable))) {
      reject(
        new TypeError(
          `${Object.prototype.toString
            .call({ iterable })
            .slice(-8, -1)
            .toLocaleLowerCase()} is not iterable`
        )
      );
      return;
    }
    const promises = Array.from(iterable);
    let resolvedOrRejected = false;
    for (const promise of promises) {
      Promise.resolve(promise).then(
        (value) => {
          if (!resolvedOrRejected) {
            resolvedOrRejected = true;
            resolve(value);
          }
        },
        (reason) => {
          if (!resolvedOrRejected) {
            resolvedOrRejected = true;
            reject(reason);
          }
        }
      );
    }
  });
}

2024-05-28T14:00:53.png

Last modification:May 29, 2024
If you think my article is useful to you, please feel free to appreciate