likes
comments
collection
share

【跳槽必备】2023常用手写面试题知识点总结

作者站长头像
站长
· 阅读数 15

前言

想想几年前一个月随便投出去一天至少3面试一个月排满面试的场景对于现在已经灭绝了,基本只有外包和驻场有回应,今年很多人都只能猥琐发育,市场上不仅岗位变少,money也少了很多。目前环境的不景气,面试难度也增加了很多,在这样的大环境下也只能不断提升提升自己,时刻警惕被优化的风险。最近刚好复习到手写面试题的基础分享分享给大家伙。

手写实现节流函数

function throttle(func, delay) {
  let timeoutId; // 用于存储定时器的ID
  let lastExecutedTime = 0; // 上次执行的时间戳

  // 返回一个新的函数作为节流函数
  return function (...args) {
    const currentTime = Date.now(); // 当前时间戳

    // 计算距离上次执行的时间间隔
    const elapsedTime = currentTime - lastExecutedTime;

    // 如果距离上次执行的时间间隔大于等于延迟时间,则执行函数
    if (elapsedTime >= delay) {
      func.apply(this, args);
      lastExecutedTime = currentTime; // 更新上次执行的时间戳
    } else {
      // 如果距离上次执行的时间间隔小于延迟时间,则设置定时器延迟执行函数
      clearTimeout(timeoutId);

      timeoutId = setTimeout(() => {
        func.apply(this, args);
        lastExecutedTime = Date.now(); // 更新上次执行的时间戳
      }, delay - elapsedTime);
    }
  };
}

该实现中,throttle 函数接收两个参数:func 是要进行节流处理的函数,delay 是时间间隔,表示在间隔时间内只能执行一次函数。

节流函数返回一个新的函数作为节流后的函数。每次调用节流后的函数时,它会记录当前时间戳,并计算距离上次执行的时间间隔。

如果距离上次执行的时间间隔大于等于延迟时间,则立即执行目标函数,并更新上次执行的时间戳。

如果距离上次执行的时间间隔小于延迟时间,则设置一个定时器,在延迟时间减去时间间隔后执行目标函数,并更新上次执行的时间戳。

这样,可以确保目标函数在一定时间间隔内只能执行一次。

示例,演示如何使用节流函数:

function handleResize() {
  console.log('Resize event throttled');
}

const throttledResize = throttle(handleResize, 200);

window.addEventListener('resize', throttledResize);

在示例中,我们定义了一个 handleResize 函数,并使用 throttle 函数对其进行节流处理。然后,我们将节流后的函数 throttledResize 添加到 resize 事件的监听器中。这样,当窗口大小调整时,handleResize 函数只会在每 200 毫秒执行一次,从而减少了函数的执行频率。

手写实现防抖函数

