《手写代码》前端面试题

js限制并发数量

const limitedRequest = (requestList, maxConcurrency) => {
    // 运行池
    const pool = new Set();
  
    // 等待队列
    const waitQueue = [];
  
    // 结果数组,用于收集每个请求的结果
    const results = [];
  
    // 执行单个请求的函数
    const executeRequest = (reqFn) => {
      return new Promise((resolve, reject) => {
        reqFn()
          .then(res => {
            results.push(res); // 将请求结果存入结果数组
            resolve(res);
          })
          .catch(err => reject(err))
          .finally(() => {
            // 请求完成后,从运行池中删除该请求
            pool.delete(reqFn);
            // 尝试从等待队列中取出下一个请求执行
            const next = waitQueue.shift();
            if (next) {
              pool.add(next);
              next();
            }
          });
      });
    };
  
    // 对每个请求进行处理
    const requestPromises = requestList.map(async (reqFn) => {
      // 包装请求函数,加入到等待队列或运行池中
      const wrappedReqFn = () => executeRequest(reqFn);
  
      // 判断运行池是否已满
      const isFull = pool.size >= maxConcurrency;
  
      if (isFull) {
        // 如果运行池已满,则将请求放入等待队列
        waitQueue.push(wrappedReqFn);
      } else {
        // 如果运行池未满,则直接加入运行池并执行该请求
        pool.add(wrappedReqFn);
        wrappedReqFn();
      }
    });
  
    // 等待所有请求执行完毕,然后返回结果数组
    return Promise.all(requestPromises)
      .then(() => results); // 返回执行完成后的结果数组
  }; 

输入一个字符串,遇到方括号则对方括号内的字符串重复n次,n是方括号前面的数字(1 <= n <= 100),如果没有数字则为1次,可能存在嵌套 const str1 = 'a2[b]a2[b2[c]]' // abbabccbcc const str2 = '2[3[c]]a2a' // cccccca2a const str3 = '[abc][d]3[e2]4' // abcde2e2e24

function decodeString(s) {
    let stack = []; // 用于存储解码过程中的字符和数字
    let currentNum = 0; // 当前数字,用于记录重复次数
    let currentStr = ''; // 当前字符串,用于构建解码后的结果

    for (let char of s) {
        if (char >= '0' && char <= '9') {
            // 如果是数字字符,更新当前数字
            currentNum = currentNum * 10 + parseInt(char, 10);
        } else if (char === '[') {
            // 如果是 '[',将当前字符串和当前数字入栈,并重置当前字符串和当前数字
            stack.push(currentStr);
            stack.push(currentNum);
            currentStr = '';
            currentNum = 0;
        } else if (char === ']') {
            // 如果是 ']',则进行解码操作
            let num = stack.pop(); // 弹出之前存储的重复次数
            let prevStr = stack.pop(); // 弹出之前存储的字符串
            currentStr = prevStr + currentStr.repeat(num); // 构建解码后的字符串
        } else {
            // 如果是普通字符,直接加入当前字符串
            currentStr += char;
        }
    }

    return currentStr; // 返回最终解码后的字符串
}

const str1 = 'a2[b]a2[b2[c]]'; // abbabccbcc
const str2 = '2[3[c]]a2a'; // cccccca2a
const str3 = '[abc][d]3[e2]4'; // abcde2e2e24

console.log(decodeString(str1)); // 输出:abbabccbcc
console.log(decodeString(str2)); // 输出:ccccccca2a
console.log(decodeString(str3)); // 输出:abcde2e2e24
  • stack 栈用于辅助解码过程,存储了解码过程中的字符串和重复次数。
  • currentNum 用于记录当前的重复次数,当遇到数字字符时更新。
  • currentStr 用于构建当前的字符串,当遇到普通字符时追加到 currentStr
  • 在遇到 [ 时,将 currentStr 和 currentNum 压入栈中,同时重置它们。
  • 在遇到 ] 时,从栈中弹出之前存储的 prevStr 和 num,将 currentStr 重复 num 次后拼接到 prevStr 上,得到解码后的结果。
  • 最终返回 currentStr,即为解码后的字符串。

js实现 ['a', 'b', 'c', 'd'] => {a: {b: {c: {d: null}}}}

function arrayToNestedObject(arr) {
  return arr.reduceRight((acc, curr) => {
    return {[curr]: acc};
  }, null);
}

