手写几个常用的函数(forEach/filter/map/reduce/bind等等)

539 阅读6分钟

前言

今天手写几个常用的方法,实现的方式有很多,欢迎指正,如果有更好的方式可以在评论区留言

1.手写forEach方法

这个方法是在数组原型上添加一个自定义的方法,用于遍历数组并执行回调函数。回调函数的参数包括当前遍历到的元素值、元素索引和原数组对象。通过调用回调函数的call方法,可以将this指向当前数组对象。

Array.prototype.myForEach = function (callback) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }
  for (let i = 0, len = this.length; i < len; i++) {
    callback.call(this, this[i], i, this);
  }
};

const arr = ["我", "是", "大帅比"];

arr.myForEach((val, index) => {
  console.log(`${index}-${val}`);
 /** 打印结果
  *  0-我
  *  1-是
  *  2-大帅比
  */
});

2.手写Filter方法

这个方法也是在数组原型上添加一个自定义的方法,用于筛选数组中符合条件的元素,并返回一个新的数组。回调函数的参数同样包括当前遍历到的元素值、元素索引和原数组对象。如果回调函数的返回值为真,则将当前元素值保存到一个结果数组中。

Array.prototype.myFilter = function (callback) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }
  let result = [];
  for (let i = 0, len = this.length; i < len; i++) {
    // 如果执行回调函数的值为真,则保存当前索引的值
    if (callback.call(this, this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};

const animals = [
  { name: "鸡", age: 1 },
  { name: "小猪", age: 5 },
  { name: "羊咩咩", age: 3 },
];

console.log(animals.myFilter((animal) => animal.age > 2));
/** 筛选出age>2的动物数组
 * 
 * [ { name: '小猪', age: 5 }, { name: '羊咩咩', age: 3 } ]
 */

3.手写map方法

这个方法也是在数组原型上添加一个自定义的方法,用于对数组中的每一个元素执行回调函数,并返回一个新的数组。回调函数的参数同样包括当前遍历到的元素值、元素索引和原数组对象。将回调函数的返回值保存到结果数组中即可。

Array.prototype.myMap = function (callback) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }
  let result = [];
  for (let i = 0, len = this.length; i < len; i++) {
    result.push(callback.call(this, this[i], i, this));
  }
  return result;
};

const animals = [
  { name: "鸡", age: 1 },
  { name: "小猪", age: 5 },
  { name: "羊咩咩", age: 3 },
];

console.log(animals.myMap((val) => val.name));
/** 获取所有动物的名称
 * [ '鸡', '小猪', '羊咩咩' ]
 */

4.手写some方法

这个方法同样是在数组原型上添加一个自定义的方法,用于判断数组中是否有元素符合条件。回调函数的参数同样包括当前遍历到的元素值、元素索引和原数组对象。如果回调函数的返回值为真,则返回true;否则继续遍历数组。如果遍历完整个数组都没有找到符合条件的元素,则返回false。

Array.prototype.mySome = function (callback) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }
  for (let i = 0, len = this.length; i < len; i++) {
    if (callback.call(this, this[i], i, this)) return true;
  }
  return false;
};

const numArr = [1, 3, 5, 1, 2, 6, 121, 2, 33, 6];
console.log(numArr.mySome((val) => val > 20)); // true
console.log(numArr.mySome((val) => val > 200)); // false

5.手写every方法

这个方法和some方法的实现方式基本相同,只不过判断条件相反。如果回调函数对于每一个元素的返回值都为真,则返回true;否则返回false。

Array.prototype.myEvery = function (callback) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }
  for (let i = 0, len = this.length; i < len; i++) {
    if (!callback.call(this, this[i], i, this)) return false;
  }
  return true;
};

const numArr = [1, 3, 5, 1, 2, 6, 121, 2, 33, 6];
// every和some的实现方式只能说一模一样
console.log(numArr.myEvery((val) => val > 0)); // true
console.log(numArr.myEvery((val) => val > 100)); // false

6.手写reduce方法

这个方法是对数组进行累加的方法,它接收一个回调函数作为参数,这个回调函数接收四个参数,分别是累加器、当前值、当前索引和数组本身。这个方法有两种用法,一种是不传初始值,这时候累加器默认为数组的第一项,从数组第二项开始执行回调函数;另一种是传递了初始值,这时候累加器为初始值,从数组第一项开始执行回调函数。这个方法的实现思路就是遍历数组,将累加器和当前值传入回调函数,回调函数返回的结果再作为累加器,不断更新累加器的值,最后返回累加器的值。

