Appearance
JavaScript 编程题与算法实现
导航目录
核心概念
本章节收集整理了前端开发中常见的编程题目和算法实现,包括数据结构操作、异步控制、设计模式、作用域与闭包等核心知识点。
目录
算法与数据结构
斐波那契数列
斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21...(从第三项开始,每一项等于前两项之和)
循环实现(推荐)
时间复杂度:O(n),空间复杂度:O(1)
javascript
function fib(n) {
if (n <= 1) return n;
let prev = 0;
let curr = 1;
for (let i = 2; i <= n; i++) {
const temp = curr;
curr = prev + curr;
prev = temp;
}
return curr;
}
// 或返回整个数列
function fibSequence(n) {
if (n <= 1) return [1];
const arr = [1, 1];
for (let i = 2; i < n; i++) {
arr.push(arr[i - 1] + arr[i - 2]);
}
return arr;
}
console.log(fib(10)); // 55
console.log(fibSequence(10)); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]递归实现(性能较差)
时间复杂度:O(2^n),存在大量重复计算
javascript
function fibRecursive(n) {
if (n <= 1) return n;
return fibRecursive(n - 1) + fibRecursive(n - 2);
}
// 优化:记忆化递归
function fibMemo(n, memo = {}) {
if (n <= 1) return n;
if (memo[n]) return memo[n];
memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
return memo[n];
}检测循环引用
javascript
// 模块依赖关系示例
const modules = {
a: { dependencies: ["b"] },
b: { dependencies: ["c"] },
c: { dependencies: ["d"] },
d: { dependencies: ["a"] }, // 形成循环:a -> b -> c -> d -> a
};
/**
* 检测对象中是否存在循环引用
* @param {Object} obj - 模块依赖关系对象
* @returns {boolean} - 是否存在循环
*/
function hasCycle(obj) {
if (!obj) return false;
const dfs = (key, visited) => {
if (visited.has(key)) {
return true; // 发现循环
}
visited.add(key);
const deps = obj[key]?.dependencies || [];
for (const dep of deps) {
if (dfs(dep, visited)) {
return true;
}
}
visited.delete(key); // 回溯
return false;
};
for (const key in obj) {
if (dfs(key, new Set())) {
return true;
}
}
return false;
}
console.log(hasCycle(modules)); // true数组操作
求最大值/最小值
javascript
const arr = [2, 34, 54, 34, 2, 345];
// 方法1:遍历
function getMax(arr) {
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 方法2:Math.max + apply(ES5)
const max1 = Math.max.apply(null, arr);
// 方法3:Math.max + 展开运算符(ES6,推荐)
const max2 = Math.max(...arr);
// 方法4:reduce
const max3 = arr.reduce((max, curr) => Math.max(max, curr));
console.log(getMax(arr)); // 345
console.log(Math.min(...arr)); // 2数组去重
javascript
const arr = [1, 2, 2, 3, 3, 3, 4];
// 方法1:Set(ES6,推荐)
const unique1 = [...new Set(arr)];
// 方法2:filter + indexOf
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);
// 方法3:reduce
const unique3 = arr.reduce((acc, curr) => {
return acc.includes(curr) ? acc : [...acc, curr];
}, []);
// 方法4:Map(保持顺序,适合对象数组)
function uniqueByKey(arr, key) {
const map = new Map();
arr.forEach((item) => {
const val = key ? item[key] : item;
if (!map.has(val)) {
map.set(val, item);
}
});
return [...map.values()];
}
console.log(unique1); // [1, 2, 3, 4]数组扁平化
javascript
const arr = [1, 2, 3, [4, 5], 6, [7, 8, [9, 10]]];
// 方法1:flat(ES2019,推荐)
const flat1 = arr.flat(Infinity);
// 方法2:toString(仅限数字数组)
const flat2 = arr.toString().split(",").map(Number);
// 方法3:reduce + 递归
function flatten(arr) {
return arr.reduce((acc, curr) => {
return acc.concat(Array.isArray(curr) ? flatten(curr) : curr);
}, []);
}
// 方法4:栈实现(非递归)
function flattenStack(arr) {
const result = [];
const stack = [...arr];
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.unshift(item);
}
}
return result;
}
console.log(flat1); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]树形结构转换
列表转树
javascript
const list = [
{ id: 1, name: "电子产品", parentId: 0 },
{ id: 2, name: "服装", parentId: 0 },
{ id: 3, name: "手机", parentId: 1 },
{ id: 4, name: "电脑", parentId: 1 },
{ id: 5, name: "T恤", parentId: 2 },
{ id: 6, name: "裤子", parentId: 2 },
];
/**
* 将扁平列表转换为树形结构
* @param {Array} list - 扁平列表
* @param {Object} options - 配置选项
* @returns {Array} - 树形结构
*/
function listToTree(list, options = {}) {
const {
idKey = "id",
parentKey = "parentId",
childrenKey = "children",
} = options;
const map = new Map();
const tree = [];
// 先构建映射表
list.forEach((item) => {
map.set(item[idKey], { ...item, [childrenKey]: [] });
});
// 构建树
list.forEach((item) => {
const node = map.get(item[idKey]);
if (item[parentKey] == 0 || !map.has(item[parentKey])) {
tree.push(node);
} else {
const parent = map.get(item[parentKey]);
parent[childrenKey].push(node);
}
});
return tree;
}
console.log(JSON.stringify(listToTree(list), null, 2));树转列表
javascript
/**
* 将树形结构转换为扁平列表
* @param {Array} tree - 树形结构
* @param {Object} options - 配置选项
* @returns {Array} - 扁平列表
*/
function treeToList(tree, options = {}) {
const { childrenKey = "children", keepChildren = false } = options;
const list = [];
const dfs = (nodes, depth = 0) => {
nodes.forEach((node) => {
const { [childrenKey]: children, ...rest } = node;
const item = keepChildren ? node : { ...rest, depth };
list.push(item);
if (children?.length) {
dfs(children, depth + 1);
}
});
};
dfs(tree);
return list;
}异步控制
间隔输出函数
javascript
/**
* 创建间隔执行函数
* @param {Function} fn - 要执行的函数
* @param {number} repeat - 重复次数
* @param {number} interval - 间隔时间(秒)
* @returns {Function} - 包装后的函数
*/
function createRepeat(fn, repeat, interval) {
return function (params) {
let count = 0;
const execute = () => {
if (count < repeat) {
fn(params);
count++;
setTimeout(execute, interval * 1000);
}
};
execute();
};
}
// 使用
const repeatLog = createRepeat(console.log, 3, 4);
repeatLog("helloWorld"); // 每4秒输出一次,共3次并发请求控制
javascript
/**
* 限制并发数的请求池
* @param {Array} urls - 请求地址数组
* @param {number} limit - 最大并发数
* @param {Function} requestFn - 请求函数
*/
async function concurrentRequest(urls, limit, requestFn) {
const results = [];
const executing = [];
for (const [index, url] of urls.entries()) {
const promise = requestFn(url).then((result) => {
results[index] = result;
return result;
});
results.push(promise);
if (urls.length >= limit) {
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(
executing.findIndex((p) => p === promise),
1
);
}
}
}
return Promise.all(results);
}作用域与闭包
变量提升案例
javascript
// 案例1:var 变量提升
var a = 1;
function fn() {
if (!a) {
// 此时 a 为 undefined,!undefined 为 true
var a = 10; // var 声明提升,但赋值不提升
}
console.log(a); // 10
}
fn();
// 案例2:函数提升优先级高于 var
function fn2() {
console.log(typeof fn2); // function
var fn2 = 3;
function fn2() {}
}
fn2();闭包经典案例
javascript
// 案例1:函数执行上下文
function fn1() {
let a = 10;
return function () {
console.log(a); // 10,访问定义时的作用域
};
}
let f = fn1();
function fn2() {
let a = 20;
f(); // 输出 10,而非 20
}
fn2();
// 案例2:循环中的闭包问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// 解决方案1:使用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// 解决方案2:使用闭包
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log(j), 100); // 0, 1, 2
})(i);
}this 指向
this 指向规则总结
| 场景 | this 指向 |
|---|---|
| 普通函数调用 | window(严格模式 undefined) |
| 对象方法调用 | 调用该方法的对象 |
| 构造函数调用 | 新创建的实例 |
| call/apply/bind | 指定的第一个参数 |
| 箭头函数 | 定义时上层作用域的 this |
javascript
// 案例1:隐式绑定丢失
const obj = {
name: "Alice",
getName() {
console.log(this.name);
},
};
obj.getName(); // Alice
const fn = obj.getName;
fn(); // undefined(this 指向 window)
// 案例2:箭头函数的 this
const obj2 = {
name: "Bob",
getName: () => {
console.log(this.name); // undefined,指向 window
},
getName2() {
const arrow = () => {
console.log(this.name); // Bob,继承外层 this
};
arrow();
},
};
// 案例3:类数组中的 this
let a = [1];
a.push(function () {
console.log(this); // [1, function],指向数组
});
a[1]();函数式编程
函数柯里化(Curry)
javascript
/**
* 柯里化:将多参数函数转换为单参数函数链
* @param {Function} fn - 原函数
* @returns {Function} - 柯里化后的函数
*/
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function sum(a, b, c, d) {
return a + b + c + d;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)(4)); // 10
console.log(curriedSum(1, 2)(3, 4)); // 10
console.log(curriedSum(1)(2, 3, 4)); // 10
// 实际应用:创建配置函数
const addTax = curry((tax, price) => price * (1 + tax));
const addVAT = addTax(0.2); // 创建 20% 增值税函数
console.log(addVAT(100)); // 120反柯里化(Uncurry)
javascript
/**
* 反柯里化:借用其他对象的方法
*/
Function.prototype.uncurry = function () {
const self = this;
return function (context, ...args) {
return self.apply(context, args);
};
};
// 使用示例
const push = Array.prototype.push.uncurry();
const obj = { length: 0 };
push(obj, 1);
push(obj, 2);
console.log(obj); // { '0': 1, '1': 2, length: 2 }
// 类型判断
const toString = Object.prototype.toString.uncurry();
console.log(toString([])); // [object Array]
console.log(toString({})); // [object Object]
console.log(toString(123)); // [object Number]设计模式
发布订阅模式(Pub/Sub)
javascript
/**
* 发布订阅模式:解耦的事件系统
*/
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
// 取消订阅
off(event, callback) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter((cb) => cb !== callback);
}
// 发布
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach((callback) => {
try {
callback(...args);
} catch (error) {
console.error(`Error in event ${event}:`, error);
}
});
}
// 一次性订阅
once(event, callback) {
const onceCallback = (...args) => {
callback(...args);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
// 使用
const emitter = new EventEmitter();
const unsubscribe = emitter.on("message", (data) => {
console.log("收到消息:", data);
});
emitter.emit("message", "Hello World"); // 收到消息: Hello World
unsubscribe(); // 取消订阅观察者模式(Observer)
javascript
/**
* 观察者模式:被观察者主动通知观察者
*/
// 被观察者(Subject)
class Subject {
constructor(name) {
this.name = name;
this.observers = [];
this.state = "";
}
// 添加观察者
attach(observer) {
this.observers.push(observer);
}
// 移除观察者
detach(observer) {
this.observers = this.observers.filter((obs) => obs !== observer);
}
// 状态变更,通知所有观察者
setState(state) {
this.state = state;
console.log(`${this.name} 状态变更为: ${state}`);
this.notify();
}
notify() {
this.observers.forEach((observer) => {
observer.update(this.state);
});
}
}
// 观察者(Observer)
class Observer {
constructor(name) {
this.name = name;
}
update(state) {
console.log(`${this.name} 收到通知: ${state}`);
}
}
// 使用
const subject = new Subject("天气系统");
const observer1 = new Observer("小明");
const observer2 = new Observer("小红");
subject.attach(observer1);
subject.attach(observer2);
subject.setState("下雨了");
// 天气系统 状态变更为: 下雨了
// 小明 收到通知: 下雨了
// 小红 收到通知: 下雨了手写实现
深拷贝
javascript
/**
* 深拷贝:完整复制对象,处理循环引用
* @param {*} data - 要拷贝的数据
* @param {WeakMap} weakMap - 用于处理循环引用
* @returns {*} - 拷贝后的数据
*/
function deepClone(data, weakMap = new WeakMap()) {
// 基本类型直接返回
if (data === null || typeof data !== "object") {
return data;
}
// 处理函数
if (typeof data === "function") {
return data;
}
// 处理日期
if (data instanceof Date) {
return new Date(data.getTime());
}
// 处理正则
if (data instanceof RegExp) {
return new RegExp(data.source, data.flags);
}
// 处理循环引用
if (weakMap.has(data)) {
return weakMap.get(data);
}
// 处理 Map
if (data instanceof Map) {
const cloneMap = new Map();
weakMap.set(data, cloneMap);
data.forEach((value, key) => {
cloneMap.set(deepClone(key, weakMap), deepClone(value, weakMap));
});
return cloneMap;
}
// 处理 Set
if (data instanceof Set) {
const cloneSet = new Set();
weakMap.set(data, cloneSet);
data.forEach((value) => {
cloneSet.add(deepClone(value, weakMap));
});
return cloneSet;
}
// 处理对象和数组
const clone = Array.isArray(data) ? [] : {};
weakMap.set(data, clone);
for (const key in data) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
clone[key] = deepClone(data[key], weakMap);
}
}
return clone;
}
// 测试
const obj = {
a: 1,
b: { c: 2 },
d: [1, 2, 3],
e: new Date(),
f: /abc/g,
};
obj.circular = obj; // 循环引用
const cloned = deepClone(obj);
console.log(cloned.b === obj.b); // false
console.log(cloned.circular === cloned); // true节流与防抖
javascript
/**
* 节流(Throttle):固定时间内只执行一次
* 适用场景:滚动、resize、鼠标移动
*/
function throttle(fn, delay) {
let lastTime = 0;
let timer = null;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
// 立即执行
fn.apply(this, args);
lastTime = now;
} else {
// 延迟执行最后一次
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
lastTime = Date.now();
}, delay - (now - lastTime));
}
};
}
/**
* 防抖(Debounce):延迟执行,期间再次触发则重新计时
* 适用场景:搜索输入、表单验证
*/
function debounce(fn, delay, immediate = false) {
let timer = null;
return function (...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) {
fn.apply(this, args);
}
}, delay);
if (callNow) {
fn.apply(this, args);
}
};
}
// 使用
window.addEventListener(
"scroll",
throttle(() => {
console.log("scroll");
}, 200)
);
const searchInput = debounce((value) => {
console.log("搜索:", value);
}, 500);手写 Promise
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((fn) => fn());
}
};
const reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === "fulfilled") {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
} else if (this.state === "rejected") {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
} else {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
return new MyPromise((resolve) => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let completed = 0;
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then((value) => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
}, reject);
});
});
}
}
// 辅助函数
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError("Chaining cycle detected for promise"));
return;
}
if (x !== null && (typeof x === "object" || typeof x === "function")) {
let called = false;
try {
const then = x.then;
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) return;
reject(error);
}
} else {
resolve(x);
}
}手写 call、apply、bind
javascript
/**
* 手写 call
*/
Function.prototype.myCall = function (context, ...args) {
context = context ? Object(context) : window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
};
/**
* 手写 apply
*/
Function.prototype.myApply = function (context, args = []) {
context = context ? Object(context) : window;
const fnSymbol = Symbol("fn");
context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
};
/**
* 手写 bind
*/
Function.prototype.myBind = function (context, ...args1) {
const fn = this;
return function (...args2) {
return fn.apply(context, [...args1, ...args2]);
};
};
// 使用
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: "Alice" };
greet.myCall(person, "Hello", "!"); // Hello, Alice!
greet.myApply(person, ["Hi", "?"]); // Hi, Alice?
const boundGreet = greet.myBind(person, "Hey");
boundGreet("..."); // Hey, Alice...经典面试题
让 a == 1 && a == 2 && a == 3 成立
javascript
// 方法1:重写 valueOf 或 toString
let a = {
value: 1,
valueOf() {
return this.value++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log("成立!");
}
// 方法2:使用 Proxy
let index = 1;
a = new Proxy(
{},
{
get() {
return index++;
},
}
);
if (a == 1 && a == 2 && a == 3) {
console.log("成立!");
}对象深度比较
javascript
/**
* 深度比较两个对象是否相等
*/
function isEqual(obj1, obj2) {
// 基本类型直接比较
if (obj1 === obj2) return true;
// 类型不同
if (typeof obj1 !== typeof obj2) return false;
// null 和 undefined
if (obj1 == null || obj2 == null) return obj1 === obj2;
// 日期
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
// 正则
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
return obj1.toString() === obj2.toString();
}
// 数组
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) return false;
return obj1.every((item, index) => isEqual(item, obj2[index]));
}
// 对象
if (typeof obj1 === "object" && typeof obj2 === "object") {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every(
(key) => keys2.includes(key) && isEqual(obj1[key], obj2[key])
);
}
return false;
}
// 测试
console.log(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })); // true
console.log(isEqual([1, 2, 3], [1, 2, 3])); // true最佳实践总结
- 优先使用 ES6+ 语法:展开运算符、解构赋值、箭头函数等
- 注意 this 指向:箭头函数没有自己的 this,普通函数的 this 取决于调用方式
- 避免 var:使用 let 和 const,避免变量提升带来的问题
- 处理异步:使用 async/await 替代回调地狱
- 函数式编程:善用 map、filter、reduce 等高阶函数
- 性能优化:大数据量操作考虑时间复杂度和空间复杂度