ほとんどのコーディングの課題では、パズルを解く方法を学びます。 LeetCode の 30 日間の JavaScript 学習プランは、何か違うことをします。パズルのピースがどのようにレンガに変化し、現実世界のプロジェクトを構築する準備ができるかを示します。
この区別は重要です。典型的なアルゴリズムの問題を解決するとき、抽象的に考えるように心を訓練していることになります。しかし、デバウンス1関数を実装したり、イベント エミッター2を構築したりすると、実際のソフトウェアがどのように動作するかを学ぶことになります。
私は自分自身で課題に取り組んでいるときにこれを発見しました。この経験は頭の体操というよりは、具体的な最新の JavaScript の概念を明らかにする考古学に似ていました。各セクションでは、JS の最新機能の別の部分に焦点を当てました。
この学習プランの奇妙な点は、JavaScript を教えないことです。実際、JavaScript を活用するには、JavaScript について十分に理解している必要があると思います。代わりに、実際のエンジニアリングの問題を解決するために JavaScript が実際にどのように使用されるかを教えてくれます。
Memoize3 チャレンジを考えてみましょう。表面的には、関数の結果をキャッシュすることです。しかし、あなたが本当に学んでいるのは、React のようなライブラリがコンポーネントのレンダリングを効率的に処理するためになぜメモ化が必要なのかということです。あるいは、Debounce1 問題を考えてみましょう。これは単に遅延を実装するだけではありません。これは、最新のフロントエンド フレームワーク、エレベーター、そして基本的に対話型 UI を備えたシステムにこのパターンが必要な理由を直接理解するのに役立ちます。
言語の基本ではなく実践的なパターンに重点を置くことで、興味深い制約が生まれます。利益を得るには、次の 2 つの立場のいずれかに就く必要があります:
コンピューター サイエンスの学習とソフトウェア エンジニアリングの実践の間には、何か奇妙なことが起こります。この移行は、チェスの理論を何年も学んでいるような気分ですが、気が付くとまったく別のゲームをプレイしていることに気づきます。ルールは変わり続け、ほとんどの手がどの本にも載っていないゲームです。
CS では、バイナリ ツリーがどのように機能するかを学びます。ソフトウェア エンジニアリングでは、API のデバッグに何時間も費やして、応答キャッシュが機能しない理由を理解しようとします。遠くから見ると、これらの世界の重なり合いは実際よりもかなり大きく見えるかもしれません。そこにはギャップがあり、CS卒業生がキャリアを始めるときにショックを受けることがよくあります。残念ながら、ほとんどの教育リソースはそれを埋めることができません。これらは純粋に理論的なもの (「クイックソートの仕組みはこちら」) か、純粋に実践的なもの (「React アプリのデプロイ方法はこちら」) のどちらかです。
この JavaScript 学習計画が興味深いのは、それが特によく設計されているということではありません。それは、これらの世界間のつながりを生み出すということです。メモ化の問題を考えてみましょう: 2623。メモ化3。 CS の用語で言えば、計算された値をキャッシュすることです。しかし、これを実装するには、オブジェクト参照、関数コンテキスト、メモリ管理に関する JavaScript の特性に対処する必要があります。突然、
あなたは単にアルゴリズムを学んでいるのではなく、なぜ Redis のようなものが存在するのかを理解し始めています。
このスタイルはチャレンジ全体で繰り返されます。 Event Emitter2 の実装は、単なる教科書的なオブザーバー パターンに関するものではありません。これは、ブラウザーから V8 エンジンを取り出し、それを中心に Node.js を構築することが実際に理にかなっている理由として見ることができます。 。 Promise Pool4 は、データベースに接続制限が必要な理由である並列実行に取り組みます。
この学習計画の問題の順序はランダムではありません。最新の JavaScript のメンタル モデルをレイヤーごとに構築しています。
それはクロージャから始まります。クロージャが最も単純な概念であるためではなく、クロージャが混乱を招くことで有名です。それは、クロージャが JavaScript が状態を管理する方法の基礎であるからです。
function createCounter(init) { let count = init; return function() { return count++; } } const counter1 = createCounter(10); console.log(counter1()); // 10 console.log(counter1()); // 11 console.log(counter1()); // 12 // const counter1 = createCounter(10); // when this^ line executes: // - createCounter(10) creates a new execution context // - local variable count is initialized to 10 // - a new function is created and returned // - this returned function maintains access // to the count variable in its outer scope // - this entire bundle // (function (the inner one) + its access to count) // is what we call a closure
このパターンは、JavaScript におけるすべての状態管理のシードです。このカウンターがどのように機能するかを理解すると、React の useState が内部でどのように機能するかが理解できます。 ES6 以前の JavaScript にモジュール パターンが登場した理由がわかりました。
その後、計画は関数の変換に移ります。これらは関数の装飾、つまり関数が他の関数をラップしてその動作を変更する方法を教えます。これは単なる技術的なトリックではありません。それは、Express ミドルウェアがどのように動作するか、React の上位コンポーネントがどのように動作するか、
また、TypeScript デコレーターがどのように機能するかについても説明します。
非同期の課題に到達するまでに、単に Promise について学習しているだけではなく、そもそもなぜ JavaScript に Promise が必要だったのかを発見していることになります。 Promise Pool4 の問題は、革新的で風変わりな JS の概念を教えているわけではありません。すべてのデータベース エンジンに接続プーリングが存在する理由がわかります。
これは、学習計画の各セクションを実際のソフトウェア エンジニアリングの概念に大まかにマッピングしたものです:
この学習計画の真の価値を示すいくつかの問題を詳しく見てみましょう。
Memoize チャレンジについて考えてみましょう。私が気に入っている点は、(私が思いついた) 最良の解決策が見つかったという事実です
は非常に単純なので、コード自体がその動作を優しく教えてくれているかのようです (それでも、いくつかのコメントを含めました)。
だからと言って、#2623 が簡単な問題になるわけでは決してありません。これをきれいにするために、以前に 2 回の反復が必要でした:
function createCounter(init) { let count = init; return function() { return count++; } } const counter1 = createCounter(10); console.log(counter1()); // 10 console.log(counter1()); // 11 console.log(counter1()); // 12 // const counter1 = createCounter(10); // when this^ line executes: // - createCounter(10) creates a new execution context // - local variable count is initialized to 10 // - a new function is created and returned // - this returned function maintains access // to the count variable in its outer scope // - this entire bundle // (function (the inner one) + its access to count) // is what we call a closure
エレベーターに乗っていて、「ドアを閉める」ボタンを必死で繰り返し押している人がいると想像してください。
押す 押す 押す 押す 押す
デバウンスなし: エレベーターは 1 回押すたびにドアを閉めようとするため、ドアの機構が非効率的に動作し、破損する可能性があります。
デバウンスあり: エレベーターは、人が押すのを一定時間 (0.5 秒としましょう) 止めるまで待ってから、実際にドアを閉めようとします。これははるかに効率的です。
これは別のシナリオです:
ユーザーが次のように入力すると結果を取得する検索機能を実装していると想像してください。
デバウンスなし:
/** * @param {Function} fn * @return {Function} */ function memoize(fn) { // Create a Map to store our results const cache = new Map(); return function(...args) { // Create a key from the arguments const key = JSON.stringify(args); // If we've seen these arguments before, return cached result if (cache.has(key)) { return cache.get(key); } // Otherwise, calculate result and store it const result = fn.apply(this, args); cache.set(key, result); return result; } } const memoizedFn = memoize((a, b) => { console.log("computing..."); return a + b; }); console.log(memoizedFn(2, 3)); // logs "computing..." and returns 5 console.log(memoizedFn(2, 3)); // just returns 5, no calculation console.log(memoizedFn(3, 4)); // logs "computing..." and returns 7 // Explanantion: // It's as if our code had access to an external database // Cache creation // const cache = new Map(); // - this^ uses a closure to maintain the cache between function calls // - Map is perfect for key-value storage // Key creation // const key = JSON.stringify(args); // - this^ converts arguments array into a string // - [1,2] becomes "[1,2]" // - we are now able to use the arguments as a Map key // Cache check // if (cache.has(key)) { // return cache.get(key); // } // - if we've seen these arguments before, return cached result; // no need to recalculate
これにより、10 回の API 呼び出しが行われます。ユーザーがまだ入力しているため、それらのほとんどは役に立ちません。
デバウンスあり (300ms 遅延):
// typing "javascript" 'j' -> API call 'ja' -> API call 'jav' -> API call 'java' -> API call 'javas' -> API call 'javasc' -> API call 'javascr' -> API call 'javascri' -> API call 'javascrip' -> API call 'javascript' -> API call
デバウンスは、コードに「ユーザーが何かをするのを X ミリ秒間やめてから、実際にこの関数を実行するまで待ってください」と指示するのと同じです。
LeetCode #2627 の解決策は次のとおりです:
// typing "javascript" 'j' 'ja' 'jav' 'java' 'javas' 'javasc' 'javascr' 'javascri' 'javascrip' 'javascript' -> API call (only one call, 300ms after user stops typing)
デバウンスに関するその他の一般的な現実世界の使用例 (検索バーを除く):
この記事の全体的に前向きな論調から、30 Days of JS についての私の意見がもう明らかになったことを願っています。
しかし、完璧な教育リソースは存在せず、限界がある場合には、正直さが重要です。この学習計画には、検討する価値のある盲点がいくつかあります。
まず、学習計画は一定レベルの予備知識を前提としています。
JavaScript にまだ慣れていない場合は、いくつかの課題は困難を伴う可能性があります。これは、学習計画に別の期待を抱いていた初心者にとっては落胆する可能性があります。
第二に、課題は個別に提示されます。
これは最初は理にかなっていますが、計画が進むにつれて残念なことに気づくことがあります。現実の問題では、多くの場合、複数のパターンや手法を組み合わせる必要があります。研究計画は、複数の概念を一緒に使用する必要がある、より統合された課題から恩恵を受ける可能性があります (例外: 計画全体でクロージャを使用します)。これらは、ボーナス セクション (すでにプレミアム ユーザー向けに予約されています) にうまく収まる可能性があります。
最後に、この一連の課題の主な弱点は、その概念の説明にあります。競技プログラミングから生まれた
私は、問題文の中で新しい用語や概念の定義を明確にすることに慣れています。ただし、LeetCode の説明は不必要に複雑であることが多く、デバウンス関数の説明を理解することは、実際のソリューションを実装するよりも困難です。
欠点はあるものの、学習計画書は最新の JavaScript を理解するための貴重なリソースです。
これらのパターンを理解することは始まりにすぎません。
本当の課題は、実稼働コードにそれらをいつ、どのように適用するかを認識することです。以下は、私が実際にこれらのパターンに遭遇した後に発見したことです。
まず、これらのパターンが単独で現れることはほとんどありません。実際のコードベースは、課題では調査できない方法でそれらを組み合わせます。最初から実装された検索機能を考えてみましょう。次のようなものを使用しているかもしれません:
これらすべてのパターンが相互作用し、単一の課題に備えることのできない複雑さを生み出します。ただし、各部分を自分で実装すると、実装全体がどのように機能するかについての一般的なアイデアが得られます。
直観に反して、あなたが獲得できる最も価値のあるスキルは、これらのパターンを実装することではなく、他の人のコードでパターンを認識することです。
この学習計画を完了した後、これらのパターンを認識できるのはコーディング面接だけではありません。
オープンソース コードや同僚のプル リクエストでそれらを発見し、過去のプロジェクトでもそれらに気づき始めるかもしれません。あなたも気付かないうちに、以前にそれらを実装していたかもしれません。最も重要なのは、それらがなぜそこにあるのかを理解できることです。
パズルを解くことから始まったものは、最新の JavaScript エコシステムのより深い理解に変わりました。
それがこの学習計画で埋められるギャップであり、理論的知識と実践的な工学知識の橋渡しとなります。
2627。デバウンス (約束と時間) ↩
2694。イベント エミッター (クラス) ↩
2623。 Memoize (関数変換) ↩
2636。プロミスプール (ボーナス) ↩
以上がLeetCode の JavaScript の日数が実際に埋めるギャップの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。