likes
comments
collection
share

带你手写常见js代码(持续更新)

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

前言

最近想的事情比较多,索性就写一下常见的js手写代码吧!也权当回顾一下。

代码集合

1.flat

数组扁平化,就是把数组进行展开,常见的使用方法是直接使用数组的flat函数。

原理:reduce整个数组判断当前项是否是数组类型,如果是则继续递归使用flat函数,反之直接拼接到结果数组上即可

实现:

// 基于递归实现
function flat(arr) {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
  }, []);
}

用例:

let array = flat([1, 2, [3, 4], [[5, 6, 7]]]);  

2.myFilter

数组的过滤器,筛选出符合条件的数组内容。

原理:遍历整个数组,调用函数后将结果为true的项加到结果数组中即可

实现:

Array.prototype.myFilter = function (callback, is) {
  if (typeof callback != "function") {
    return false;
  }
  let res = [];
  const arr = this;
  this.forEach((item, index) => {
    callback.call(is, item, index, arr) && res.push(item); // 符合条件就加到res数组中
  });
  return res;
};

用例:

let array =[1, 2, 4, 8].myFilter((item, index)=>{
    return item > 1
});  

3.myMap

将数组每个元素都通过传入的回调进行对应操作转换,返回新的数组,常用于数据加工。

原理:遍历整个数组,调用函数后将每一次函数返回值加到结果数组中

实现:

Array.prototype.myMap = function (callback, is) {
  if (typeof callback != "function") {
    return false;
  }
  let res = [];
  const arr = this;
  // 遍历原数组转换后加入res数组中
  this.forEach((item, index) => { 
    res.push(callback.call(is, item, index, arr));
  });
  return res;
};

用例:

let array =[1, 2, 4, 8].myMap((item, index)=>{
    return item + 1
});  

4.mySome

通过回调函数对数组每一项进行验证,如果有一项符合要求则返回true,后续数组都不再执行校验,反之返回false。

原理:遍历整个数组,调用函数后当函数返回值符合条件,则直接return true,后续不再遍历

实现:

Array.prototype.mySome = function (callback, is) {
  if (typeof callback != "function") {
    return false;
  }
  const arr = this;
  for (let index = 0; index < arr.length; index++) {
    // 对数组项进行校验,符合就return,注意:这里不能用foreach
    if (callback.call(content, content[index], index, arr)) {
      return true;
    }
  }
  return false;
};

用例:

[1,2,3,4,5].mySome((item, index) => {
    return item > 1
})

5.myForEach

类似map方法都会让数组项去执行回调,但是不同于map,forEach没有返回值

实现:

Array.prototype.myForEach = function (callback, is) {
  if (typeof callback != "function") {
    return false;
  }
  const arr = this;
  for (let index = 0; index < arr.length; index++) {
    callback.call(is, this[index], index, arr);
  }
};

用例:

[1,2,3,4,5].myForEach((item, index) => {
    console.log(item)
})

6.myReduce

数组的累计方法,上一次回调函数的结果会作为下一个回调函数执行时的输入,最后返回完整的数组计算结果

原理:遍历整个数组,将每一项作为参数传入函数中执行即可

实现:

Array.prototype.myReduce = function (callback, is) {
  if (typeof callback != "function") {
    return false;
  }
  let arr = this
  let res = is ? is : arr[0];
  for (let index = is ? 0 : 1; index < arr.length; index++) {
    res = callback(res, arr[index], index, arr);
  }
  return res;
};

用例:

[1,2,3,4,5].myReduce ((res, item, index) => {
    return res + item
})

7.myCall

用于改变this的指向,使用call的时候接收系列参数,函数会立即执行

原理:将传入的对象的属性fn指向this,执行对象的fn函数,由于作用域链的关系就会使得函数的this指向整个目标对象,执行完之后再删除对象下的fn属性即可

实现:

