手写防抖-节流-深拷贝-事件总线
2022/2/20大约 9 分钟
手写防抖、节流、深拷贝、事件总线
防抖
认识防抖
当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
当事件密集触发时,函数的触发会被频繁的推迟;
只有等待了一段时间也没有事件触发,才会真正的执行响应函数;
underscore库的使用

基本实现
<!DOCTYPE html>
<html lang="en">
<body>
<input type="text" placeholder="请输入要查找的值">
<button id="cancel">取消</button>
<!-- <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.2/underscore-umd-min.js"></script> -->
<script src="./01_防抖函数基本实现.js"></script>
<script>
const input = document.querySelector('input');
let counter = 0;
// 要触发的函数
const inputChange = function(event) {
console.log(`发送了${++counter}次请求`, this, event);
// 返回值
return "response data";
}
// underscore库防抖处理
// input.oninput = _.debounce(inputChange, 1000);
// input.oninput = inputChange;
// 回调函数方式获取返回值
const debounceChange = _debounce(inputChange, 1000, false, (res) => {
console.log('拿到的返回值:', res);
});
// promise方式拿返回值
const callback = () => {
debounceChange().then(res => {
console.log('promise方式拿返回值:', res);
})
}
input.oninput = callback;
// 取消请求
const cancelBtn = document.querySelector("#cancel");
cancelBtn.onclick = debounceChange.cancel;
</script>
</body>
</html>function _debounce(fn, delay) {
let timer = null;
function debounce(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args); // this指向input元素
}, delay);
}
return debounce;
}立即执行
function _debounce(fn, delay, immediate = false) { // 立即执行设置为false
let timer = null;
let isInvoke = false; // 表示未执行过立即函数
function debounce(...args) {
clearTimeout(timer);
if (immediate && !isInvoke) {
fn.apply(this, args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false; // 重新调整回需要立即执行
}, delay);
}
}
return debounce;
}取消功能实现
function _debounce(fn, delay, immediate = false) { // 立即执行设置为false
let timer = null;
let isInvoke = false; // 表示未执行过立即函数
function debounce(...args) {
clearTimeout(timer);
if (immediate && !isInvoke) {
fn.apply(this, args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false; // 重新调整回需要立即执行
}, delay);
}
}
// 取消功能(初始化)
debounce.cancel = function() {
if(timer) clearTimeout(timer);
timer = null;
isInvoke = false;
}
return debounce;
}返回值处理
function _debounce(fn, delay, immediate = false, resCallback) {
let timer = null;
let isInvoke = false;
function debounce(...args) {
return new Promise((resolve, reject) => {
// 取消上一次的定时器
if(timer) clearTimeout(timer);
// 是否立即执行
if (immediate && !isInvoke) {
const result = fn.apply(this, args);
resolve(result); // Promise方式返回值
if (resCallback) resCallback(result); // 回调返回值
isInvoke = true;
} else {
timer = setTimeout(() => {
const result = fn.apply(this, args);
resolve(result); // Promise方式返回值
if (resCallback) resCallback(result); // 回调返回值
isInvoke = false;
}, delay);
}
})
}
// 取消功能(初始化)
debounce.cancel = function() {
if(timer) clearTimeout(timer);
timer = null;
isInvoke = false;
}
return debounce;
}节流
认识节流
- 当事件触发时,会执行这个事件的响应函数;
- 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数;
- 不管在这个中间有多少次触发这个事件,执行函数的频繁总是固定的;

基本实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" placeholder="请输入要查找的值">
<button id="cancel">取消</button>
<!-- <script src="https://cdn.jsdelivr.net/npm/underscore@1.13.2/underscore-umd-min.js"></script> -->
<script src="./09_节流函数基本实现-返回值.js"></script>
<script>
const inputEl = document.querySelector('input');
let counter = 0;
const inputChange = function() {
console.log(`发送了第${++counter}次请求`, this, event);
return 'abcde';
}
// 节流实现
// inputEl.oninput = _.throttle(inputChange, 400);
const throttle = _throttle(inputChange, 2000, { leading: true, trailing: true }, (res) => {
console.log('result callback:', res);
})
const callback = function(...args) {
throttle.apply(inputEl, args).then(res => {
console.log('promise callback:', res);
})
}
inputEl.oninput = callback;
// 取消实现
const cancelBtn = document.querySelector("#cancel");
cancelBtn.onclick = function() {
throttle.cancel();
}
</script>
</body>
</html>下列代码错误,未考虑被节流函数的执行,直接去掉了需要节流执行的函数
function _throttle(fn, interval) {
let lastTime = 0;
function throttle() {
// 获取当前事件触发的时间
const nowTime = new Date().getTime();
// 计算还要多长时间触发执行函数
const remainTime = interval - (nowTime - lastTime);
if(remainTime <= 0) {
fn();
lastTime = nowTime; // 重置初始时间(保留上次触发时间)
}
}
return throttle;
}优化代码实现基本的节流
type F = (...args: any[]) => void
function throttle(fn: F, t: number): F {
let lastExecuteTime = 0
let timer
return function (...args) {
const curTime = Date.now()
// 触发时间大于规定间隔
if((curTime - lastExecuteTime) >= t) {
fn(...args)
lastExecuteTime = curTime
} else { // 已节流函数的后续处理
clearTimeout(timer)
timer = setTimeout(() =>{
fn(...args)
lastExecuteTime = Date.now()
}, lastExecuteTime + t - curTime) // 对于节流执行的函数还要多久执行
}
}
};首次触发调整
function _throttle(fn, interval, options = { leading: true, trailing: false }) { // 默认第一次触发,最后一次不触发
let lastTime = 0;
const { leading, trailing } = options;
function throttle() {
// 获取当前事件触发的时间
const nowTime = new Date().getTime();
if(!leading && lastTime === 0) lastTime = nowTime; // 如果第一次不触发
// 计算还要多长时间触发执行函数
const remainTime = interval - (nowTime - lastTime);
if(remainTime <= 0) {
fn();
lastTime = nowTime; // 重置初始时间(保留上次触发时间)
}
}
return throttle;
}最后触发(理解remainTime)
function _throttle(fn, interval, options = { leading: true, trailing: false }) { // 默认第一次触发,最后一次不触发
let lastTime = 0;
const { leading, trailing } = options;
let timer = null;
function throttle() {
// 获取当前事件触发的时间
const nowTime = new Date().getTime();
if(!leading && lastTime === 0) lastTime = nowTime; // 如果第一次不触发
// 计算还要多长时间触发执行函数
const remainTime = interval - (nowTime - lastTime);
if(remainTime <= 0) {
if(timer) {
clearTimeout(timer);
timer = null;
}
fn();
lastTime = nowTime; // 重置初始时间(保留上次触发时间)
return;
}
// 最后触发
if(trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading? 0 : new Date().getTime();
fn();
console.log('123');
}, remainTime);
}
}
return throttle;
}this完善,取消功能
function _throttle(fn, interval, options = { leading: true, trailing: false }) { // 默认第一次触发,最后一次不触发
let lastTime = 0;
const { leading, trailing } = options;
let timer = null;
function throttle(...args) {
// 获取当前事件触发的时间
const nowTime = new Date().getTime();
if(!leading && lastTime === 0) lastTime = nowTime; // 如果第一次不触发
// 计算还要多长时间触发执行函数
const remainTime = interval - (nowTime - lastTime);
if(remainTime <= 0) {
if(timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
lastTime = nowTime; // 重置初始时间(保留上次触发时间)
return;
}
// 最后触发
if(trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading? 0 : new Date().getTime();
fn.apply(this, args);
console.log('123');
}, remainTime);
}
}
throttle.cancel = function() {
if(timer) clearTimeout(timer);
timer = null;
lastTime = 0;
}
return throttle;
}返回值处理方案
function _throttle(fn, interval, options = { leading: true, trailing: false }, argCallback) { // 默认第一次触发,最后一次不触发
let lastTime = 0;
const { leading, trailing } = options;
let timer = null;
function throttle(...args) {
return new Promise((resolve, reject) => {
// 获取当前事件触发的时间
const nowTime = new Date().getTime();
if(!leading && lastTime === 0) lastTime = nowTime; // 如果第一次不触发
// 计算还要多长时间触发执行函数
const remainTime = interval - (nowTime - lastTime);
if(remainTime <= 0) {
if(timer) {
clearTimeout(timer);
timer = null;
}
const result = fn.apply(this, args);
argCallback(result); // 返回值
resolve(result);
lastTime = nowTime; // 重置初始时间(保留上次触发时间)
return;
}
// 最后触发
if(trailing && !timer) {
timer = setTimeout(() => {
timer = null;
lastTime = !leading? 0 : new Date().getTime();
const result = fn.apply(this, args);
argCallback(result);
resolve(result);
}, remainTime);
}
})
}
throttle.cancel = function() {
if(timer) clearTimeout(timer);
timer = null;
lastTime = 0;
}
return throttle;
}深拷贝
JSON.parse(JSON.stringify(obj))
const s1 = Symbol();
const s2 = Symbol();
const obj = {
name: 'zjf',
friends: {
name: 'why'
},
foo: function() { // JSON.parse(JSON.stringify(obj))不能将函数拷贝下来
console.log('foo');
},
[s1]: 'abc', // symbol作为key,JSON.parse(JSON.stringify(obj))拷贝不了
s2: s2 // symbol作为value,JSON.parse(JSON.stringify(obj))拷贝不了
}
// obj.inner = obj; // JSON.parse(JSON.stringify(obj))不支持
const info = JSON.parse(JSON.stringify(obj)); // 简单深拷贝
obj.friends.name = 'zxc';
console.log(info);structuredClone深克隆(Node: V17.4.0及以上版本)
const obj = {
name: 'zjf',
age: 22,
friends: {
name: 'why'
}
}
const newObj = structuredClone(obj);
newObj.friends.name = 'zxc';
console.log(obj.frinends.name); // why递归基本实现
function isObject(originValue) {
const valueType = typeof originValue;
return (value !== null) && (valueType === 'object' || valueType === 'function');
}
function deepClone(originObj) {
// 判断传入的originObj是否是一个对象类型
if(!isObject(originObj)) {
return originObj;
}
const newObject = {};
for(let key in originObj) {
newObject[key] = deepClone(originObj[key])
}
return newObject;
}
const obj = {
name: 'zjf',
age: 22,
friend: {
name: 'why',
address: {
city: '广州'
}
}
}
const newObj = deepClone(obj);
obj.friend.name = 'zxc';
console.log(newObj === obj);
console.log(newObj);其他各种不同数据类型的处理
function isObject(originValue) {
const valueType = typeof originValue;
return (originValue !== null) && (valueType === 'object' || valueType === 'function');
}
function deepClone(originObj) {
// 判断如果是Map类型
if(originObj instanceof Map) {
return new Map([...originObj]);
}
// 判断如果是Set类型
if(originObj instanceof Set) {
return new Set([...originObj]);
}
// 判断如果是symbol类型,返回新symbol
if(typeof originObj === 'symbol') {
return Symbol(originObj.description);
}
// 判断是函数类型,直接使用同一个函数
if(typeof originObj === 'function') {
return originObj;
}
// 判断传入的originObj是否是一个对象类型
if(!isObject(originObj)) {
return originObj;
}
// 针对数组处理
const newObject = Array.isArray(originObj) ? [] : {} ;
for(const key in originObj) {
newObject[key] = deepClone(originObj[key]);
}
// 对Symbol的key进行特殊的处理
const symbolKeys = Object.getOwnPropertySymbols(originObj); // 获取originObj所有的symbol的key值
for(const skey of symbolKeys) {
newObject[skey] = deepClone(originObj[skey]);
}
return newObject;
}
// 要拷贝的对象
let s1 = Symbol("aaa");
let s2 = Symbol("bbb");
const obj = {
name: 'zjf',
age: 22,
friend: {
name: 'why',
address: {
city: '广州'
}
}, // 数组
arr: ["abc", "asd", "aws"],
foo: function() { // 函数类型
console.log('foo function');
}, // Symbol作为key和value
[s1]: 'abc',
s2: s2,
// Set/Map
set: new Set(['aaa', 'bbb', 'ccc']),
map: new Map([['aaa', 'abc'], ['bbb', 'asd']])
}
const newObj = deepClone(obj);
obj.friend.name = 'zxc';
console.log(newObj === obj);
console.log(newObj);
console.log(obj.s2 === newObj.s2);循环引用的处理(WeakMap)
function isObject(originValue) {
const valueType = typeof originValue;
return (originValue !== null) && (valueType === 'object' || valueType === 'function');
}
function deepClone(originObj, map = new WeakMap()) {
// 判断如果是Map类型
if(originObj instanceof Map) {
return new Map([...originObj]);
}
// 判断如果是Set类型
if(originObj instanceof Set) {
return new Set([...originObj]);
}
// 判断如果是symbol类型,返回新symbol
if(typeof originObj === 'symbol') {
return Symbol(originObj.description);
}
// 判断是函数类型,直接使用同一个函数
if(typeof originObj === 'function') {
return originObj;
}
// 判断传入的originObj是否是一个对象类型
if(!isObject(originObj)) {
return originObj;
}
// 针对循环返回对应的原map对象
if(map.has(originObj)) {
return map.get(originObj);
}
// 针对数组处理
const newObject = Array.isArray(originObj) ? [] : {} ;
map.set(originObj, newObject);
for(const key in originObj) {
newObject[key] = deepClone(originObj[key], map);
}
// 对Symbol的key进行特殊的处理
const symbolKeys = Object.getOwnPropertySymbols(originObj); // 获取originObj所有的symbol的key值
for(const skey of symbolKeys) {
newObject[skey] = deepClone(originObj[skey]);
}
return newObject;
}
// 要拷贝的对象
let s1 = Symbol("aaa");
let s2 = Symbol("bbb");
const obj = {
name: 'zjf',
age: 22,
friend: {
name: 'why',
address: {
city: '广州'
}
}, // 数组
arr: ["abc", "asd", "aws"],
foo: function() { // 函数类型
console.log('foo function');
}, // Symbol作为key和value
[s1]: 'abc',
s2: s2,
// Set/Map
set: new Set(['aaa', 'bbb', 'ccc']),
map: new Map([['aaa', 'abc'], ['bbb', 'asd']])
}
obj.self = obj;
const newObj = deepClone(obj);
obj.friend.name = 'zxc';
console.log(newObj === obj);
console.log(newObj);
console.log(obj.s2 === newObj.s2);事件总线
理解
- 自定义事件总线属于一种观察者模式,其中包括三个角色:
- 发布者(Publisher):发出事件(Event);
- 订阅者(Subscriber):订阅事件(Event),并且会进行响应(Handler);
- 事件总线(EventBus):无论是发布者还是订阅者都是通过事件总线作为中台的;
class JFEventBus {
constructor() {
this.eventBus = {};
}
on(eventName, eventCallback, thisArg ) {
let handlers = this.eventBus[eventName];
if(!handlers) {
handlers = [];
this.eventBus[eventName] = handlers;
}
handlers.push({
eventCallback,
thisArg
});
}
emit(eventName, ...payload) {
const handlers = this.eventBus[eventName];
if(!handlers) return;
handlers.forEach(handler => {
handler.eventCallback.apply(handler.thisArg, payload);
})
}
off(eventName, eventCallback) {
const handlers = this.eventBus[eventName];
if(!handlers) return;
const newHandlers = [...handlers];
for(let i = 0; i < newHandlers.length; i++) {
const handle = newHandlers[i];
if(handle.eventCallback === eventCallback) {
const index = handlers.indexOf(handle);
handlers.splice(index, 1);
}
}
}
}
const eventbus = new JFEventBus();
eventbus.on("abc", function() {
console.log('abc1事件被触发');
}, {name: 'zjf'});
const callback = function() {
console.log('abc2事件被触发');
}
eventbus.on("abc", callback, {name: 'zjf'});
eventbus.emit("abc", 123);
eventbus.off("abc", callback);
eventbus.emit("abc")