蓝桥杯 2022 年国赛题解(二)

May 28 · 12min

Tip

建议先刷题,再看题解!😎

开学礼物大放送

题目: 开学礼物大放送

又是一道还原页面布局的题目,这里就略过不讲了。

权限管理

题目: 权限管理

这是一道使用 jQuery 的题目,因为这两年已经不考察了,所以我们换用原生 JavaScript DOM API 来实现。

第一问要求我们异步获取数据,并渲染到页面上。要注意我们渲染后的内容会在后面数据动态修改时同步修改,所以最好抽出一个函数 render() 来重新渲染页面:

let data
function getData() {
  fetch('./js/userList.json')
    .then(res => res.json())
    .then((_data) => {
      data = _data
      render()
    })
}
function render() {
  const userList = document.querySelector('#userList')
  userList.innerHTML = `
  <tr>
          <td>用户</td>
          <td>权限</td>
  </tr>
  `
  data.forEach((row) => {
    document.querySelector('#userList').innerHTML += `
        <tr>
          <td>${row.name}</td>
          <td>${row.right ? '管理员' : '普通用户'}</td>
        </tr>
      `
  })
}

在首次请求数据和后面修改数据时,都调用一次 render() 函数,这样就能保证页面内容与数据同步了。

接着我们需要实现将 option 从一个下拉框移动到另一个下拉框的功能:

function toggle(toRight, options) {
  if (toRight) {
    options.forEach((option) => {
      leftSelect.removeChild(option)
      rightSelect.appendChild(option)
      option.selected = false
    })
  }
  else {
    options.forEach((option) => {
      rightSelect.removeChild(option)
      leftSelect.appendChild(option)
      option.selected = false
    })
  }
}

传入一个布尔值 toRight,表示是否将 option 移动到右边。options 是一个 NodeList,表示要移动的 option 集合。

还要实现在 data 中修改权限,并重新渲染页面的功能:

function changeAccess(right, changeList) {
  changeList
    .map(option => option.value)
    .forEach((value) => {
      data.find(member => member.name === value).right = right
    })
  render()
}

先从 Element 中提取出 value,然后在 data 中找到对应的用户,修改其权限,最后重新渲染页面。

最后如何调用这两个函数呢?来看一下:

$('#add').click(() => {
  // console.dir(leftSelect)
  const options = [...leftSelect.selectedOptions]
  toggle(true, options)
  changeAccess(true, options)
})
$('#addAll').click(() => {
  const options = [...leftSelect.options]
  toggle(true, options)
  changeAccess(true, options)
})
$('#remove').click(() => {
  const options = [...rightSelect.selectedOptions]
  toggle(false, options)
  changeAccess(false, options)
})
$('#removeAll').click(() => {
  const options = [...rightSelect.options]
  toggle(false, options)
  changeAccess(false, options)
})

注意!我们必须将 left.selectedOptions 拷贝出来,因为如果你将 left.selectedOptions 传入 toggle,在 toggle 函数内部会在 循环内部修改被迭代的对象,导致结果出错!

建议通过 JavaScript DOM API 得到的类数组、可迭代对象,都应该拷贝成数组再使用,这样既安全,又可以方便地使用数组的 API。

还有一个问题,我是如何知道 selectedOptions 这个属性的?肯定不是因为我背下来了这个属性,而是使用了 console.dir 来查看 DOM 对象的全部 JS 属性 ,然后找到的。这个经验非常有用。

一起会议吧

题目: 一起会议吧

一道 Vue2 的题目,简单看一下就行,现在考察 Vue3 了。

我们重点看下第三问和第五问,这两问可以封装同一个计算属性 displayData,对异步请求的结果 data 实现计算,遵循以下规则:

  • 始终把当前登录的用户排在第一位;
  • 根据三个状态按钮,决定返回空、返回仅登录用户或返回所有数据。

看下代码:

const WindowState = {
  Zero: 1,
  OnlyLogin: 2,
  All: 3
}
displayData() {
  if (!this.data || !this.select) {
    return this.data
  }
  let allData = [this.data.find(it => it.name == this.select), ...this.data.filter(it => it.name != this.select)]
  if (this.windowState == 1) {
    return []
  } else if (this.windowState == 2) {
    return [allData[0]]
  } else {
    return allData
  }
}

在计算属性中,如果 select 为空,则直接返回 data。接着计算 allData,将当前登录的用户放在第一位;然后根据 windowState 返回对应的数据。

“将当前登录的用户放在第一位” 这个逻辑,你可以像我一样写:[this.data.find(it => it.name == this.select), ...this.data.filter(it => it.name != this.select)],手动组装一个新数组。如果你第一时间想到是使用 sort 方法来解题,也可以这样做:

const allData = data.sort((a, b) => {
  if (a.name === this.select)
    return -1
  if (b.name === this.select)
    return 1
  return 0
})

本题的判题有点问题,可能会无法通过。

JSON 生成器

题目: JSON 生成器

一道考察正则表达式为主的综合题,要求我们替换模板字符串中的占位符,生成一个 JSON 字符串。

首先先来处理 {{bool}}{{integer(3,5)}} 这种占位符:

const patterns = {
  bool: /\{\{bool\(\)\}\}/,
  integer: /\{\{integer\((\d+),\s*(\d+)\)\}\}/,
  repeatFixed: /\{\{repeat\((\d+)\)\}\}/,
  repeatRange: /\{\{repeat\((\d+),\s*(\d+)\)\}\}/
}

// 生成指定范围内的随机整数
function generateBetween(start, end) {
  return Math.floor(Math.random() * (end - start + 1)) + start
}

// 替换模板值为随机结果
function replacePlaceholders(obj) {
  return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
    if (typeof value !== 'string')
      return [key, value]

    if (patterns.integer.test(value)) {
      const [_, start, end] = value.match(patterns.integer)
      return [key, generateBetween(+start, +end)]
    }

    if (patterns.bool.test(value)) {
      return [key, Math.random() > 0.5]
    }

    return [key, value]
  }))
}

在函数 replacePlaceholders 中,我们遍历 obj 的每一个键值对,如果值是字符串,则判断是否匹配正则表达式,如果匹配,则替换为随机结果。

再来处理 {{repeat(3)}}{{repeat(3,5)}} 这种占位符(仅在 data 为数组时处理):

function generateData(data) {
  if (!Array.isArray(data))
    return replacePlaceholders(data)

  const [repeatExpr, template] = data // repeatExpr 为 第一项

  const times = patterns.repeatFixed.test(repeatExpr)
    ? +repeatExpr.match(patterns.repeatFixed)[1]
    : repeatRange.test(repeatExpr)
      ? generateBetween(...repeatExpr.match(patterns.repeatRange).slice(1, 3).map(Number))
      : 1

  return Array.from({ length: times }, () => replacePlaceholders(template))
}

times 的计算我们精心地使用函数式的写法整合在了一起:

  • 如果匹配正则 repeatFixed,则直接取匹配结果中的数字作为 times
  • 如果匹配正则 repeatRange,则生成一个在指定范围内的随机整数作为 times
  • 如果均不匹配,则说明不存在占位符,times 自然为 1。

函数式的写法讲究的是一气呵成,如果你对自己一口气写一长段代码的能力表示怀疑的话,用正常写法也未尝不可。


>