ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript キャッシュの詳細については、こちらをご覧ください。

JavaScript キャッシュの詳細については、こちらをご覧ください。

青灯夜游
青灯夜游転載
2020-11-06 17:55:022903ブラウズ

JavaScript キャッシュの詳細については、こちらをご覧ください。

アプリケーションが成長し続け、複雑な計算を実行し始めると、速度に対するニーズがますます高まるため、プロセスの最適化が不可欠になります。この問題を無視すると、実行に時間がかかり、実行中に大量のシステム リソースを消費するプログラムが作成されてしまいます。

推奨チュートリアル: 「JavaScript ビデオ チュートリアル

キャッシュは、高コストの関数の実行結果を保存し、同じ入力で再表示する最適化手法です。キャッシュされた結果が返されます。アプリケーションを高速化します。

これがあなたにとってあまり意味がなければ、それでも大丈夫です。この記事では、キャッシュが必要な理由、キャッシュとは何か、実装方法、およびキャッシュを使用する必要がある場合について詳しく説明します。

キャッシュとは

キャッシュとは、高コストの関数の実行結果を保存し、同じ入力で再度取得することによる最適化手法です。結果が発生したときに結果を報告し、アプリケーションを高速化します。

現時点で、キャッシュの目的が「高価な関数呼び出し」の実行に費やされる時間とリソースを削減することであることは明らかです。

コストのかかる関数呼び出しとは? 混乱しないでください、ここではお金を使っているわけではありません。コンピューター プログラムの文脈では、私たちが持つ 2 つの主なリソースは時間とメモリです。したがって、負荷の高い関数呼び出しとは、計算量が多く実行時に多くのコンピュータ リソースと時間を消費する関数呼び出しを指します。

しかし、お金と同じように、節約する必要があります。この目的のために、将来の迅速かつ簡単なアクセスのために、関数呼び出しの結果を保存するためにキャッシュが使用されます。

キャッシュは、そのデータに対する今後のリクエストをより速く処理できるように、データを保存する単なる一時的なデータ ストレージです。

したがって、高価な関数が一度呼び出されると、結果はキャッシュに保存されるため、アプリケーションで関数が再度呼び出されるたびに、再実行することなく、結果がキャッシュから非常に迅速にフェッチされます。あらゆる計算を実行します。

なぜキャッシュが重要ですか?

次は、キャッシュの重要性を示す例です。

想像してみてください。彼らは公園で魅力的な表紙の新しい小説を読んでいます。人は前を通るたびに表紙に惹かれ、タイトルや作者を尋ねます。初めてこの質問をされたとき、あなたは本を開いてタイトルと著者名を読みます。今、同じ質問をしてここに来る人がますます増えています。あなたはとてもいい人なので、質問にはすべて答えてくれます。

表紙を開いて、本のタイトルと著者の名前を 1 つずつ伝えますか?それとも記憶から答え始めますか?どちらの方が時間を節約できますか?

類似点を見つけてくださいわかりましたか? ニーモニックを使用すると、関数に入力が提供されると、値を返す前に必要な計算が実行され、結果がキャッシュに保存されます。今後同じ入力が受信された場合、それを何度も繰り返す必要はなく、キャッシュ (メモリ) から応答を提供するだけで済みます。

#キャッシュの仕組み

JavaScript におけるキャッシュの概念は、主に次の 2 つの概念に基づいています。

    Closure
  • 高階関数 (関数を返す関数)

Closure

Closure は、関数と関数を組み合わせたものです。関数が宣言される語彙環境。
よくわかりません?私もそう思います。

理解を深めるために、JavaScript の字句スコープの概念を簡単に学習してみましょう。字句スコープとは、コードを記述するときにプログラマが指定した変数とブロックの物理的な位置を指します。次のコード:

function foo(a) {
  var b = a + 2;
  function bar(c) {
    console.log(a, b, c);
  }
  bar(b * 2);
}

foo(3); // 3, 5, 10
このコードから、3 つのスコープを決定できます:

    グローバル スコープ (一意の識別子として
  • foo を含む)
  • foo スコープ。識別子 ab、および bar
  • # bar スコープを持ちます。 c 識別子
を含む 上記のコードを注意深く見ると、関数

foo 内にネストされているため、変数 a と b にアクセスできることがわかります。ふー。関数 bar とその実行環境が正常に保存されたことに注意してください。したがって、bar には foo のスコープに対するクロージャがあると言えます。

これは、個人が現在の環境の外でも遺伝形質を獲得して発現する機会を持つ遺伝学の文脈で理解できます。このロジックは、クロージャの別の要素を強調しており、これが 2 番目の点につながります。メインコンセプト。

関数から関数を返す

他の関数をパラメーターとして受け取ったり、他の関数を返したりする関数は、高階関数と呼ばれます。
クロージャを使用すると、囲んでいる関数の字句スコープへのアクセスを維持しながら、囲んでいる関数の外側で内部関数を呼び出すことができます。

これを説明するために、前の例のコードにいくつかの調整を加えてみましょう。

