面试总结之常见js算法

135 阅读8分钟

1. 数组去重

// 方法一 局限性:不支持对象方法
function unique(ary){
  return Array.from(new Set(ary))
}

// 方法一
function unique(ary){
  let newAry = [];
  for (let i = 0; i < ary.length; i++) {
    if(newAry.indexOf(ary[i]) === -1){
      newAry.push(ary[i])
    }
  }
  return newAry;
}

// 方法三
 function unique(ary){
  let newAry = [];
  newAry = ary.filter((item,index,ary) => {
    return ary.indexOf(item) === index
  })
  return newAry;
}

2. 找到某个字符串每个字符出现的次数

function getNeedObj(str){
  let arr = str.split("");
  let needArr = [];
  let needArrPlan = [];
  for (let i = 0; i < arr.length; i++){
    if (needArrPlan.indexOf(arr[i]) !== -1) {
      for (let j = 0; j < needArr.length; j++) {
        if(needArr[j].name === arr[i]){
          needArr[j].num += 1;
        }
      }
    } else {
      needArrPlan.push(arr[i])
      needArr.push({
        name: arr[i],
        num: 1
      })
    }
  }
  needArr.sort(function(a,b){
    return a.num-b.num
  })
  return needArr
}

3. 数组扁平化

let arr=[1,2,[5,6,[7,[9,10]]],3,[4]];
// 方法一
function flat(arr) {
  // 以下代码还可以简化,不过为了可读性,还是....
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  }, []);
}

// 方法二
function flat(arr){
  let res = [];
  while(arr.some(item => Array.isArray(item))){
    arr = [].concat(...arr);
  }
  return arr;
}

// 方法三
function flat(arr){
  const res = arr.map(item => {
    if(Array.isArray(item)){
      return flat(item)
    }
    return [item]
  });
  return [].concat(...res);
}

4. 递归——list to tree

// 数组转为tree
const oldData = [
  {id:1,name:'boss',parentId:0},
  {id:2,name:'lily',parentId:1},
  {id:3,name:'jack',parentId:1},
  {id:4,name:'john',parentId:2},
  {id:5,name:'boss2',parentId:0},
]
function listToTree(oldArr){
  oldArr.forEach(element => {
    let parentId = element.parentId;
    if(parentId !== 0){
      oldArr.forEach(ele => {
        if(ele.id == parentId){ //当内层循环的ID==外层循环的parendId时,(说明有children),需要往该内层id里建个children并push对应的数组;
          if(!ele.children){
            ele.children = [];
          }
          ele.children.push(element);
        }
      });
    }
  });
  console.log(oldArr)  //此时的数组是在原基础上补充了children;
  oldArr = oldArr.filter(ele => ele.parentId === 0); //这一步是过滤,按树展开,将多余的数组剔除;
  console.log(oldArr)
  return oldArr;
}
const res = listToTree(oldData);
console.log(res);

5. 深拷贝和浅拷贝

// 1,对象深拷贝
// JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串
// JSON.parse() 方法将数据转换为 JavaScript 对象
JSON.parse(JSON.stringify())

// 2,循环和递归
function deepClone(obj, map = new WeakMap()) {
  if (obj === null || typeof obj !== "object") return obj;

  if (map.has(obj)) return map.get(obj); // 处理循环引用

  let clone = Array.isArray(obj) ? [] : {}; 
  map.set(obj, clone); // 存储已拷贝对象

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], map);
    }
  }
  return clone;
}

6. 防抖和节流

  • 防抖: 在事件被触发后,等待一定的时间再执行事件处理函数,如果在等待时间内事件再次被触发,则重新计时。
  • 节流: 在规定的时间间隔内,只允许事件触发一次,无论期间事件被触发多少次都不响应,只有到达时间间隔后才能触发下一次事件
// 防抖
 function be(fn, delay) {
  let timer = null
  return function(...args) {
    const context = this;
    if (timer) clearTimeout(timer);

    timer = setTimeout(() => {
      fn.apply(context, args)
    }, delay)
  }
}

// 节流
function th(fn, delay) {
  let timer = null

  return function(...args) {
    const context = this;

    if (timer) return

    timer = setTimeout(() => {
      fn.apply(context, args)

      timer = null
    }, delay)
  }
}

7. 手写call,apply ,bind函数