const result = arrayToNestedObject(['a', 'b', 'c', 'd']);
console.log(result); // 输出 { a: { b: { c: { d: null } } } }

使用了 reduceRight 方法来从数组中的最后一个元素开始,逐步向内层嵌套构建对象,最终得到了您想要的嵌套对象结构。

寻找最长无重复子串长度

function longestSubstring(s) {
  const n = s.length;
  let maxLength = 0;
  let left = 0;
  const charSet = new Set();

  for (let right = 0; right < n; right++) {
    const currentChar = s[right];

    // 如果当前字符在集合中,移动左边界直到没有重复字符
    while (charSet.has(currentChar)) {
      charSet.delete(s[left]);
      left++;
    }

    // 将当前字符加入集合
    charSet.add(currentChar);

    // 更新最长无重复子串的长度
    maxLength = Math.max(maxLength, right - left + 1);
  }

  return maxLength;
}

寻找最长无重复子串

function longestSubstring(s) {
  const n = s.length;
  let maxLength = 0;
  let left = 0;
  let result = '';
  const charSet = new Set();

  for (let right = 0; right < n; right++) {
    const currentChar = s[right];

    // 如果当前字符在集合中,移动左边界直到没有重复字符
    while (charSet.has(currentChar)) {
      charSet.delete(s[left]);
      left++;
    }

    // 将当前字符加入集合
    charSet.add(currentChar);

    // 更新最长无重复子串
    if (right - left + 1 > maxLength) {
      maxLength = right - left + 1;
      result = s.substring(left, right + 1);
    }
  }

  return result;
}

// 测试例子
console.log(longestSubstring("abcabcbb")); // 输出 "abc"
console.log(longestSubstring("bbbbb"));    // 输出 "b"
console.log(longestSubstring("pwwkew"));   // 输出 "wke"
console.log(longestSubstring(""));         // 输出 ""
console.log(longestSubstring("au"));       // 输出 "au"

计算x的n次方


function power(x, n) {
  // 处理负指数情况
  if (n < 0) {
    x = 1 / x;  // 将底数变为其倒数
    n = -n;     // 将指数变为其绝对值
  }
  
  let result = 1;  // 初始化结果为1,任何数的0次幂都是1

  // 计算幂的主循环
  while (n > 0) {
    if (n % 2 === 1) {  // 如果当前指数是奇数
      result *= x;      // 将当前底数乘到结果中
    }
    x *= x;             // 底数平方,相当于指数减半
    n = Math.floor(n / 2);  // 指数除以2,取整
  }

  return result;  // 返回最终结果
}

这个函数 power(x, n) 使用了二进制指数法来计算幂,其时间复杂度为 O(log n)

函数说明

  • 函数 power(x, n) 计算底数 x 的指数 n 次幂的结果。
  • 如果指数 n 是负数,首先将底数 x 变为其倒数,并将指数 n 变为其绝对值。
  • 使用了二进制指数法来优化计算:每次循环中,通过将底数平方和将指数除以2的方式,减少了计算的次数。
  • 最终返回计算得到的幂值 result

js写一个下划线驼峰格式互转

// 下划线转驼峰
function underlineToCamel(str) {
  // 初始化结果字符串
  let result = '';
  // 标记下一个字符是否应该转为大写
  let nextIsUpper = false;

  // 遍历字符串的每个字符
  for (let i = 0; i < str.length; i++) {
    // 如果当前字符是下划线,则标记下一个字符需要转为大写
    if (str[i] === '_') {
      nextIsUpper = true;
    } else {
      // 如果当前字符不是下划线
      // 如果下一个字符需要转为大写,则将当前字符转为大写
      // 否则保持不变
      result += nextIsUpper ? str[i].toUpperCase() : str[i];
      // 重置下一个字符的转换标记
      nextIsUpper = false;
    }
  }

  // 返回转换后的结果字符串
  return result;
}

// 驼峰转下划线
function camelToUnderline(str) {
  // 初始化结果字符串
  let result = '';

  // 遍历字符串的每个字符
  for (let i = 0; i < str.length; i++) {
    // 获取当前字符
    const char = str[i];
    // 判断当前字符是否为大写字母
    const isUpperCase = char !== char.toLowerCase();

    // 如果当前字符是大写字母且不是第一个字符,则在其前面添加下划线
    if (isUpperCase && i > 0) {
      result += '_';
    }

    // 将当前字符转换为小写并拼接到结果字符串中
    result += char.toLowerCase();
  }

  // 返回转换后的结果字符串
  return result;
}