function debounce(func, delay) {
  let timeoutId; // 用于存储定时器的ID

  // 返回一个新的函数作为防抖函数
  return function (...args) {
    // 如果已经设置了定时器,则清除它
    if (timeoutId) {
      clearTimeout(timeoutId);
    }

    // 设置新的定时器,延迟执行目标函数
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

该实现中,debounce 函数接收两个参数:func 是要进行防抖处理的函数,delay 是延迟时间,表示在最后一次触发后等待多久执行函数。

防抖函数返回一个新的函数作为防抖后的函数。每次调用防抖后的函数时,它会判断是否已经设置了定时器。如果已经设置了定时器,则清除之前的定时器,以防止函数执行。然后,设置一个新的定时器,延迟执行目标函数。

当最后一次调用防抖后的函数后,如果在延迟时间内没有再次调用,则定时器会触发,执行目标函数 func。这样,可以确保目标函数只在最后一次调用后延迟一段时间执行。

示例,演示如何使用防抖函数:

function handleScroll() {
  console.log('Scroll event debounced');
}

const debouncedScroll = debounce(handleScroll, 200);

window.addEventListener('scroll', debouncedScroll);

在示例中,我们定义了一个 handleScroll 函数,并使用 debounce 函数对其进行防抖处理。然后,我们将防抖后的函数 debouncedScroll 添加到 scroll 事件的监听器中。这样,当页面滚动时,handleScroll 函数只会在最后一次滚动后的 200 毫秒延迟执行,从而减少了函数的执行次数。

函数柯里化的实现

function curry(fn) {
  // 定义内部函数用于递归调用
  function curried(...args) {
    // 如果传入的参数数量大于或等于原始函数的参数数量,则直接调用原始函数
    if (args.length >= fn.length) {
      return fn(...args);
    } else {
      // 如果参数数量不足,则返回一个新的函数,继续等待接收剩余的参数
      return function (...moreArgs) {
        return curried(...args, ...moreArgs);
      };
    }
  }

  return curried; // 返回柯里化后的函数
}

手写实现new操作符

// 自定义new操作符函数:模拟实现new操作符的功能
function myNew(constructorFn, ...args) {
  // 创建一个空对象,并将其原型设置为构造函数的原型
  const obj = Object.create(constructorFn.prototype);
  
  // 调用构造函数,并将this绑定到新创建的对象上
  const result = constructorFn.apply(obj, args);
  
  // 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象
  if (typeof result === 'object' && result !== null) {
    return result;
  }
  
  return obj;
}

// 示例构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};

// 使用示例
const john = myNew(Person, 'John', 25);
john.sayHello(); // Output: Hello, my name is John and I'm 25 years old.

手写实现instanceof

// 自定义instanceof操作符函数:模拟实现instanceof操作符的功能
function myInstanceOf(obj, constructorFn) {
  // 获取构造函数的原型对象
  const prototype = constructorFn.prototype;
  
  // 判断对象的原型链中是否存在构造函数的原型
  while (obj !== null) {
    if (obj.__proto__ === prototype) {
      return true;
    }
    obj = obj.__proto__;
  }
  
  return false;
}

// 示例构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 使用示例
const john = new Person('John', 25);
console.log(myInstanceOf(john, Person)); // Output: true
console.log(myInstanceOf(john, Object)); // Output: true
console.log(myInstanceOf(john, Array)); // Output: false

手写实现Promise:

// 自定义Promise类:模拟实现Promise的基本功能
class MyPromise {
  constructor(executor) {
    // Promise的三个状态:pending、fulfilled、rejected
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    
    // 用于存储异步操作的回调函数
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    
    // 执行executor函数
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }
  
  // 解决Promise(状态变为fulfilled)
  resolve(value) {
    if (this.state === 'pending') {
      this.state = 'fulfilled';
      this.value = value;
      
      // 执行所有已注册的fulfilled回调函数
      this.onFulfilledCallbacks.forEach(callback => callback(this.value));
    }
  }
  
  // 拒绝Promise(状态变为rejected)
  reject(reason) {
    if (this.state === 'pending') {
      this.state = 'rejected';
      this.reason = reason;
      
      // 执行所有已注册的rejected回调函数
      this.onRejectedCallbacks.forEach(callback => callback(this.reason));
    }
  }
  
  // 注册Promise的回调函数
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    } else if (this.state === 'rejected') {
      onRejected(this.reason);
    } else if (this.state === 'pending') {
      // 异步操作时,将回调函数存储起来,待异步操作完成后执行
      this.onFulfilledCallbacks.push(onFulfilled);
      this.onRejectedCallbacks.push(onRejected);
    }
    
    // 返回新的Promise对象,实现链式调用
    return this;
  }
}

// 使用示例
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Hello, Promise!');
  }, 2000);
});

promise.then(value => {
  console.log(value); // Output: Hello, Promise!
});

promise.all()的实现

Promise.all 方法用于接收一个 Promise 数组,并在所有 Promise 都成功解析时返回一个包含所有 Promise 结果的新 Promise。如果任何一个 Promise 失败,则返回的 Promise 将立即被拒绝,并带有失败的原因。