Array.prototype.myReduce = function (callback, initialValue) {
  if (typeof callback !== "function") {
    throw new Error("callback must be a function");
  }
  let result = initialValue;
  let index = 0;
  if (initialValue === undefined) {
    result = this[0];
    index = 1;
  }
  for (let len = this.length; index < len; index++) {
    result = callback(result, this[index], index, this);
  }
  return result;
};

const numArr = [2, 5, 6, 8];

console.log(
  numArr.myReduce((sum, val) => {
    console.log(val); // 因为没传初始值,这时候初始值为数组的第一项,从数组第二项开始执行,所以只会打印3次,也就是说这时候会打印 5 6 8,整个myReduce函数执行返回的值为21
    return sum + val;
  })
);
console.log(
  numArr.myReduce((sum, val) => {
    console.log(val); // 因为传了初始值,所以从数组第一项开始执行,这时候会全部打印也就是说会打印4次 => 2 5 6 8,整个myReduce函数执行返回的值为24
    return sum + val;
  }, 3)
);

7.手写bind方法

这个方法是用来改变函数执行时的this指向的,它接收一个对象作为this指向,以及一些参数。这个方法的实现思路就是在调用bind方法的函数中保存原函数的引用,然后返回一个新的函数,在这个新函数中调用原函数,并将this指向传入的对象,同时将bind方法传入的参数和新函数执行时传入的参数合并起来并传入原函数。

Function.prototype.myBind = function () {
  const _func = this;
  const _self = arguments[0];
  const _arguments = Array.prototype.slice(arguments, 1);
  return function () {
    _func.apply(_self, _arguments.concat(arguments));
  };
};

function kun(num1, num2) {
  console.log(this.name, num1, num2);
}
kun(1, 2); // 打印 undefined 1 2

const my = {
  name: "大帅逼",
};
const peng = kun.bind(my, 8);
peng(9); // 打印 大帅逼 8 9

8.手写防抖函数debounce

这个方法是用来解决高频事件触发时出现的性能问题的,它会等待一段时间后再执行函数,如果在这段时间内再次触发了事件,则重新计时。这个方法的实现思路就是在每次触发事件时清除上一次的定时器,然后设置一个新的定时器,等待一段时间后执行函数。

function myDebounce(func, delay = 500) {
  if (typeof func !== "function" || typeof delay !== "number") {
    throw new Error("func must be a function and delay must be a number");
  }
  let _code = null;
  return function () {
    clearTimeout(_code);
    let _self = this;
    _code = setTimeout(() => {
      func.apply(_self, arguments);
    }, delay);
  };
}

const myFunc = myDebounce(function () {
  console.log("嘿嘿嘿");
}, 2000);
window.onresize = myFunc; // 对于高频事件,不管执行多少次,都只会等最后一次触发后的一段间隔后才会执行事件,也就是说快速拖动窗口不会触发打印,等待停止拖动2s后才会打印

9.手写节流函数throttle

这个方法也是用来解决高频事件触发时出现的性能问题的,它会在一段时间内只执行一次函数,如果在这段时间内再次触发了事件,则忽略这次触发。这个方法的实现思路就是设置一个标志位,用来记录是否可以执行函数,如果可以执行,则执行函数,并将标志位设置为false,然后在一段时间后将标志位重新设置为true,以便下一次可以执行函数。

function myThrottle(func, delay) {
  if (typeof func !== "function" || typeof delay !== "number") {
    throw new Error("func must be a function and delay must be a number");
  }
  let flag = true;
  return function () {
    let _self = this;
    if (flag) {
      func.apply(_self, arguments);
      flag = false;
      setTimeout(() => {
        flag = true;
      }, delay);
    }
  };
}

const myFunc = myThrottle(function () {
  console.log("哈哈哈");
}, 2000);
window.onresize = myFunc; // 对于高频事件,每2s最多执行一次,适用于一些按钮的执行事件,每隔一段时间只会触发一次

暂时就写到这吧,如果有错误欢迎指出,如果有更好的方式也可以在评论区留言,下期再见