// 示例
const underscoreStr = "my_variable_name";
const camelStr = "myVariableName";

console.log(underlineToCamel(underscoreStr)); // 输出:myVariableName
console.log(camelToUnderline(camelStr)); // 输出:my_variable_name

二分查找

非递归

       function search(arr,key) {
         let min = 0
         let max = arr.length-1
         while (min<=max) {
           let  mid = parseInt((min+max)/2)
           if(arr[mid]==key){
             return mid
           }else if(arr[mid]>key){
             max = mid-1
           }else if(arr[mid]<key){
             min = mid+1
           }else{
             return -1
           }           
         }
       }

递归

       function search(min,max,key,arr) {
         let mid = parseInt((min+max)/2)
         if(arr[mid]=key){
           returnn mid
         }else if(arr[mid]>key){
           max = mid-1
           search(min,max,key,arr)
         }else if(arr[mid]<key){
           min = mid+1
           search(min,max,key,arr)
         }else{
           return -1
         }
       }

时间复杂度

总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数。
最差的情况就是 n/2^k=1, 即区间大小为1, 所以 k=log2n, 时间复杂度为 O(logn)

call

Function.prototype.myCall = function(context) {
 context = context||window
 context.fn = this
 let args = [...arguments].splice(1)
 let result = context.fn(...args)
 delete context.fn
 return result 
}

apply

Function.prototype.myApply = function (context) {
  context = context||window
  context.fn = this
  let args = arguments[1]
  let result
  if(args){
    result = context.fn(..args)
  }else{
    result = context.fn()
  }
  delete context.fn
  return result
}

bind

Function.prototype.myBind = function (context) {
  if(typeof this!=='function'){
    return TypeError('error')
  }
  let _this = this
  let args = [...arguments].splice(1)
  return function F() {
    if(this instanceof F){
      return _this.apply(this,args.concat([...arguments]))
    }
    return _this.apply(context,args.concat([...arguments]))
  }
}

扁平化对象

var entry = {
  a: {
    b: {
      c: {
        dd: 'abcdd'
      }
    },
    d: {
      xx: 'adxx'
    },
    e: 'ae'
  }
};

function flattenObject(obj, parentKey = '') {
  let flattened = {};
  for (let key in obj) {
    if (typeof obj[key] === 'object') {
      let nestedKeys = flattenObject(obj[key], `${parentKey}${key}.`);
      flattened = { ...flattened, ...nestedKeys };
    } else {
      flattened[`${parentKey}${key}`] = obj[key];
    }
  }
  return flattened;
}

const flattenedEntry = flattenObject(entry);
console.log(flattenedEntry);

{
  'a.b.c.dd': 'abcdd',
  'a.d.xx': 'adxx',
  'a.e': 'ae'
}

扁平化数组(递归)

Array.prototype.flat2 =function(){
  let reslut=[]
  for(let i=0;i<this.length;i++){
      if(Array.isArray(this[i])){
          reslut=reslut.concat(this[i].flat2())
      }else{
          reslut.push(this[i])
      }
  } 
  return reslut
}
let arr=[1,[2,[3,[4],[5],[6]]]]
arr.flat2()

扁平化数组(迭代)

    function *flat(arr) {
       for (const item of arr) {
           if (Array.isArray(item)) {
               yield *flat(item);
           } else {
               yield item;
           }
       }
   }
   const result = [...flat([1,2,[3,[4]]])]
   console.log(result) //  [1, 2, 3, 4]

定义一个生成器函数:迭代当前数组,如果值也是数组则生成扁平化的值,否则直接生成值。然后在我们的扁平化函数里调用这个生成器函数得到我们的一维数组。

这里有两点需要注意:

  1. 嵌套 yield 需要再加一个星号,这被称为生成器委托。
  2. 不能使用 forEach 代替 for...of 但可以用 for 循环,因为 for 循环和for...of 可以中断迭代去执行 yield,forEach 不行

归并排序

