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 洗牌算法通常从后向前遍历数组,原因如下:

  1. 随机索引的范围

    • 在每次迭代中,随机索引的范围是 [0, i],其中 i 是当前索引。

    • 这样可以确保每个元素都有机会被交换到任何位置。

  2. 概率均匀

    • 从后向前遍历时,每个元素被交换到当前位置的概率是均匀的。

    • 例如,最后一个元素 arr[n-1] 可以被交换到任何位置(包括它自己的位置)。

    • 这样可以保证每个元素被选中的概率一致。

  3. 逻辑简单

    • 从后向前遍历的逻辑更简单,代码更容易理解和实现。


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

 

posted @ 2025-01-13 17:31  恣肆zisi  阅读(19)  评论(0)    收藏  举报