Introduction
What is a closure in JavaScript? This is really a hard question, and I see lots of different answers on the internet. I list some of them below:
- A closure is a function that has access to the outer function’s variables.
- A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope. In JavaScript, closures are created every time a function is created, at function creation time. - from MDN
Lexical scoping
在JavaScript中,词法作用域(Lexical Scoping)是一种确定变量作用域的规则。词法作用域基于变量在代码中声明的位置来决定其可见性和生命周期。具体来说,词法作用域是在编写代码时就确定下来的,它取决于函数和块(如if语句或循环)的嵌套结构,而不是在运行时动态确定的。
词法作用域的特点
静态确定:词法作用域在代码编写阶段就已经确定。编译器在解析代码时会创建一个作用域链,这个链决定了在何处查找变量。
基于嵌套:作用域是根据函数和块的嵌套关系来定义的。内层作用域可以访问外层作用域中的变量,但外层作用域不能访问内层作用域中的变量。
函数优先:在JavaScript中,函数创建了自己的作用域,即使这个函数没有立即执行,它的作用域也是在定义时就确定的。
词法作用域的例子
下面是一个简单的例子来说明词法作用域:
1 | function outerFunction(outerArg) { |
词法作用域是闭包的基础,因为闭包是在词法作用域的基础上实现的。
闭包的特性
- 数据封装性 - 可以用来封装私有变量,模拟模块化。
- 保持状态 - 保持函数的状态,比如计数器。防抖和节流使用的都是这个特性。
Use case
Data encapsulation(private variables)
In the follow example, the inner function increase
has access to the outer function’s variable count
. The variable count
is private and cannot be accessed from outside the function createCounter
. But you can still change count
by invoking the increase
function.
1 | function createCounter() { |
Note that
1 | return { |
is shorthand for
1 | return { |
We can also return the function increase
directly.(This is not work when you have multiple functions to return, you must use the object literal in that case.)
1 | function createCounter() { |
And you should also change the way to invoke the function since you return an anonymous function. so there is no increase
property in the counter
object.
1 | const counter = createCounter(); |
Simulate module(Modernization)
In the following example, we can simulate a module by using closure. The private variables and methods are defined inside the outer function MyModule
. The public variables and methods are returned as an object literal. So the private variables and methods are encapsulated and cannot be accessed from outside the function. The public variables and methods can be accessed by invoking the returned object.
1 | function MyModule() { |
Event handler(or callback)
Some event handler or callback functions use closure to access the outer function’s variables.
In the following example, the event handler for click
event is an anymous function. The function has access to the outer function’s variable count
.
1 |
|
Design patter - Singleton
In the following code, we use a local variable instance
to check whether the instance has been created or not. The instance
variable must memorize its state during different calls to the getInstance
function. So we use closure to achieve this.
1 | const Singleton = (function () { |
Debounce
防抖和节流都是为了解决在短段时间内大量触发某个函数执行而导致的性能问题。
防抖的原理
事件处理函数在事件触发一段时间后(比如500ms)再执行,如果在此时间内(比如500ms)事件再次触发,则重新计时。
防抖的应用:
输入框实时搜索,如果不加防抖处理的话,用户每输入一个字符就会调用一次接口,极大的浪费带宽和后端服务器资源。加入防抖后,用户停止输入一段时间后,才调用接口。
1 | function debounce(func, wait) { |
注意,这个实现比较简单,没有考虑到immediate
参数,即是否立即执行函数。如果要实现immediate
参数,可以参考下面的代码:
1 | function debounce(func, wait, immediate = false) { |
Throttle
节流是控制某个事件在一段时间内只触发一次函数调用,经常用在处理用户频繁触发的事件中,比如resize、scroll、mousemove等。此类事件如果不加控制,每秒会触发上百次,极大浪费资源。
以下代码是一个节流函数的实现,它确保在每300ms响应一次resize事件。
1 | function throttle(func, wait) { |
注意,节流函数的另一个实现方式是不是用setTimeout
函数,如下:这个版本的好处是,当事件第一次发生时,立即执行处理函数,而上面使用setTimeout
函数的版本则要等待wait
时间后才执行处理函数。
1 | function throttle(func, wait) { |
Currying
在JavaScript中,柯里化(Currying)是一种将使用多个参数的函数转换成一系列使用单一参数的函数的技术。通过柯里化,我们可以将一个多参数的函数逐步转化为一系列单参数的函数。每个函数都返回一个新的函数,直到所有参数都被提供,最终返回结果。
1 | add(a, b) ===> curriedAdd(a)(b) |
1 | function curry(func) { |
实际应用
参数复用:通过柯里化,可以预先设置好某些参数,然后得到一个预设了部分参数的新函数。这对于创建特定配置的函数非常有用。
1 | const add5 = curriedAdd(5); |
- 延迟执行:柯里化允许你在需要的时候才完成函数的执行,这在处理异步操作或事件处理时特别有用。
- 简化高阶函数:在处理高阶函数时,柯里化可以帮助简化代码,使得函数更易于理解和测试。
- 函数组合:柯里化可以与其他函数式编程概念如函数组合结合使用,以构建复杂的逻辑链路,同时保持代码清晰简洁。
- 部分应用:柯里化有时也被称作部分应用的一种形式,尽管严格来说二者有所不同。部分应用是指固定一个或多个参数的值,而柯里化则是逐步接收参数的过程。
柯里化是一种强大的技术,尤其是在函数式编程范式中,它能够帮助开发者写出更加模块化和可重用的代码。