首頁 > web前端 > js教程 > LeetCode 的 JavaScript 時代實際上填補了空白

LeetCode 的 JavaScript 時代實際上填補了空白

Patricia Arquette
發布: 2024-12-16 16:34:10
原創
907 人瀏覽過

The Gap That LeetCode

大多數程式設計挑戰都會教你解決難題。 LeetCode 的 30 天 JavaScript 學習計畫做了一些不同的事情:它向您展示了拼圖如何變成磚塊,準備好建立現實世界的專案。

這種區別很重要。當您解決典型的演算法問題時,您正在訓練您的思維進行抽象思考。但是,當您實現去抖1函數或建構事件發射器2時,您正在學習真正的軟體是如何運作的。

我自己在應對挑戰時發現了這一點。這種體驗不太像解決腦筋急轉彎問題,而更像考古學——揭示特定的現代 JavaScript 概念。每個部分都重點介紹 JS 的另一個現代功能。

這個學習計畫的奇特之處在於它不會教你 JavaScript。事實上,我相信您需要相當了解 JavaScript 才能從中受益。相反,它教導的是如何使用 JavaScript 來解決實際的工程問題。

考慮 Memoize3 挑戰。從表面上看,它是關於快取函數結果的。但你真正學到的是為什麼像 React 這樣的函式庫需要記憶來有效地處理元件渲染。或以 Debounce1 問題為例 - 這不僅僅是實現延遲;它還涉及延遲。它可以幫助您直接理解為什麼每個現代前端框架、電梯以及基本上任何具有互動式 UI 的系統都需要這種模式。

這種對實用模式而不是語言基礎知識的關注創造了一個有趣的限制;您需要處於以下兩個位置之一才能受益:

  1. 您了解 CS 基礎(尤其是資料結構和演算法)並且熟悉 JavaScript
  2. 您精通 CS 理論並且之前接觸過一些 JavaScript

連結電腦科學和軟體工程

學習電腦科學和實踐軟體工程之間會發生一些奇怪的事情。這種轉變感覺就像學習了多年的國際象棋理論,卻發現自己在玩一種完全不同的遊戲 - 規則不斷變化,而且大多數動作都不在任何書本中。

在電腦科學中,您將學習二元樹的工作原理。在軟體工程中,您需要花費數小時來除錯 API,試圖了解回應快取不起作用的原因。從遠處看,這些世界之間的重疊可能看起來比實際上大得多。其中存在差距,這常常會讓電腦科學畢業生在開始職業生涯時感到震驚。不幸的是,大多數教育資源都無法彌補這一點。它們要么純粹是理論性的(「這是快速排序的工作原理」),要么是純粹的實用性(「這是如何部署 React 應用程式」)。

這個 JavaScript 學習計畫的有趣之處並不是它設計得特別好 - 而是它在這些世界之間創造了連結。以記憶問題為例:2623。記憶3。用計算機科學術語來說,它是關於快取計算值的。但實作它會迫使您應對 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 Pool4 問題不是在教你一個創新的、古怪的 JS 概念;而是在教你一個創新的、古怪的 JS 概念。它向您展示了為什麼每個資料庫引擎中都存在連接池。

以下是學習計畫各部分與現實世界軟體工程概念的粗略映射:

  • 關閉 → 狀態管理
  • 基本陣列轉換 → 基本技能(輔助);實際範例:資料操作
  • 函數轉換 → 中間件模式
  • 承諾與時間 ->非同步控制流
  • JSON ->基礎技能(輔助);實例:資料序列化、API通訊
  • 類別(特別是在事件發射器的上下文中)→訊息傳遞系統
  • 獎金(高級鎖定)->可能包含在上述部分中的更難的挑戰的組合; Promise Pool4 是本節中我最喜歡的一個

模式識別,而不是解決問題

