天天看點

2020年,我們該如何學習 WEB 前端開發我的從 0 開始接觸 Node.js架構時代工程之路邏輯抽象能力更高層次的思考能力EOF

2020年,我們該如何學習 WEB 前端開發我的從 0 開始接觸 Node.js架構時代工程之路邏輯抽象能力更高層次的思考能力EOF

作者|小問

編輯|橙子君

出品|阿裡巴巴新零售淘系技術

我們可以把學習路線比作遊戲中的段位上分,在不同的分段都有自己的定位和要鍛煉的事情:

青銅—從零開始國小生:懷着滿腔的熱血,看到了這一個行業的希望和未來,準備開始學習 Web 開發知識。

  • 先通過 w3cschool 等免費學習資源把 HTML、CSS 和 JavaScript 的基本操作學會了
  • 寫一個簡單的表白頁面送給你的女/男朋友,展示一下自己努力的成果,如果沒有就當我沒說

白銀 —懵懵懂懂初學者:懂得如何使用 HTML、CSS 和 JavaScript 三大件來實作基本頁面開發功能

  • 選擇一個可以覆寫多種場景、可以随自己意願調整難度的項目嘗試實作,如部落格系統、記賬本、Markdown 編輯器等
  • 從 React 和 Vue 這兩個架構中選擇一個進行學習

黃金—輕車熟路新玩家:懂得使用架構來實作上面所舉例項目

  • 學習 Redux、Vuex 或者 MobX 等狀态管理工具,并将他們使用到前面的項目中
  • 思考狀态管理工具為你的項目帶來了什麼好處

鉑金 V—初出茅廬新司機: 懂得如何使用腳手架建立項目,并且能将代碼結構根據子產品化的思想進行安排

  • 學習 TypeScript,對前面的項目進行重寫,注重對資料結構和類型的控制
  • 學習 Node.js,試着配合資料庫實作一個比部落格系統更為複雜的 CMS(内容管理系統),如 圖書館管理系統、倉庫管理系統

鉑金 I—基本上手好司機:如果是懂得如何利用 Node.js 或 TypeScript 編寫業務代碼的

  • 思考在前面使用架構開發的項目中,有哪些代碼是重複備援的,有哪些邏輯是可以在多個元件之間共用的
  • 學習利用 ES2015 或更新的 JavaScript 标準,逐漸替換使用架構所編寫的代碼

鑽石 V—淡定自然老司機:如果是對邏輯抽象、子產品封裝有了一定的了解和經驗的

  • 思考如何使用純 JavaScript 對業務元件中的非渲染、非 DOM 相關代碼進行抽象
  • 引入單元測試工具,對純邏輯代碼進行測試,争取覆寫率達到 80% 以上

鑽石 I—賽道新手初學者:如果上面的條件你都已經滿足了

  • 思考不同的代碼哲學(OO、FP 等)、不同的代碼結構(MVC、MVVM 等)的差別
  • 思考不同的架構之間設計的初衷,思考不同的程式設計語言中對同一類問題不同解法的差別

到這裡我劃了一條從 0 到進階前端工程師級别的純技術路線。相信有不少有經驗的同學會發現中間我省略了不少内容,但也不難發現路線中從前半段的“學習”逐漸變成後半段的“思考”。優秀的工程師除了需要有在純技術領域的沉澱以外,還需要更多對技術、團隊、ROI(投資回報率)的思考,當然這依然不足以支撐我們平穩地渡過“程式員 35 歲危機”,前面的路還有很長,鑽石往上還有王者呢,誰說程式員就是青春飯碗的?

回想起很多年前我也跟你一樣是一個完全的新手,從 0 開始慢慢自學摸索 Web 開發,甚至後來我也沒有進入科班學習計算機,那麼來聽聽我作為一個“前人”是如何完全靠自學至今的故事吧。

我的從 0 開始