function mergeSort(arr) {
    // 基本情况:数组长度为1时已经是有序的
    if (arr.length <= 1) {
        return arr;  // 如果数组长度小于等于1,直接返回该数组,因为长度为1的数组已经是有序的
    }
 
    // 二分数组并递归排序
    var middle = Math.floor(arr.length / 2),  // 找到数组的中间位置
        left = arr.slice(0, middle),  // 将数组分成左右两部分
        right = arr.slice(middle);
 
    return merge(mergeSort(left), mergeSort(right));  // 递归调用 mergeSort 对左右两部分数组进行排序,并调用 merge 函数合并
}
 
function merge(left, right) {
    // 合并两个已排序的数组
    var result = [];  // 用于存放合并后的结果
 
    while (left.length && right.length) {
        if (left[0] <= right[0]) {  // 比较左右两个数组的第一个元素,将较小的元素推入 result 数组
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }
 
    // 将剩余的元素直接加到结果数组中
    while (left.length)
        result.push(left.shift());  // 当其中一个数组为空时,将另一个数组中剩余的元素直接加入到 result 数组中
 
    while (right.length)
        result.push(right.shift());  // 当其中一个数组为空时,将另一个数组中剩余的元素直接加入到 result 数组中
 
    return result;  // 返回合并后的有序数组 result
}
 
// 使用示例
var unsortedArray = [4, 3, 2, 10, 12, 1, 5, 6];
var sortedArray = mergeSort(unsortedArray);  // 调用 mergeSort 函数对未排序数组进行排序
console.log(sortedArray); // 输出: [1, 2, 3, 4, 5, 6, 10, 12] // 打印排序后的数组

冒泡排序

function bubbleSort(arr) {
  let len = arr.length
  for (let i = 0; i < len-1; i++) {
   for (let j = 0; j < len-i-1; j++) {
     if(arr[j]>arr[j+1])
     let temp = arr[j]
     arr[j] = arr[j+1]
     arr[j+1] = temp
   }    
  }
}

快速排序

function quickSort(arr) {
  let len = arr.length
  let mid = Math.floor(len/2)
  let midValue = arr.splice(mid,1)[0]
  let leftArr=[]
  let rigthArr=[]
  for (let i = 0; i < len; i++) {
    if(arr[i]>midValue){
      rigthArr.push(arr[i])
    }else{
      leftArr.push(arr[i])
    }   
  }
  return quickSort(leftArr).concat([midValue,quickSort(rigthArr)])
}

排序算法的时间复杂度

数组去重

let arr=[1,'1',1,2,3,4,4,5]
[...new Set(arr)]
//也可用Array.from(new Set(arr) 


let newArr=[]
for(let i=0,i>arr.lengeh,i++){
  if(newArr.indexOf(arr[i])==-1){
    newArr.push(arr[i]
}

柯里化函数

// curry函数接受一个函数作为参数
function curry(func) {
  // 返回一个内部函数curried,该函数使用...args语法接收任意数量的参数
  return function curried(...args) {
    // 如果传入的参数个数大于或等于func函数的参数个数
    if (args.length >= func.length) {
      // 则调用func函数并传入这些参数,并返回结果
      return func(...args);
    } else {
      // 否则返回一个新的函数,该函数接收更多的参数,并将它们与之前的参数合并后递归调用curried
      return function(...moreArgs) {
        return curried(...args, ...moreArgs);
      };
    }
  };
}

function add(...args) {
  return args.reduce((acc, val) => acc + val, 0);
}

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)(4)(5)); // 输出 15
console.log(curriedAdd(1, 2, 3, 4, 5)); // 输出 15
console.log(curriedAdd(1, 2)(3, 4)(5)); // 输出 15
function currying(fn) {
  let args_arr = []
  max_length = fn.length
  let closure = function (...args) {
    // 先把参数加进去
    args_arr = args_arr.concat(args)
    // 如果参数没满,返回闭包等待下一次调用
    if (args_arr.length < max_length) return closure
    // 传递完成,执行
    return fn(...args_arr)
  }
  return closure
}
function add(x, y, z) {
  return x + y + z
}
curriedAdd = currying(add)
console.log(curriedAdd(1, 2)(3))

防抖

  function debounce(fn) {
            let timeout = null; // 创建一个标记用来存放定时器的返回值
            return function(...args) {
                clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
                timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
                    fn(...args);
                }, 500);
            };
        }

