>  기사  >  웹 프론트엔드  >  JavaScript의 클로저에 대한 자세한 설명

JavaScript의 클로저에 대한 자세한 설명

青灯夜游
青灯夜游원래의
2023-04-24 17:57:493451검색

JavaScript 클로저는 JavaScript 프로그래밍에서 널리 사용되는 중요한 개념입니다. 초보자에게는 혼란스러울 수 있지만 JavaScript 언어의 핵심을 이해하는 데 있어 핵심 개념 중 하나입니다. 이 기사에서는 JavaScript 클로저에 대해 자세히 알아보고 실제 애플리케이션에서 작동 방식과 사용 방법을 이해할 것입니다.

JavaScript의 클로저에 대한 자세한 설명

JavaScript 클로저란 무엇인가요?

JavaScript에서 클로저는 함수가 외부에 정의된 변수에 액세스할 수 있음을 의미합니다. 이러한 변수는 함수에 국한되지도 않고 함수에 대한 인수도 아니기 때문에 종종 "자유 변수"라고 합니다. 클로저는 함수 내부 또는 함수 외부에서 생성될 수 있습니다.

JavaScript의 모든 함수는 모두 자유 변수에 접근할 수 있기 때문에 클로저입니다. 함수가 호출되면 함수의 지역 변수와 매개변수를 포함하는 새로운 실행 환경이 생성됩니다. 실행 컨텍스트에는 함수가 정의된 범위에 대한 참조도 포함됩니다. 이 참조를 함수의 정의를 포함하는 모든 범위 개체의 연결된 목록인 함수의 "범위 체인"이라고 합니다. [추천 학습: javascript 비디오 튜토리얼]

함수 내에서 자유 변수에 접근해야 할 때, 먼저 해당 변수가 자신의 지역 변수에 존재하는지 확인합니다. 존재하지 않으면 변수를 찾을 때까지 범위 체인을 계속 진행합니다. 이것이 클로저의 핵심 메커니즘입니다.

간단히 말하면, 클로저는 외부 변수에 대한 참조를 포함하는 함수입니다. 이러한 변수는 함수 외부에서 정의되지만 여전히 함수 내부에서 액세스하고 조작할 수 있습니다. 클로저의 본질은 함수와 그것이 참조하는 외부 변수를 캡슐화하여 외부 간섭이 없는 환경을 형성하여 함수가 외부 변수에 접근하고 수정할 수 있도록 하는 것이며, 이러한 수정은 함수 외부의 변수에도 반영됩니다.

클로저 작동 방식을 이해하는 것은 고품질 JavaScript 코드를 작성하는 데 중요합니다. 이를 통해 변수와 함수의 범위를 더 잘 관리하고 더 복잡한 기능을 구현할 수 있습니다.

클로저 사용

변수 및 함수 캡슐화

클로저는 변수가 외부 간섭을 받지 않도록 캡슐화하는 데 사용할 수 있습니다. 이는 클로저가 함수 내부에 변수를 정의하고 해당 변수에 액세스하는 함수 외부에 함수를 생성할 수 있기 때문입니다. 이 접근 기능은 변수에 접근할 수 있으나, 외부에서 변수에 직접 접근할 수 없으므로 변수의 보안이 보장됩니다.

예를 들어, 클로저를 사용하여 카운터를 구현할 수 있습니다:

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

이 예에서는 클로저를 사용하여 카운터 변수 개수를 캡슐화하여 외부 간섭을 받지 않도록 합니다. 카운터 함수가 호출될 때마다 카운터의 다음 값을 반환합니다.

데이터 캐싱

클로저를 사용하면 함수의 계산 결과를 캐시하여 동일한 값을 여러 번 계산하는 것을 방지하여 코드 성능을 향상시킬 수 있습니다. 이 방법은 피보나치 수열과 같이 계산량이 많지만 결과가 자주 변하지 않는 함수에 적합합니다.

아래 코드 예제를 보세요:

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,直接从缓存中读取