我是一個完全從自學開始的前端工程師,想起來第一次接觸前端就是國中那會特别流行合租 VPS 然後注冊一個 .tk 的免費域名。而作為一個剛入門 Web 開發不久的小屁孩來說,用這種方式一探“大人的世界”屬實讓人興奮。而當時最流行的部落格管理軟體就是用 PHP 寫的 WordPress,作為一個十分成熟的 CMS 軟體來說 WordPress 當時就有了非常豐富的社群資源,比如主題、模闆、插件等等。

而作為一個十分注重個性化的小屁孩來說,當然是要自己做一個主題的啊!于是我就從此踏上了 Web 開發的不歸路,在此之前我所接觸的都是 Visual Basic 這樣的 Native 的語言。

以 WordPress 主題作為切入點,我開始學習 PHP 用于調用 WordPress 的 API 并輸出内容、學習 HTML 用于寫主題的模闆、學習 CSS 用于“裝潢”我的部落格、學習 jQuery 用于實作頁面動态效果。是的,那個時候基本上大部分人接觸的是 jQuery 而不是 JavaScript,一個 $ 函數就可以完成非常多的效果這讓我第一次感受到了“架構”所帶來的價值。于是便一步一步地發生了以下事情(不一定完全對,畢竟時間過太久了):

1、我發現頁面上的一些樣式效果無法在 IE 浏覽器上正常顯示,于是我就開始到網上學習 CSS 在 IE 的各種特殊處理,包括 reset.css、normalize.css 等工具的使用;

2、每次點選連結都要重新整理頁面,在那個網速不怎麼好的年代體驗非常糟糕,于是乎就開始研究怎麼用 jQuery/JavaScript 實作不需要重新整理頁面的情況下切換頁面的内容;通過檢視文檔發現浏覽器支援一種叫做 XMLHTTPRequest 的技術,可以讓我們不需要通過跳轉的方式從伺服器擷取到資訊,從這裡開始了解到 HTML、XML 和 JSON 三種不同格式的差別;

第一次知道了可以通過伺服器傳遞 JSON 格式的純資料,然後前端通過 JavaScript 對資料進行解析,并且結合前端的模闆引擎渲染成完整的 HTML;

從這裡又可以學習到如何通過 URL 中的 path、query、hash 以及 POST 和 PUT 請求正文等資訊向伺服器傳遞資訊,伺服器通過這些資訊動态地對各種資料進行處理并傳回結果;

SPA(Single Page Application)開發習慣初見雛形;

這樣我就來到了“白銀”階段了。

接觸 Node.js

當我正在愉快地設計着 WordPress 的自定義主題時,偶然間我在某前端網站上了解到了一個新的技術 —— Node.js。與它的相遇改變了我以後的學習路徑,影響至今。

2009 年 Ryan Dahl 釋出了一個基于 Chrome JavaScript V8 引擎開發的程式運作環境 Node.js,它允許開發者在除了浏覽器以外的地方運作 JavaScript 語言,并且提供一些标準庫允許 JavaScript 腳本啟動進行啟動一個 HTTP 服務端應用這種以前在浏覽器無法完成的事情。

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8124, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8124/');           

這一份代碼是 2010 年寫在 Node.js 官網的一段執行個體代碼,機緣巧合之下我被這麼一段簡單的代碼深深地吸引住了,雖然當時安裝它仍需要從 GitHub 上克隆整個項目代碼到本地并依次運作以下指令:

$ ./configure
$ make
$ make install           

這一次編譯就得花上至少十分鐘,但完成安裝後運作上面的一段代碼,并在浏覽器中打開

http://127.0.0.1:8124/

,然後在浏覽器上看到 Hello World 字樣時仿佛新世界的門打開了。因為當時我所接觸過的服務端程式隻有 PHP,而 PHP 本質上就是一個模闆引擎,它并不能很直覺地處理請求本身而是借助 CGI 進行響應。能做更多的事情,這件事情對剛學習程式設計不久的新手來說是具有很大誘惑力的。