function foo(){
  var a = 2;

  function bar() {
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz();//2

注意函数 foo 如何返回另一个函数 bar。这里我们执行函数 foo 并将返回值赋给baz。但是在本例中,我们有一个返回函数,因此,baz 现在持有对 foo 中定义的bar 函数的引用。

最有趣的是,当我们在 foo 的词法作用域之外执行函数 baz 时,仍然会得到 a 的值,这怎么可能呢?

请记住,由于闭包的存在,bar 总是可以访问 foo 中的变量(继承的特性),即使它是在 foo 的作用域之外执行的。

案例研究:斐波那契数列

斐波那契数列是什么?

斐波那契数列是一组数字,以1 或 0 开头,后面跟着1,然后根据每个数字等于前两个数字之和规则进行。如

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

或者

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

挑战:编写一个函数返回斐波那契数列中的 n 元素,其中的序列是:

[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …]

知道每个值都是前两个值的和,这个问题的递归解是:

function fibonacci(n) {
  if (n <p>确实简洁准确!但是,有一个问题。请注意,当 <code>n</code> 的值到终止递归之前,需要做大量的工作和时间,因为序列中存在对某些值的重复求值。</p><p>看看下面的图表,当我们试图计算 <code>fib(5)</code>时,我们注意到我们反复地尝试在不同分支的下标 <code>0,1,2,3</code> 处找到 Fibonacci 数,这就是所谓的冗余计算,而这正是缓存所要消除的。</p><p><img src="https://img.php.cn/upload/image/136/162/211/1604656285576117.jpg" title="1604656285576117.jpg" alt="JavaScript キャッシュの詳細については、こちらをご覧ください。"></p><pre class="brush:php;toolbar:false">function fibonacci(n, memo) {
  memo = memo || {}
  if (memo[n]) {
    return memo[n]
  }
  if (n <p>在上面的代码片段中,我们调整函数以接受一个可选参数 <code>memo</code>。我们使用 <code>memo</code> 对象作为缓存来存储斐波那契数列,并将其各自的索引作为键,以便在执行过程中稍后需要时检索它们。</p><pre class="brush:php;toolbar:false">memo = memo || {}

在这里,检查是否在调用函数时将 memo 作为参数接收。如果有,则初始化它以供使用;如果没有,则将其设置为空对象。

if (memo[n]) {
  return memo[n]
}

接下来,检查当前键 n 是否有缓存值,如果有,则返回其值。

和之前的解一样,我们指定了 n 小于等于 1 时的终止递归。

最后,我们递归地调用n值较小的函数,同时将缓存值(memo)传递给每个函数,以便在计算期间使用。这确保了在以前计算并缓存值时,我们不会第二次执行如此昂贵的计算。我们只是从 memo 中取回值。

注意,我们在返回缓存之前将最终结果添加到缓存中。

使用 JSPerf 测试性能

可以使用些链接来性能测试。在那里,我们运行一个测试来评估使用这两种方法执行fibonacci(20) 所需的时间。结果如下:

JavaScript キャッシュの詳細については、こちらをご覧ください。

哇! ! !这让人很惊讶,使用缓存的 fibonacci 函数是最快的。然而,这一数字相当惊人。它执行 126,762 ops/sec,这远远大于执行 1,751 ops/sec 的纯递归解决方案,并且比较没有缓存的递归速度大约快 99%。

注:“ops/sec”表示每秒的操作次数,就是一秒钟内预计要执行的测试次数。

现在我们已经看到了缓存在函数级别上对应用程序的性能有多大的影响。这是否意味着对于应用程序中的每个昂贵函数,我们都必须创建一个修改后的变量来维护内部缓存?

不,回想一下,我们通过从函数返回函数来了解到,即使在外部执行它们,它们也会导致它们继承父函数的范围,这使得可以将某些特征和属性从封闭函数传递到返回的函数。

使用函数的方式

在下面的代码片段中,我们创建了一个高阶的函数 memoizer。有了这个函数,将能够轻松地将缓存应用到任何函数。

function memoizer(fun) {
  let cache = {}
  return function (n) {
    if (cache[n] != undefined) {
      return cache[n]
    } else {
      let result = fun(n)
      cache[n] = result
      return result
    }
  }
}

上面,我们简单地创建一个名为 memoizer 的新函数,它接受将函数 fun 作为参数进行缓存。在函数中,我们创建一个缓存对象来存储函数执行的结果,以便将来使用。

memoizer 函数中,我们返回一个新函数,根据上面讨论的闭包原则,这个函数无论在哪里执行都可以访问 cache

在返回的函数中,我们使用 if..else 语句检查是否已经有指定键(参数) n 的缓存值。如果有,则取出并返回它。如果没有,我们使用函数来计算结果,以便缓存。然后,我们使用适当的键 n 将结果添加到缓存中,以便以后可以从那里访问它。最后,我们返回了计算结果。

很顺利!

要将 memoizer 函数应用于最初递归的 fibonacci 函数,我们调用 memoizer 函数,将 fibonacci 函数作为参数传递进去。

const fibonacciMemoFunction = memoizer(fibonacciRecursive)

测试 memoizer 函数

当我们将 memoizer 函数与上面的例子进行比较时,结果如下:

JavaScript キャッシュの詳細については、こちらをご覧ください。

memoizer 函数以 42,982,762 ops/sec 的速度提供了最快的解决方案,比之前考虑的解决方案速度要快 100%。

关于缓存,我们已经说明什么是缓存 、为什么要有缓存和如何实现缓存。现在我们来看看什么时候使用缓存。

何时使用缓存

当然,使用缓存效率是级高的,你现在可能想要缓存所有的函数,这可能会变得非常无益。以下几种情况下,适合使用缓存:

  • 对于昂贵的函数调用,执行复杂计算的函数。
  • 对于具有有限且高度重复输入范围的函数。
  • 用于具有重复输入值的递归函数。
  • 对于纯函数,即每次使用特定输入调用时返回相同输出的函数。

缓存库

总结

使用缓存方法 ,我们可以防止函数调用函数来反复计算相同的结果,现在是你把这些知识付诸实践的时候了。

更多编程相关知识,请访问:编程入门!!

以上がJavaScript キャッシュの詳細については、こちらをご覧ください。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。