本文将深入探讨前端面试中常见的算法和编程题,提供多种实现方案和性能优化策略,帮助大家全面掌握核心面试技能。
1. 函数柯里化(Currying)
1.1 基础柯里化实现
javascript
/**
* 基础柯里化函数
* 将多参数函数转换为一系列单参数函数
*/
function curry(fn) {
return function curried(...args) {
// 如果参数数量足够, 直接执行原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 否则返回一个新函数, 继续收集参数
return function (...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
};
}
// 示例:加法函数柯里化
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
// 测试
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
1.2 高级柯里化实现(支持占位符)
javascript
/**
* 高级柯里化函数, 支持占位符
*/
function advancedCurry(fn) {
return function curried(...args) {
// 检查参数是否足够且没有占位符
const complete =
args.length >= fn.length &&
!args.slice(0, fn.length).includes(advancedCurry.placeholder);
if (complete) {
return fn.apply(this, args);
}
return function (...nextArgs) {
// 替换占位符
const combinedArgs = args
.map((arg) =>
arg === advancedCurry.placeholder && nextArgs.length
? nextArgs.shift()
: arg
)
.concat(nextArgs);
return curried.apply(this, combinedArgs);
};
};
}
// 定义占位符
advancedCurry.placeholder = Symbol("_");
// 示例使用
const curriedMultiply = advancedCurry((a, b, c) => a * b * c);
const _ = advancedCurry.placeholder;
// 测试
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(_, 3)(2)(4)); // 24
console.log(curriedMultiply(2, _, 4)(3)); // 24
2. 函数组合(Compose)
2.1 基础函数组合
javascript
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
// 测试
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const square = x => x * x;
const composed = compose(square, multiply2, add1);
console.log(composed(2)); // 36
3. 斐波那契数列优化
3.1 多种实现对比
javascript
// 1. 递归(性能差)
function fibonacciRecursive(n) {
if (n <= 1) return n;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
// 2. 记忆化递归
function fibonacciMemo(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
return memo[n];
}
// 3. 动态规划
function fibonacciDP(n) {
if (n <= 1) return n;
const dp = [0, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 4. 空间优化
function fibonacciOptimized(n) {
if (n <= 1) return n;
let prev = 0, curr = 1;
for (let i = 2; i <= n; i++) {
const next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
4. 数组去重多种方法
4.1 基础方法
javascript
// 1. Set
function uniqueSet(arr) {
return [...new Set(arr)];
}
// 2. filter + indexOf
function uniqueFilter(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// 3. reduce
function uniqueReduce(arr) {
return arr.reduce((acc, curr) => {
if (!acc.includes(curr)) acc.push(curr);
return acc;
}, []);
}
4.2 复杂对象去重
javascript
function uniqueComplex(arr, keyFn) {
const seen = new Map();
const result = [];
for (let item of arr) {
const key = keyFn ? keyFn(item) : JSON.stringify(item);
if (!seen.has(key)) {
seen.set(key, true);
result.push(item);
}
}
return result;
}
// 测试
const users = [
{ id: 1, name: 'Alice' },
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
console.log(uniqueComplex(users, user => user.id));
5. 深比较(DeepEqual)
javascript
function deepEqual(a, b) {
if (a === b) return true;
if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) return false;
}
return true;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
6. 防抖与节流
javascript
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
7. Promise实现
手写 Promise:深入理解 JavaScript 异步编程的核心
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(cb => cb());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(cb => cb());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e };
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const handleRejected = () => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === 'fulfilled') {
handleFulfilled();
} else if (this.state === 'rejected') {
handleRejected();
} else {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
let called = false;
if (x && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
8. call、apply、bind实现
JavaScript 核心方法深度解析:手写 call、apply、bind 和 Object.create
javascript
Function.prototype.myCall = function(context = window, ...args) {
const fnKey = Symbol('fn');
context[fnKey] = this;
const result = context[fnKey](...args);
delete context[fnKey];
return result;
};
Function.prototype.myApply = function(context = window, args = []) {
const fnKey = Symbol('fn');
context[fnKey] = this;
const result = context[fnKey](...args);
delete context[fnKey];
return result;
};
Function.prototype.myBind = function(context = window, ...bindArgs) {
const self = this;
return function(...callArgs) {
return self.apply(context, [...bindArgs, ...callArgs]);
};
};
9. 事件总线(EventEmitter)
javascript
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(listener);
}
off(event, listener) {
if (!this.events.has(event)) return;
const listeners = this.events.get(event);
const index = listeners.indexOf(listener);
if (index > -1) listeners.splice(index, 1);
}
emit(event, ...args) {
if (!this.events.has(event)) return false;
this.events.get(event).forEach(listener => listener.apply(this, args));
return true;
}
once(event, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
}
10. LRU缓存
JavaScript性能与优化:手写实现关键优化技术 JavaScript 性能与优化:数据结构和算法
javascript
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return -1;
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
11. 快速排序
JavaScript 数组原生方法手写实现 JavaScript 性能与优化:数据结构和算法
javascript
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr[pivotIndex];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (i === pivotIndex) continue;
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
12. 二分查找
javascript
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
总结
本文涵盖了前端面试中常见的算法和编程题,包括函数柯里化、函数组合、斐波那契数列、数组去重、深比较、防抖节流、Promise实现等核心知识点。掌握这些内容有助于提升编程能力和面试表现。
关键要点:
- 函数柯里化: 理解函数式编程思想,掌握基础实现和高级功能
- 函数组合: 学会构建可复用的函数管道,支持同步和异步操作
- 算法优化: 掌握递归优化、动态规划、空间优化等技巧
- 数据处理: 了解不同去重方法的适用场景和性能差异
- 深度比较: 处理复杂对象的比较,包括循环引用和特殊类型
- 异步控制: 实现防抖和节流,优化高频事件处理
- Promise实现: 深入理解异步编程模型
- 原生方法实现: 掌握call、apply、bind的内部原理
- 设计模式: 实现事件总线,理解发布-订阅模式
这些知识点不仅是面试的常见考点,也是实际开发中的重要技能。建议大家不仅要理解代码实现,更要掌握背后的设计思想和适用场景。