從這裡開始,Node.js 配合 npm 便開始了長達 10 年的快速發展。從純服務端應用開發,到開發工具、工程工具,再到如今的 FaaS(Function as a Service,Serverless)開發方式。Node.js 已經成為 Web 工程師不可或缺的一項技能,不管是用來開發服務端應用還是開發工具類應用,甚至是使用 Electron 開發桌面端應用還是配合 React Native 開發移動端 App,Node.js 能讓前端工程師了解更多系統級别的概念,如網絡、I/O、記憶體、檔案系統等等,這些很多都是原本在浏覽器端上看不到的。而學習這些知識對你了解前端開發背後的一些原理有非常好的價值,就跟學習算法一樣。

結論:請學習 Node.js 和其中涉及到的一些基本計算機原理。

架構時代

當我在做 WordPress 主題的時候,絕大部分的主題開發者都會在前端做一些簡單的效果,甚至有甚者會通過 JavaScript 實作一些原本隻能通過後端來完成的事情,比如文章清單、文章内容的加載和渲染。而當年這些主題開發者基本上都會使用 jQuery 來進行這些 JavaScript 的操作,因為純手寫 JavaScript 在當時來說非常的繁瑣(ES4時代,很多現在被廣泛使用的原生 API 都仍未具備)是以當時 jQuery 就是大家的首選方案。

從非常早的 PrototypeJS、後來的 jQuery、進入 MVC 時代的 Backbone,AngularJS 開啟 MVVM 模式,React 引入 FP 的概念,Vue 成功開啟了漸進式開發體驗的道路。一路下來一地的雞毛,被各路人馬诟病前端領域一個月開發一個新架構,“學不動了”。然而作為一個也寫過架構、寫過工具類庫的開發者,我很喜歡用一個經常用于泛科技領域的例子來類比前端領域:

科技的終極目标,就是讓人民感覺不到科技。

jQuery 時代,前端開發者使用 JavaScript 的模式是從頁面中擷取 DOM 元素,添加事件,然後通過 class 和 style 對頁面進行動态地變更,以完成對使用者行為的響應;

Backbone 時代,原本用在桌面端軟體開發中的 MVC 模式被引入到了前端開發中,前端開發者們發現 Web 開發的複雜度已經需要用這些更成熟的開發模式進行管理了;

AngularJS 時代,從這裡開始 Google 把資料雙向綁定模式帶到前端開發中,将原本需要通過 JavaScript 控制 DOM 元素這一繁瑣的操作變成了隻需要關心 Model 層需要改動什麼内容即可。而 Vue 則将這種模式的開發成本降低到了一種相當可觀的程度,讓很多新手開發者也能很簡單地入手這種便捷的開發模式。

React 時代,Facebook 的科學家們把函數式程式設計的思想引入到前端開發中,注重的是資料鍊路的可跟蹤、可回溯、可管理,讓整個資料鍊路是盡可能以單鍊路流轉。

2020年,我們該如何學習 WEB 前端開發我的從 0 開始接觸 Node.js架構時代工程之路邏輯抽象能力更高層次的思考能力EOF

雖然前端領域常被說“一個月一個新架構”,但實際上每一個架構在疊代的過程中都是解決了它們所在業務場景的實際需求的,并不是“拍腦袋”地想要把每一個技術細節做出一個 break change。

而目前我目前推薦的學習的架構是 React 和 Vue:

同樣都是目前最流行的架構之一,而且可以預見未來 3~5 年内都是能滿足找工作的需求的;

React:引入函數式程式設計(Functional Programming)的概念,使得寫代碼的思路更加嚴謹,更具有可維護性和邏輯可導性;

Vue:将 MVVM 模式變得非常簡單易于入手,把 Progressive JavaScript Framework 的定位實踐得非常到位。且如今 Vue 3.0 的 Composition API 更是在某種程度上将 Hooks 的玩法實作得比 React Hooks 更優;

結論:請不要害怕學習!不要懼怕新技術!

工程之路

雖然我在接觸了架構和 Node.js 之後,發現 JavaScript 除了能實作一般隻用于展示内容和呈現簡單互動以外還能做更多的事情。但本質上還是圍繞着多個頁面進行頁面上 DOM 元素的控制,而直到我打開了 Google 的一些網站時,我才發現原來網站除了能叫頁面以外,還能稱之為“應用”。

自從 Google 上線了一個完全不需要重新整理頁面就能完成所有事情而且體驗很不錯的 GMail 之後,我們發現網頁原來也是可以承載那麼複雜的邏輯和應用場景的。大家的熱情異常地高漲,想着能不能讓自己所負責的項目也有這麼厲害進階的樣子。但随着項目不斷地複雜,代碼規模也變得非常難以管理,而這個時候就需要工程化的引入。

▐ 工程化協作

對于企業來說除了研發效率要足夠高以外,研發鍊路的安全、合規也是同樣重要的。

什麼叫安全合規?可管理的代碼版本、可控制的釋出流程、可管控的灰階機制,都是大廠用于保證項目流程穩定進行的必要工具。

有很多初學者或者還沒有大公司經驗的同學在寫項目時都是單打獨鬥的,但更多的一線項目都需要至少 2~3 個甚至更多的人員一同參與開發的。

而這種時候,因為每個人的水準和開發習慣都是不一緻的,而這些不一緻就直接導緻整體研發效率和項目進度受到極大的影響。是以就需要一種能夠讓大家在一個水準線上進行開發的模式,工程化需求便應運而生。

版本控制

  • Git:GitHub、GitLab、Coding……
  • SVN:BitBucket、Google Code……

代碼樣式檢查工具 JavaScript/TypeScript:ESLint

測試工具

  • 單元測試:Karma、Jest、Mocha……
  • 持續內建:CI
  • ……

▐ 工程化開發工具

從直接将 JavaScript 代碼用 script 标簽,到需要将 jQuery 檔案和主要程式檔案分别引入,再到 Node.js 出現後使用 npm 進行依賴庫管理并使用 webpack 進行打包和壓縮。工程類工具的發展見證着前端工程近十年的發展曆史,對目前我們所常用的工程工具有更好的了解和實踐,絕對是通往優秀路上不可或缺的一步。

  • 依賴包管理工具:npm、yarn
  • 打包工具:webpack、rollup
  • 腳手架工具:create-react-app……

▐ 工程化開發語言

相信很多同學都聽說過 JavaScript 誕生之初的一些轶事,比如根本沒有特别多的嚴謹思考,或者在非常多的場景中十分地晦澀,比如隐性轉換等。

有人認為 JavaScript 能發展到如今的地位跟它的這種“靈活度”或者“松散度”有關聯,雖然在某種程度上确實因為這種特性造成的 JavaScript 學習門檻比較低而間接導緻。但就如我上面所說,當項目規模和人員規模不斷發展乃至膨脹過後,這些特性會逐漸表現出來非常糟糕的體驗:

團隊之間因為沒有良好的技術文檔沉澱,資訊不對等的情況直接導緻代碼在沒有良好的單元測試時出現邏輯沖突;

第三方依賴庫的 API 在設計上大量使用了 JavaScript 松散的特性,導緻使用方在引用時頻繁出現“迷惑”的狀态;

當需要使用 JavaScript 與其他語言(特别是強類型語言)進行互動時,JS 過于松散的習慣會讓對接方感到非常迷惑,對于雙方的實際接入成本會比前期預估的大得多;

為了解決這種情況,來自不同程式設計領域的大牛們都紛紛開始想辦法,于是乎便誕生了非常多的“輪子”:

  • Java 系:Scala.js、ClojureScript
  • Go 系:GopherJS
  • Microsoft:TypeScript
  • Facebook:Flow、Reason

目前 TypeScript 已經影響了前端乃至整個 Web 領域的開發生态,TypeScript 之父 Anders Hejlsberg 創造過 Turbo Pascal、Delphi、C# 等在整個計算機科學領域都舉足輕重的語言,而 TypeScript 又再次創造出翻天覆地的變化:

