Home  >  Article  >  Web Front-end  >  A detailed explanation of closures in JavaScript

A detailed explanation of closures in JavaScript

青灯夜游
青灯夜游Original
2023-04-24 17:57:493515browse

JavaScript Closure is an important concept that is widely used in JavaScript programming. Although it can be confusing for beginners, it is one of the key concepts in understanding the core of the JavaScript language. This article takes an in-depth look at JavaScript closures so you understand how they work and how to use them in real-world applications.

A detailed explanation of closures in JavaScript

#What are JavaScript closures?

In JavaScript, a closure means that a function can access variables defined outside it. These variables are often called "free variables" because they are not local to the function, nor are they arguments to the function. Closures can be created inside a function or outside a function.

Every function in JavaScript is a closure because they all have access to free variables. When a function is called, it creates a new execution environment that contains the function's local variables and parameters. The execution context also includes a reference to the scope in which the function is defined. This reference is called the function's "scope chain," which is a linked list of all scope objects that contain the function's definition. [Recommended learning: javascript video tutorial]

When a free variable needs to be accessed inside a function, it will first search whether the variable exists in its own local variables. If it doesn't exist, it continues up the scope chain until it finds the variable. This is the core mechanism of closures.

Simply put, a closure is a function that contains references to external variables. These variables are defined outside the function but can still be accessed and manipulated inside the function. The essence of closure is to encapsulate a function and the external variables it refers to, forming an environment free from external interference, so that the function can access and modify external variables, and these modifications will also be reflected in the variables outside the function.

Understanding how closures work is crucial to writing high-quality JavaScript code, because it allows us to better manage the scope of variables and functions, and implement more complex functionality.

The purpose of closure

Encapsulating variables and functions

Closure can be used to encapsulate variables so that they are not protected from external interference. This is because a closure can define a variable inside a function and create a function outside the function that accesses that variable. This access function can access the variable, but the variable cannot be directly accessed from the outside, thus ensuring the security of the variable.

For example, we can use closures to implement a counter:

function createCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

In this example, we use closures to encapsulate the counter variable count so that it is not subject to external interference. Each time the counter function is called, it returns the next value of the counter.

Caching data

Using closures can cache the calculation results of the function to avoid calculating the same value multiple times, thereby improving the performance of the code. This method is suitable for functions that require a large amount of calculation but whose results do not change frequently, such as the Fibonacci sequence.

Look at a code example below:

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    } else {
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  }
}