// 手写call
Function.prototype.myCall = function(){
  // 获取原始this
  const oldSelf = this;
  // 获取接受的参数,将伪数组变为数组
  let getArr = [];
  for(let i=0;i<arguments.length;i++){
    getArr.push(arguments[i])
  }
  // 获取新的this,和新的参数
  const newSelf = getArr.shift();
  newSelf.fn = oldSelf;
  const res = newSelf.fn(...getArr);
  delete newSelf.fn;
  return res;
}
function fn(a, b){
  console.log(a,b,this)
  return 123
};

const res = fn.myCall({name:"lx"},"dhusfh","hfdhgf");
console.log(res)

// 手写bind
Function.prototype.myBind = function(){
  // 拿到原始this
  const oldSelf = this;
  // 拿到传进来的参数,将伪数组变为数组
  let argus = [];
  for (let i=0;i<arguments.length;i++) {
    argus.push(arguments[i]);
  }
  // 拿到第一个参数,且参数数组同时把第一个数给移除
  const newSelf = argus.shift();
  return function(...argus2){
    console.log(new.target)
    if (new.target === undefined){
      //执行时改变原始this为newSelf
      newSelf.fn = oldSelf;
      const res = newSelf.fn(...argus);
      delete newSelf.fn;
      return res;
    } else {
      // new调用了bind
      return oldSelf(...argus,...argus2)
    }      
  }
}
function fn(a,b){
  console.log(a,b,this)
  return 123
}
const p = fn.myBind({name:"lx"},["aaa","bbb"],'ccc')
p()
new p()

8. 图片懒加载

const images = document.querySelector("img");
const callback = entries => {
    entries.forEach(item => {
        if(item.isIntersecting){
            const image = item.target;
            const data_src = image.getAttribute('data-src');
            image.setAttribute('src',data_src);
            observer.unobserve(image);
        }
    });
}
const observer = new IntersectionObserver(callback);
images.forEach(item => {
    observer.observe(item);
})

9. 柯里化函数

柯里化:返回一个从接受多个参数,变为只接受单一参数的函数 -》 柯里化之后的函数不会立即执行,会把传入的参数通过闭包保存起来,直到检测到最后一个参数时,之前的参数都会被一次性用于求值

function curry(fn) {
  console.log(fn)
  return function curried(...args) {
    console.log(111, args, fn)
    // 如果传入的参数数量足够,调用原函数
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      // 否则,返回一个新的函数,继续接受参数
      return function(...moreArgs) {
        console.log(222, moreArgs)
        return curried.apply(this, args.concat(moreArgs));
      };
    }
  };
}
function add(a,b,c){
    return a + b + c
}

const curryAdd = curry(add);
console.log(curryAdd)
const res = curryAdd(10)(20)(30);
console.log(res)

10. 手写一个promise

const p = new Promise((resolve,reject) => {
  resolve(123)
})
p.then(res => {
  console.log(res)
})

class MyPromise {
  static PEDING = '待定';
  static REJECT = '拒绝';
  static FULFILLED = '成功';
  constructor (func){
    this.status = MyPromise.PEDING;
    this.result = null;
    this.resolveCallbacks = [];
    this.rejectCallbacks = [];
    try{
      func(this.resolve.bind(this),this.reject.bind(this));
    }catch(error){
      this.reject(error)
    }     
  }
  resolve(result){
    setTimeout(()=>{
      if(this.status === MyPromise.PEDING){
        this.status = MyPromise.FULFILLED;
        this.result = result;
        this.resolveCallbacks.forEach(callback => {
          callback(result);
        })
      }
    })
  }
  reject(result){
    setTimeout(() => {
      if(this.status === MyPromise.PEDING){
        this.status = MyPromise.REJECT;
        this.result = result;
        this.rejectCallbacks.forEach(callback => {
          callback(result)
        })
      }
    })
  }
  then(onFULFILLED,onREJECTED){
    return new MyPromise((resolve,reject) => {
      onFULFILLED = typeof onFULFILLED === 'Function' ? onFULFILLED : () => {};
      onREJECTED = typeof onREJECTED === 'Function' ? onFULFILLED : () => {};
      if(this.status === MyPromise.PEDING){
        this.resolveCallbacks.push(onFULFILLED);
        this.rejectCallbacks.push(onREJECTED);
      }
      if(this.status === MyPromise.FULFILLED){
        setTimeout(() => {
          onFULFILLED(this.result);
        })
      }
      if(this.status === MyPromise.REJECT){
        setTimeout(() => {
          onREJECTED(this.result);
        })
      }
    })
  }
}
let myP = new MyPromise((resolve,reject) => {
  resolve("123");
})
myP.then(result => {
  console.log(result);
})

11. 手写一个promise.all

// 基础版本:理解核心原理
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    // 参数校验:如果不是可迭代对象,直接reject
    if (!promises || typeof promises[Symbol.iterator] !== 'function') {
      return reject(new TypeError('Argument is not iterable'))
    }
    
    const results = [] // 存储所有Promise的结果
    let count = 0      // 记录已完成的Promise数量
    
    // 处理空数组的情况
    if (promises.length === 0) {
      return resolve(results)
    }
    
    promises.forEach((promise, index) => {
      // 统一处理:将非Promise值转换为Promise
      Promise.resolve(promise).then(
        value => {
          // 保持结果顺序:按索引存储
          results[index] = value
          count++
          
          // 所有Promise都完成时resolve
          if (count === promises.length) {
            resolve(results)
          }
        },
        // 任何一个Promise reject,整个all就reject
        reject
      )
    })
  })
}

12. 并发限制版本

// Promise.all + 并发限制
Promise.allWithLimit = function(promises, limit) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Promises must be an array'))
    }
    
    if (promises.length === 0) {
      return resolve([])
    }
    
    const results = new Array(promises.length)
    let completedCount = 0
    let currentIndex = 0
    let isRejected = false
    
    // 执行下一个Promise
    const runNext = () => {
      if (currentIndex >= promises.length || isRejected) {
        return
      }
      
      const index = currentIndex++
      Promise.resolve(promises[index]).then(
        value => {
          if (isRejected) return
          
          results[index] = value
          completedCount++
          
          if (completedCount === promises.length) {
            resolve(results)
          } else {
            runNext() // 继续执行下一个
          }
        },
        reason => {
          if (!isRejected) {
            isRejected = true
            reject(reason)
          }
        }
      )
    }
    
    // 启动初始的并发任务
    const initialTasks = Math.min(limit, promises.length)
    for (let i = 0; i < initialTasks; i++) {
      runNext()
    }
  })
}

13. 宏任务/微任务

// 写出下面代码运行结果
// 题目1
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function () {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end');

// 题目2
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    //async2做出如下更改:
    new Promise(function (resolve) {
        console.log('promise1');
        resolve();
    }).then(function () {
        console.log('promise2');
    });
}
console.log('script start');
setTimeout(function () {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise3');
    resolve();
}).then(function () {
    console.log('promise4');
});
console.log('script end');
 
// 题目3
async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function () {
        console.log('setTimeout1')
    }, 0)
}
async function async2() {
    //更改如下:
    setTimeout(function () {
        console.log('setTimeout2')
    }, 0)
}
console.log('script start');
setTimeout(function () {
    console.log('setTimeout3');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end');

// 题目4
async function a1() {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2() {
    console.log('a2')
}
console.log('script start')
setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
    console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})
promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})

14. 三数之和,给定一个数组nums,判断nums中是否存在三个元素a,b,c,是的a+b+c=target,找到满足条件且不重复的三元组合

两数之和

function toSum(arr, target) {
  const indexNums = arr.map((num, index) => ({num, index}))
  let left = 0;
  let right = arr.length - 1;
  indexNums.sort((a, b) => a.num - b.num);
  console.log(indexNums)
  while(left < right) {
    const sum = indexNums[left].num + indexNums[right].num;
    if (sum === target) {
      return [indexNums[left].num, indexNums[right].num]
    } else if (sum < target) {
      left++
    } else {
      right--
    }
  }

  return []
}

三数之和:排序 + 双指针(最优解)

