一、闭包是什么?
1、概念
闭包是指在函数内部定义的函数,能够访问到外部函数的变量,并且保持对这些变量的引用,即使外部函数已经执行完毕。闭包形成了一个封闭的作用域,使得内部函数可以访问外部函数的局部变量,从而延长了这些变量的生命周期。
function outer() { let x = 10; function inner() { console.log(x); // 内部函数可以访问外部函数的变量 x } return inner; // 返回内部函数}const closureFunction = outer(); // 调用外部函数,并将内部函数保存在变量中closureFunction(); // 执行保存的内部函数,依然可以访问外部函数的变量 x
二、为什么有闭包?
闭包在 JavaScript 中的存在是因为 JavaScript 采用了词法作用域的机制。词法作用域指的是变量的作用域是在代码编写阶段就确定的,而不是在执行阶段确定的。
当函数被定义时,它会创建一个闭包,保存了函数内部的局部变量和当前作用域链。这个闭包使得函数能够在定义它的词法作用域之外的地方被调用时,仍然能够访问到其内部的变量。
个人(理解记忆)
这个应该是牵扯到作用域的概念,一个内部函数无法访问到外部函数定义的变量,在没有闭包的情况下,函数只能访问其定义时的作用域,而无法在定义后继续访问外部作用域的变量。
比如,你进了一个房间(外部函数),在房间里有一个特殊的盒子(内部函数),你把一些东西放进这个盒子里。
当你走出房间,房间的大门关闭了(外部函数执行完毕),但是这个盒子里的东西你还能记得,而且你可以随时打开这个盒子,取出里面的东西。这个魔法盒子就好比闭包,记住了外部函数的一些东西,就算外部函数执行完毕了,闭包还能保留这些记忆
function outer() { let x = 10; function inner() { console.log(x); // 内部函数尝试访问外部函数的变量 x,但会报错 } inner(); // 直接调用内部函数,无法访问外部函数的变量 x}outer();
三、闭包的优点和缺点
优点:
1. 数据封装
闭包允许将变量隐藏在内部函数中,只暴露必要的接口,提高代码的安全性和可维护性。
function createCounter() { let count = 0; return function() { count++; console.log(count); };}const counter = createCounter();counter(); // 输出: 1counter(); // 输出: 2
2. 保持状态
闭包可以保持外部函数调用时的状态,使得状态在多次调用之间得以保留
function createIncrementer(initialValue) { let value = initialValue; return function() { value++; console.log(value); };}const incrementByTwo = createIncrementer(2);incrementByTwo(); // 输出: 3incrementByTwo(); // 输出: 4
3. 实现局部变量
通过闭包,可以模拟实现局部变量的效果,创建一个局部作用域。
function outer() { let outerVariable = 'I am from outer'; function inner() { let innerVariable = 'I am from inner'; console.log(outerVariable); // 访问外部变量 console.log(innerVariable); // 访问内部变量 } inner();}outer();
4. 函数工厂
闭包允许创建函数工厂,即动态生成具有特定行为的函数
function multiplier(factor) { return function(x) { return x * factor; };}const double = multiplier(2);console.log(double(5)); // 输出: 10
缺点 :
内存消耗: 闭包中的变量不会被垃圾回收,直到闭包不再被引用。如果闭包中包含大量变量或者循环引用,可能导致内存泄漏。
性能问题: 由于闭包涉及维护外部函数的作用域链,可能会导致性能损失,尤其是在嵌套层次较深或循环中。
滥用导致的可读性降低: 过度使用闭包可能导致代码的可读性下降。程序员需要理解整个作用域链,以便正确理解变量的值和生命周期。
可能导致意外的行为: 如果不小心修改了闭包中的变量,可能导致意外的行为,因为这些变量在外部函数执行完毕后仍然存在。
维护困难: 在大型项目中,如果滥用闭包,可能导致难以维护和调试的代码。特别是在多人协作的项目中,过度使用闭包可能增加理解的难度。
四、项目中的使用
防抖、节流、柯里化函数都是闭包
防抖(Debouncing): 防抖是一种技术,用于限制一段时间内函数的调用次数。通常在处理输入框输入、滚动等频繁触发的事件时使用。通过使用闭包来存储计时器,可以确保在一定时间内只执行最后一次函数调用。
function debounce(fn, delay) { let timer; return function() { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, arguments); }, delay); };}
节流(Throttling): 节流是一种控制函数调用频率的技术,确保一定时间内只执行一次函数。也可以使用闭包来存储状态,例如上一次函数调用的时间戳
function throttle(fn, delay) { let lastTime = 0; return function() { const now = Date.now(); if (now - lastTime >= delay) { fn.apply(this, arguments); lastTime = now; } };}
柯里化(Currying): 柯里化是将一个多参数函数转化为一系列单参数函数的过程。通过使用闭包,可以存储每个部分函数的参数,直到所有参数都被收集完成,然后执行原始函数。
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...moreArgs) { return curried.apply(this, args.concat(moreArgs)); }; } };}
五、总结(以上便于理解记忆)
闭包的概念?怎么形成闭包?闭包的优缺点?项目中哪里使用闭包?
闭包就是函数嵌套函数被嵌套的函数叫做闭包函数。外部函数里面嵌套一个内部函数,外部函数在定义一个变量,再将内部函数作为整体作为返回值返回出去,外部定义一个全局变量接受,就能是这个变量生命周期延长,形成闭包。闭包的优点就是有:
数据封装: 闭包可以用于创建私有变量,实现数据封装,防止外部直接访问内部变量。保持状态: 闭包可以保持外部函数调用时的状态,例如计数器等。实现柯里化和高阶函数: 闭包支持函数式编程的概念,实现柯里化和高阶函数等功能。缺点:
内存泄漏: 如果不适当使用,闭包可能导致内存泄漏,因为它会保留外部函数的整个作用域链。性能问题: 闭包涉及维护作用域链,可能导致性能问题,尤其是在嵌套较深的情况下。可读性降低: 过度使用闭包可能导致代码可读性降低,因为读者需要理解整个作用域链。项目中闭包通常用于实现私有变量、保持状态或者创建工厂函数,或者防抖节流。