1. 使用闭包实现计数器
const counter = (() => {
let n = 0;
return () => ++n;
})();
2. 实现数组转树
function arrayToTree(array, options = {}) {
// 配置默认值
const {
id = 'id',
parentId = 'parentId',
rootValue = null
} = options;
// 创建ID到节点的映射(优化查找性能)
const nodeMap = new Map();
array.forEach(item => {
nodeMap.set(item[id], { ...item, children: [] });
});
// 构建树结构
const tree = [];
array.forEach(item => {
const currentNode = nodeMap.get(item[id]);
const parentNode = nodeMap.get(item[parentId]);
if (parentNode) {
// 有父节点,添加到父节点的children中
parentNode.children.push(currentNode);
} else if (item[parentId] === rootValue) {
// 无父节点且符合根节点条件,直接加入树
tree.push(currentNode);
}
});
return tree;
}
- 实现千分位
// 整数部分添加千分位
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
- 实现 call apply bind
// 1. 实现call方法
Function.prototype.myCall = function(context, ...args) {
// 处理context为null/undefined的情况,默认指向window
context = context || window;
// 创建一个唯一的属性名,避免覆盖context原有的属性
const fnKey = Symbol('fn');
// 将当前函数(this)赋值给context的临时属性
context[fnKey] = this;
// 调用函数,传入参数
const result = context[fnKey](...args);
// 删除临时属性,避免污染context
delete context[fnKey];
// 返回函数执行结果
return result;
};
// 2. 实现apply方法(与call的区别是参数以数组形式传入)
Function.prototype.myApply = function(context, args = []) {
context = context || window;
const fnKey = Symbol('fn');
context[fnKey] = this;
// 注意:args需要确保是数组,这里用扩展运算符传入
const result = context[fnKey](...args);
delete context[fnKey];
return result;
};
// 3. 实现bind方法(返回一个绑定了this的新函数,支持柯里化)
Function.prototype.myBind = function(context, ...args1) {
const self = this; // 保存原函数
// 返回一个新函数
function boundFn(...args2) {
// 合并两次传入的参数
const totalArgs = [...args1, ...args2];
// 注意:如果是通过new调用boundFn,this应该指向实例对象
if (this instanceof boundFn) {
// 用原函数作为构造函数创建实例
return new self(...totalArgs);
}
// 普通调用,用call改变this指向
return self.myCall(context, ...totalArgs);
}
// 维护原型链关系
boundFn.prototype = Object.create(self.prototype);
boundFn.prototype.constructor = boundFn;
return boundFn;
};
// 测试用例
const obj = { name: 'Alice' };
function testFn(age, hobby) {
console.log(`Name: ${this.name}, Age: ${age}, Hobby: ${hobby}`);
return { name: this.name, age, hobby };
}
// 测试call
testFn.myCall(obj, 20, 'reading'); // Name: Alice, Age: 20, Hobby: reading
// 测试apply
testFn.myApply(obj, [25, 'sports']); // Name: Alice, Age: 25, Hobby: sports
// 测试bind
const boundFn = testFn.myBind(obj, 30);
boundFn('coding'); // Name: Alice, Age: 30, Hobby: coding
// 测试bind的构造函数用法
const instance = new boundFn('singing');
console.log(instance); // { name: undefined, age: 30, hobby: 'singing' }(this指向实例)
- 最长公共子序列
/**
* 计算两个字符串的最长公共子序列长度
* @param {string} str1 - 第一个字符串
* @param {string} str2 - 第二个字符串
* @returns {number} 最长公共子序列的长度
*/
function lcsLength(str1, str2) {
const m = str1.length;
const n = str2.length;
// 创建二维数组dp,dp[i][j]表示str1[0..i-1]和str2[0..j-1]的LCS长度
const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
// 填充dp数组
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (str1[i - 1] === str2[j - 1]) {
// 当前字符相同,LCS长度为左上角值+1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 当前字符不同,取上方或左方的最大值
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
- 函数柯里化
/**
* 通用的函数柯里化工具
* @param {Function} fn - 需要柯里化的函数
* @param {Array} args - 已接收的参数(用于递归传递)
* @returns {Function} 柯里化后的函数
*/
function curry(fn, args = []) {
// 获取原函数的参数数量
const arity = fn.length;
// 返回一个新函数接收剩余参数
return function(...rest) {
// 合并已接收的参数和新参数
const newArgs = [...args, ...rest];
// 如果参数数量足够,执行原函数;否则继续柯里化
if (newArgs.length >= arity) {
return fn.apply(this, newArgs);
} else {
return curry(fn, newArgs);
}
};
}
- 实现LRU缓存
/**
* LRU缓存实现
* @param {number} capacity - 缓存最大容量
*/
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
// 使用Map存储缓存,Map的迭代顺序是插入顺序,可利用此特性维护访问顺序
this.cache = new Map();
}
/**
* 获取缓存值
* @param {*} key - 缓存键
* @returns {*} 缓存值,若不存在则返回-1
*/
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;
}
/**
* 设置缓存值
* @param {*} key - 缓存键
* @param {*} value - 缓存值
*/
put(key, value) {
// 如果键已存在,先删除(后续会重新插入)
if (this.cache.has(key)) {
this.cache.delete(key);
}
// 如果缓存已满,删除最久未使用的键(Map的第一个元素)
else if (this.cache.size >= this.capacity) {
// 获取Map的第一个键并删除
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
// 插入新值(最新访问的键会放在最后)
this.cache.set(key, value);
}
}
// 测试用例
const lruCache = new LRUCache(2);
lruCache.put(1, 1); // 缓存: {1 => 1}
lruCache.put(2, 2); // 缓存: {1 => 1, 2 => 2}
console.log(lruCache.get(1)); // 返回 1,缓存变为 {2 => 2, 1 => 1}(1被访问,移到最后)
lruCache.put(3, 3); // 容量已满,删除最久未使用的2,缓存: {1 => 1, 3 => 3}
console.log(lruCache.get(2)); // 返回 -1(2已被淘汰)
lruCache.put(4, 4); // 容量已满,删除最久未使用的1,缓存: {3 => 3, 4 => 4}
console.log(lruCache.get(1)); // 返回 -1(1已被淘汰)
console.log(lruCache.get(3)); // 返回 3,缓存变为 {4 => 4, 3 => 3}
console.log(lruCache.get(4)); // 返回 4,缓存变为 {3 => 3, 4 => 4}