function threeSum(nums, target) {
    const result = [];
    const n = nums.length;
    
    // 1. 先排序(方便去重和双指针移动)
    nums.sort((a, b) => a - b);
    
    // 2. 遍历数组,固定第一个数
    for (let i = 0; i < n - 2; i++) {
        // 跳过重复的第一个数
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        
        let left = i + 1;
        let right = n - 1;
        
        // 3. 双指针寻找另外两个数
        while (left < right) {
            const sum = nums[i] + nums[left] + nums[right];
            
            if (sum === target) {
                result.push([nums[i], nums[left], nums[right]]);
                
                // 跳过重复的第二个数
                while (left < right && nums[left] === nums[left + 1]) left++;
                // 跳过重复的第三个数
                while (left < right && nums[right] === nums[right - 1]) right--;
                
                // 移动指针继续寻找
                left++;
                right--;
            } else if (sum < target) {
                left++; // 和太小,左指针右移
            } else {
                right--; // 和太大,右指针左移
            }
        }
    }
    
    return result;
}

15. 给定一个字符串,找出其中无重复自负的最长子串的长度

滑动窗口 + Set(最优解)

function lengthOfLongestSubstring(s) {
  const set = new Set();
  let maxLength = 0;
  let left = 0; // 左指针
  let right = 0; // 右指针
  
  while (right < s.length) {
    const char = s[right];
    
    // 如果字符不在Set中,加入Set并更新最大长度
    if (!set.has(char)) {
      set.add(char);
      maxLength = Math.max(maxLength, right - left + 1);
      right++;
    } else {
      // 如果字符已存在,移动左指针直到删除重复字符
      set.delete(s[left]);
      left++;
    }
  }
  
  return maxLength;
}

// 时间复杂度:O(n)
// 空间复杂度:O(min(n, m)),m是字符集大小

变种1:找到最长子串本身

function longestSubstring(s) {
  const charIndex = new Map();
  let maxLength = 0;
  let left = 0;
  let start = 0; // 最长子串的起始位置
  
  for (let right = 0; right < s.length; right++) {
    const char = s[right];
    
    if (charIndex.has(char) && charIndex.get(char) >= left) {
      left = charIndex.get(char) + 1;
    }
    
    charIndex.set(char, right);
    
    const currentLength = right - left + 1;
    if (currentLength > maxLength) {
      maxLength = currentLength;
      start = left;
    }
  }
  
  return s.substring(start, start + maxLength);
}

20. vue的响应式

vue2的响应式

// vue响应式原理
let data = {
  name: "lianxiao",
  age: 18,
  friend: {
    origin: "lx",
  },
  colors: ["red","origin","balana"]
}
// 处理数组
const oldArrayProto = Array.prototype; // 拿到Array原型
const newArrayProto = Object.create(oldArrayProto); // 把现有对象的属性,挂到新建对象的原型上,新建对象为空对象
["push","unshift","pop","splice"].forEach(item => {
  newArrayProto[item] = function(){
    console.log("视图更新");
    oldArrayProto[item].call(this,...arguments)
  }
})
// 绑定响应数据
observer(data);
function observer(data){
  if (typeof data !== "object" || data === null){
    return data;
  }
  if (Array.isArray(data)){
    data.__proto__ = newArrayProto; // 数组原型替换
  }
  for(let key in data){
    console.log(key,data[key])
    defineReactive(data,key,data[key])
  }
}
function defineReactive(data,key,value){
  observer(value);
  Object.defineProperty(data,key,{
    get(){
      return value
    },
    set(newValue){
      observer(newValue);
      if (newValue !== value) {
        value = newValue;
        console.log("视图更新")
      }
    }
  })
}

vue3的响应式

// 创建响应式
function reactive(target = {}) {
  if (typeof target !== 'object' || target == null) {
    // 不是对象或数组,则返回
    return target
  }

  // 代理配置
  const proxyConf = {
    get(target,key,receiver){
      // 只处理本身(非原型的)属性
      const ownKeys = Reflect.ownKeys(target);
      if(ownKeys.includes(key)){
        console.log('get',key) // 监听
      }

      const result = Reflect.get(target,key,receiver);
      return reactive(result) // 返回结果
    },
    set(target,key,val,receiver){
      // 重复的数据,不处理
      if(val===target[key]){
        return true
      }
      const ownKeys = Reflect.ownKeys(target);
      if(ownKeys.includes(key)){
        console.log('已有的key',key) // 监听
      }else{
        console.log('新增的key',key) // 监听
      }
      const result = Reflect.set(target,key,val,receiver);
      return result // 是否设置成功
    },
    deleteProperty(target,key){
      const result = Reflect.deleteProperty(target,key);
      return result // 是否删除成功
    }
  };

  // 生成代理对象
  const observed = new Proxy(target,proxyConf);
  return observed;
}