ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のクロージャの詳細な説明

JavaScript のクロージャの詳細な説明

青灯夜游
青灯夜游オリジナル
2023-04-24 17:57:493518ブラウズ

JavaScript クロージャは、JavaScript プログラミングで広く使用されている重要な概念です。初心者にとってはわかりにくいかもしれませんが、JavaScript 言語の中核を理解する上で重要な概念の 1 つです。この記事では、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

この例では、クロージャを使用してカウンタ変数 count をカプセル化し、外部干渉を受けないようにします。カウンタ関数が呼び出されるたびに、カウンタの次の値が返されます。

データのキャッシュ

クロージャを使用すると、関数の計算結果をキャッシュして、同じ値が複数回計算されることを回避できるため、コードのパフォーマンスが向上します。この方法は、フィボナッチ数列など、計算量は多くても結果が頻繁に変化しない関数に適しています。

以下のコード例を見てください:

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

クロージャは、関数内で定義された関数を指します。この関数は、外部関数の変数とパラメータにアクセスでき、外部関数が呼び出された後もこれらの変数とパラメータを使用し続けることができます。クロージャが作成されると、必要なときにアクセスできるように、外側の関数のスコープ チェーンへの参照が保存されます。

クロージャは外部関数スコープ チェーンへの参照を保存するため、関数呼び出しごとに新しいスコープ チェーンが作成されます。これは、関数が同じクロージャによって作成された場合でも、関数を呼び出すたびに新しい関数スコープ チェーンが作成されるためです。これは、各クロージャが独自のスコープ チェーンを持ち、クロージャへの呼び出しごとに新しいスコープ チェーンが作成されることを意味します。

これが、クロージャを使用するときに注意が必要な理由の 1 つです。クロージャを呼び出すたびに新しいスコープ チェーンが作成されるため、メモリ消費とパフォーマンスの問題が発生する可能性があります。場合によっては、メモリ リークの問題を回避するために、クロージャのリソースを手動で解放することが必要になる場合があります。

セキュリティの問題

クロージャは外部関数のローカル変数にアクセスできるため、プライベート データが誤ってローカル変数に格納された場合、そのデータがクロージャによってアクセスされ、アクセスされる可能性があります。変更すると、セキュリティ上の問題が発生します。

可読性の問題

クロージャは変数の有効期間を延長し、暗黙的にデータを渡すため、コードの理解とデバッグが困難になる可能性があります。これは、特に複数のレベルのクロージャをネストする場合に当てはまります。機能。

まとめ

したがって、クロージャは強力なプログラミング手法ですが、使用する場合は上記の欠点に注意し、適切なアプリケーション シナリオとプログラミングを選択する必要があります。 style. 、コードの保守性とパフォーマンスを確保するため。

プログラミング関連の知識について詳しくは、プログラミング教育をご覧ください。 !

以上がJavaScript のクロージャの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。