強類型的引入能讓我們在寫代碼的時候從值優先的思維轉變成類型優先;

強類型的引入能幫助開發工具(IDE 等)更好地為開發者提供便利性能力,如智能補全、類型檢測、編譯時檢查等等;

TypeScript 可以讓 JavaScript 更好地與其他語言進行互動,甚至轉換為其他語言;

▐ 工程化通用元件

當需求不斷變多後,“愛偷懶”的工程師們就會把經常用到的内容進行抽象,比如從很早以前就有的 ExtJS、Twitter 工程師釋出的 Bootstrap 再到今天的 Ant Design、Element UI 等,都幫助我們更快更好更穩定地完成一些通用頁面能力的開發。

  • React:Ant Design、Fusion Design
  • Vue:Element UI、iView、Ant Design of Vue

邏輯抽象能力

随着我對 JavaScript 應用的編寫經驗不斷增加,我所嘗試的技術和場景也在不斷地變得更加複雜。而當邏輯代碼變得越來越複雜時我也漸漸發現一個新的問題,很多時候我所編寫的邏輯代碼是相似的,但相似之餘其中的一些細節不盡相同,而這些代碼往往是後期維護成本最高的。

這就讓我感到十分困惑,如何讓我的代碼寫起來沒有那麼繁瑣的同時,又不丢失原本代碼的應有邏輯呢?這就讓我想起了之前學習的架構,它們的實作原理不就是把原本我們寫得非常繁瑣的邏輯代碼進行壓縮,讓我們寫起來更加簡潔直覺嗎?

這是我曾經面試過的一位校招候選人寫的代碼,其背景是用于快速判斷自走棋類遊戲中不同的增益能力(Buff)的成立狀态。但顯然這樣的代碼在實際開發中是絕對不允許存在的:

代碼邏輯過于備援;

一旦通用判斷邏輯出現變動,需要每一個都進行手動維護;

沒有良好的可維護性;

是以我便提出如何讓這些代碼寫得更加“優雅”和利于維護。

export default {
  beastBuff: (state) => {
    let arr = [];
    if (state.raceCount[0]['beast'] == 2 || state.raceCount[0]['beast'] == 3) {
      console.log(`you got 2 beast`)
      arr.push(state.racebuffdata[8])
    } else if (state.raceCount[0]['beast'] == 4 || state.raceCount[0]['beast'] == 5) {
      console.log(`you got 4 beast`)
      arr.pop()
      arr.push(state.racebuffdata[9])
    } else if (state.raceCount[0]['beast'] == 6) {
      console.log(`you got 6 beast`)
      arr.pop()
      arr.push(state.racebuffdata[10])
    } else if (state.raceCount[0]['beast'] < 2 && arr.length == 1) {
      arr.pop()
    }
    return arr;
  },
  caveclanBuff: (state) => {
    let arr = [];
    if (state.raceCount[1]['caveclan'] == 2 || state.raceCount[1]['caveclan'] == 3) {
      console.log(`you got 2 caveclan`)
      arr.push(state.racebuffdata[11])
    } else if (state.raceCount[1]['caveclan'] == 4) {
      console.log(`you got 4 caveclan`)
      arr.pop()
      arr.push(state.racebuffdata[12])
    } else if (state.raceCount[1]['caveclan'] < 2 && arr.length == 1) {
      arr.pop()
    }
    return arr;
  },
  demonBuff: (state) => {
    let arr = [];
    if (state.raceCount[2]['demon'] == 1) {
      console.log(`you got 1 demon`)
      arr.push(state.racebuffdata[5])
    } else if (state.raceCount[2]['demon'] < 1 && arr.length == 1) {
      arr.pop()
    }
    return arr;
  }
  // ...
}           

我們不難發現這幾個 xxxBuff 函數中的邏輯都非常接近,但也各有不同。那麼如何能将這段代碼進行優化和抽象呢?我當時給 TA 提出了一份示例代碼:

