带你手写常见js代码(持续更新)
前言
最近想的事情比较多,索性就写一下常见的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)
})
结语
各位大佬有兴趣的话可以看看上面的内容,觉得什么地方不对也可以提出来,有想看的函数评论我就会去更新这个文章。
谢谢各位大佬
转载自:https://juejin.cn/post/7237022394791444535