function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    const results = []; // 用于保存每个Promise的结果
    let completedPromises = 0; // 已经完成的Promise数量

    // 遍历传入的Promise数组
    for (let i = 0; i < promises.length; i++) {
      // 对每个Promise进行处理
      promises[i]
        .then((result) => {
          // 如果Promise成功解决,则将结果保存在相应的位置
          results[i] = result;
          completedPromises++;

          // 当所有Promise都完成时,返回结果
          if (completedPromises === promises.length) {
            resolve(results);
          }
        })
        .catch((error) => {
          // 如果有一个Promise被拒绝,则立即返回拒绝的结果
          reject(error);
        });
    }

    // 处理空Promise数组的情况
    if (promises.length === 0) {
      resolve(results);
    }
  });
}

promise.race的实现

Promise.race方法接收一个包含多个Promise的可迭代对象(如数组),并返回一个新的Promise。这个新的Promise将与第一个解决(resolve)或拒绝(reject)的Promise具有相同的解决值或拒绝原因。

function promiseRace(promises) {
  return new Promise((resolve, reject) => {
    // 遍历传入的Promise数组
    for (const promise of promises) {
      // 对每个Promise进行处理
      promise
        .then((result) => {
          // 如果有一个Promise解决,则使用resolve解决新的Promise
          resolve(result);
        })
        .catch((error) => {
          // 如果有一个Promise被拒绝,则使用reject拒绝新的Promise
          reject(error);
        });
    }
  });
}

Promise.allSettled的实现

Promise.allSettled 方法接收一个包含多个 Promise 的可迭代对象(如数组),并返回一个新的 Promise。这个新的 Promise 在所有传入的 Promise 都解决或拒绝后才会被解决,并返回一个包含每个 Promise 结果的对象数组。对象数组中的每个对象都表示对应的 Promise 结果,包含 status 字段表示 Promise 的状态("fulfilled" 表示解决,"rejected" 表示拒绝),以及对应的 valuereason 字段表示解决值或拒绝原因。

function promiseAllSettled(promises) {
  const settledPromises = [];

  // 遍历传入的 Promise 数组
  for (const promise of promises) {
    // 对每个 Promise 进行处理
    settledPromises.push(
      Promise.resolve(promise)
        .then((value) => ({
          status: 'fulfilled',
          value: value,
        }))
        .catch((reason) => ({
          status: 'rejected',
          reason: reason,
        }))
    );
  }

  return Promise.all(settledPromises);
}

手写实现apply函数:

// 自定义apply函数:模拟实现apply的功能
function myApply(fn, context, args) {
  if (typeof fn !== 'function') {
    throw new TypeError('fn must be a function');
  }
  
  // 创建唯一的属性名,用于防止上下文对象属性的覆盖
  const uniqueKey = Symbol();
  
  // 将函数fn作为上下文对象的一个属性
  context[uniqueKey] = fn;
  
  // 调用函数fn,并传入参数数组
  const result = context[uniqueKey](...args);
  
  // 删除添加的属性
  delete context[uniqueKey];
  
  return result;
}

// 使用示例
function greet(greeting) {
  return `${greeting}, ${this.name}!`;
}

const person = { name: 'John' };
const greeting = myApply(greet, person, ['Hello']);
console.log(greeting); // Output: Hello, John!

手写实现call函数:

// 自定义call函数:模拟实现call的功能
function myCall(fn, context, ...args) {
  if (typeof fn !== 'function') {
    throw new TypeError('fn must be a function');
  }
  
  // 创建唯一的属性名,用于防止上下文对象属性的覆盖
  const uniqueKey = Symbol();
  
  // 将函数fn作为上下文对象的一个属性
  context[uniqueKey] = fn;
  
  // 调用函数fn,并传入参数列表
  const result = context[uniqueKey](...args);
  
  // 删除添加的属性
  delete context[uniqueKey];
  
  return result;
}

// 使用示例
function greet(greeting) {
  return `${greeting}, ${this.name}!`;
}

const person = { name: 'John' };
const greeting = myCall(greet, person, 'Hello');
console.log(greeting); // Output: Hello, John!