Function.prototype.myCall = function (content, ...args) {
  if (typeof this != "function") {
    return false;
  }
  content =
    content == null || content == undefined ? window : new Object(content);
  // 将函数挂载到对象下执行,执行完后再移除对象这个属性
  let fn = Symbol("fn");
  content[fn] = this;
  let res = content[fn](...args);
  delete content[fn];
  return res;
};

用例:

function a() {
    console.log(this.a)
}
a.myCall({a:1})

8.myApply

用于改变this的指向,使用Apply的时候接收参数数组,函数会立即执行

原理:将传入的对象的属性fn指向this,执行对象的fn函数,由于作用域链的关系就会使得函数的this指向整个目标对象,执行完之后再删除对象下的fn属性即可

实现:

Function.prototype.myApply = function (content, args) {
  if (typeof this != "function") {
    return false;
  }
  content =
    content == null || content == undefined ? window : new Object(content);
  // 将函数挂载到对象下执行,执行完后再移除对象这个属性
  let fn = Symbol("fn");
  content[fn] = this;
  let res = content[fn](...args);
  delete content[fn];
  return res;
};

用例:

function a(c) {
    console.log(this, c)
}
a.myApply({a: 1}, [1])

9.myBind

同样用于改变this的指向,不同于call,apply,会返回一个this改变的函数,参数接收上与call相同。

原理:暂存传入的this,然后返回一个函数,这个函数执行时会调用call函数去更新函数的指向,类似于对call封装成一个可执行的函数(注意new的情况)

实现:

Function.prototype.myBind = function (content, ...args) {
  if (typeof this != "function") {
    return false;
  }
  content =
    content == null || content == undefined ? window : new Object(content);
  let self = this;
  return function F() {
    // 处理new的情况
    if (this instanceof F) {
      return new self(...args, ...arguments);
    }
    // 通过call去改变this
    return self.call(content, ...args);
  };
};

用例:

function a(c) {
    console.log(this, c)
}
let b = a.myBind({a: 1}, 1)
b()

10.debounce

防抖,在n秒内只执行一次函数,常用于下拉加载等场景。

原理:通过闭包创建一个变量timer,返回一个函数,执行这个函数会给timer赋值定时器,如果后续再执行timer,若timer存在则拦截代码执行

实现:

function debounce(callback, delay) {
  if (typeof callback !== "function") {
    return false;
  }
  let timer = null;
  return function () {
    // 通过闭包存储timer,如果timer存在则拦截代码执行
    if (timer) return;
    timer = setTimeout(() => {
      callback();
      clearTimeout(timer);
    }, delay);
  };
}

用例:

let a1 = debounce(()=>{console.log(123)},1000)
a1()
a1()
a1()
a1()

11.throttle

节流,时间间隔内多次触发函数只执行最后一次,常用于输入框输入查询。

原理:通过闭包创建一个变量timer,返回一个函数,执行这个函数会给timer赋值定时器,如果后续再执行timer,清空timer(终止timer的执行,赋值null不能终止),再重新给timer赋值定时器,保证最后一次触发函数时,timer指向一个等待执行的定时器

实现:

function throttle(callback, delay) {
  if (typeof callback !== "function") {
    return false;
  }
  let timer = null;
  return function () {
    // 通过闭包存储timer,每次执行函数都会清空定时器,重新执行
    clearTimeout(timer);
    timer = setTimeout(() => {
      callback();
      clearTimeout(timer);
    }, delay);
  };
}

用例:

let a1 = throttle(()=>{console.log(123)},1000)
a1()
a1()
a1()
a1()

12.myInstanceof

instanceof 是用来判断左侧对象是否是右侧构造函数的实例化对象

原理:对象的构造函数的prototype指向原型对象,实例化对象的__proto__也指向原型对象,instanceof就是判断实例化对象的__proto__和目标的prototype是否相同,如果不同,则实例化对象的__proto__也是一个对象它也有自己的__proto__再判断,一直往上找看目标的prototyp是否在自己的原型链上

实现:

