# 闭包
# 闭包的定义
闭包是一个有权访问另一个作用域变量的函数。闭包本身作为一个它外层函数的返回值,可以访问到外界的变量值,但外界访问不到这个函数内部的变量值。当创建这个外层函数实例时,打印出来的是一个 function,也就是创建了返回的这个闭包(内部函数)。
# 闭包的作用(使用场景)
闭包可以实现永久保存局部变量。
- 记忆函数
- 函数防抖
- 函数节流
- 设置私有变量(private)
- setTimout 内的回调函数
- 柯里化函数
- IIFE(自执行函数)
- 循环赋值(例如 ES5 时期,for 循环保存每轮变量 i)
# 闭包的性质
- 闭包可以维持函数内局部变量,使其得不到释放。
- 在闭包内,可分为内外两层作用域,内层可以访问到外层(包括闭包之外)的变量,而外层无法访问到内层的变量
# 闭包引发的问题
因 Javascript 的垃圾回收机制没有将闭包内的变量视为 “不再使用的变量”,因此内存不会被释放。且闭包携带着其他函数的作用域,因此会相对其他函数占用更多的内存。如果过度使用闭包则会引发内存泄漏,谨慎使用闭包便可预防这种情况。
# 手写闭包
一个简单的累加器
function add(num) { let sum = 0; return function () { sum += num; // 内部sum变量访问外界sum变量 return sum; // 返回即保存,且不会被释放。 }; } let add5 = add(5); // 创建一个累加5的闭包; add5(); // 5 add5(); // 10 add5(); // 15
记忆函数
function memorize(callback) { let cache = []; return function () { let key = arguments.length + Array.prototype.join.call(arguments, ","); if (key in cache) { return cache[key]; } else { return (cache[key] = callback.apply(this, arguments)); } }; }
for 循环输出 i
// 做法1 IIFE
for (var i = 0; i < 5; i++) {
(function (a) {
console.log(a);
})(i);
}
// 做法2
for (var i = 0; i < 5; i++) {
(function (a) {
setTimeout(() => {
console.log(a);
}, 0);
})(i);
}
节流
function throttle2(callback, delay) { let timeout = null; return function (args) { // console.log(timeout); if (timeout) { console.log("你已在5秒内使用过,稍后再试"); return; } console.log("arguments", args); timeout = setTimeout(() => { callback.call(this, args); time = null; // 执行完置空 }, delay); }; } function print(n) { console.log("n===", n); } let tPrint = throttle2(print, 5000); for (let i = 0; i < 5; i++) { tPrint(i); } // arguments 0 // 你已在5秒内使用过,稍后再试 // 你已在5秒内使用过,稍后再试 // 你已在5秒内使用过,稍后再试 // 你已在5秒内使用过,稍后再试 // n=== 0
闭包经典题(字节)
var result = []; var a = 3; var total = 0; function foo(a) { for (var i = 0; i < 3; i++) { result[i] = function () { total += i * a; console.log(total); }; } } // 在上述循环中,foo函数内的a取得是参数中的a,这当中涉及到作用域,当 当前环境使用到a但没有声明该值时,便会往上一级作用域去寻找。 // for()中的i变量为全局变量,因此在三次循环完后,i已经变为3。 result[i]中没有做IIFE处理,因此里面的i取的是全局变量i。所以total每次加的值是 3*1, 3*1 , 3*1 foo(1); result[0](); // 3 result[1](); // 6 result[2](); // 9
柯里化函数
function curry(fn) { // 获取原函数的参数长度 const argLen = fn.length; // 保存预置参数 const presetArgs = [].slice.call(arguments, 1); // 返回一个新函数 return function () { // 新函数调用时会继续传参 const restArgs = [].slice.call(arguments); const allArgs = [...presetArgs, ...restArgs]; if (allArgs.length >= argLen) { // 如果参数够了,就执行原函数 return fn.apply(this, allArgs); } else { // 否则继续柯里化 return curry.call(null, fn, ...allArgs); } }; } function fn(a, b, c) { return a + b + c; } var curried = curry(fn); curried(1, 2, 3); // 6 curried(1, 2)(3); // 6 curried(1)(2, 3); // 6 curried(1)(2)(3); // 6 curried(7)(8)(9); // 24
← 正则表达式的基础使用 ES6之变量声明 →