手写实现bind函数:

// 自定义bind函数:模拟实现bind的功能
function myBind(fn, context, ...args) {
  if (typeof fn !== 'function') {
    throw new TypeError('fn must be a function');
  }
  
  return function (...newArgs) {
    // 将bind时传入的参数和调用时传入的参数合并
    const allArgs = [...args, ...newArgs];
    
    // 调用函数fn,并绑定上下文和参数列表
    return fn.apply(context, allArgs);
  };
}

// 使用示例
function greet(greeting) {
  return `${greeting}, ${this.name}!`;
}

const person = { name: 'John' };
const greetPerson = myBind(greet, person, 'Hello');
const greeting = greetPerson();
console.log(greeting); // Output: Hello, John!

手写实现深拷贝函数:

function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);

手写实现数组的find方法:

// 自定义数组的find方法:模拟实现数组的find方法
Array.prototype.myFind = function(callback, thisArg) {
  // 判断调用myFind方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('myFind called on non-array object');
  }
  
  // 遍历数组,并调用回调函数查找满足条件的元素
  for (let i = 0; i < this.length; i++) {
    // 调用回调函数,并传入当前元素、索引和原数组作为参数
    const satisfiesCondition = callback.call(thisArg, this[i], i, this);
    
    // 如果回调函数返回true,则返回当前元素
    if (satisfiesCondition) {
      return this[i];
    }
  }
  
  // 如果没有满足条件的元素,则返回undefined
  return undefined;
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的myFind方法查找数组中第一个偶数
const evenNumber = numbers.myFind(function(num) {
  return num % 2 === 0;
});

console.log(evenNumber); // Output: 2

手写实现数组的every方法:

// 自定义数组的every方法:模拟实现数组的every方法
Array.prototype.myEvery = function(callback, thisArg) {
  // 判断调用myEvery方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('myEvery called on non-array object');
  }
  
  // 遍历数组,并调用回调函数判断是否满足条件
  for (let i = 0; i < this.length; i++) {
    // 调用回调函数,并传入当前元素、索引和原数组作为参数
    const satisfiesCondition = callback.call(thisArg, this[i], i, this);
    
    // 如果有一个元素不满足条件,则返回false
    if (!satisfiesCondition) {
      return false;
    }
  }
  
  // 如果所有元素都满足条件,则返回true
  return true;
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的myEvery方法判断数组是否所有元素都是奇数
const isAllOdd = numbers.myEvery(function(num) {
  return num % 2 !== 0;
});

console.log(isAllOdd); // Output: false

手写实现数组的some方法:

// 自定义数组的some方法:模拟实现数组的some方法
Array.prototype.mySome = function(callback, thisArg) {
  // 判断调用mySome方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('mySome called on non-array object');
  }
  
  // 遍历数组,并调用回调函数判断是否满足条件
  for (let i = 0; i < this.length; i++) {
    // 调用回调函数,并传入当前元素、索引和原数组作为参数
    const satisfiesCondition = callback.call(thisArg, this[i], i, this);
    
    // 如果回调函数返回true,则返回true
    if (satisfiesCondition) {
      return true;
    }
  }
  
  // 如果没有满足条件的元素,则返回false
  return false;
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的mySome方法判断数组是否存在偶数
const hasEvenNumber = numbers.mySome(function(num) {
  return num % 2 === 0;
});

console.log(hasEvenNumber); // Output: true

手写实现数组的forEach方法:

// 自定义数组的forEach方法:模拟实现数组的forEach方法
Array.prototype.myForEach = function(callback, thisArg) {
  // 判断调用myForEach方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('myForEach called on non-array object');
  }
  
  // 遍历数组,并调用回调函数对每个元素进行操作
  for (let i = 0; i < this.length; i++) {
    // 调用回调函数,并传入当前元素、索引和原数组作为参数
    callback.call(thisArg, this[i], i, this);
  }
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的myForEach方法对数组进行遍历
numbers.myForEach(function(num) {
  console.log(num);
});