节流

function throttle(fn) {
    let canRun = true; // 通过闭包保存一个标记
    return function (...args) {
        if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
        canRun = false; // 立即设置为false
        setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
            fn(...args);
            // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
            canRun = true;
        }, 500);
    };
}

promise.all

 Promise.all= function (promiseList) {
    return new Promise((resolve,reject)=>{  // 返回一个promise实例
      if(!Array.isArray(promiseList)){  // 判断参数是否是数组
        return reject(new TypeError('error'))
      }
       // 数组为空,直接resolve
       if (promiseList.length == 0) {
         resolve(promiseList);
      }
      let cont=0 // 用于计数,当等于cont=len时就resolve
      let len=promiseList.length
      let values=new Array(len) //创建一个和传入数组长度相同的数组,用于存放结果
      for (let i = 0; i < len; i++) {
       Promise.resolve(promiseList[i]) //如果元素不是 Promise 对象,则使用 Promise.resolve 转成 Promise 对象
       .then((res)=>{
          cont++  //每次成功后+1
          values[i]=res //将成功后的值放入数组
          if(cont===len){ // 数据接收完成
            resolve(values) 
          }
       })
       .catch(err=>{ //只要有一个失败,抛出异常,状态变为 rejected
         reject(err)
       })
        
      }
    })
  }

Promise.race

Promise.race=function(promiseList){
 return new Promise((resolve,reject)=>{
   if(!Array.isArray(promiseList)){
     return reject (new TypeError('error'))
   }
   promiseList.forEach(item => {
     Promise.resolve(item)
     .then(res=>{
        resolve(res)
     })
     .catch(err=>{
        reject(err)
     })
   });
 }) 
}

简单promise

function Promise(executor){ //executor执行器
    let self = this;
    self.status = 'pending'; //等待态
    self.value  = undefined; // 表示当前成功的值
    self.reason = undefined; // 表示是失败的值
    function resolve(value){ // 成功的方法
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(reason){ //失败的方法
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}
 
Promise.prototype.then = function(onFufiled,onRejected){
    let self = this;
    if(self.status === 'resolved'){
        onFufiled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}
module.exports = Promise;

promise实现,红色三秒打印一次、绿色两秒打印一次、黄色一秒打印一次

        function red() {
            console.log("red");
        }
        function green() {
            console.log("green");
        }
        function yellow() {
            console.log("yellow");
        }

        function light(time,name){
            return new Promise(resolve=>{
                setTimeout(()=>{
                        name()
                        resolve()
                    },time)
            })
        }
        
        function step(){
            Promise.resolve().then(()=>{
                return light(3000,red)
            }).then(()=>{
                return light(2000,green)
            }).then(()=>{
                return light(1000,yellow)
            }).then(()=>{
                // 黄色循环完毕再调用一次step,实现不断循环打印
                return step()
            })
        }
        step()

简单数据劫持

function definedRective(data,key,val){
                Object.defineProperty(data,key,{
                    get:function(){
                        return val;
                    },
                    set:function(newVal){
                        val = newVal;
                        update();
                    }
                })
            }

简单发布订阅

class EventEmitter {
  constructor() {
    // 初始化一个空对象用于存储事件名和对应的回调函数列表
    this.cache = {}
  }

  // 订阅事件的方法
  on(name, fn) {
    // 如果已经有其他回调函数订阅过相同的事件名,则将该回调函数添加到已存在的回调函数列表中
    if (this.cache[name]) {
      this.cache[name].push(fn)
    } else {
      // 否则,创建一个新的回调函数列表,并将该回调函数添加到列表中
      this.cache[name] = [fn]
    }
  }

  // 取消订阅事件的方法
  off(name, fn) {
    // 获取该事件名对应的回调函数列表
    const tasks = this.cache[name]
    if (tasks) {
      // 找到要取消订阅的回调函数在列表中的索引,并从列表中删除
      const index = tasks.findIndex((f) => f === fn || f.callback === fn)
      if (index >= 0) {
        tasks.splice(index, 1)
      }
    }
  }

  // 触发事件的方法
  emit(name, once = false) {
    // 获取该事件名对应的回调函数列表
    if (this.cache[name]) {
      // 创建副本,避免在执行回调函数时修改原始列表,因为可能会导致意外行为
      const tasks = this.cache[name].slice()
      // 依次执行每个回调函数
      for (let fn of tasks) {
        fn();
      }
      // 如果需要只触发一次,则在执行完所有回调函数后,将该事件名从缓存中删除
      if (once) {
        delete this.cache[name]
      }
    }
  }
}



// 测试
const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }
eventBus.on('task', task1)
eventBus.on('task', task2)

setTimeout(() => {
  eventBus.emit('task')
}, 1000)

发布者核心功能: 增加/删除订阅者 ,通知订阅者更新

观察者核心功能: 在发布者通知更新后,及时更新,绑定订阅者

简单深拷贝,递归

// 定义一个深拷贝函数  接收目标target参数
function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === 'object') {
    // 如果是一个数组的话
        if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
                // 递归克隆数组中的每一项
                result.push(deepClone(target[i]))
            }
         // 判断如果当前的值是null的话;直接赋值为null
        } else if(target===null) {
            result = null;
         // 判断如果当前的值是一个RegExp对象的话,直接赋值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
        result = target;
    }
     // 返回最终结果
    return result;
}

