JS 常见工具函数的实现原理
防抖函数
防抖是 N 秒内函数只会被执行一次,如果 N 秒内再次被触发,则重新计算延迟时间(该方法会在最后一次操作的一段时间后才会执行,如 window.keyup、搜索提示等)
类似于狙击手的瞄准过程,狙击手在瞄准目标之后再过一段稳定时间(防止手抖)后才会射击目标
function debounce(fn, delay = 500, immediate = false) {
let timer;
return function() {
const args = arguments;
const context = this;
if (immediate && !timer) {
fn.apply(context, args);
}
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
}
};
节流函数
节流是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行(对于频繁操作,任意两次操作只会在一定时间间隔内才会执行,如 window.resize、window.scroll 等事件)
类似于水龙头上的水滴,水滴总会在积累到一定的大小之后才会滴下
// throttle 节流
function throttle(fn, delay) {
let timeout;
let start = new Date();
let threshhold = delay || 200;
return function () {
let context = this,
args = arguments,
curr = new Date();
clearTimeout(timeout); //总是干掉事件回调
if (curr - start >= threshhold) {
fn.apply(context, args); //只执行一部分方法,这些方法是在某个时间段内执行一次
start = curr;
} else {
//让方法在脱离事件后也能执行一次
timeout = setTimeout(function () {
fn.apply(context, args);
}, threshhold);
}
};
};
function throttle(fn, delay = 500, immediate = false) {
let timer;
return function() {
const args = arguments;
const context = this;
if (immediate) {
fn.apply(context, args);
immediate = false;
}
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, delay);
}
}
};
call、apply 和 bind 函数
Function.prototype.$call = function(context) {
context = context || window;
const fn = +new Date() + '' + Math.random(); // 防止同名的 key
context[fn] = this;
const args = [...arguments].slice(1);
const result = eval('context[fn](' + args.toString() +')')
delete context[fn];
return result;
}
Function.prototype.$apply = function(context, args = []) {
context = context || window;
const fn = +new Date() + '' + Math.random(); // 防止同名的 key
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
}
// Function.prototype.bind 方法的实现
Function.prototype.$bind = function (context) {
let self = this;
let args = Array.prototype.slice.call(arguments, 1);
let fBind = function () {
// 说明:一个绑定函数也能使用 new 操作符创建对象,并需要继承原函数的原型链方法,这种行为就像把原函数当成构造器,提供的 this 值被忽略,同时调用时的参数被提供给模拟函数
// 通过 new 方法使用该绑定函数时,this 指向新创建的对象,则 this instanceof fBind 成立
return this.apply(this instanceof fBind ? this : context, args.concat(arguments));
};
// 用于把原型链传递下去
fBind.prototype = Object.create(this.prototype);
// ES5 中才加入 Object.create() 和 Function.prototype.bind,所以 Function.prototype.bind 不能使用时,Object.create() 也不能使用,所以使用中转函数将原型链传递下去
// function F () {}
// F.prototype = this.prototype;
// fBind.prototype = new F();
return fBind;
}
实现 new 操作符
使用 new 操作符创建一个对象时,会进行以下步骤:
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[ 原型 ]] 连接。
- 这个新对象会绑定到函数调用的 this 。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
function myNew(fn, ...args) {
// let obj = {};
// obj.__proto__ fn.prototype;
let obj = Object.create(fn.prototype);
let result = fn.apply(obj, ...args);
if ((result && typeof result === 'object') || typeof result === 'function')) {
return result;
}
return obj;
}
实现 instanceof 操作符
instanceof 的原理是右侧对象的原型对象(prototype )是否在左侧对象的原型链上面
function myInstanceof(left, right) {
if (typeof obj !== 'object' || obj === null) return false
let leftProp = left.__proto__;
let rightProp = right.prototype;
// 一直会执行循环 直到函数return
while (true) {
// 遍历到了原型链最顶层
if (leftProp === null) return false;
if (leftProp === rightProp) return true;
// 遍历赋值__proto__做对比
leftProp = leftProp.__proto__;
}
}
实现 Promise.retry 函数
// 函数执行失败后可重试
Promise.retry = (fn, times) => {
return new Promise(async (resolve, reject) => {
while (times--) {
try {
const res = await fn();
resolve(res);
break;
} catch (err) {
if (!times) reject(err)
}
}
})
}
实现深度拷贝
// 简易版实现
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;
}