讓我們剖析一些問題,以展示該學習計劃的真正價值。

  1. 記憶 (#2623)

考慮 Memoize 挑戰。我喜歡它的原因是(我能想出的)最好的解決方案
非常簡單,就好像程式碼本身在溫和地告訴您它的作用(不過,我添加了一些註釋)。

無論如何,這並不會讓 #2623 成為一個簡單的問題。我之前需要兩次迭代才能讓它變得如此乾淨:

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. 反跳 (#2627)

想像一下你在電梯裡,有人瘋狂地重複按下「關門」按鈕。

沒有去抖:電梯會在每按一次門時嘗試關門,導致門機構工作效率低下,甚至可能損壞。

防手震:電梯會等待,直到人停止按下一定時間(假設 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 呼叫。其中大多數都沒用,因為用戶仍在打字。
// 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
登入後複製

帶去抖動(300 毫秒延遲):


去抖就像告訴你的程式碼:「等到使用者停止執行某些動作 X 毫秒後再實際執行此函數。」
// typing "javascript"
'j'
'ja'
'jav'
'java'
'javas'
'javasc'
'javascr'
'javascri'
'javascrip'
'javascript' -> API call (only one call, 300ms after user stops typing)
登入後複製

這是 LeetCode #2627 的解決方案:

  • 現實世界中其他常見的去抖動用例(搜尋列除外):
儲存草稿(等待使用者停止編輯)

提交按鈕(防止重複提交)

出了什麼問題

我希望,從這篇文章整體正面的基調來看,我對JS 30天的看法現在已經清晰了。

但是沒有任何教育資源是完美的,而且,當涉及到局限性時,誠實是有價值的。這個學習計畫有幾個盲點值得檢視。

首先,學習計畫假設有一定程度的先驗知識。
如果您還不太熟悉 JavaScript,那麼有些挑戰可能會令人難以承受。這可能會讓初學者感到沮喪,因為他們可能對學習計劃有其他期望。

其次,挑戰是以孤立的方式呈現的。
這在一開始是有道理的,但隨著計劃的進展,你可能會感到失望。現實世界的問題通常需要結合多種模式和技術。研究計劃可以受益於更全面的挑戰,這些挑戰需要一起使用多個概念(例外:我們在整個計劃中都使用了閉包)。這些很適合放在獎勵部分(已經為高級用戶保留)。

最後,這群挑戰的主要弱點在於其概念解釋。來自競爭性節目,
我習慣在問題陳述中明確新術語和概念的定義。然而,LeetCode 的描述通常過於複雜——理解他們對去抖函數的解釋比實現實際的解決方案更難。

儘管有缺點,該學習計劃仍然是理解現代 JavaScript 的寶貴資源。

30 天之後

理解這些模式只是個開始。
真正的挑戰是識別何時以及如何將它們應用到生產代碼中。這是我在野外遇到這些模式後發現的。

首先,這些模式很少是單獨出現。真實的程式碼庫以挑戰無法探索的方式將它們組合起來。考慮一個從頭開始實現的搜尋功能。您可能會發現自己使用:

  • 輸入處理的反跳
  • 結果緩存的記憶
  • 承諾 API 呼叫逾時
  • 用於搜尋狀態管理的事件發射器

所有這些模式相互作用,創造了複雜性,沒有任何單一的挑戰能讓您做好準備。但是,在自己實現了每個部分之後,您將大致了解整個實現的運作方式。

與直覺相反,您將獲得的最有價值的技能不是實現這些模式 - 而是在其他人的程式碼中識別它們。

最後的想法

完成本學習計畫後,程式設計面試並不是您認識這些模式的唯一地方。

您會在開源程式碼、同事的拉取請求中發現它們,並且可能會開始在您過去的專案中註意到它們。您以前可能已經實現了它們,甚至沒有意識到。最重要的是,您將會明白它們為何存在。

最初的解謎轉變為對現代 JavaScript 生態系統的更深入理解。

這就是本學習計畫填補的空白:將理論知識與實務工程智慧連結起來。



  1. 2627。 Debounce(承諾與時間)↩

  2. 2694。事件發射器(類)↩

  3. 2623。 Memoize(函數轉換)↩

  4. 2636。承諾池(獎金)↩

以上是LeetCode 的 JavaScript 時代實際上填補了空白的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板