问题:闭包产生的三个条件
- 必须是嵌套关系
function A() { function B() { } }
- 内部函数引用了外部函数中的数据(属性、函数)
例如:function A() { var a = "a"; function B() { console.log(a); } }
- 执行外部函数(也可理解为定义内部函数)
function A() { var a = "a"; function B() { console.log(a); } } A();
再放一段经典的产生闭包的代码:
function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); //2
一个产生闭包的关键角色:垃圾回收器
正常情况下foo()函数在执行结束以后,foo()整个的作用域都会被垃圾回收器销毁。但是var baz = foo()这里变量baz是foo()函数返回的bar()函数的引用,且bar()中还使用了foo()的变量,因此foo()函数的作用域会一直存在,且可以在foo()函数作用域之外还可以访问foo()的作用域。这样就产生了一个闭包。
闭包的最大用处有两个:一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。→ 优点:
1.保护函数内的变量安全
2.在内存中维持一个变量(用的太多就变成了缺点,占内存) ;
3. 逻辑连续,当闭包作为另一个函数调用的参数时,避免你脱离当前逻辑而单独编写额外逻辑。
4. 方便调用上下文的局部变量。
5. 加强封装性,可以达到对变量的保护作用。
→ 缺点:
1.常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
2.还有有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅因为它常驻内存,更重要的是,对闭包的使用不当会造成无效内存的产生。
→ 特性:
1. 函数嵌套函数
2. 内部函数可以访问外部函数的变量
3. 参数和变量不会被回收。用闭包实现防抖和节流:
防抖简单来讲就是 当一个持续事件( 如:onscroll )一直执行时,若我们有一个事件触发函数( 如:console.log(1)),我们不想让这个函数一直触发,而是当事件停止后再触发,这就叫防抖
function debounce(fn,delay){ let timer = null //借助闭包 return function() { if(timer){ clearTimeout(timer) } timer = setTimeout(fn,delay) } } function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滚动条位置:' + scrollTop); } window.onscroll = debounce(showTop,1000)
如上,我们不会在滚动条滚动时一直打印,而是在停止后打印一次高度。
节流
类似节流,如果我们想在事件一直发生时间接性的 、有规律的触发一个函数,而不是只在结束时触发一次,这就叫节流
function throttle(fn,delay){ let valid = true return function() { if(!valid){ //休息时间 暂不接客 return false } // 工作时间,执行函数并且在间隔期内把状态位设为无效 valid = false setTimeout(() => { fn() valid = true; }, delay) } } /* 请注意,节流函数并不止上面这种实现方案, 例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。 也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样 */ // 以下照旧 function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滚动条位置:' + scrollTop); } window.onscroll = throttle(showTop,1000)