在线代码运行BanShow Run

JavaScript 练习题

10 道 JavaScript 练习题,难度从打印输出到分组统计逐步递进,每题都附参考答案,可以直接在浏览器里运行验证,不需要本地环境。

打开 JavaScript 运行器 →

下面每道题都能直接在上面的编辑器里运行。「参考」是给卡住时看的,建议自己先写一遍。

1. 数组统计:和、平均、最大、最小

给一组数,输出总和、平均值(保留两位小数)、最大值、最小值,以及大于平均值的元素。

javascript
const nums = [88, 92, 76, 95, 61, 79];

// 参考
const sum = nums.reduce((a, b) => a + b, 0);
const avg = sum / nums.length;
console.log("总和:", sum);
console.log("平均:", avg.toFixed(2));
console.log("最大 / 最小:", Math.max(...nums), Math.min(...nums));
console.log("高于平均:", nums.filter((x) => x > avg));

要注意:Math.max(nums) 是错的,Math.max 接收的是多个参数,要用展开 ...nums。nums.reduce((a,b)=>a+b) 不给初始值时,空数组会抛 TypeError: Reduce of empty array with no initial value。

2. FizzBuzz

打印 1 到 30:能被 3 整除输出 Fizz,能被 5 整除输出 Buzz,同时能被 3 和 5 整除输出 FizzBuzz,否则输出数字。

javascript
// 参考
for (let i = 1; i <= 30; i++) {
  const s = (i % 3 === 0 ? "Fizz" : "") + (i % 5 === 0 ? "Buzz" : "");
  console.log(s || i);
}

if / else 版:

javascript
for (let i = 1; i <= 30; i++) {
  if (i % 15 === 0) console.log("FizzBuzz");
  else if (i % 3 === 0) console.log("Fizz");
  else if (i % 5 === 0) console.log("Buzz");
  else console.log(i);
}

判断顺序很关键:先判 3 就永远轮不到 FizzBuzz。

3. 字符串处理:反转、回文、词频

javascript
const s = "上海自来水来自海上";
const text = "the quick brown fox jumps over the lazy dog the fox";

// 参考
const reverse = (str) => [...str].reverse().join("");
console.log(reverse(s), reverse(s) === s);      // 反转 + 是否回文

// 用 [...str] 而不是 str.split(""),能正确处理 emoji 之类的多字节字符
console.log(reverse("👨‍👩‍👧 abc"));

const words = text.toLowerCase().split(/\s+/).filter(Boolean);
const counts = {};
for (const w of words) counts[w] = (counts[w] || 0) + 1;
console.log(counts);
console.log(Object.entries(counts).sort((a, b) => b[1] - a[1]));

// 用 Map 更规范
const map = new Map();
for (const w of words) map.set(w, (map.get(w) || 0) + 1);
console.log([...map.entries()].sort((a, b) => b[1] - a[1]));

要注意:split(/\s+/) 会把开头的空串也切出来,所以要 .filter(Boolean)。

4. 从 stdin 读两个数并做四则运算

「输入 (stdin)」面板里填三行:3、5、+。

javascript
// 本站提供 input():每调用一次读取「输入 (stdin)」的一行
const a = Number(input());
const b = Number(input());
const op = input().trim();

const ops = {
  "+": (x, y) => x + y,
  "-": (x, y) => x - y,
  "*": (x, y) => x * y,
  "/": (x, y) => (y === 0 ? null : x / y),
};

if (!(op in ops)) {
  console.log("不支持的运算符:", op);
} else {
  const result = ops[op](a, b);
  console.log(result === null ? "除数不能为 0" : `${a} ${op} ${b} = ${result}`);
}

要注意:input() 返回的是字符串。Number("") 是 0 而不是 NaN,所以空行会被当成 0,最好先 trim() 再判断是否为空。

5. 对象数组:筛选、排序、分组

javascript
const students = [
  { name: "小明", score: 88, city: "北京" },
  { name: "小红", score: 92, city: "上海" },
  { name: "小刚", score: 59, city: "北京" },
  { name: "小美", score: 76, city: "广州" },
  { name: "小强", score: 95, city: "上海" },
];

// 参考
console.log("按分数从高到低:", [...students].sort((a, b) => b.score - a.score).map((s) => s.name));
console.log("及格名单:", students.filter((s) => s.score >= 60).map((s) => s.name));
console.log("平均分:", (students.reduce((t, s) => t + s.score, 0) / students.length).toFixed(1));

// 按城市分组
const groups = {};
for (const s of students) (groups[s.city] ??= []).push(s.name);
console.log(groups);
console.log(Object.keys(groups).map((c) => `${c}: ${groups[c].length} 人`));

要注意:sort() 会修改原数组,不想改动就先 [...arr].sort() 或用 arr.toSorted()。默认排序是按字符串比较的,数字必须传比较函数。新版浏览器还有 Object.groupBy(students, s => s.city) 可以一行分组,但旧浏览器没有,手写循环更保险。