function myInstanceof(content, type) {
  if ((typeof type != 'object' && typeof type != 'function') || type == null) {
    return false;
  }
  let R = content.__proto__;
  while (true) {
    // 原型链找到最后是null
    if (R == null) {
      return false;
    }
    // 看目标对象的__proto__会不会指向目标类型的prototype即原型对象
    if (R == type.prototype) {
      return true;
    }
    R = R.__proto__;
  }
}

用例:

let a2 = {a:1}
myInstanceof(a2 , Object)

13.myAssign

assign 可以用来拼接对象。

原理:获取参数列表中的所有对象,逐个遍历给目标对象添加属性即可

实现:

Object.prototype.myAssign = function (target, ...args) {
  if (target == undefined || target == null) {
    throw TypeError(target + "is not a object");
  }
  target = Object(target); //保证target是一个对象
  for (let item of args) {
    //获取参数列表中的所有对象
    for (let val in item) {
      //获取这个对象的所有属性
      item[val] && (target[val] = item[val]);
      //如果这个属性是这个对象本身的,就将它添加到target对象上
    }
  }
  return target;
};

用例:

Object.myAssign({}, {a:1, b:2})

14.myIsArray

isArray判断是否是数组

实现:

Array.myIsArray = function (arr) {
  // 直接基于Object.prototype.toString.call判断
  return Object.prototype.toString.call(arr) == "[object Array]";
};

用例:

Array.myIsArray([1])

15.myNew

用于新建对象实例,本质上就是让创建的对象和构造函数使用同一个原型,于实例的原型对象和参数创建一个新的对象

原理:创建一个空对象并将其原型指向构造函数的原型对象,然后调用该构造函数,并将 this 关键字设置为新创建的对象。最后,如果构造函数返回一个对象,则返回该对象;否则返回创建的对象。

实现:

function myNew(constructor, ...args) {
  const obj = Object.create(constructor.prototype); // 让创建的对象和构造函数使用同一个原型
  const res = constructor.apply(obj, args); // 基于实例的原型对象和参数创建一个新的对象res
  return res instanceof Object ? res : obj;
}

用例:

let p = function (name, age) {
        this.name = name;
        this.age = age;
};
p.prototype.sayName = function () {
        console.log(this.name);
};
myNew(p , 1, 2)
let c = myNew(p , 1, 2)
c.sayName()

16.cloneDeep

深拷贝,因为js中对象使用的是堆内存,实际上只保存了一个地址,因此简单的赋值,并不能拷贝对象,需要通过递归的方式完全复制整个对象。(这里没处理Symbol的情况)

原理:遍历目标对象,将每个属性值克隆到新对象中。如果当前属性值是对象(非null),则递归调用 cloneDeep() 方法,并将这个对象作为新目标对象的属性值。否则,它直接将该值作为新对象的属性值。同时接收一个map,每一次cloneDeep时都会以源对象作为key,新对象作value进行存储,每次deepclone的时候都先判断是否在map上,如果在直接返回clone的对象即可,防止递归死循环。

实现:

function cloneDeep(target, hash = new WeakMap()) {
  // 对于传入参数处理
  if (typeof target !== "object" || target === null) {
    return target;
  }
  // 哈希表中存在直接返回
  if (hash.has(target)) return hash.get(target);

  // 设置哈希表,将源数据和拷贝数据都存进去,如何下次读取的时候遇到如下去情况
  //   a:{
  //     b:{
  //         c:a
  //     }
  // }
  // 递归引用a的值,遇到这种情况就直接将之前clone的a返回回去就行,防止递归死循环
  const cloneTarget = Array.isArray(target) ? [] : {};
  hash.set(target, cloneTarget);

  for (const i in target) {
    if (Object.prototype.hasOwnProperty.call(target, i)) {
       // 判断当前项是否是对象等属性,如果是则递归去继续解析
      cloneTarget[i] =
        typeof target[i] === "object" && target[i] !== null
          ? cloneDeep(target[i], hash)
          : target[i];
    }
  }
  return cloneTarget;
}

用例:

