1. JavaScript练习题解析:从入门到实战
作为一名有十年全栈开发经验的工程师,我经常通过解决各种JavaScript练习题来保持编码手感。今天分享几个典型题目及其解题思路,这些题目覆盖了数据类型、算法逻辑、DOM操作等核心知识点,特别适合初中级开发者巩固基础。
1.1 变量与数据类型练习
javascript复制// 题目1:实现变量交换
let a = 5, b = 10;
[a, b] = [b, a]; // ES6解构赋值方案
这个简单的交换操作揭示了JavaScript的赋值机制。传统方式需要临时变量,而ES6的解构赋值更优雅。实际开发中要注意:
- 基本类型是按值传递
- 对象类型是按引用传递
- 解构赋值的右侧表达式会先求值
1.2 数组操作实战
javascript复制// 题目2:数组去重
const arr = [1,2,2,3,4,4,5];
const uniqueArr = [...new Set(arr)];
// 低版本浏览器兼容方案
const legacyUnique = arr.filter((v,i) => arr.indexOf(v) === i);
Set对象是ES6引入的高效去重方案,时间复杂度O(n)。而filter+indexOf的传统方案是O(n²),在大数组场景下性能差异明显。实际项目中要考虑:
- 目标运行环境的ES支持情况
- 数据规模对算法选择的影响
- 是否需要保持原数组顺序
1.3 闭包与作用域
javascript复制// 题目3:实现计数器工厂
function createCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count
};
}
这个例子展示了闭包的经典应用。每个计数器实例都维护独立的词法环境,其中count变量对外不可见,只能通过暴露的方法访问。工程实践中要注意:
- 避免内存泄漏(如将闭包赋给全局变量)
- 合理控制闭包数量(每个闭包都会占用内存)
- 在React Hooks等现代框架中闭包很常见
2. DOM操作与事件处理
2.1 动态列表渲染
javascript复制// 题目4:实现列表渲染与删除
function renderList(items) {
const ul = document.createElement('ul');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
li.onclick = () => li.remove();
ul.appendChild(li);
});
return ul;
}
虽然现代前端多用框架,但理解原生DOM操作很有必要。这里有几个优化点:
- 使用DocumentFragment减少重绘
- 事件委托替代单个元素绑定
- 考虑无障碍访问(ARIA属性)
2.2 表单验证实现
javascript复制// 题目5:表单实时验证
const form = document.getElementById('myForm');
form.addEventListener('input', (e) => {
const input = e.target;
if (input.required && !input.value) {
input.classList.add('invalid');
} else {
input.classList.remove('invalid');
}
});
表单验证是前端常见需求,现代浏览器已内置很多验证属性(required、pattern等),但自定义验证仍然必要。注意:
- 既要客户端验证也要服务端验证
- 提供清晰的错误提示
- 避免过度频繁的验证(可配合防抖)
3. 异步编程解决方案
3.1 Promise链式调用
javascript复制// 题目6:顺序执行异步任务
function asyncTask1() {
return new Promise(resolve => {
setTimeout(() => resolve('结果1'), 1000);
});
}
asyncTask1()
.then(result => {
console.log(result);
return asyncTask2();
})
.catch(err => console.error(err));
Promise解决了回调地狱问题,但then链过长仍会影响可读性。实际建议:
- 合理拆分Promise链
- 始终处理rejection情况
- 在Node.js中util.promisify很好用
3.2 async/await最佳实践
javascript复制// 题目7:用async/await重构
async function runTasks() {
try {
const r1 = await asyncTask1();
const r2 = await asyncTask2(r1);
return [r1, r2];
} catch (e) {
// 统一错误处理
}
}
async/await让异步代码看起来像同步代码,但要注意:
- await会阻塞后续代码执行
- 并行任务应该用Promise.all
- 顶层await现在被多数环境支持
4. 算法与数据结构应用
4.1 递归与迭代
javascript复制// 题目8:斐波那契数列实现
function fibonacci(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 2) return 1;
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo);
return memo[n];
}
这个记忆化递归方案时间复杂度O(n),空间复杂度O(n)。对比迭代方案:
javascript复制function fibIterative(n) {
let [a, b] = [0, 1];
for (let i = 0; i < n; i++) {
[a, b] = [b, a + b];
}
return a;
}
迭代方案空间复杂度降到O(1)。选择依据:
- 递归代码更直观但可能有栈溢出风险
- 迭代性能更好但可能牺牲可读性
- 大数据集考虑尾调用优化
4.2 常见排序算法
javascript复制// 题目9:快速排序实现
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [], right = [];
for (let i = 1; i < arr.length; i++) {
arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
虽然实际项目多用Array.prototype.sort(),但理解算法原理很重要:
- V8引擎的sort使用TimSort(混合插入+归并)
- 快速排序平均O(n log n)但最差O(n²)
- 小数组(<10)用插入排序更高效
5. 设计模式实战
5.1 观察者模式实现
javascript复制// 题目10:简单EventEmitter
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
(this.events[event] || (this.events[event] = [])).push(listener);
}
emit(event, ...args) {
(this.events[event] || []).forEach(listener => listener(...args));
}
}
这个简单的发布-订阅实现揭示了前端状态管理的核心原理。实际应用时要注意:
- 及时取消订阅避免内存泄漏
- 考虑添加once()方法
- 现代前端可用RxJS等专业库
5.2 单例模式应用
javascript复制// 题目11:全局配置管理器
const ConfigManager = (function() {
let instance;
function createInstance() {
const config = {};
return {
set(key, value) { config[key] = value },
get(key) { return config[key] }
};
}
return {
getInstance() {
if (!instance) instance = createInstance();
return instance;
}
};
})();
模块模式是实现单例的经典方式。在现代ES模块系统中,直接导出实例即可:
javascript复制const config = {};
export default config;
但了解原理有助于:
- 理解框架中的服务注入机制
- 合理控制全局状态
- 实现延迟初始化
6. 性能优化技巧
6.1 防抖与节流
javascript复制// 题目12:实现防抖函数
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
与节流(throttle)的区别:
- 防抖:连续触发时只执行最后一次
- 节流:固定间隔最多执行一次
应用场景: - 防抖适合搜索建议
- 节流适合滚动事件
- Resize事件两者都可
6.2 虚拟列表优化
javascript复制// 题目13:大数据列表渲染
function renderVirtualList(items, container, itemHeight) {
const visibleCount = Math.ceil(container.clientHeight / itemHeight);
let startIndex = 0;
function update() {
const endIndex = startIndex + visibleCount;
container.innerHTML = '';
items.slice(startIndex, endIndex).forEach(item => {
const div = document.createElement('div');
div.style.height = `${itemHeight}px`;
div.textContent = item;
container.appendChild(div);
});
}
container.onscroll = () => {
startIndex = Math.floor(container.scrollTop / itemHeight);
update();
};
update();
}
这个简化实现揭示了React/Vue中虚拟滚动的原理。关键点:
- 只渲染可视区域DOM
- 通过transform模拟滚动位置
- 配合IntersectionObserver更高效
- 提前加载缓冲区域项
7. 现代JavaScript特性
7.1 可选链与空值合并
javascript复制// 题目14:安全访问嵌套属性
const userName = user?.profile?.name ?? '匿名';
这两个操作符(?./??)解决了:
- 冗长的&&链判断
- ||对假值(0、'')的误判
但要注意: - 编译目标需要支持ES2020
- 过度使用可能掩盖设计问题
- 在TypeScript中配合类型检查更安全
7.2 Generator应用
javascript复制// 题目15:实现可中断任务
function* taskRunner() {
yield doStep1();
yield doStep2();
return '完成';
}
const gen = taskRunner();
gen.next(); // 执行第一步
gen.next(); // 执行第二步
虽然async/await更常用,但Generator适合:
- 实现自定义迭代器
- 惰性求值
- 协程-like控制
- Redux-Saga等库的核心
8. 测试与调试
8.1 单元测试示例
javascript复制// 题目16:测试工具函数
function sum(a, b) { return a + b }
// 使用Jest测试
test('加法测试', () => {
expect(sum(1, 2)).toBe(3);
expect(sum('1', 2)).toBe('12'); // 注意类型转换
});
良好的单元测试应该:
- 覆盖边界条件
- 测试失败场景
- 保持测试独立
- 避免过度Mock
- 与类型系统配合
8.2 调试技巧
javascript复制// 题目17:条件断点调试
function complexCalculation(data) {
debugger; // 或使用浏览器DevTools的条件断点
// ...
}
高效调试建议:
- 学会使用条件断点
- 掌握Chrome DevTools的Performance面板
- 使用console.time()进行性能测量
- 结构化console输出(%O, table())
- 利用Source Map调试编译代码
9. 综合项目实战
9.1 待办事项应用
javascript复制// 题目18:实现TodoList类
class TodoList {
constructor() {
this.todos = [];
}
add(todo) {
this.todos.push({
id: Date.now(),
text: todo,
completed: false
});
}
toggle(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.completed = !todo.completed;
}
get filtered() {
return {
all: [...this.todos],
active: this.todos.filter(t => !t.completed),
completed: this.todos.filter(t => t.completed)
};
}
}
这个基础实现可以扩展:
- 持久化到localStorage
- 添加撤销/重做功能
- 实现服务器同步
- 添加分类标签
9.2 图片懒加载
javascript复制// 题目19:实现懒加载
function lazyLoad(images) {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));
}
现代优化方案:
- 使用loading="lazy"原生属性
- 配合响应式图片(srcset)
- 预加载关键资源
- 渐进式图片加载
- 考虑CLS(布局偏移)问题
10. 进阶挑战
10.1 实现Promise
javascript复制// 题目20:Promise polyfill
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.callbacks = [];
const resolve = value => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = value;
this.callbacks.forEach(cb => cb.onFulfilled(value));
};
const reject = reason => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.value = reason;
this.callbacks.forEach(cb => cb.onRejected(reason));
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = () => {
try {
const cb = this.state === 'fulfilled' ? onFulfilled : onRejected;
if (typeof cb !== 'function') {
this.state === 'fulfilled' ? resolve(this.value) : reject(this.value);
return;
}
const result = cb(this.value);
resolve(result);
} catch (err) {
reject(err);
}
};
if (this.state !== 'pending') {
setTimeout(handle, 0);
} else {
this.callbacks.push({
onFulfilled: value => setTimeout(() => {
try {
typeof onFulfilled === 'function'
? resolve(onFulfilled(value))
: resolve(value);
} catch (err) {
reject(err);
}
}, 0),
onRejected: reason => setTimeout(() => {
try {
typeof onRejected === 'function'
? resolve(onRejected(reason))
: reject(reason);
} catch (err) {
reject(err);
}
}, 0)
});
}
});
}
}
这个简化实现揭示了:
- Promise的状态不可逆特性
- 异步执行then回调
- 链式调用原理
- 值穿透行为
实际项目中应使用标准Promise,但理解实现原理很有帮助
10.2 函数式编程
javascript复制// 题目21:实现函数组合
function compose(...fns) {
return fns.reduce((f, g) => (...args) => f(g(...args)));
}
// 使用示例
const toUpperCase = x => x.toUpperCase();
const exclaim = x => `${x}!`;
const shout = compose(exclaim, toUpperCase);
shout('hello'); // "HELLO!"
函数式编程要点:
- 纯函数(无副作用)
- 不可变数据
- 高阶函数
- 柯里化
- 配合reduce/map/filter使用
在React生态中,函数式风格越来越流行,特别是Redux和React Hooks的设计深受影响