6. 找出重复元素与只出现一次的元素

javascript
const arr = [4, 3, 2, 7, 8, 2, 3, 1];

// 参考:出现次数
const count = new Map();
for (const x of arr) count.set(x, (count.get(x) || 0) + 1);
console.log("重复的:", [...count].filter(([, n]) => n > 1).map(([x]) => x));
console.log("只出现一次:", [...count].filter(([, n]) => n === 1).map(([x]) => x));

// 一行流写法
console.log("重复的(Set):", arr.filter((x, i) => arr.indexOf(x) !== i));
console.log("去重:", [...new Set(arr)]);

// 异或技巧:只有一个数出现一次、其余都出现两次时
console.log("异或结果:", [1, 2, 2, 1, 5].reduce((a, b) => a ^ b, 0));

要注意:arr.indexOf(x) !== i 这种写法是 O(n²),数组很大时会很慢,用 Map 计数是 O(n)。

7. 用 Promise 模拟并发请求

javascript
const delay = (ms, value) => new Promise((resolve) => setTimeout(resolve, ms, value));
const fetchUser = (id) => delay(200 + Math.random() * 300, { id, name: `用户${id}` });

// 参考:并发发起,全部完成再继续
const t0 = Date.now();
const users = await Promise.all([fetchUser(1), fetchUser(2), fetchUser(3)]);
console.log("并发耗时:", Date.now() - t0, "ms", users);

// 串行(慢)
const t1 = Date.now();
const slow = [];
for (let i = 1; i <= 3; i++) slow.push(await fetchUser(i));
console.log("串行耗时:", Date.now() - t1, "ms");

// 其中一个失败也不影响其它结果
const results = await Promise.allSettled([
  fetchUser(4),
  Promise.reject(new Error("网络错误")),
]);
for (const r of results) {
  console.log(r.status === "fulfilled" ? r.value : `失败:${r.reason.message}`);
}

8. 二维数组:转置与矩阵相加

javascript
const matrix = [
  [1, 2, 3],
  [4, 5, 6],
];

// 参考:转置(行列互换)
const transpose = (m) => m[0].map((_, col) => m.map((row) => row[col]));
console.log(transpose(matrix));

// 矩阵相加
const other = [
  [1, 1, 1],
  [2, 2, 2],
];
const add = (a, b) => a.map((row, i) => row.map((v, j) => v + b[i][j]));
console.log(add(matrix, other));

// 打印成对齐的文本表格
for (const row of matrix) console.log(row.map((v) => String(v).padStart(4)).join(""));

// 展平
console.log(matrix.flat(), matrix.flat().reduce((a, b) => a + b, 0));

9. 简易待办列表(用数组和对象模拟)

javascript
const todos = [];
let nextId = 1;

const addTodo = (title) => {
  const item = { id: nextId++, title, done: false };
  todos.push(item);
  return item;
};
const toggle = (id) => {
  const item = todos.find((t) => t.id === id);
  if (item) item.done = !item.done;
  return item;
};
const remove = (id) => {
  const i = todos.findIndex((t) => t.id === id);
  if (i >= 0) todos.splice(i, 1);
};
const show = () => {
  console.log(todos.map((t) => `${t.done ? "[x]" : "[ ]"} ${t.id}. ${t.title}`).join("\n") || "(空)");
};

addTodo("学 JavaScript 数组方法");
addTodo("写一个 FizzBuzz");
addTodo("理解闭包");
show();
toggle(2);
show();
remove(1);
show();
console.log("未完成:", todos.filter((t) => !t.done).length, "已完成:", todos.filter((t) => t.done).length);

要注意:find 找不到时返回 undefined,直接 .done = ... 会抛 TypeError,所以要先判断。remove 用 findIndex + splice 而不是 filter,是因为要原地修改同一个数组。

10. 猜数字游戏(读多行 stdin)

程序想一个 1 到 100 的数,每次从 stdin 读一个猜测并给出「大了 / 小了 / 猜对了」。stdin 填 50、25、37、42。

javascript
// 参考
const answer = 42;               // 想随机就写 Math.floor(Math.random() * 100) + 1
let tries = 0;

while (true) {
  const line = input();
  if (line === null || line === "") {
    console.log(`输入用完了,答案是 ${answer},共猜了 ${tries} 次`);
    break;
  }
  const guess = Number(line);
  if (Number.isNaN(guess)) {
    console.log("请输入数字,收到的是:", line);
    continue;
  }
  tries++;
  if (guess > answer) console.log(`第 ${tries} 次:${guess} 大了`);
  else if (guess < answer) console.log(`第 ${tries} 次:${guess} 小了`);
  else { console.log(`猜对了!用了 ${tries} 次`); break; }
}

要注意:本站的 input() 在读完所有行之后返回 null 而不是抛异常,所以循环里要判断 null,否则会陷入死循环(然后被超时中止)。

换一门语言