考虑循环引用、symbol类型的深拷贝

需要解决拷贝对象内的循环引用问题(使用数组记录拷贝过的对象,记录一个拷贝对象的数据结构,{source:原拷贝对象,target:拷贝后的对象})

/*
    实现深拷贝
    1。 判断循环引用
    2. 判断正则对象
    3. 判断日期对象
    4. 属性对象直接进行递归拷贝
    5. 考虑拷贝时不能丢失原本对象的原型继承关系
    6. 考虑拷贝时的属性修饰符
  */
function cloneDeep(value, map = new WeakMap()) {
  if (value.constructor === Date) {
    return new RegExp(value)
  }
  if (value.constructor === RegExp) {
    return new RegExp(value)
  }
  // 如果value是普通类型 直接返回
  if (typeof value !== 'object' || value === null) {
    return value
  }
  // 考虑对象的原型 获得原本对象的原型 创建一个新的对象继承这个对象的原型
  const prototype = Object.getPrototypeOf(value)
  // 考虑拷贝时不能 丢失对原有对象的属性描述符 
  const description = Object.getOwnPropertyDescriptors(value)
  // 创建新的空对象 同时继承原有对象原型 同时拥有对应的描述符
  const object = Object.create(prototype, description)
  // 遍历对象的属性 进行拷贝 Reflect.ownKeys 遍历获取自身的不可枚举以及key为Symbol的属性
  map.set(value, object)
  Reflect.ownKeys(value).forEach(key => {
    // key是普通类型
    if (typeof key !== object || key === null) {
      // 直接覆盖
      object[key] = value[key]
    } else {
      //  解决循环引用的关键是 每一个对象都给他存放在weakMap中 因为WeakMap是一个弱引用
      //  每次如果进入是对象 那么就把这个对象 优先存放在weakmap中 之后如果还有引用这个对象的地方 直接从weakmap中拿出来 而不需要再进行遍历造成爆栈
      //  同理,如果使用相同引用为了保证同一份引用地址的话 可以使用weakMap中直接拿出保证同一份引用
      //  这里判断之前是否存在相同的引用 如果存在相同的引用直接返回引用即可
      const mapValue = map.get(value)
      mapValue ? (object[key] = map.get(value)) : (object[key] = cloneDeep(value[key]))
    }
  })
  return object
}

考虑递归爆栈的深拷贝,迭代

function cloneForce(x) {
  //拷贝对象记录
  const uniqueList = []; 
 
  let root = {};
 
  // 循环数组
  const loopList = [{
    parent: root,
    key: undefined,
    data: x,
  }];
 
  while (loopList.length) {
    //深拷贝,元素出栈
    const node = loopList.pop();
    const parent = node.parent;
    const key = node.key;
    const data = node.data;
 
    let res = parent;
    if (typeof key !== 'undefined') {
      res = parent[key] = {};
    }
 
    // 判断数据是否存在
    let uniqueData = find(uniqueList, data);、
    //数据存在
    if (uniqueData) {
      parent[key] = uniqueData.target;
      break; // 中断本次循环
    }
 
    //数据不存在,将其放入数组
    uniqueList.push({
      source: data,
      target: res,
    });
 
    for (let k in data) {
      if (data.hasOwnProperty(k)) {
        if (typeof data[k] === 'object') {
          // 下一次循环
          loopList.push({
            parent: res,
            key: k,
            data: data[k],
          });
        } else {
          res[k] = data[k];
        }
      }
    }
  }
 
  return root;
}
 