cloneDeep({a:1, b:{c:1})

17.异步控制并发数

有一些浏览器最大连接数有限制,因此会通过一些手段控制请求并发,这里是使用promise的方式去实现的。(有兴趣可以基于此继续封装优化成单例使用)

原理:遍历arr,对于每一项,创建一个promise实例存储到resArr中,创建的时候就已经开始执行了,将创建的promise传入的实例数组中,对于每一项的promise设置其then操作,并将其存储到running数组中,作为执行中的标识,当then操作触发之后则将running中的对应这一项删除,执行中的数组减一,在遍历的回调函数最后判断当前是否超出阈值,当数量达到限制时开始批量执行,用await去处理异步,处理完一个即跳走,重新往running中注入新的实例

实现:

async function asyncLimit(limitNum, arr, fn) {
  let resArr = []; // 所有promise实例
  let running = []; // 执行中的promise数组
  for (const item of arr) {
    const p = Promise.resolve(fn(item)); // 遍历arr,对于每一项,创建一个promise实例存储到resArr中,创建的时候就已经开始执行了
    resArr.push(p);
    if (arr.length >= limitNum) {
      // 对于每一项设置其then操作,并将其存储到running数组中,作为执行中的标识,当then操作触发之后则将running中的对应这一项删除,执行中的数组减一
      const e = p.then(() => running.splice(running.indexOf(e), 1));
      running.push(e);
      if (running.length >= limitNum) {
        // 当数量达到限制时开始批量执行,处理完一个即跳走,重新往running中注入新的实例
        await Promise.race(running);
      }
    }
  }
  return Promise.allSettled(resArr);
}

用例:

fn = (item) => {
  return new Promise((resolve) => {
    console.log("开始",item);
    setTimeout(() => {
      console.log("结束", item);
      resolve(item);
    }, item)
  });
};
asyncLimit(2, [1000, 2000, 5000, 2000, 3000], fn)

18.myAll

promise的all方法,接收一个数组,当数组的所有promise都resolve的时候,promise的all才结束

原理:返回一个新的promise,在大的promise下循环传入的promise列表,当任意一个promise出现resolve的时候就将结果插入到结果列表对应的位置中,然后判断结果列表的长度是否已经和传入的promise列表长度相当,如果相当则执行大的promise的resolve,把结果数据返回即可

实现:

Promise.myAll = function (promiseArr) {
  return new Promise((resolve, reject) => {
    const resultList = [];
    for (let index = 0; index < promiseArr.length; index++) {
      promiseArr[index]
        .then((res) => {
          // 保证结果顺序正确 
          if (index + 1 > resultList.length) {
            resultList.push(res)
          } else {
            resultList.splice(index, 0, res)
          }
          if (resultList.length == promiseArr.length) {
            resolve(resultList);
          }
        })
        .catch((err) => reject(err));
    }
  });
};

用例:

let a = function(t) {
    return new Promise((resolve, reject)=>{setTimeout(()=>{resolve(t)}, t)})
}
Promise.myAll([a(3000),a(2000),a(3000),a(1000),a(1000)]).then(res=>{
    console.log(res)
})

19.myRace

promise的Race方法,接收一个数组,当数组的任意一个promise resolve的时候,promise的Race结束

原理:返回一个新的promise,在大的promise下循环传入的promise列表,当任意一个promise出现resolve的时候,就执行大的promise的resolve,把当前promise的结果数据返回即可

实现:

Promise.myRace = function (promiseArr) {
  return new Promise((resolve, reject) => {
    for (let index = 0; index < promiseArr.length; index++) {
      promiseArr[index]
        .then((res) => {
          resolve(res);
        })
        .catch((err) => reject(err));
    }
  });
};

用例:

let a = function(t) {
    return new Promise((resolve, reject)=>{setTimeout(()=>{resolve(t)}, t)})
}
Promise.myRace([a(3000),a(2000),a(3000),a(1000),a(1000)]).then(res=>{
    console.log(res)
})

结语

各位大佬有兴趣的话可以看看上面的内容,觉得什么地方不对也可以提出来,有想看的函数评论我就会去更新这个文章。

谢谢各位大佬