이 예제에서는 함수를 매개변수로 받아들이고 클로저 함수를 반환하는 memoize 함수를 정의합니다. 클로저 함수는 함수의 계산 결과를 저장하는 데 사용되는 캐시 개체 캐시를 내부적으로 유지합니다. 클로저 함수가 호출될 때마다 전달된 매개변수를 기반으로 고유한 키 값을 생성하고 캐시에서 계산 결과를 읽으려고 시도합니다. 키 값이 캐시에 이미 존재하는 경우 캐시 결과를 직접 반환하고, 그렇지 않은 경우 전달된 함수를 호출하여 결과를 계산하고 결과를 캐시에 저장합니다. 이 접근 방식은 동일한 값을 여러 번 계산하는 것을 방지하여 코드 성능을 향상시킵니다.

모듈화 달성

클로저를 사용하면 모듈형 프로그래밍을 구현할 수 있습니다. 이 방법을 사용하면 코드를 여러 모듈로 나눌 수 있으므로 각 모듈은 해당 기능에만 집중할 수 있으므로 코드의 유지 관리성과 안전성이 향상됩니다. 동시에 클로저는 전역 변수의 오염을 방지하면서 공개 및 비공개 변수를 캡슐화할 수도 있습니다.

예를 들어, 클로저를 사용하여 간단한 모듈을 구현할 수 있습니다:

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(); // 报错,无法访问私有函数

이 예에서는 내부적으로 객체를 반환하는 즉시 실행 함수를 정의합니다. 객체에는 공용 변수와 함수뿐만 아니라 개인 변수와 함수도 포함됩니다. 이러한 방식으로 코드를 여러 모듈로 나눌 수 있으며, 각 모듈은 자체 기능에만 중점을 두어 코드의 유지 관리성과 가독성을 향상시킵니다. 동시에 개인 변수와 함수는 함수 내부에서만 볼 수 있고 외부에서는 접근 및 수정할 수 없으므로 전역 변수의 오염을 피할 수 있습니다.

이벤트 처리

다음은 이벤트 처리에 클로저를 사용하는 예입니다.

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

클로저란 외부 함수의 변수와 매개변수에 액세스할 수 있고 외부 함수가 호출된 후에도 이러한 변수와 매개변수를 계속 사용할 수 있는 함수 내부에 정의된 함수를 의미합니다. 클로저가 생성되면 필요할 때 액세스할 수 있도록 외부 함수의 범위 체인에 대한 참조를 저장합니다.

클로저는 외부 함수의 범위 체인에 대한 참조를 보유하므로 각 함수 호출마다 새 범위 체인이 생성됩니다. 이는 함수가 동일한 클로저에 의해 생성되더라도 함수를 호출할 때마다 새로운 함수 범위 체인이 생성되기 때문입니다. 즉, 각 클로저에는 자체 범위 체인이 있고 클로저에 대한 각 호출은 새 범위 체인을 생성합니다.

이것이 클로저를 사용할 때 주의해야 하는 이유 중 하나입니다. 클로저를 호출할 때마다 새로운 범위 체인이 생성되므로 메모리 소비 및 성능 문제가 발생할 수 있습니다. 어떤 경우에는 메모리 누수 문제를 피하기 위해 클로저의 리소스를 수동으로 해제해야 할 수도 있습니다.

보안 문제

클로저는 외부 함수의 로컬 변수에 접근할 수 있으므로 개인 데이터가 실수로 로컬 변수에 저장되면 클로저에 의해 접근 및 수정되어 보안 문제가 발생할 수 있습니다.

가독성 문제

클로저는 변수의 수명을 연장하고 데이터를 암시적으로 전달하기 때문에 특히 여러 수준의 함수를 중첩할 때 코드를 이해하고 디버깅하기 어렵게 만들 수 있습니다.

요약

따라서 클로저는 강력한 프로그래밍 기술이지만 이를 사용할 때는 위의 단점에 주의해야 하며 코드의 유지 관리 가능성과 성능을 보장하기 위해 적절한 응용 프로그램 시나리오와 프로그래밍 스타일을 선택해야 합니다.

더 많은 프로그래밍 관련 지식을 보려면 프로그래밍 교육을 방문하세요! !

위 내용은 JavaScript의 클로저에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.