题目:手写 Promise

  • TODO

题目:手写 Promise.all

要手写实现一个 Promise.all 方法,我们需要考虑几个关键点:

  1. 接收一个 promise 数组作为参数。

  2. 返回一个新的 promise。

  3. 等待所有的输入 promise 都成功完成(fulfilled),然后以一个数组的形式完成返回的 promise,数组中的元素顺序与输入数组的顺序相对应。

  4. 如果任何一个输入 promise 失败(rejected),则返回的 promise 应该立即以该 promise 的失败原因拒绝。

function PromiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 检查输入是否为数组
    if (!Array.isArray(promises)) {
      return reject(new TypeError('You must pass an array to Promise.all.'));
    }

    // 创建一个与输入数组长度相同的结果数组,用来存储每个promise的结果
    let results = new Array(promises.length);
    let completed = 0;

    // 遍历每个 promise
    promises.forEach((promise, index) => {
      // 确保每个元素都是 promise
      Promise.resolve(promise).then(
        (value) => {
          // 存储结果并检查是否所有 promise 都已完成
          results[index] = value;
          completed++;
          if (completed === promises.length) {
            resolve(results);
          }
        },
        (error) => {
          // 如果任何一个 promise 失败,立即拒绝返回的 promise
          reject(error);
        }
      );
    });
  });
}

// 使用示例
let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

PromiseAll([promise1, promise2, promise3]).then((values) => {
  console.log(values); // 输出 [3, 42, 'foo']
}).catch((error) => {
  console.error(error);
});

题目:手写 await async

  • TODO

题目:手写 applycallbind

实现apply

Function.prototype.myApply = function (context, args) {
  // 判断是否为函数
  if (typeof this !== "function") {
    throw new TypeError("not a function");
  }
  // 确定上下文,默认为全局对象
  context = context || (this instanceof Window ? this : window);
  // 设置函数引用
  context.fn = this;
  // 调用函数,并使用扩展运算符展开参数
  const result = context.fn(...args);
  delete context.fn;
  // 清理,删除函数引用
  return result;
};

实现call

与 apply 基本一致,主要区别在获取参数处理步骤。

Function.prototype.myCall = function (context) {
  // 检查调用者是否为函数
  if (typeof this !== "function") {
    throw new TypeError("not a function");
  }
  // 设置默认上下文
  context = context || (this instanceof Window ? this : window);
  // 设置函数引用
  context.fn = this;
  // 获取除了 context 之外的所有参数
  let args = [...arguments].slice(1);
  // 调用函数并传递参数
  let result = context.fn(...args);
  // 清理函数引用
  delete context.fn;
  // 返回结果
  return result;
};

实现bind

实现关键点:

  1. 修改this指向;

  2. 动态传递参数;

  3. 兼容new关键字;

Function.prototype.myBind = function (context) {
  // 判断是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  const args = [...arguments].slice(1);
  // 将当前的函数保存在fn变量中
  const fn = this;
  // 返回一个函数
  return function F() {
    // 判断是否为构造函数
    // 如果F是作为构造函数使用(即this指向F实例),则会创建并返回一个新的fn实例,并将args和F的参数传递给fn的构造函数
    // 否则,会使用apply方法将fn应用到指定的上下文context上,并将args和F的参数合并后传递给fn
    if (this instanceof F) {
      return new fn(...args, ...arguments);
    } else {
      return fn.apply(context, args.concat(...arguments));
    }
  };
};

题目:手写 防抖节流

实现 防抖

通常使用setTimeout来实现延迟执行,如果在这个延迟时间内事件再次被触发,则取消之前的定时器,并重新设置一个新的定时器。

// 防抖函数实现
function debounce(func, wait) {
    let timeout;
    return function(...args) {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(context, args);
        }, wait);
    };
}

// 使用防抖函数
// 使用示例:假设我们有一个搜索输入框,我们希望在用户停止输入后执行搜索
const handleSearch = debounce(function(event) {
    console.log('Searching for:', event.target.value);
}, 300);

document.getElementById('search-input').addEventListener('input', handleSearch);

实现 节流

通常使用setTimeout或时间戳来实现。在setTimeout的实现中,如果事件在延迟时间内再次被触发,则忽略这些触发;在时间戳的实现中,会检查自上次执行以来是否已经过了足够的时间间隔。

// 节流函数实现
function throttle(func, limit) {
    let lastFunc;
    let lastRan;
    return function() {
        const context = this;
        const args = arguments;
        if (!lastRan) {
            func.apply(context, args);
            lastRan = Date.now();
        } else {
            clearTimeout(lastFunc);
            lastFunc = setTimeout(function() {
                if ((Date.now() - lastRan) >= limit) {
                    func.apply(context, args);
                    lastRan = Date.now();
                }
            }, limit - (Date.now() - lastRan));
        }
    };
}

// 使用节流函数
// 使用示例:假设我们有一个滚动事件,我们希望限制检查是否到达底部的操作频率
const handleScroll = throttle(function() {
    console.log('Scroll event handler called');
}, 300);

window.addEventListener('scroll', handleScroll);

题目:手写 深拷贝

简单对象深拷贝

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 3;
console.log(obj1.b.c); // 输出2,说明obj1没有被修改

复杂对象拷贝

function deepClone(obj, hash = new WeakMap()) {
    if (obj === null) return null; // 判空处理
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    // ...
    if (typeof obj !== 'object') return obj; // 基本数据类型直接返回
    if (hash.has(obj)) return hash.get(obj); // 解决循环引用问题

    let cloneObj = new obj.constructor();
    hash.set(obj, cloneObj);

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash); // 递归复制
        }
    }
    return cloneObj;
}

题目:手写 继承方式实现

  • TODO

题目:数组去重

方法1:使用Set对象:

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];

方法2:使用filter方法:

通过 filter 方法和一个辅助数组来过滤掉重复的元素。

const uniqueArray = array.filter((item, index) => array.indexOf(item) === index);

方法3:使用mapindexOf:

const uniqueArray = [];
array.map((item) => {
  if (uniqueArray.indexOf(item) === -1) {
    uniqueArray.push(item);
  }
});

方法4:使用reduce方法:

reduce 方法可以遍历数组,并利用一个回调函数来决定新数组的元素。

const uniqueArray = array.reduce((acc, current) => {
  if (!acc.includes(current)) {
    acc.push(current);
  }
  return acc;
}, []);

方法5:使用Object作为映射:

利用对象的键值唯一性来过滤掉重复的元素。

const uniqueArray = Object.keys(array.reduce((acc, cur) => {
  acc[cur] = true;
  return acc;
}, {}));

方法6:使用indexOfconcat:

使用 indexOf 检查元素是否已经存在于新数组中,如果不存在则添加。

let uniqueArray = [];
array.forEach((item) => {
  if (uniqueArray.indexOf(item) === -1) {
    uniqueArray = uniqueArray.concat(item);
  }
});

方法7:使用lastIndexOf:

lastIndexOf 方法从数组的末尾开始向前查找,可以用来检查元素是否重复出现。

const uniqueArray = array.filter((item, index) => array.lastIndexOf(item) === index);

方法8:使用Symbol作为键:

如果数组元素是对象或数组,可以使用 Symbol 作为键来创建一个映射。

const uniqueMap = new Map();
array.forEach((item) => {
  uniqueMap.set(JSON.stringify(item), item);
});
const uniqueArray = Array.from(uniqueMap.values());

方法9:使用第三方库:

使用如 Lodash 等第三方库中的 _.uniq 或 _.uniqBy 方法。

import _ from 'lodash';
const uniqueArray = _.uniq(array);

题目:合并数组的重叠区间

  • TODO

题目:求两个数组的交集

求两个数组的交集,即找出两个数组中都存在的元素。

const array1 = [1, 2, 3, 4];
const array2 = [3, 4, 5, 6];

// 交集结果为:[3, 4]

方法1:使用filterincludes

const intersection = array1.filter(value => array2.includes(value));

方法2:使用reduceincludes

const intersection = array1.reduce((acc, value) => {
  return array2.includes(value) ? [...acc, value] : acc;
}, []);

方法3:使用Set

const intersection = [...new Set(array1.filter(value => array2.includes(value)))];

方法4:使用some

const intersection = array1.filter(value => array2.some(item => item === value));

方法5:使用双层for循环

第二层循环也可以使用 includes 代替,较少代码量。

const intersection = [];
for (let i = 0; i < array1.length; i++) {
  for (let j = 0; j < array2.length; j++) {
    if (array1[i] === array2[j] && intersection.indexOf(array1[i]) === -1) {
      intersection.push(array1[i]);
    }
  }
}

方法6:排序后去重

首先对两个数组进行排序,然后使用双指针技术找到交集。

const sortAndMerge = (arr1, arr2) => {
  // 合并数组并排序
  const sorted = arr1.concat(arr2).sort((a, b) => a - b);
  const intersection = [];
  let i = 0;
  while (i < sorted.length - 1) {
    if (sorted[i] === sorted[i + 1]) {
      intersection.push(sorted[i]);
      i++;
    }
    i++;
  }
  return intersection;
};

const array1 = [1, 2, 2, 3];
const array2 = [2, 3, 4, 4];
const intersection = sortAndMerge(array1, array2);

方法7:使用 Map

适用于数组元素为对象的情况。

const array1 = [{ id: 1 }, { id: 2 }];
const array2 = [{ id: 2 }, { id: 3 }];

const intersection = array1.filter(item1 =>
  array2.some(item2 => item1.id === item2.id)
);

题目:求两个数组的并集

找出两个数组的并集(即两个数组中所有的唯一元素)可以通过多种方式实现。

const array1 = [1, 2, 3];
const array2 = [3, 4, 5];

方法1:使用concatSet

const union = [...new Set(array1.concat(array2))];

方法2:使用reduceSet

const union = array1.reduce((acc, val) => {
  acc.add(val);
  return acc;
}, new Set()).size > 0
  ? Array.from(new Set([...array1, ...array2]))
  : [];

方法3:使用pushindexOf

const union = [];
array1.forEach(item => {
  if (union.indexOf(item) === -1) {
    union.push(item);
  }
});
array2.forEach(item => {
  if (union.indexOf(item) === -1) {
    union.push(item);
  }
});

方法4:使用filterindexOf

const union = [
  ...array1.filter(item => array2.indexOf(item) === -1),
  ...array2.filter(item => array1.indexOf(item) === -1)
].filter((item, index, arr) => index === arr.indexOf(item));

方法5:使用somepush

const union = [];
[array1, array2].forEach(arr => {
  arr.forEach(item => {
    if (!union.some(elem => elem === item)) {
      union.push(item);
    }
  });
});

方法6:排序后合并

如果数组可以排序,可以先对两个数组进行排序,然后合并并去除重复项。

const sortMerge = (arr1, arr2) => {
  const sorted = arr1.concat(arr2).sort();
  const union = [];
  for (let i = 0; i < sorted.length; i++) {
    if (sorted[i] !== sorted[i - 1]) {
      union.push(sorted[i]);
    }
  }
  return union;
};

const array1 = [1, 2, 2];
const array2 = [2, 3, 3];
const union = sortMerge(array1, array2);

手写题:将arr转换为tree结构

  • TODO
文章作者: 小森森
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 小森森博客
博客 源码 面试 经验 前端
喜欢就支持一下吧
打赏
微信 微信
支付宝 支付宝