const beastConfig = [
  [2, [2, 3], 8],
  [4, [4, 5], 9],
  [6, [6], 10],
  [2]
]           

這份代碼中的每一個數字在上面的 beastBuff 函數中都可以一一找到,那麼要怎麼将它們複用到邏輯代碼中,實作與原本的代碼一樣的功能呢?

2020年,我們該如何學習 WEB 前端開發我的從 0 開始接觸 Node.js架構時代工程之路邏輯抽象能力更高層次的思考能力EOF

我同樣給他寫了一份參考答案:

const generateBuff = (rate, configArr) => {
  return state => {
    const arr = []

    for (const [ output, conditions, index ] of configArr) {
      if (conditions && index) {
        // Buff calculating
        const isHit = conditions.some(condition => state.raceCount[0][race] == condition)
        if (isHit) {
          console.log(`you got ${output} ${race}`)
          arr.pop()
          arr.push(state.racebuffdata[index])
          break
        }
      } else if (state.raceCount[0][race] < output && arr.length === 1) {
        // Last condition
        arr.pop()
      }
    }

    return arr
  }
}

export default {
  beastBuff: generateBuff('beast', [
    [2, [2, 3], 8],
    [4, [4, 5], 9],
    [6, [6], 10],
    [2]
  ]),

  caveclanBuff: generateBuff('caveclan', [
    [2, [2, 3], 11],
    [4, [4], 12],
    [2]
  ]),
  
  // ...
}           

原本代碼裡面通過 hard code 實作的判斷邏輯,通過觀察其中的共同點,并思考能否通轉換為可抽象部分,這同樣也是一名優秀的工程師所必須具備的能力。

Be D.R.Y.! (Don't repeat yourself)

更高層次的思考能力

随着我對不同業務、不同場景和不同代碼難度的不斷探索和研究,我發現在前端領域乃至整個程式設計領域裡,不同的架構和架構層出不窮地發展,其實在根本上就是各種實際業務場景在尋找更合适的 Better Practice(更好實踐)。就如前面的所說的那樣,不同的架構作者在開發的時候會采取不同的代碼結構甚至代碼哲學,這些不同的思維角度可能在架構的源碼中并不會直接表現出來。但我不會說研讀源碼完全沒有用!因為研讀源碼最起碼可以學習其中的一些 trick 或者代碼習慣。

但更重要的是了解從 API、系統架構上進行思考,因為隻有多思考了,你才能逐漸變得比其他人更加對不同的技術遊刃有餘。

EOF

這一個流程并不是嚴謹的學習路線,更多的是我個人的一些經驗總結。當然除了我所提到的學習知識點以外,還有很多不同的分支對應着不同的實際業務和場景,比如配合 Electron 開發桌面端應用、配合 React Native/Flutter 開發移動端應用、配合 Node.js/QuickJS/FibJS 開發嵌入式應用、配合 TensorFlow.js 開發适用于前端甚至适用于邊緣計算的機器學習應用、配合 WebAssembly 将 Web 應用的使用體驗提升到接近原生應用的境界……

關于 JavaScript 有一個很有名的預言:

凡是能用 JavaScript 重寫的,終将會使用 JavaScript 重寫

無論這句話會不會最終完全實作,但目前我們已經能看到很多應用逐漸通過 Web 應用的形式雲端化,比如 Photoshop、音視訊編輯軟體、代碼編輯器甚至是大型遊戲等等原本我們完全沒想到可以運作在浏覽器中。

前端開發困難嗎?不困難、門檻相對比較低。簡單嗎?不簡單,通過相信看到這裡的你也已經有所體會了。當然實際要如何選擇路線和方向,還是你自己所遇到的經曆和機遇來決定的。

關注「淘系技術」微信公衆号,一個有溫度有内容的技術社群~

2020年,我們該如何學習 WEB 前端開發我的從 0 開始接觸 Node.js架構時代工程之路邏輯抽象能力更高層次的思考能力EOF