function fibonacci(n) {
  if (n <= 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // 输出 55
console.log(memoizedFib(10)); // 输出 55,直接从缓存中读取

In this example, we define a memoize function, which accepts a function as a parameter and returns a closure function. The closure function maintains a cache object cache internally, which is used to save the calculation results of the function. Each time the closure function is called, it generates a unique key value based on the parameters passed in, and attempts to read the calculation result from the cache. If the key value already exists in the cache, the cache result is returned directly, otherwise the passed function is called to calculate the result and the result is saved in the cache. This approach improves the performance of your code by avoiding calculating the same value multiple times.

Achieve modularization

Using closures can achieve modular programming. This method can divide the code into multiple modules, so that each module only focuses on own functions, thereby improving the maintainability and readability of the code. At the same time, closures can also encapsulate public and private variables, avoiding the pollution of global variables.

For example, we can use closures to implement a simple module:

const module = (function() {
  const privateVar = &#39;I am private&#39;;
  const publicVar = &#39;I am public&#39;;
  function privateFn() {
    console.log(&#39;I am a private function&#39;);
  }
  function publicFn() {
    console.log(&#39;I am a public function&#39;);
  }
  return {
    publicVar,
    publicFn
  };
})();

console.log(module.publicVar); // 输出 &#39;I am public&#39;
module.publicFn(); // 输出 &#39;I am a public function&#39;
console.log(module.privateVar); // 输出 undefined
module.privateFn(); // 报错,无法访问私有函数

In this example, we define an immediate execution function that returns an object internally. Objects contain public variables and functions, as well as private variables and functions. In this way, we can split the code into multiple modules, each module only focuses on its own functions, thus improving the maintainability and readability of the code. At the same time, private variables and functions are only visible inside the function, and they cannot be accessed and modified by the outside, thus avoiding the pollution of global variables.

Event processing

The following is an example of using closures for event processing:

function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log(`Clicked ${count} times`);
  }

  function decrement() {
    count--;
    console.log(`Clicked ${count} times`);
  }

  function getCount() {
    return count;
  }

  return {
    increment,
    decrement,
    getCount
  };
}

const counter = createCounter();

document.querySelector(&#39;#increment&#39;).addEventListener(&#39;click&#39;, counter.increment);
document.querySelector(&#39;#decrement&#39;).addEventListener(&#39;click&#39;, counter.decrement);

在这个示例中,我们定义了一个名为createCounter的函数,该函数返回一个对象,该对象包含三个方法:increment,decrement和getCount。increment方法将计数器加1,decrement方法将计数器减1,getCount方法返回当前计数器的值。

我们使用createCounter函数创建了一个计数器对象counter,并将increment方法和decrement方法分别注册为加1和减1按钮的点击事件处理函数。由于increment和decrement方法内部引用了createCounter函数内部的局部变量count,因此它们形成了闭包,可以访问和修改count变量。

这个示例中,我们将计数器对象的逻辑封装在一个函数内部,并返回一个包含方法的对象,这样可以避免全局变量的使用,提高代码的可维护性和可重用性。

函数柯里化

以下是一个使用闭包实现的函数柯里化例子:

function add(x) {
  return function(y) {
    return x + y;
  }
}

const add5 = add(5); // x = 5
console.log(add5(3)); // 输出 8
console.log(add5(7)); // 输出 12

在这个例子中,我们定义了一个名为add的函数,该函数接受一个参数x并返回一个内部函数,内部函数接受一个参数y,并返回x + y的结果。

我们使用add函数创建了一个新的函数add5,该函数的x值为5。我们可以多次调用add5函数,每次传入不同的y值进行求和运算。由于add函数返回了一个内部函数,并且内部函数引用了add函数内部的参数x,因此内部函数形成了一个闭包,可以访问和保留x值的状态。

这个例子中,我们实现了一个简单的函数柯里化,将接收多个参数的函数转化为接收一个参数的函数。函数柯里化可以帮助我们更方便地进行函数复合和函数重用。

异步编程

以下是一个使用闭包实现的异步编程的例子:

function fetchData(url) {
  return function(callback) {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        callback(null, data);
      })
      .catch(error => {
        callback(error, null);
      });
  }
}

const getData = fetchData(&#39;https://jsonplaceholder.typicode.com/todos/1&#39;);
getData(function(error, data) {
  if (error) {
    console.error(error);
  } else {
    console.log(data);
  }
});

在这个例子中,我们定义了一个名为fetchData的函数,该函数接受一个URL参数,并返回一个内部函数。内部函数执行异步操作,请求URL并将响应解析为JSON格式的数据,然后调用传入的回调函数并将解析后的数据或错误作为参数传递。

我们使用fetchData函数创建了一个getData函数,该函数请求JSONPlaceholder API的一个TODO项,并将响应解析为JSON格式的数据,然后将数据或错误传递给回调函数。由于fetchData函数返回了一个内部函数,并且内部函数引用了fetchData函数内部的URL参数和回调函数,因此内部函数形成了闭包,可以访问和保留URL参数和回调函数的状态。

这个例子中,我们使用了异步编程模型,通过将回调函数作为参数传递,实现了在异步请求完成后执行相关的操作。使用闭包可以方便地管理异步请求和相关的状态,提高代码的可读性和可维护性。

闭包的缺陷

JS 闭包具有许多优点,但也有一些缺点,包括:

内存泄漏问题

由于闭包会将外部函数的局部变量引用保存在内存中,因此如果闭包一直存在,外部函数的局部变量也会一直存在,从而导致内存泄漏。

在 JavaScript 中,闭包是指一个函数能够访问并操作其父级作用域中的变量,即便该函数已经执行完毕,这些变量仍然存在。由于闭包会引用父级作用域中的变量,因此,这些变量不会在函数执行完毕时被垃圾回收机制回收,从而占用了内存资源,这就是闭包引起内存泄漏的原因。

以下是一个闭包引起内存泄漏的示例:

function myFunc() {
  var count = 0;
  setInterval(function() {
    console.log(++count);
  }, 1000);
}

myFunc();

在这个示例中,myFunc 函数中定义了一个变量 count,然后创建了一个计时器,在每秒钟打印 count 的值。由于计时器函数是一个闭包,它会保留对 myFunc 中的 count 变量的引用,这意味着即使 myFunc 函数执行完毕,计时器函数仍然可以访问 count 变量,从而阻止 count 变量被垃圾回收机制回收。如果我们不停地调用 myFunc 函数,将会创建多个计时器函数,每个函数都会占用一定的内存资源,最终会导致内存泄漏。

性能问题

由于闭包会在每次函数调用时创建新的作用域链,因此会增加函数的内存消耗和运行时间。在循环中创建闭包时,尤其需要注意性能问题。

在JavaScript中,每当创建一个函数时,都会为该函数创建一个新的作用域链。函数作用域链是一个指向其父级作用域的指针列表,其中包含了该函数能够访问的变量和函数。

A closure refers to a function defined inside a function, which can access the variables and parameters of the external function, and can continue to use these variables and parameters after the external function is called. When a closure is created, it saves a reference to the outer function's scope chain so that it can be accessed when needed.

Because the closure saves a reference to the outer function scope chain, a new scope chain is created on each function call. This is because each call to a function creates a new function scope chain, even if the function is created by the same closure. This means that each closure has its own scope chain, and each call to the closure creates a new scope chain.

This is one of the reasons why you need to be careful when using closures. Since each call to a closure creates a new scope chain, this can cause memory consumption and performance issues. In some cases, it may be necessary to manually release the closure's resources to avoid memory leak issues.

Security Issues

Since closures can access local variables of external functions, if private data is accidentally stored in local variables, it may be accessed and accessed by the closure. modification, resulting in security issues.

Readability issues

Because closures extend the lifetime of variables and implicitly pass data, they can make code difficult to understand and debug, especially This is when nesting multiple levels of functions.

Summary

Therefore, although closure is a powerful programming technique, you need to pay attention to the above shortcomings when using it, and choose the appropriate application scenario and programming style. , to ensure code maintainability and performance.

For more programming-related knowledge, please visit: Programming Teaching! !

The above is the detailed content of A detailed explanation of closures in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn