主题
js工具函数
一、关于函数
1、高阶函数
高阶函数有两个特点:
- 将函数作为参数的函数(回调)
- 返回值是一个函数的函数
只要满足以上两个特点中的一个的函数,就是高阶函数
1.1、before函数
高阶函数 before 函数是一个包装函数。也叫装饰器函数,在保证原有函数fn的逻辑不变的前提下,对fn进行包装扩展,以扩展更多功能
js
function fn(a, b, c) {
console.log("todo...", a, b, c);
}
// 将before挂到Function原型上而不是fn原型上,避免了每次调用都要new 一下fn
Function.prototype.before = function (beforeFn) {
return (...args) => {
beforeFn(); // 先执行回调
this(...args); // 再执行fn方法(因为这里运用了箭头函数,所以this指向before调用者fn)
};
};
// 使用 -> 返回了一个函数
let newFn = fn.before(() => {
console.log("fn before...");
});
// 只有调用了返回的函数,才会执行内部逻辑,
newFn("a", "b", "c");
/*
输出:
fn before...
todo... a b c
*/AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,其实就是给原函数增加一层,不用管原函数内部实现
1.2、类型检测函数
类型检测的方法有4个typeof、constroctor、instanceof、Object.prototype.toString.call;
- typeof 不能区分对象类型
- controctor 是判断构造函数
- instanceof 是判断实例
- Object.protortpe.toString.call 很准确
类型检测方法
js
function isType(value,typeing) {
return Object.prototype.toString.call(value) === `[object ${typeing}]`
}
isType(123,'Number') // true缺点: 不方便,不健壮。若是参数typeing传入错误就凉凉
改进 1、不让用户输入类型 2、创建一个utils,封装好各种方法,可以直接调用 3、函数分步传递参数,将函数拆分成功能更具体化的函数
js
function isType(value,typeing) {
return Object.prototype.toString.call(value) === `[object ${typeing}]`
}
let utils = {};
['Number','String','Boolean','Array','Object'].forEach(typeing=>{
utils[`is`+typeing] = function(val) {
return isType(val,typeing)
}
})
console.log(utils.isString('hello')); // true
console.log(utils.isString(123)); // false1.3、柯里化函数
柯里化函数也是一个高阶函数,当传出的参数个数没有达到需要的参数总个数时,会将传入的参数进行缓存,直到参数个数满足要求才会输出结果
js
// 这个函数的入参个数是固定的
function add(a, b, c, d, e) {
return a + b + c + d + e;
}
// 柯理化函数
function curring(fn, arg = []) {
let len = fn.length; // 实际需要的参数个数
return (...args) => {
let arr = [...arg, ...args]; // 我们要收集的每次传入的参数
if (arr.length === len) {
return fn(...arr);
}
return curring(fn, arr);
};
}
// 柯理化函数,每次入参都是一个参数
let r = curring(add)(1)(2)(3)(4)(5); // 15
// 偏函数,每次入参个数不定
let p = curring(add)(1, 2)(3, 4, 5); // 15柯理化类型校验函数
js
// 柯理化函数
function curring(fn, arg = []) {
let len = fn.length; // 实际需要的参数个数
return (...args) => {
let arr = [...arg, ...args]; // 我们要收集的每次传入的参数
if (arr.length === len) {
return fn(...arr);
}
return curring(fn, arr);
};
}
// 校验类型方法
function isType(typeing, val) {
return Object.prototype.toString.call(val) === `[object ${typeing}]`;
}
let utils = {};
["String", "Number", "Boolean"].forEach((typeing) => {
utils["is" + typeing] = curring(isType)(typeing);
});
utils.isString(123); // true
utils.isBoolean(true); // true1.4、after函数
应用场景:有一堆异步函数在执行(ajax),我们想在这堆异步函数全部执行完成后进行某项操作,比如进行页面渲染,但是,异步函数返回结果的时间是不确定的,这时,after就派上用场了。
具体实现:
js
const fs = require('fs');
fs.readFile("./a.txt", "utf8", (err, data) => {
console.log("a.txt读取完毕");
fn();
});
fs.readFile("./b.txt", "utf8", (err, data) => {
console.log("b.txt读取完毕");
fn();
});
// 这里的2表示有两个异步方法,当after被调用了两次后就会执行回调函数
let fn = after(2, () => {
console.log("文件读取完毕");
// todo...
});
// after核心方法
function after(timer, callback) {
return function () {
if (--timer === 0) {
callback();
}
};
}二. 常用类型检测
1. 判断可迭代对象
例如:数组、字符串...
js
function isIterable(val) {
return val !== null && typeof val[Symbol.iterator] === "function";
}2. 判断Promise
如果一个变量是 Object, 有 then 和 catch 方法, 就认为是 Promise
js
function isPromise(val) {
return (
val !== null &&
typeof val === "object" &&
typeof val.then === "function" &&
typeof val.catch === "function"
);
}三.其他
判断当前环境的node版本是否符合预期
前提, 项目中的package.json文件中应该存在engines节点
json
{
"name": "demo",
"version": "1.0.0",
.......
"engines": {
"node": "^12.0.0 || >= 14.0.0"
}
}具体检测方法
js
const semver = require("semver"); //
const chalk = require("chalk"); // 粉笔
// 检查Nodejs版本号
function checkNodeVersion(wanted, id) {
if (!semver.satisfies(process.version, wanted, { includePrerelease: true })) {
console.log(
chalk.red(
"You are using Node " +
process.version +
", but this version of " +
id +
" requires Node " +
wanted +
".\nPlease upgrade your Node version."
)
);
process.exit(1);
}
}
/* 使用 */
const requiredVersion = require("../package.json").engines.node;
checkNodeVersion(requiredVersion, "demo");四. 防抖函数 (debounce)
防抖函数的作用是在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。常用于搜索框输入、窗口 resize、按钮点击等场景。
4.1、基础版防抖函数
js
function debounce(fn, delay) {
let timer = null;
return function (...args) {
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 设置新的定时器
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}使用示例:
js
// 搜索框输入防抖
const searchInput = document.getElementById("search");
const handleSearch = debounce(function (e) {
console.log("搜索:", e.target.value);
// 执行搜索逻辑...
}, 500);
searchInput.addEventListener("input", handleSearch);4.2、增强版防抖函数(支持立即执行)
支持 leading(立即执行)和 trailing(延迟执行)选项:
js
function debounce(fn, delay, options = {}) {
let timer = null;
let leadingTimer = null;
const { leading = false, trailing = true } = options;
return function (...args) {
const context = this;
const callNow = leading && !timer;
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 如果设置了 leading,且当前没有定时器,立即执行
if (callNow) {
fn.apply(context, args);
// 设置一个标志,防止在 delay 时间内再次立即执行
timer = setTimeout(() => {
timer = null;
}, delay);
} else if (trailing) {
// 延迟执行
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, delay);
}
};
}使用示例:
js
// 立即执行版本(leading: true)
const handleClick = debounce(
function () {
console.log("按钮被点击");
},
1000,
{ leading: true, trailing: false }
);
// 延迟执行版本(默认,trailing: true)
const handleResize = debounce(
function () {
console.log("窗口大小改变");
},
300,
{ trailing: true }
);4.3、完整版防抖函数(支持取消和立即执行)
js
function debounce(fn, delay, options = {}) {
let timer = null;
let result;
const { leading = false, trailing = true } = options;
const debounced = function (...args) {
const context = this;
const callNow = leading && !timer;
// 清除之前的定时器
if (timer) {
clearTimeout(timer);
}
// 如果设置了 leading,且当前没有定时器,立即执行
if (callNow) {
result = fn.apply(context, args);
// 设置一个标志,防止在 delay 时间内再次立即执行
timer = setTimeout(() => {
timer = null;
}, delay);
} else if (trailing) {
// 延迟执行
timer = setTimeout(() => {
result = fn.apply(context, args);
timer = null;
}, delay);
}
return result;
};
// 取消防抖
debounced.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
// 立即执行
debounced.flush = function (...args) {
if (timer) {
clearTimeout(timer);
timer = null;
return fn.apply(this, args);
}
return result;
};
return debounced;
}使用示例:
js
const handleSearch = debounce(function (keyword) {
console.log("搜索:", keyword);
return `搜索结果: ${keyword}`;
}, 500);
// 正常使用
handleSearch("JavaScript");
// 取消防抖
handleSearch.cancel();
// 立即执行
const result = handleSearch.flush("TypeScript");
console.log(result);4.4、应用场景
- 搜索框输入:用户输入停止 500ms 后再执行搜索
- 窗口 resize:窗口大小改变停止 300ms 后再执行回调
- 按钮点击:防止用户快速重复点击
- 滚动事件:滚动停止后再执行某些操作
- 表单验证:用户输入停止后再进行验证
五. 节流函数 (throttle)
节流函数用于限制高频事件在固定时间窗口内只执行一次,常用于滚动、窗口大小改变、按钮快速点击等高频操作,避免频繁触发造成的性能问题。
5.1、时间戳版节流(leading 触发)
js
function throttle(fn, wait) {
let lastInvokeTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastInvokeTime >= wait) {
lastInvokeTime = now;
return fn.apply(this, args);
}
};
}特点:进入时立即执行一次,停止触发后不再补尾。
5.2、定时器版节流(trailing 触发)
js
function throttle(fn, wait) {
let timer = null;
return function (...args) {
if (timer) return;
const context = this;
timer = setTimeout(() => {
timer = null;
fn.apply(context, args);
}, wait);
};
}特点:进入时不立即执行,停止触发后补执行一次。
5.3、可配置版节流(支持 leading/trailing、取消与立即执行)
js
function throttle(fn, wait, options = {}) {
let timer = null;
let lastInvokeTime = 0;
let leadingResult;
const { leading = true, trailing = true } = options;
const later = (context, args) => {
timer = null;
lastInvokeTime = Date.now();
return fn.apply(context, args);
};
const throttled = function (...args) {
const context = this;
const now = Date.now();
if (!lastInvokeTime && leading === false) {
lastInvokeTime = now;
}
const remaining = wait - (now - lastInvokeTime);
if (remaining <= 0 || remaining > wait) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastInvokeTime = now;
leadingResult = fn.apply(context, args);
return leadingResult;
}
if (!timer && trailing !== false) {
timer = setTimeout(() => later(context, args), remaining);
}
return leadingResult;
};
throttled.cancel = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastInvokeTime = 0;
};
throttled.flush = function (...args) {
if (timer) {
clearTimeout(timer);
const context = this;
timer = null;
lastInvokeTime = Date.now();
return fn.apply(context, args);
}
return leadingResult;
};
return throttled;
}使用示例:
js
// 滚动事件:每 200ms 触发一次
const onScroll = throttle(() => {
console.log('scroll', window.scrollY);
}, 200);
window.addEventListener('scroll', onScroll);
// 配置版:进入时执行,停止触发后也补一次
const onResize = throttle(() => {
console.log('resize');
}, 300, { leading: true, trailing: true });
window.addEventListener('resize', onResize);
// 取消与立即执行
onResize.cancel();
onResize.flush();5.4、选择建议
- 高频 UI 反馈(按钮点击):时间戳版(leading)
- 日志/上报等收敛:定时器版(trailing)
- 需要首尾皆可控:可配置版(leading/trailing)