// Output:
// 1
// 2
// 3
// 4
// 5

手写实现数组的reduce方法:

// 自定义数组的reduce方法:模拟实现数组的reduce方法
Array.prototype.myReduce = function(callback, initialValue) {
  // 判断调用myReduce方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('myReduce called on non-array object');
  }
  
  // 判断数组是否为空
  if (this.length === 0 && initialValue === undefined) {
    throw new TypeError('Reduce of empty array with no initial value');
  }
  
  let accumulator = initialValue !== undefined ? initialValue : this[0];
  
  // 遍历数组,并调用回调函数对每个元素进行聚合操作
  for (let i = initialValue !== undefined ? 0 : 1; i < this.length; i++) {
    // 调用回调函数,并传入累加器、当前元素、索引和原数组作为参数
    accumulator = callback(accumulator, this[i], i, this);
  }
  
  // 返回聚合结果
  return accumulator;
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的myReduce方法对数组进行求和
const sum = numbers.myReduce(function(acc, num) {
  return acc + num;
}, 0);

console.log(sum); // Output: 15

手写实现数组的filter方法:

// 自定义数组的filter方法:模拟实现数组的filter方法
Array.prototype.myFilter = function(callback, thisArg) {
  // 判断调用myFilter方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('myFilter called on non-array object');
  }
  
  // 创建一个新的空数组,用于存储过滤后的结果
  const filteredArray = [];
  
  // 遍历数组,并调用回调函数对每个元素进行过滤
  for (let i = 0; i < this.length; i++) {
    // 调用回调函数,并传入当前元素、索引和原数组作为参数
    const shouldKeep = callback.call(thisArg, this[i], i, this);
    
    // 如果回调函数返回true,则将当前元素添加到新数组中
    if (shouldKeep) {
      filteredArray.push(this[i]);
    }
  }
  
  // 返回过滤后的新数组
  return filteredArray;
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的myFilter方法对数组进行过滤
const evenNumbers = numbers.myFilter(function(num) {
  return num % 2 === 0;
});

console.log(evenNumbers); // Output: [2, 4]

手写实现数组的map方法:

// 自定义数组的map方法:模拟实现数组的map方法
Array.prototype.myMap = function(callback, thisArg) {
  // 判断调用myMap方法的对象是否为数组
  if (!Array.isArray(this)) {
    throw new TypeError('myMap called on non-array object');
  }
  
  // 创建一个新的空数组,用于存储映射后的结果
  const mappedArray = [];
  
  // 遍历数组,并调用回调函数对每个元素进行映射
  for (let i = 0; i < this.length; i++) {
    // 调用回调函数,并传入当前元素、索引和原数组作为参数
    const mappedValue = callback.call(thisArg, this[i], i, this);
    
    // 将映射后的值添加到新数组中
    mappedArray.push(mappedValue);
  }
  
  // 返回映射后的新数组
  return mappedArray;
};

// 使用示例
const numbers = [1, 2, 3, 4, 5];

// 使用自定义的myMap方法对数组进行映射
const doubledNumbers = numbers.myMap(function(num) {
  return num * 2;
});

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

在这个示例中,我们手写实现了数组的map方法。我们在Array.prototype对象上添加了一个名为myMap的方法。该方法接受一个回调函数作为参数,用于对数组的每个元素进行映射操作。我们遍历数组的每个元素,并调用回调函数,将当前元素、索引和原数组作为参数传递给回调函数。然后,我们将映射后的值添加到一个新的数组中,并最后返回该数组。

结尾

希望有帮助到的朋友可以帮忙点点赞点点关注是对我最大的支持,同时最近需要跳槽面试的同学需要高频面试宝典大全也可以私信我或者关注公众号【前端嚣张农民】回复->999

同时后面也会更新性能优化相关、vue3的实战项目、nodejs的知识点、包括最近开始使用react项目了,之前一直使用的都是vue和angular,也会更新最近写的项目用nextjs框架,喜欢的可以点点关注不迷路,大家一起学习