function find(arr, item) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].source === item) {
      return arr[i];
    }
  }
 
  return null;
}

给ul下的所有li绑定点击事件,弹出下标和内容

  var itemli = document.getElementsByTagName("li");
 
   for(var i = 0; i<itemli.length; i++){
 
    itemli[i].index = i; //给每个li定义一个属性索引值
 
    itemli[i].onclick = function(){
 
      alert(this.index+this.innerHTML); 
 
    }
 
   }

 var ul = document.querySelector("ul");
    ulItem.onclick = function (e) {
        e = e || window.event; //这一行及下一行是为兼容IE8及以下版本
        var target = e.target || e.srcElement;
        if (target.tagName.toLowerCase() === "li") {
            var li = this.querySelectorAll("li");
            index = Array.prototype.indexOf.call(li, target);
            alert(target.innerHTML + index);
        }
    }

杨辉三角

 
      function compute(m, n) {
        if (n == 0) {
          return 1; //每行第一个数为1
        } else if (m == n) {
          return 1; //最后一个数为1
        } else {
          //其余都是相加而来
          return compute(m - 1, n - 1) + compute(m - 1, n);
        }
      }
      function yanghui(n) {
        //杨辉三角,N为行数
        for (var i = 0; i < n; i++) {
          //一共N行
          for (var j = 0; j <= i; j++) {
            //每行数字的个数即为行号、例如第1行1个数、第2行2个数
            document.write(compute(i, j) + " ");
          }
          document.write("<br>");
        }
      }
      yanghui(15)
    
 

实现接口失败重新请求,并限制并发请求Retry

/**
 * 重试指定的异步函数,按照给定的重试次数和延迟时间。
 * 
 * @param {Function} fn - 需要重试的异步函数。该函数应返回一个Promise。
 * @param {number} times - 重试的次数。
 * @param {number} delay - 每次重试之间的延迟时间(毫秒)。
 * @return {Promise} - 一个Promise,如果函数最终成功则resolve,否则在所有重试失败后reject。
 */
const retry = function(fn, times, delay) {
  // 初始化一个错误变量来存储任何遇到的错误。
  var err = null;

  // 返回一个新的Promise,它将管理重试逻辑。
  return new Promise(function(resolve, reject) {

    // 定义尝试执行传入函数 `fn` 的函数。
    var attempt = function() {
      // 调用传入的函数 `fn`,期望它返回一个Promise。
      fn()
        .then(resolve)  // 如果 `fn` 成功(Promise resolved),则解决外部Promise。
        .catch(function(err) {  // 如果 `fn` 失败(Promise rejected),处理错误。
          console.log(`第 ${times} 次尝试失败`);

          // 检查是否还有剩余的重试次数。
          if (0 == times) {
            // 如果没有剩余的重试次数,使用遇到的错误拒绝外部Promise。
            reject(err);
          } else {
            // 如果还有剩余的重试次数,递减重试计数器。
            times--;

            // 设置一个定时器,在指定的延迟后再次尝试。
            setTimeout(function() {
              attempt();  // 再次调用尝试函数。
            }, delay);
          }
        });
    };

    // 立即开始第一次尝试。
    attempt();
  });
};

instanceof

function instanceOf(left, right) {
    let leftValue = left.__proto__;
    let rightValue = right.prototype;
    while(leftValue ){ 
        if(leftValue === rightValue ){
        return true
        }else{
          leftValue = leftValue.__proto__
        }         
   }
   return false   
}

闭包解决settimeout打印同一个值的问题

for (var i = 0; i < 5; i++) {
  (function (i) {
      setTimeout(function() {
     console.log(i);
  }, 1000);
  })(i)
}

交集、并集、补集、差集

//交集
var c = a.filter(function(v){ return b.indexOf(v) > -1 })
//差集
var d = a.filter(function(v){ return b.indexOf(v) == -1 })
//补集
var e = a.filter(function(v){ return !(b.indexOf(v) > -1) })
 .concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}))
//并集
var f = a.concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}));

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值