Tip
建议先刷题,再看题解!😎
分一分 #
题目: 分一分
这题比较简单,应该在几分钟内做完。要求我们将一个数组排序,并按照 num
来分割。
首先给数组按数值大小排序:
function splitArray(oldArr, num) {
// TODO:请补充代码实现功能
return oldArr.sort((a, b) => a - b) // TODO
}
注意,sort
的默认实现是按照字符串的字典序排序,所以如果按数值大小排序,需要传入一个比较函数。
接着,我们按照 num
来分割数组:基本思路是,我们可以遍历原数组来构造一个新数组,分为两种情况:
- 当新数组最后一个子数组的长度小于
num
时,直接将当前元素添加到新数组的最后一个子数组中。 - 当新数组最后一个子数组的长度等于
num
时,我们新建一个只含有当前元素的子数组,添加到新数组中。
这种需求非常适合用 reduce
来实现,具体代码如下:
function splitArray(oldArr, num) {
// TODO:请补充代码实现功能
return oldArr.sort((a, b) => a - b).reduce((acc, cur) => {
if (acc[acc.length - 1].length < num) {
acc[acc.length - 1].push(cur)
return acc
}
else {
acc.push([cur])
return acc
}
}, [])
}
要注意不要忘记在 reduce
的回调函数中返回值(return acc
),否则下一轮循环 acc
就变成空值了。
上面的代码是有问题的。试着找找问题在哪?
在第一轮的时候,acc
是一个空数组,所以 acc[acc.length - 1]
会报错。我们可以有两种做法来修正:
第一种方法是,在判断 acc[acc.length - 1].length < num
之前做一个逻辑短路:
if (acc[acc.length - 1].length < num) { /* ... */ }
if (acc.length !== 0 && acc[acc.length - 1].length < num) { /* ... */ }
这样第一轮循环时,就会进 else
分支,新建一个子数组。
第二种方法更为简单,直接给 acc
的初始值设为 [[]]
而不是 []
,来规避第一轮的问题。
新鲜的蔬菜 #
题目: 新鲜的蔬菜
这道题使用 grid
是更加直观的:
.box {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
}
#box1 > .item {
grid-area: 2 / 2;
}
#box2 .item:nth-child(2) {
grid-area: 3 / 3;
}
#box3 .item:nth-child(2) {
grid-area: 2 / 2;
}
#box3 .item:nth-child(3) {
grid-area: 3 / 3;
}
前面的题目中我们已经具体介绍了 grid-template-columns
等属性的使用方法,在此不再赘述。
思考一下这题可以用 flex
来实现吗?
.box {
display: flex;
}
#box1 {
flex-direction: row;
align-items: center;
justify-content: center;
}
#box2 {
justify-content: space-between;
}
#box2 .item:nth-child(2) {
align-self: end;
}
#box3 {
justify-content: space-between;
}
#box3 .item:nth-child(2) {
align-self: center;
}
#box3 .item:nth-child(3) {
align-self: end;
}
这是题解区一个同学的答案。
拓展:
align-content
在flex
中也是可用的,它的作用是调整主轴上所有行的对齐方式,前提是需要有多个行(使用flex-wrap
允许在可折行时折行)。
水果消消乐 #
题目: 水果消消乐
不愧是国赛,第三道就弄了一道比较麻烦的题!
首先先定义几个辅助函数:
function showCard(box) {
box.firstElementChild.style.display = 'block'
}
function hideCard(box) {
box.firstElementChild.style.display = 'none'
}
function removeCard(box) {
box.style.visibility = 'hidden'
hideCard(box)
}
function updateScore(value) {
const scoreNode = document.querySelector('#score')
scoreNode.textContent = String(+scoreNode.textContent + value)
}
注意 updateScore
中,我们需要处理 scoreNode.textContent
是字符串的情况,所以需要用 +
来将其转为数值。还有我们要隐藏一个元素时,要根据其是否保留在页面布局中来选择 visibility: hidden
和 display: none
属性。这两个属性更详细的区别可以看这篇文章。
接着看一下游戏的主逻辑:
function startGame() {
const flipped = [] // 使用数组更清晰处理顺序
let canClick = true // 控制点击节流
document.querySelector('#start').style.display = 'none'
document.querySelectorAll('.img-box').forEach((box) => {
box.addEventListener('click', function () {
if (!canClick || flipped.includes(this) || this.style.visibility === 'hidden')
return
showCard(this)
flipped.push(this)
if (flipped.length === 2) {
canClick = false
const [first, second] = flipped
const firstImg = first.firstElementChild
const secondImg = second.firstElementChild
const isMatch = firstImg.src === secondImg.src
setTimeout(() => {
if (isMatch) {
removeCard(first)
removeCard(second)
updateScore(2)
}
else {
hideCard(first)
hideCard(second)
updateScore(-2)
}
flipped.length = 0 // 清空数组
canClick = true
}, 600) // 增加延迟
}
})
})
}
点击一张图片后,代码会按如下逻辑执行:
- 如果上一次点击动画还没结束 / 当前图片当前是翻开的状态 / 当前图片已经被移除了,就不执行任何操作。
- 如果不是如上的三种情况,就显示图片,并添加到
flipped
数组中。(flipped
数组用来记录当前翻开的图片,最多只会有两张照片) - 如果翻开的图片数达到两张,就进入匹配逻辑。如果两张照片匹配,就移除它们;否则就隐藏它们。然后清空
flipped
数组。从翻开图片到移除 / 隐藏图片的时间内,保持canClick
为false
,防止用户在这段时间内点击其他图片。
这个 600ms 的延迟是为了让用户能看清两张图片是否匹配。实际上为了通过评测,这个时间可以更短甚至为 0。
想完整地考虑所有情况还是需要一些谨慎的思考的。
用什么来做计算 #
题目: 用什么来做计算
这题有点抽象,让我们计算一个带括号的四则运算表达式。
手写语法解析?不可能的!(事实上题解区都没人手写)用下 eval
吧!
const calculator = document.querySelector('.calculator')
const formula = document.getElementById('formula')
const result = document.getElementById('result')
calculator.addEventListener('click', (e) => {
if (e.target.className === 'calc-button') {
switch (e.target.textContent) {
case '=':
// eslint-disable-next-line no-eval
result.value = eval(formula.value.replaceAll('x', '*').replaceAll('÷', '/'))
break
case '√':
result.value = Math.sqrt(Number.parseFloat(formula.value))
break
case 'AC':
formula.value = ''
result.value = ''
break
default:
formula.value += e.target.textContent
}
}
})
当按下 =
时,表示应该得出结果。我们应该把表达式的 x
和 ÷
替换为 *
和 /
。然后调用 eval
评估 JavaScript 表达式计算出结果。
按下 √
时,表示应该计算当前表达式的平方根(此功能下表达式 formula
限定为一个值),并直接得出结果。
按下 AC
时,表示应该清空输入框。
按下除此之外的其它键时,表示应该将当前按键的值添加到输入框中。