JS闭包
概念
嵌套函数,内部函数可以引用外部函数的参数和变量。参数和变量不会被垃圾回收机制收回。
1 | function outer() { |
参数也可以
1 | function outer(count = 0) { |
闭包
闭包是 JS 中非常重要一个概念。 JS 中变量是 function 级作用域,也就是说,在 function 中定义的变量可以在 function 内部(包括内部定义的嵌套function 中)使用,而在 function 外部是无法使用的。但是,之前也介绍过,函数就是一块保存了现有数据的内存,只要找到这块内存就可以对其进行调用。因此,如果想办法获取到内部定义的嵌套函数,那样不就可以在外部使用嵌套函数来调用内部定义的局部变量了吗?这种用法就是闭包,例如下面这个例子。
1 | function fl(){ |
这个例子中,函数 f1 中定义了变量 v ,正常情况下在 f1 外面是无法访问 v 的,但是 f1 中嵌套定义的函数口是可以访问 v 的,而且在调用 f1 时会返回函数 f2,这样就可以在 n 外部访问 n 的局部变量 v ,这就是闭包。当然,如果需要还可以在 f2 中直接返回 v 的值,这样就可以在 f1 外部获取 v 的值。
需要注意的是,在使用闭包时,在保存返回函数的变量失效之前定义闭包的 function 会 一直保存在内存中,例如下面的例子 。
1 | function fl(){ |
这个例子中,但函数在打印出 v 的值后又将 v 的值加了 1 ,连续调用 f 函数时会在控制台依次打印出 1 2 3 ,这说明 f1 函数一直在内存中保存着。这是因为保存 f1 返回嵌套函数 f1 的变量 f 是全局变量,它会一直保存在内存中,而 f 所指向的 f2 函数在执行时需要依赖 f1 所以 f1 就会一直保存在内存守。这里需要注意 f2本身因为没有被依赖,所以 f2 并不会一直保存在内存中,通过下面的例子可以清楚地看到这一点。
1 | function fl(){ |
从上面的例子可以看出, f1 中定义的变量 v1 在每次调用时会累加,这说明每次调用时使用的都是原来的数据,而 f2 中定义的变量 v2 则在每次调用时都会创建新的数据。其原理其实非常简单,在函数 f1 执行时会创建一套 f1 的变量数组,在函数 f2 执行时会创建另外一套 f2 的变量数组。按照 JS 中变量作用域链的规则,在 f2 中可以调用执行 f1 时所创建的变量数组,为了 f2 可以正确执行,只要在 f2 还可能被调用的时候执行 f1 时所创建的执行环境(包括变量数组)就不会被释放,因此, f1 中定义的变量 v1 会使用同一个,而 f2 每次执行完之后所创建的执行环境就没用了,会被释放,而在下次执行时又会创建新的执行环境。
闭包有什么作用,防抖可以使用闭包吗
防抖和节流
闭包(Closure)是 JavaScript 中一个强大的特性,它有几个重要的作用:
- 数据封装:
- 闭包允许你将数据和操作这些数据的函数绑定在一起,从而实现数据的封装和保护。
- 访问外部函数的变量:
- 闭包可以访问其外部函数作用域中的变量,即使外部函数已经执行完毕。
- 创建私有变量:
- 通过闭包可以创建私有变量,这些变量只能在闭包内部访问,外部无法直接访问。
- 记忆函数状态:
- 闭包可以记忆函数的状态,即使函数执行完毕后,闭包仍然可以访问这些状态。
- 实现模块模式:
- 闭包常用于实现模块模式,隐藏内部状态和实现细节,只暴露有限的接口。
- 延迟计算:
- 闭包可以用来实现延迟计算,即只有在需要时才计算某个值。
防抖(Debounce)是一种限制函数执行频率的技术,确保函数在指定的时间间隔内最多只执行一次。闭包可以用于实现防抖功能。以下是一个使用闭包实现防抖的简单示例:
- 闭包可以用来实现延迟计算,即只有在需要时才计算某个值。
1 | function debounce(func, wait) { |
在这个例子中,debounce 函数返回一个新的函数,这个新函数内部使用了一个闭包来保存一个 timeout 变量。每次调用这个新函数时,都会清除之前的定时器并重新设置一个新的定时器。这样,只有当最后一次调用后超过 wait 毫秒时,func 函数才会被执行。
闭包在这里的作用是保存了 timeout 变量的状态,使得每次调用返回的函数都能访问到同一个 timeout 变量,从而实现了防抖的效果。如果没有闭包,每次调用可能都会创建一个新的 timeout 变量,导致防抖逻辑失效。
闭包
JavaScript中的闭包(Closure)是一个常见的概念,它指的是一个函数能够记住并访问它的词法作用域,即使这个函数是在当前词法作用域之外执行的。闭包的产生主要是由于JavaScript的词法作用域机制和函数的特性。
闭包的定义
闭包是由函数以及创建该函数的词法环境组合而成的。简单来说,闭包就是能够访问其外部函数作用域中变量的函数。
闭包的形成
闭包的形成通常依赖于以下条件:
- 嵌套函数:一个函数内部定义了另一个函数。
- 外部函数的局部变量:外部函数的局部变量在内部函数中被引用。
- 外部函数返回内部函数:外部函数返回内部函数,使得内部函数在外部函数执行完毕后仍然可以访问外部函数的作用域。
闭包的例子
1 | function createCounter() { |
在这个例子中,createCounter 函数返回了一个内部函数,该内部函数可以访问并修改 createCounter 函数中的局部变量 count。即使 createCounter 函数已经执行完毕,返回的内部函数仍然可以访问 count 变量,这就是闭包的效果。
闭包的应用
- 封装私有变量:闭包可以用来创建私有变量,这些变量只能通过闭包返回的函数访问和修改。
- 延迟执行:闭包可以用于延迟执行某些操作,例如在事件处理或定时器中使用。
- 函数工厂:闭包可以用来创建具有不同行为的函数,例如创建多个不同的计数器。
闭包的缺点
- 内存消耗:由于闭包会持有外部函数的作用域,可能导致外部函数的局部变量无法被垃圾回收,从而增加内存消耗。
- 性能问题:过多的闭包可能会导致性能问题,尤其是在循环中使用闭包时,需要特别注意。
注意事项
- 避免在循环中创建闭包:如果在循环中创建闭包,可能会导致闭包引用的变量不是预期的值。可以使用
let关键字来创建块级作用域,或者使用立即执行函数表达式(IIFE)来解决这个问题. - 合理使用闭包:虽然闭包功能强大,但应合理使用,避免不必要的内存消耗和性能问题.
闭包是JavaScript中一个非常重要的概念,理解闭包有助于更好地掌握JavaScript的函数和作用域机制.
闭包的作用
闭包在JavaScript中非常有用,尤其是在以下场景中:
1. 封装私有变量
闭包可以用于创建私有变量,这些变量只能通过闭包返回的函数访问和修改。这有助于隐藏实现细节,防止外部代码直接访问或修改内部状态。
1 | function createPrivateCounter() { |
2. 延迟执行
闭包可以用于延迟执行某些操作,特别是在事件处理和定时器中。通过闭包,可以确保在延迟执行时仍然能够访问到初始时的变量值。
1 | function setupTimeout() { |
3. 函数工厂
闭包可以用来创建具有不同行为的函数。这种模式在创建多个具有相同结构但不同参数的函数时非常有用。
1 | function createGreeting(greeting) { |
4. 回调函数
在异步编程中,闭包常用于回调函数,以确保在异步操作完成后能够访问到正确的上下文和变量值。
1 | function fetchData(url, callback) { |
5. 模块化编程
闭包可以用于实现模块化编程,通过闭包来封装模块的私有状态和公共接口。
1 | const myModule = (function() { |
6. 缓存和记忆化
闭包可以用于实现缓存机制,通过存储计算结果来避免重复计算,从而提高性能。
1 | function memoize(fn) { |
这些场景展示了闭包在JavaScript中的强大功能和灵活性,帮助开发者实现更复杂和高效的代码结构.
柯里化
柯里化(Currying)是一种将接收多个参数的函数转换为一系列只接收单一参数的函数的技术。它通过逐步接收参数并返回新的函数,直到所有参数都接收完毕后才执行最终的计算。
柯里化的实现
以下是一个通用的柯里化函数实现:
1 | function curry(fn) { |
在这个实现中:
fn.length表示原函数的形参个数。- 如果传入的参数数量达到或超过原函数的形参个数,则直接调用原函数。
- 如果参数数量不足,则返回一个新的函数,继续接收剩余的参数。
柯里化的优点
- 参数复用:通过固定部分参数,可以创建新的函数,使函数调用更加灵活。
- 延迟执行:可以在需要时才提供所有参数,从而延迟函数的执行。
- 提高代码可读性:通过将复杂的多参数函数拆分为多个简单的一元函数,代码结构更加清晰。
柯里化的应用场景
- 参数预设:例如,创建一个固定的问候函数:
1
2
3
4
5function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
const sayHello = curry(greet)("Hello");
console.log(sayHello("Alice")); // "Hello, Alice!" - 动态创建函数:例如,惰性加载事件监听器。
- 构建复杂的数据流管道:在函数式编程中,柯里化可以用于构建复杂的函数组合。
示例
假设有一个求和函数:
1 | function sum(a, b, c) { |
通过柯里化,可以灵活地分步传递参数。
总之,柯里化是一种强大的函数式编程技术,能够使代码更加模块化、可复用,并提高代码的灵活性。
实例
- test函数的作用就是让num值自增(这里涉及到闭包),只不过因为是num++所以最终返回的值是还没有完成本次自增的num值即20,而num本身已经完成自增是为21。
1 | var test = (function() { |