JS中的惰性函数(Lazy Function)其实是一种非常巧妙的性能优化设计模式,也就是利用js的动态重写函数 的思想,用第一次调用时微乎其微的代价,换取了后续所有调用的极致性能,它的核心思想非常直白:让函数在第一次执行时"记住"环境特性或计算结果,并在后续调用中直接走捷径,不再做无意义的重复判断或计算。
你可以把它想象成下班回家认路:第一天到一个新小区,你需要停下来辨认方向、确认门牌号(这是第一次执行的"脏活累活");但如果你住了十年,每天回家还在每个路口重新认路就太荒谬了。惰性函数就是让你"第一次辛苦认路,以后闭着眼走直线"。
核心原理:函数自我重写
在 JavaScript 中,函数是一等公民,这意味着函数可以在运行时被重新赋值和修改。惰性函数正是利用了这一特性,在函数内部将自己重写为一个优化后的"精简版"函数。
在 JS 中,实现惰性函数主要有以下两种经典写法:
1. 函数内直接重写(延迟执行版)
这种写法在第一次调用时才进行环境检测和函数重写。
javascript
function createXHR() {
// 第一次进入这里,做繁重的环境检测
if (window.XMLHttpRequest) {
// 如果是标准浏览器,将 createXHR 重写为直接返回新对象的函数
createXHR = function () {
return new XMLHttpRequest();
};
} else {
// 如果是古董 IE,重写为兼容版本
createXHR = function () {
return new ActiveXObject("Microsoft.XMLHTTP");
};
}
// 重写完后,手动调用一次新函数,保证第一次调用也能拿到正确的结果
return createXHR();
}
运行流程: 第一次调用 createXHR() 时,它进入原函数体做判断,把全局的 createXHR 指针指向新函数,然后返回结果。以后再调用 createXHR(),它就已经是那个精简版的新函数了,直接返回结果,完全跳过了 if/else 判断。
2. 闭包 + 立即执行(立即执行版)
这种写法在代码加载阶段(函数被赋值时)就立刻执行判断,并返回最终的函数形态。
javascript
const addEvent = (function () {
// 这个外层自执行函数只跑一遍
if (document.addEventListener) {
// 标准浏览器,直接返回优化后的函数
return function (el, type, handler) {
el.addEventListener(type, handler, false);
};
} else if (document.attachEvent) {
// 古董 IE,返回兼容版函数
return function (el, type, handler) {
el.attachEvent("on" + type, handler);
};
}
})();
运行流程: addEvent 在定义时,外层函数就立即执行并根据环境返回了最终形态。后续无论调用多少次 addEvent,它都是最终那个没有判断逻辑的函数。
经典应用场景
- 浏览器兼容性检测 :比如检测
addEventListener、localStorage或IntersectionObserver等 API 是否存在。因为浏览器的运行环境在页面打开那一刻就已经确定了,不会中途改变,完全没必要每次调用都去扫描一遍。 - 单次初始化逻辑 :比如生成一次性的复杂正则表达式、拉取远程配置后把
init函数替换成空函数(防止重复初始化)。 - 复杂计算或资源加载缓存:对于一些第一次计算非常耗时的操作,可以在第一次算出结果后缓存起来,后续直接返回缓存值。
概念辨析:惰性函数 vs 惰性求值
在学习过程中,你可能会遇到另一个相似的概念叫**"惰性求值(Lazy Evaluation)"**。这两者虽然都带"惰性",但解决的问题完全不同:
- 惰性函数(Lazy Function) :侧重于一次判断,终身受益。通过重写函数来消除重复的条件判断,提升高频调用时的执行效率。
- 惰性求值(Lazy Evaluation) :侧重于按需计算,节省资源。通常利用 ES6 的生成器来实现,推迟昂贵计算或处理无限数据流,只有当真正需要某个值时才去计算它(例如处理超大数组的过滤和映射)。
掌握惰性函数,能让你在处理跨浏览器兼容或高频工具函数时,写出性能更极致、逻辑更优雅的代码。