前言
想想几年前一个月随便投出去一天至少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具有相同的解决值或拒绝原因。
promise.race的实现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” 表示拒绝),以及对应的 value 或 reason 字段表示解决值或拒绝原因。
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);