bfe.dev刷题记录
1.柯里化实现
1 function curry(fn) { 2 return function innerFun(...args) { 3 //判断是否收集完参数 4 if (args.length >= fn.length) { 5 //没有收集完 6 return fn.apply(this, args); 7 } else { 8 return function (...args2) { 9 //收集参数 10 return innerFun(...args, ...args2); 11 }; 12 } 13 }; 14 }
另一种实现方式,箭头函数。
1 const curry = (fn, ...args) => 2 // 函数的参数个数可以直接通过函数数的.length属性来访问 3 args.length >= fn.length // 这个判断很关键!!! 4 // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数 5 ? fn(...args) 6 /** 7 * 传入的参数小于原始函数fn的参数个数时 8 * 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数 9 */ 10 : (..._args) => curry(fn, ...args, ..._args);
2.实现array.flat
思路:递归,累加
1 const flat = (arr, level) => { 2 if (level == 0) return arr; 3 return arr.reduce((pre, cur) => { 4 if (Array.isArray(cur) && level > 0) { 5 return pre.concat(flat(cur, level - 1)); 6 } else { 7 return pre.concat(cur); 8 } 9 }, []); 10 };
3.throttle 实现
基本,无拓展实现
1 const throttle = (fn, delay) => { 2 let timer = null; 3 return function (...args) { 4 if (!timer) { 5 // 立即执行一次 6 fn.apply(this, args); 7 // 设置定时器,在延迟时间后清除定时器 8 timer = setTimeout(() => { 9 timer = null; 10 }, delay); 11 } 12 }; 13 };
本题目中你需要实现一个增强的throttle(),使其支持第三个参数option: {leading: boolean, trailing: boolean}
leading: 是否立即执行
trailing: 是否在冷却后执行
增强实现思路:1.第一次执行加入leading的判断 2.冷却后执行:指的是定时器结束时,需要执行上次被打断的执行,这里需要记录上一次的this
1 const throttle = (fn,delay,options = { leading: true, trailing: true }) => { 2 const { leading, trailing } = options; 3 let timer = null; 4 let lastArgs = null 5 let lastThis = null 6 return function (...args) { 7 //立即执行判断 8 if(!timer && leading){ 9 fn.apply(this,args); 10 11 }else{ 12 lastArgs = args 13 lastThis = this 14 } 15 if(timer) return 16 timer = setTimeout(()=>{ 17 if(trailing && lastArgs){ 18 //执行完后需要清空已存的内容 19 fn.apply(lastThis,lastArgs) 20 lastArgs = null 21 lastThis = null 22 } 23 timer = null 24 },delay) 25 26 } 27 }
4.debounce 实现
1 const debounce = (fn, delay) => { 2 let timer = null; // 用于保存定时器 3 4 return function (...args) { 5 // 如果已经有定时器,清除之前的定时器 6 if (timer) { 7 clearTimeout(timer); 8 } 9 10 // 设置新的定时器 11 timer = setTimeout(() => { 12 // 在延迟时间结束后执行函数 13 fn.apply(this, args); 14 // 重置 timer 为 null,允许下一次调用 15 timer = null; 16 }, delay); 17 }; 18 };
增强,实现立即执行和冷却执行,冷却执行的意思是在定时器结束时执行
1 const debounce = (fn, delay, options = { leading: true, trailing: true }) => { 2 const { leading, trailing } = options; 3 let timer = null; // 定时器 4 let isLeadingCalled = false; // 标记 leading 是否已经执行 5 6 return function (...args) { 7 // 如果 leading 为 true,且没有定时器,立即执行 8 if (leading && !timer) { 9 fn.apply(this, args); 10 isLeadingCalled = true; // 标记 leading 已执行 11 } else { 12 isLeadingCalled = false; // 重置标记 13 } 14 15 // 清除之前的定时器 16 if (timer) { 17 clearTimeout(timer); 18 } 19 20 // 设置新的定时器 21 timer = setTimeout(() => { 22 // 如果 trailing 为 true,且 leading 未执行过,执行最后一次调用 23 if (trailing && !isLeadingCalled) { 24 fn.apply(this, args); 25 } 26 27 // 重置定时器和标记 28 timer = null; 29 isLeadingCalled = false; 30 }, delay); 31 }; 32 };
5.洗牌算法 我自己的实现,时间复杂度O(n²)
1 function shuffle(arr) { 2 const result = []; 3 let len = arr.length; 4 5 while (len--) { 6 // 生成均匀的随机索引 7 const randomIndex = Math.floor(Math.random() * len); 8 // 使用 splice 删除该索引的元素,并添加到结果中 9 result.push(arr.splice(randomIndex, 1)[0]); 10 11 } 12 13 return result; 14 }
标准答案,一次遍历交换
1
从前向后
function shuffle(arr) { 2 for (let i = 0; i < arr.length; i++) { 3 // 随机生成一个索引(i 到 arr.length - 1) 4 const j = Math.floor(Math.random() * (arr.length - i)) + i; 5 // 交换 arr[i] 和 arr[j] 6 [arr[i], arr[j]] = [arr[j], arr[i]]; 7 } 8 return arr; 9 }
为什么从后向前遍历更好
Fisher-Yates 洗牌算法通常从后向前遍历数组,原因如下:
-
随机索引的范围:
-
在每次迭代中,随机索引的范围是
[0, i],其中i是当前索引。 -
这样可以确保每个元素都有机会被交换到任何位置。
-
-
概率均匀:
-
从后向前遍历时,每个元素被交换到当前位置的概率是均匀的。
-
例如,最后一个元素
arr[n-1]可以被交换到任何位置(包括它自己的位置)。 -
这样可以保证每个元素被选中的概率一致。
-
-
逻辑简单:
-
从后向前遍历的逻辑更简单,代码更容易理解和实现。
-
function shuffle(arr) {
// 从后向前遍历数组
for (let i = arr.length - 1; i > 0; i--) {
// 随机生成一个索引(0 到 i)
const j = Math.floor(Math.random() * (i + 1));
// 交换 arr[i] 和 arr[j]
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
6.解密消息 有点类似z字型变换 ,同时考虑不规则数组,感觉这个也不需要考虑
1 const caesarDecrypt = (arr) => { 2 //最长宽度 3 let row = arr.length; 4 let col = 0; 5 let res = []; 6 arr.forEach((element) => { 7 col = Math.max(col, element.length); 8 }); 9 let j = 0; 10 for (let i = 0; i < col; i++) { 11 if (arr[j][i] !== undefined) { 12 if (j < row-1) { 13 res.push(arr[j][i]); 14 j++; 15 } else { 16 res.push(arr[j][i]); 17 j--; 18 } 19 } 20 } 21 return res.join(","); 22 };
7.实现instanceof
const chain = (L, R) => { while (L !== null) { if (L.__proto__ == R.prototype) { return true; } L = L.__proto__; } return false; }; // 更推荐使用 Object.getPrototypeOf() 来访问原型。 Object.getPrototypeOf(L) === R.prototype
8.实现new操作符
const myNew = (fn, ...args) => { let obj = Object.create(fn.prototype); let res = fn.apply(obj, args); return res instanceof Object ? res : obj; };
解释为什么最后的返回值需要判断res是否是对象实例
-
构造函数返回对象:
-
如果构造函数显式地返回了一个对象,JavaScript 会认为你想要使用这个对象作为
new表达式的结果。这种行为允许构造函数返回一个预先创建的对象,而不是默认创建的新对象。
-
-
构造函数不返回对象:
-
如果构造函数没有返回对象(或者返回了原始值),JavaScript 会忽略这个返回值,并返回默认创建的新对象
obj。这是大多数情况下new操作符的行为。
-
new操作可能会对null,undefined等非引用对象进行操作,所以需要判断类型
9.dfs bfs

浙公网安备 33010602011771号