🔍 搜尋結果:with

🔍 搜尋結果:with

使用外掛程式和主題在 OhMyZsh 和 Hyper 上設定自動完成功能的初學者指南!

您的普通 bash 可能具有您通常需要的功能,但**如果您是常規終端用戶,zsh 將改變您鍵入命令的方式。** zsh、ohmyzsh 和 hyper 一起提供的功能將讓您大吃一驚。 > 您知道您可以從終端控制 Spotify 嗎?是的,超級插件可以讓您做到這一點。 對於初學者來說,設定這些東西可能會讓人不知所措,所以這裡有一個非常簡單的入門指南! 🤩 --- 🔥 簡介 ---- 如果您使用的是如下所示的常規終端,則您會錯過 OhMyZsh 提供的許多功能。 ![Flaviocope 的 MacOS 終端](https://flaviocopes.com/macos-terminal-setup/Screenshot%202019-01-29%20at%2018.34.04.png) 今天,您將進行終端改造,使其看起來像這樣... ![我的超級終端](https://i.ibb.co/DW05RzF/Hyper-Terminal-Kumar-Abhirup.jpg) 不僅僅是外觀,OhMyZsh 還具有豐富的功能來點亮您的程式設計之旅。 在教程結束時,這就是您可以在終端機中執行的操作... - NPM、Git 自動完成 - 在終端機中輸入時自動建議 - 語法高亮顯示指令是否已定義 - 使用遊標編輯終端命令 - 查看目前目錄的`git branch`和`git status` - 開啟與目前分頁相同目錄的新分頁 - 使用 OhMyZsh 功能,例如不使用`cd`進行導航、使用`ll` 、更簡單的基於 Tab 鍵單擊的導航等等! --- ❤️ 開始吧 ------ 首先,您必須安裝`zsh` 。在某些情況下(取決於您正在執行的作業系統),它可能已經安裝。因此,請透過在終端機中執行`zsh --version`檢查它是否已安裝。 `zsh`在不同作業系統的安裝過程有所不同。查看[Zsh 安裝指南](https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH)來安裝 zsh。 安裝 Zsh 後,請確保將其設為預設 shell。為此,請在終端機中執行以下命令。 ``` $ sudo chsh -s $(which zsh) ``` 登出並登入回預設 shell。執行`echo $SHELL`並預期輸出`/bin/zsh`或類似內容。 --- 🔰 安裝 OhMyZsh ------------ > 請注意,zsh 和 OhMyZsh 是不同的。 透過在終端機中執行以下命令來安裝`OhMyZsh` 。 ``` $ sudo sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" ``` 當您安裝 OhMyZsh 時,它會附帶許多插件來幫助您! 若要新增實用的插件,請在 TextEdit/Notepad/Vim/VSCode 中開啟`~/.zshrc` 。 在檔案中看到的插件清單中,只需新增一個名為`npm`的插件,如下所示 👇 ``` plugins=( git bundler dotenv osx rake rbenv ruby npm # you added this ) ``` 瞧!您已經完成了 OhMyZsh!若要查看更改,請在終端機中執行`source ~/.zshrc` ,現在您就擁有了 OhMyZsh shell 的功能。 --- 🔰 依時間安裝 HyperTerm ----------------- Zeit(now.sh 和 Next.js 的建立者)為我們建立了一個很棒的終端應用程式,它是用 Electron 建置的。 從[這裡](https://hyper.is/)下載 Hyper。 --- ### ⚛️ 使用 OhMyZsh 設定 Hyper 打開超級終端機。您不會看到 OhMyZsh 在那裡執行。因此,請轉到超級設定。在 OSX 上,它是`Hyper > Preferences` 。 這將在您最喜歡的編輯器中開啟一個`.hyper.js`檔案。該文件包含您的終端的所有設置,非常容易控制! 若要在 Hyper 中啟用 OhMyZsh 作為預設 shell,請在`.hyper.js`中進行此變更 👇 ``` - shell: '/bin/bash' + shell: '/bin/zsh' ``` **這將使 OhMyZsh 成為您的預設超級終端 shell!** --- ### 🤩 輸入指令時自動完成 Git 將`zsh-autocomplete`插件複製到 OhMyZsh 插件資料夾中。 ``` $ sudo git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions ``` 完成後,將外掛程式新增至`~/.zshrc`檔案的外掛程式清單中。 ``` plugins=( ... zsh-autosuggestions ) ``` --- ### 🎉 Zsh 語法高亮 Git 將`zsh-syntax-highlighting`外掛程式克隆到 OhMyZsh 外掛程式資料夾中。 ``` $ sudo git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting ``` 並再次將其新增至`.zshrc`檔案的外掛程式清單。 ``` plugins=( ... zsh-syntax-highlighting ) ``` > 注意:若要反映您所做的每項更改,請在終端機中執行`source ~/.zshrc` 。 --- ### 📯 啟用 Hyper 相關功能與主題 透過切換超級終端的設定來開啟`.hyper.js` 。 請查看`plugins: [...]`部分並將這些插件名稱貼到此處。 ``` plugins: [ ... 'hypercwd', 'hyper-statusline', 'hyper-alt-click', 'hyperterm-safepaste', 'hyper-search', 'hypergoogle', 'hyperborder', 'hyper-tab-icons', 'hyper-hide-title', 'shades-of-purple-hyper' ], ``` 儲存文件,Hyper 會自動為您安裝這些外掛程式和主題。要反映更改,只需關閉並再次啟動超級終端即可。 萬歲!**現在,您的終端機中已擁有本 DEV.to 文章開頭列出的所有功能。** --- 獎勵:在 VSCode 中為整合終端設定相同的終端配置 --------------------------- 在 VSCode 設定中,新增以下 JSON 鍵值對,然後就可以開始了! ``` { ... "terminal.integrated.shell.osx": "/bin/zsh", "terminal.integrated.fontSize": 16 } ``` **就是這樣,夥計們!** --- 🔥 資源 ---- - <https://ohmyz.sh> - <https://hyper.is> --- 🏆 關於我 ----- **我是 Kumar Abhirup,一位來自印度的 16 歲 JavaScript React 開發人員,每天都在學習新事物。** [在 Twitter 上與我聯絡 🐦](https://twitter.com/kumar_abhirup) [我的個人網站和作品集🖥️](https://kumar.now.sh) *請在下面評論您更好的方法以及改進本文的建議。 :)* --- 原文出處:https://dev.to/kumareth/a-beginner-s-guide-for-setting-up-autocomplete-on-ohmyzsh-hyper-with-plugins-themes-47f2

4 分鐘內解釋智威湯遜(帶視覺效果)

{% 嵌入 https://youtu.be/0WH9oiYMS3M %} 介紹 == JWT 身份驗證和會話身份驗證是對 Web 應用程式使用者進行身份驗證的方法。 在本文中,我們將解釋 JWT 的詳細資訊、其結構及其優缺點。 JWT 代表**JSON Web Token** ,它是一種常用的無狀態用戶身份驗證[標準](https://datatracker.ietf.org/doc/html/rfc7519),用於以 JSON 格式在用戶端和伺服器之間安全地傳輸資訊。 預設情況下,JWT 已編碼但未加密。 它使用只有伺服器知道的密鑰進行數位簽署。 它可以加密,但在本文中,我們將重點放在簽署的非加密令牌。 智威湯遜的結構 ======= JSON Web 令牌由以句點分隔的 3 個部分組成。 標頭、有效負載和簽名。 每個部分均採用 Base64 編碼。 ![JWT編碼結構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ur5abitdnsxnmlpzs2pk.png) ### 標頭 標頭由令牌類型(JWT)和使用的簽章演算法(例如 HMAC SHA256 或 RSA)組成。 ``` { "typ": "JWT", "alg": "HS256" } ``` ### 有效載荷 有效負載由聲明組成。 聲明是關於用戶的聲明和附加資料。 例如,我們有令牌的發行時間。 我們也有它的過期時間,因為令牌應該過期。 ``` { "iss": "example_issuer", "sub": "user_id123", "exp": 1644768000, "iat": 1644744000 } ``` ### 簽名 簽名是 JWT 最重要的部分。 它是使用標頭、有效負載和秘密進行計算的,這些資訊將饋送到簽名演算法中使用。 ``` signature = HMAC-SHA256(base64urlEncode(header) + "." + base64urlEncode(payload), secret_salt ) ``` ![JWT 結構解碼](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o72ktulq29hondu66rbe.png) 腳步 == 典型的 JWT 授權流程涉及的步驟如下: **1- 身份驗證:**使用者使用使用者名稱和密碼登錄,或使用 Google 或 Facebook 等。 伺服器驗證提供的憑證。 **2- 令牌產生並將令牌發送到客戶端:**伺服器將產生 JWT 並將其發送到客戶端,客戶端儲存它以供將來使用。 **3-將令牌傳送到伺服器:**當客戶端想要存取伺服器上受保護的資源時,它會在 HTTP 請求的 Authorization 標頭中傳送 JWT。 ``` axios.get(URL, { headers: { 'Authorization': 'Bearer ' + token, }, }) ``` **4-驗證令牌:**伺服器接收請求並透過使用用於簽署的金鑰檢查其簽章來驗證 JWT。 如果 JWT 有效,伺服器會提取其中包含的資訊並使用它來確定使用者有權執行哪些操作。 **5-授權請求:**如果使用者被授權存取資源,伺服器將傳回請求的資料。如果用戶未獲得授權,伺服器將傳回錯誤訊息。 ![智威湯遜流程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igvye1880i9491jbz525.png) **優點** ====== ![智威湯遜優勢](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mg76chqzkpoz8pb7o9dt.png) - 輕的 - 便攜式:可在多個平台、網路和行動平台上處理。 - JSON 解析器在大多數程式語言中都很常見。 - 由於密鑰儲存在伺服器端,因此可以防止篡改。 - 由於 JWT 令牌的無狀態性質,伺服器不需要儲存任何會話資訊。 缺點 == ![缺點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmvzoi46hyd3wt8t6hpp.png) - 在伺服器端,應在登出時手動將未過期的 JWT 標記為無效。 即使 JWT 從客戶端刪除後仍然有效。 ![JWT 仍然有效](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fdrm27cehhd0bck5djzt.png) 使用的方法是令牌黑名單,它涉及在伺服器端維護無效令牌的列表,以防止它們重新用於身份驗證。 ![令牌黑名單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wultrfhdmneg7yoqtyv8.png) - 對於簽署的非加密令牌,我們不應該儲存敏感訊息,因為 JWT 可以防止篡改,但任何人都可以解碼和讀取。 - 如果被攔截,可以提供完全存取權。這就是為什麼客戶端的 JWT 應該儲存在安全的地方,例如在瀏覽器中的 HttpOnly cookie 中。 **我的其他文章:** - [點擊 URL 時會發生什麼 - DNS 查找、TCP 握手和 HTTP(帶視覺效果)](https://dev.to/jaypmedia/what-happens-when-you-click-a-url-dns-lookup-tcp-handshake-http-33i1) - \[TLS 憑證📝 - 為 Web 開發人員簡化 \](https://dev.to/jaypmedia/tls-certificates-2902) {% 嵌入 https://youtu.be/1XbvNSwvkAs %} --- 原文出處:https://dev.to/jaypmedia/jwt-explained-in-4-minutes-with-visuals-g3n

我正在建立一個全端應用程式:以下是我將要使用的庫......

您可以使用無數的框架和函式庫來改進您的全端應用程式。 我們將介紹令人興奮的概念,例如應用程式內通知、使用 React 製作影片、從為開發人員提供的電子郵件 API 到在瀏覽器中建立互動式音樂。 那我們就開始吧。 (不要忘記為這些庫加註星標以表示您的支持)。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqoipyuoxgb83swyoo4a.gif) https://github.com/CopilotKit/CopilotKit --- 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 在數小時內為您的產品提供 AI Copilot。 ------------------------------------------------------------------------------------ ![副駕駛套件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzuxjfog2ldam3csrl62.png) 您可以使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。它們還提供內建(完全可自訂)Copilot 原生 UX 元件,例如`<CopilotKit />` 、 `<CopilotPopup />` 、 `<CopilotSidebar />` 、 `<CopilotTextarea />` 。 開始使用以下 npm 指令。 ``` npm i @copilotkit/react-core @copilotkit/react-ui @copilotkit/react-textarea ``` 這是整合 CopilotTextArea 的方法。 ``` import { CopilotTextarea } from "@copilotkit/react-textarea"; import { useState } from "react"; export function SomeReactComponent() { const [text, setText] = useState(""); return ( <> <CopilotTextarea className="px-4 py-4" value={text} onValueChange={(value: string) => setText(value)} placeholder="What are your plans for your vacation?" autosuggestionsConfig={{ textareaPurpose: "Travel notes from the user's previous vacations. Likely written in a colloquial style, but adjust as needed.", chatApiConfigs: { suggestionsApiConfig: { forwardedParams: { max_tokens: 20, stop: [".", "?", "!"], }, }, }, }} /> </> ); } ``` 您可以閱讀[文件](https://docs.copilotkit.ai/getting-started/quickstart-textarea)。 基本概念是在幾分鐘內建立可用於基於 LLM 的全端應用程式的 AI 聊天機器人。 https://github.com/CopilotKit/CopilotKit --- 2. [Storybook](https://github.com/storybookjs/storybook) - UI 開發、測試和文件變得簡單。 --------------------------------------------------------------------------- ![故事書](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/78rfum1ydisn51qhb408.png) Storybook 是一個用於獨立建立 UI 元件和頁面的前端工作坊。它有助於 UI 開發、測試和文件編制。 他們在 GitHub 上有超過 57,000 次提交、81,000 多個 star 和 1300 多個版本。 這是您為專案建立簡單元件的方法。 ``` import type { Meta, StoryObj } from '@storybook/react'; import { YourComponent } from './YourComponent'; //👇 This default export determines where your story goes in the story list const meta: Meta<typeof YourComponent> = { component: YourComponent, }; export default meta; type Story = StoryObj<typeof YourComponent>; export const FirstStory: Story = { args: { //👇 The args you need here will depend on your component }, }; ``` 您可以閱讀[文件](https://storybook.js.org/docs/get-started/setup)。 如今,UI 除錯起來很痛苦,因為它們與業務邏輯、互動狀態和應用程式上下文糾纏在一起。 Storybook 提供了一個獨立的 iframe 來渲染元件,而不會受到應用程式業務邏輯和上下文的干擾。這可以幫助您將開發重點放在元件的每個變體上,甚至是難以觸及的邊緣情況。 https://github.com/storybookjs/storybook --- 3. [Appwrite](https://github.com/appwrite/appwrite) - 您的後端減少麻煩。 --------------------------------------------------------------- ![應用程式寫入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8x568uz21seyygw6b72z.png) ![帶有 appwrite 的 sdk 列表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp7k8qnamsluto7eifpl.png) Appwrite 的開源平台可讓您將身份驗證、資料庫、函數和儲存體新增至您的產品中,並建立任何規模的任何應用程式、擁有您的資料並使用您喜歡的編碼語言和工具。 他們有很好的貢獻指南,甚至不厭其煩地詳細解釋架構。 開始使用以下 npm 指令。 ``` npm install appwrite ``` 您可以像這樣建立一個登入元件。 ``` "use client"; import { useState } from "react"; import { account, ID } from "./appwrite"; const LoginPage = () => { const [loggedInUser, setLoggedInUser] = useState(null); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [name, setName] = useState(""); const login = async (email, password) => { const session = await account.createEmailSession(email, password); setLoggedInUser(await account.get()); }; const register = async () => { await account.create(ID.unique(), email, password, name); login(email, password); }; const logout = async () => { await account.deleteSession("current"); setLoggedInUser(null); }; if (loggedInUser) { return ( <div> <p>Logged in as {loggedInUser.name}</p> <button type="button" onClick={logout}> Logout </button> </div> ); } return ( <div> <p>Not logged in</p> <form> <input type="email" placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <input type="text" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} /> <button type="button" onClick={() => login(email, password)}> Login </button> <button type="button" onClick={register}> Register </button> </form> </div> ); }; export default LoginPage; ``` 您可以閱讀[文件](https://appwrite.io/docs)。 Appwrite 可以非常輕鬆地建立具有開箱即用的擴充功能的可擴展後端應用程式。 https://github.com/appwrite/appwrite --- 4. [Wasp](https://github.com/wasp-lang/wasp) - 用於 React、node.js 和 prisma 的類似 Rails 的框架。 --------------------------------------------------------------------------------------- ![黃蜂](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fi2mwazueoc3ezjx8a9q.png) 使用 React 和 Node.js 開發全端 Web 應用程式的最快方法。這不是一個想法,而是一種建立瘋狂快速全端應用程式的不同方法。 這是將其整合到元件中的方法。 ``` import getRecipes from "@wasp/queries/getRecipes"; import { useQuery } from "@wasp/queries"; import type { User } from "@wasp/entities"; export function HomePage({ user }: { user: User }) { // Due to full-stack type safety, `recipes` will be of type `Recipe[]` here. const { data: recipes, isLoading } = useQuery(getRecipes); // Calling our query here! if (isLoading) { return <div>Loading...</div>; } return ( <div> <h1>Recipes</h1> <ul> {recipes ? recipes.map((recipe) => ( <li key={recipe.id}> <div>{recipe.title}</div> <div>{recipe.description}</div> </li> )) : 'No recipes defined yet!'} </ul> </div> ); } ``` 您可以閱讀[文件](https://wasp-lang.dev/docs)。 https://github.com/wasp-lang/wasp --- 5. [Novu](https://github.com/novuhq/novu) - 將應用程式內通知新增至您的應用程式! -------------------------------------------------------------- ![再次](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/716b7biilet4auudjlcu.png) Novu 提供開源通知基礎架構和功能齊全的嵌入式通知中心。 這就是如何使用`React`建立 novu 元件以用於應用程式內通知。 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, } from "@novu/notification-center"; function App() { return ( <> <NovuProvider subscriberId={process.env.REACT_APP_SUB_ID} applicationIdentifier={process.env.REACT_APP_APP_ID} > <PopoverNotificationCenter> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> </> ); } export default App; ``` 您可以閱讀[文件](https://docs.novu.co/getting-started/introduction)。 https://github.com/novuhq/novu --- 6. [Remotion](https://github.com/remotion-dev/remotion) - 使用 React 以程式設計方式製作影片。 ------------------------------------------------------------------------------- ![遠端](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmnrxhsc7b9mm5oagflm.png) 使用 React 建立真正的 MP4 影片,使用伺服器端渲染和參數化擴展影片製作。 開始使用以下 npm 指令。 ``` npm init video ``` 它為您提供了一個幀號和一個空白畫布,您可以在其中使用 React 渲染任何您想要的內容。 這是一個範例 React 元件,它將當前幀渲染為文字。 ``` import { AbsoluteFill, useCurrentFrame } from "remotion";   export const MyComposition = () => { const frame = useCurrentFrame();   return ( <AbsoluteFill style={{ justifyContent: "center", alignItems: "center", fontSize: 100, backgroundColor: "white", }} > The current frame is {frame}. </AbsoluteFill> ); }; ``` 您可以閱讀[文件](https://www.remotion.dev/docs/)。 過去兩年,remotion 團隊因製作 GitHub Wrapped 而聞名。 https://github.com/remotion-dev/remotion --- [7.NocoDB](https://github.com/nocodb/nocodb) - Airtable 的替代品。 ------------------------------------------------------------- ![諾科資料庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw3tchfgyzehye5c39xq.png) Airtable 的免費開源替代品是 NocoDB。它可以使用任何 MySQL、PostgreSQL、SQL Server、SQLite 或 MariaDB 資料庫製作智慧型電子表格。 其主要目標是讓強大的計算工具得到更廣泛的使用。 開始使用以下 npx 指令。 ``` npx create-nocodb-app ``` 您可以閱讀[文件](https://docs.nocodb.com/)。 NocoDB 的建立是為了為世界各地的數位企業提供強大的開源和無程式碼資料庫介面。 您可以非常快速地將airtable資料匯入NocoDB。 https://github.com/nocodb/nocodb --- 8.[新穎](https://github.com/steven-tey/novel)- 所見即所得編輯器,具有人工智慧自動完成功能。 ------------------------------------------------------------------- ![小說](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo34vd9twpxcpbpzgchi.png) 它使用`Next.js` 、 `Vercel AI SDK` 、 `Tiptap`作為文字編輯器。 開始使用以下 npm 指令。 ``` npm i novel ``` 您可以這樣使用它。有多種選項可用於改進您的應用程式。 ``` import { Editor } from "novel"; export default function App() { return <Editor />; } ``` https://github.com/steven-tey/novel --- 9. [Blitz](https://github.com/blitz-js/blitz) - 缺少 NextJS 的全端工具包。 ----------------------------------------------------------------- ![閃電戰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz6ineg1o7xyv7pwbuqn.png) Blitz 繼承了 Next.js 的不足,為全球應用程式的交付和擴展提供了經過實戰考驗的函式庫和約定。 開始使用以下 npm 指令。 ``` npm install -g blitz ``` 這就是您如何使用 Blitz 建立新頁面。 ``` const NewProjectPage: BlitzPage = () => { const router = useRouter() const [createProjectMutation] = useMutation(createProject) return ( <div> <h1>Create New Project</h1> <ProjectForm submitText="Create Project" schema={CreateProject} onSubmit={async (values) => { // This is equivalent to calling the server function directly const project = await createProjectMutation(values) // Notice the 'Routes' object Blitz provides for routing router.push(Routes.ProjectsPage({ projectId: project.id })) }} /> </div> ); }; NewProjectPage.authenticate = true NewProjectPage.getLayout = (page) => <Layout>{page}</Layout> export default NewProjectPage ``` 您可以閱讀[文件](https://blitzjs.com/docs/get-started)。 它使建築物改善了數倍。 ![閃電戰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc4mb5wdksjv1ybx71co.png) https://github.com/blitz-js/blitz --- 10. [Supabase](https://github.com/supabase/supabase) - 開源 Firebase 替代品。 ----------------------------------------------------------------------- ![蘇帕貝斯](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksfygjhrzhmsg9cnvobs.png) 我們大多數人都已經預料到 SUPABASE 會出現在這裡,因為它實在是太棒了。 開始使用以下 npm 指令 (Next.js)。 ``` npx create-next-app -e with-supabase ``` 這是使用 supabase 建立用戶的方法。 ``` import { createClient } from '@supabase/supabase-js' // Initialize const supabaseUrl = 'https://chat-room.supabase.co' const supabaseKey = 'public-anon-key' const supabase = createClient(supabaseUrl, supabaseKey) // Create a new user const { user, error } = await supabase.auth.signUp({ email: '[email protected]', password: 'example-password', }) ``` 您可以閱讀[文件](https://supabase.com/docs)。 您可以使用身份驗證、即時、邊緣功能、儲存等功能建立一個速度極快的應用程式。 Supabase 涵蓋了這一切! 他們還提供了一些入門套件,例如 AI 聊天機器人和 Stripe 訂閱。 https://github.com/supabase/supabase --- [11.Refine](https://github.com/refinedev/refine) - 企業開源重組工具。 ------------------------------------------------------------ ![精煉](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qx0kd6t2jzdtf90k5ke3.png) 建立具有無與倫比的靈活性的管理面板、儀表板和 B2B 應用程式 您可以在一分鐘內使用單一 CLI 命令進行設定。 它具有適用於 15 多個後端服務的連接器,包括 Hasura、Appwrite 等。 開始使用以下 npm 指令。 ``` npm create refine-app@latest ``` 這就是使用 Refine 新增登入資訊的簡單方法。 ``` import { useLogin } from "@refinedev/core"; const { login } = useLogin(); ``` 您可以閱讀[文件](https://refine.dev/docs/)。 https://github.com/refinedev/refine --- 12. [Zenstack](https://github.com/zenstackhq/zenstack) - 資料庫到 API 和 UI 只需幾分鐘。 ----------------------------------------------------------------------------- ![禪斯塔克](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3b6n2ea3jeeva6uujoex.png) TypeScript 工具包,透過強大的存取控制層增強 Prisma ORM,並釋放其全端開發的全部功能。 開始使用以下 npx 指令。 ``` npx zenstack@latest init ``` 這是透過伺服器適配器建立 RESTful API 的方法。 ``` // pages/api/model/[...path].ts import { requestHandler } from '@zenstackhq/next'; import { enhance } from '@zenstackhq/runtime'; import { getSessionUser } from '@lib/auth'; import { prisma } from '@lib/db'; // Mount Prisma-style APIs: "/api/model/post/findMany", "/api/model/post/create", etc. // Can be configured to provide standard RESTful APIs (using JSON:API) instead. export default requestHandler({ getPrisma: (req, res) => enhance(prisma, { user: getSessionUser(req, res) }), }); ``` 您可以閱讀[文件](https://zenstack.dev/docs/welcome)。 https://github.com/zenstackhq/zenstack --- 13. [Buildship](https://github.com/rowyio/buildship) - 低程式碼視覺化後端建構器。 -------------------------------------------------------------------- ![建造船](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzlrynz5xephv4t9layd.png) 對於您正在使用無程式碼應用程式建構器(FlutterFlow、Webflow、Framer、Adalo、Bubble、BravoStudio...)或前端框架(Next.js、React、Vue...)建立的應用程式,您需要一個後端來支援可擴展的 API、安全工作流程、自動化等。BuildShip 為您提供了一種完全視覺化的方式,可以在易於使用的完全託管體驗中可擴展地建立這些後端任務。 這意味著您不需要在雲端平台上爭論或部署東西、執行 DevOps 等。只需立即建置和交付 🚀 https://github.com/rowyio/buildship --- 14. [Taipy](https://github.com/Avaiga/taipy) - 將資料和人工智慧演算法整合到生產就緒的 Web 應用程式中。 ----------------------------------------------------------------------------- ![打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ohv3johuz92lsaux52oq.png) Taipy 是一個開源 Python 庫,用於輕鬆的端到端應用程式開發, 具有假設分析、智慧管道執行、內建調度和部署工具。 開始使用以下命令。 ``` pip install taipy ``` 這是一個典型的Python函數,也是過濾器場景中使用的唯一任務。 ``` def filter_genre(initial_dataset: pd.DataFrame, selected_genre): filtered_dataset = initial_dataset[initial_dataset['genres'].str.contains(selected_genre)] filtered_data = filtered_dataset.nlargest(7, 'Popularity %') return filtered_data ``` 您可以閱讀[文件](https://docs.taipy.io/en/latest/)。 他們還有很多可供您建立的[演示應用程式教學](https://docs.taipy.io/en/latest/knowledge_base/demos/)。 https://github.com/Avaiga/taipy --- 15. [LocalForage](https://github.com/localForage/localForage) - 改進了離線儲存。 ------------------------------------------------------------------------ ![當地飼料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hlrka5pybvmgmo2djel.png) LocalForage 是一個 JavaScript 函式庫,它透過使用非同步資料儲存和簡單的、類似 localStorage 的 API 來改善 Web 應用程式的離線體驗。它允許開發人員儲存多種類型的資料而不僅僅是字串。 開始使用以下 npm 指令。 ``` npm install localforage ``` 只需包含 JS 檔案並開始使用 localForage。 ``` <script src="localforage.js"></script> ``` 您可以閱讀[文件](https://localforage.github.io/localForage/#installation)。 https://github.com/localForage/localForage --- 16. [Zod](https://github.com/colinhacks/zod) - 使用靜態類型推斷的 TypeScript-first 模式驗證。 ------------------------------------------------------------------------------- ![佐德](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1s6zvmqr0lv93vsrhofs.png) Zod 的目標是透過最大限度地減少重複的類型聲明來對開發人員友好。使用 Zod,您聲明一次驗證器,Zod 將自動推斷靜態 TypeScript 類型。將更簡單的類型組合成複雜的資料結構很容易。 開始使用以下 npm 指令。 ``` npm install zod ``` 這是您在建立字串架構時自訂一些常見錯誤訊息的方法。 ``` const name = z.string({ required_error: "Name is required", invalid_type_error: "Name must be a string", }); ``` 您可以閱讀[文件](https://zod.dev/)。 它適用於 Node.js 和所有現代瀏覽器 https://github.com/colinhacks/zod --- 17.[多普勒](https://github.com/DopplerHQ)- 管理你的秘密。 ----------------------------------------------- ![多普勒](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gycxnuiiwsvibryrytlc.png) 您可以透過在具有開發、暫存和生產環境的專案中組織機密來消除機密蔓延。 開始使用以下指令 (MacOS)。 ``` $ brew install dopplerhq/cli/doppler $ doppler --version ``` 這是安裝 Doppler CLI[的 GitHub Actions 工作流程](https://github.com/DopplerHQ/cli-action)。 您可以閱讀[文件](https://docs.doppler.com/docs/start)。 ``` name: Example action on: [push] jobs: my-job: runs-on: ubuntu-latest steps: - name: Install CLI uses: dopplerhq/cli-action@v3 - name: Do something with the CLI run: doppler secrets --only-names env: DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }} ``` https://github.com/DopplerHQ --- 18. [FastAPI](https://github.com/tiangolo/fastapi) - 高效能、易於學習、快速編碼、可用於生產。 ------------------------------------------------------------------------- ![快速API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2awncoia6255ycl95lk.png) FastAPI 是一個現代、快速(高效能)的 Web 框架,用於基於標準 Python 類型提示使用 Python 3.8+ 建立 API。 開始使用以下命令。 ``` $ pip install fastapi ``` 這是您開始使用 FastAPI 的方式。 ``` from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` 您的編輯器將自動完成屬性並了解它們的類型,這是使用 FastAPI 的最佳功能之一。 您可以閱讀[文件](https://fastapi.tiangolo.com/)。 https://github.com/tiangolo/fastapi --- 19. [Flowise](https://github.com/FlowiseAI/Flowise) - 拖放 UI 來建立您的客製化 LLM 流程。 ---------------------------------------------------------------------------- ![流動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct732wv07pvwx0nmavp5.png) Flowise 是一款開源 UI 視覺化工具,用於建立客製化的 LLM 編排流程和 AI 代理程式。 開始使用以下 npm 指令。 ``` npm install -g flowise npx flowise start OR npx flowise start --FLOWISE_USERNAME=user --FLOWISE_PASSWORD=1234 ``` 這就是整合 API 的方式。 ``` import requests url = "/api/v1/prediction/:id" def query(payload): response = requests.post( url, json = payload ) return response.json() output = query({ question: "hello!" )} ``` 您可以閱讀[文件](https://docs.flowiseai.com/)。 https://github.com/FlowiseAI/Flowise --- 20. [Scrapy](https://github.com/scrapy/scrapy) - Python 的快速進階網頁爬行和抓取框架.. ------------------------------------------------------------------------ ![鬥志旺盛](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1b2y1hzdsphw43b6v7b.png) Scrapy 是一個快速的高級網路爬行和網頁抓取框架,用於爬行網站並從頁面中提取結構化資料。它可用於多種用途,從資料探勘到監控和自動化測試。 開始使用以下命令。 ``` pip install scrapy ``` 建造並執行您的網路蜘蛛。 ``` pip install scrapy cat > myspider.py <<EOF import scrapy class BlogSpider(scrapy.Spider): name = 'blogspider' start_urls = ['https://www.zyte.com/blog/'] def parse(self, response): for title in response.css('.oxy-post-title'): yield {'title': title.css('::text').get()} for next_page in response.css('a.next'): yield response.follow(next_page, self.parse) EOF scrapy runspider myspider.py ``` 您可以閱讀[文件](https://scrapy.org/doc/)。 它擁有大約 50k+ 的星星,因此對於網頁抓取來說具有巨大的可信度。 https://github.com/scrapy/scrapy --- 21. [Tone](https://github.com/Tonejs/Tone.js) - 在瀏覽器中製作互動式音樂。 ------------------------------------------------------------- ![音調.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fokxsoblaohgs4tx75g3.png) 開始使用以下 npm 指令。 ``` npm install tone ``` 這是您開始使用 Tone.js 的方法 ``` // To import Tone.js: import * as Tone from 'tone' //create a synth and connect it to the main output (your speakers) const synth = new Tone.Synth().toDestination(); //play a middle 'C' for the duration of an 8th note synth.triggerAttackRelease("C4", "8n"); ``` 您可以閱讀[文件](https://github.com/Tonejs/Tone.js?tab=readme-ov-file#installation)。 https://github.com/Tonejs/Tone.js --- 22. [Spacetime](https://github.com/spencermountain/spacetime) - 輕量級 javascript 時區庫。 ----------------------------------------------------------------------------------- ![時空](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/abfyfuzt4nw4h7b8usab.png) 您可以計算遠端時區的時間;支持夏令時、閏年和半球。按季度、季節、月份、週來定位時間.. 開始使用以下 npm 指令。 ``` npm install spacetime ``` 您可以這樣使用它。 ``` <script src="https://unpkg.com/spacetime"></script> <script> var d = spacetime('March 1 2012', 'America/New_York') //set the time d = d.time('4:20pm') d = d.goto('America/Los_Angeles') d.time() //'1:20pm' </script> ``` https://github.com/spencermountain/spacetime --- 23. [Mermaid](https://github.com/mermaid-js/mermaid) - 從類似 markdown 的文字產生圖表。 ---------------------------------------------------------------------------- ![美人魚](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggubn86xv7fznxol6fw7.png) 您可以使用 Markdown with Mermaid 等文字產生流程圖或序列圖等圖表。 這就是建立圖表的方法。 ``` sequenceDiagram Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good! ``` 它將做出如下圖。 ![圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbuo2ey5q2x3sjwywizg.png) 您可以閱讀[VS Code](https://docs.mermaidchart.com/plugins/visual-studio-code)的[文件](https://mermaid.js.org/intro/getting-started.html)和外掛程式。 請參閱[即時編輯器](https://mermaid.live/edit#pako:eNpVkE1PwzAMhv9KlvM-2AZj62EIxJd24ADXXLzEbaKlcUkdUDX1v5MONomcnNevXz32UWoyKAvZ4mfCoPHRQRWhVuHeO42T7XZHNhTiFb0nMdRjYelbQETRUbpTwRM1uQ2erbaoDyqI_AbnZfjZVZYFVOBCy8J2DWlLwUQHKmAwKrwRo4gnF5Xid-gd2FEAL9hSyp12pMIpNcee2ArxEhH4LG-3D7TPoAPcnhL_4WVxcgHZkfedqIjMSI5ljbEGZ_LyxwFaSbZYo5JFLg3Eg5Iq9NkHiemjC1oWHBOOZWoM8PlQ_8Un45iiLErwbRY9gcH8PUrumuHKlWs5J2oKpasGPUWfZcvctMVsNrSnlWOb9lNN9ax1xkJk-7VZzVaL1RoWS1zdLuFmuTR6P9-sy8X1vDS3V_MFyL7vfwD_bJ1W)中的範例。 https://github.com/mermaid-js/mermaid --- 24.[公共 API](https://github.com/public-apis/public-apis) - 20 多個類別的 1400 多個 API。 ------------------------------------------------------------------------------- ![公共API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjapk9rwlzdl6bcyqdnl.png) 我們主要使用外部 API 來建立應用程式,在這裡您可以找到所有 API 的清單。網站連結在最後。 它在 GitHub 上擁有大約 279k+ 顆星。 ![公共API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rld5i88smezo1naawz7a.png) 從儲存庫取得網站連結非常困難。所以,我把它貼在這裡。 網址 - [Collective-api.vercel.app/](https://collective-api.vercel.app/) https://github.com/public-apis/public-apis --- 25. [Framer Motion](https://github.com/framer/motion) - 像魔法一樣的動畫。 ----------------------------------------------------------------- ![成幀器運動](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn4ecqkrhs8f4729bzps.png) 可用的最強大的動畫庫之一。 Framer 使用簡單的聲明性語法意味著您編寫的程式碼更少。更少的程式碼意味著您的程式碼庫更易於閱讀和維護。 您可以建立事件和手勢,並且使用 Framer 的社區很大,這意味著良好的支援。 開始使用以下 npm 指令。 ``` npm install framer-motion ``` 您可以這樣使用它。 ``` import { motion } from "framer-motion" <motion.div whileHover={{ scale: 1.2 }} whileTap={{ scale: 1.1 }} drag="x" dragConstraints={{ left: -100, right: 100 }} /> ``` 您可以閱讀[文件](https://www.framer.com/motion/introduction/)。 https://github.com/framer/motion --- 26.[順便說一句](https://github.com/btw-so/btw)- 在幾分鐘內建立您的個人部落格。 ---------------------------------------------------------- ![順便提一句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnne3lrfpolotmxkdz2m.png) 順便說一句,您可以註冊並使用,而無需安裝任何東西。您也可以使用開源版本自行託管。 ![順便提一句](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rli7hpoccqwpvba29b4.png) 使用順便說一句建立的[範例部落格](https://www.siddg.com/about)。 https://github.com/btw-so/btw --- 27. [Formbricks](https://github.com/formbricks/formbricks) - 開源調查平台。 -------------------------------------------------------------------- ![成型磚](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tp6ggyom33vdifd3m1vt.png) Formbricks 提供免費、開源的測量平台。透過精美的應用程式內、網站、連結和電子郵件調查收集用戶旅程中每個點的回饋。在 Formbricks 之上建置或利用預先建置的資料分析功能。 開始使用以下 npm 指令。 ``` npm install @formbricks/js ``` 這就是您開始使用 formbricks 的方法。 ``` import formbricks from "@formbricks/js"; if (typeof window !== "undefined") { formbricks.init({ environmentId: "claV2as2kKAqF28fJ8", apiHost: "https://app.formbricks.com", }); } ``` 您可以閱讀[文件](https://formbricks.com/docs/getting-started/quickstart-in-app-survey)。 https://github.com/formbricks/formbricks --- 28. [Stripe](https://github.com/stripe) - 支付基礎設施。 ------------------------------------------------- ![條紋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79yvcgsi4744cmryh15j.png) 數以百萬計的各種規模的公司在線上和親自使用 Stripe 來接受付款、發送付款、自動化財務流程並最終增加收入。 開始使用以下 npm 指令 (React.js)。 ``` npm install @stripe/react-stripe-js @stripe/stripe-js ``` 這就是使用鉤子的方法。 ``` import React, {useState} from 'react'; import ReactDOM from 'react-dom'; import {loadStripe} from '@stripe/stripe-js'; import { PaymentElement, Elements, useStripe, useElements, } from '@stripe/react-stripe-js'; const CheckoutForm = () => { const stripe = useStripe(); const elements = useElements(); const [errorMessage, setErrorMessage] = useState(null); const handleSubmit = async (event) => { event.preventDefault(); if (elements == null) { return; } // Trigger form validation and wallet collection const {error: submitError} = await elements.submit(); if (submitError) { // Show error to your customer setErrorMessage(submitError.message); return; } // Create the PaymentIntent and obtain clientSecret from your server endpoint const res = await fetch('/create-intent', { method: 'POST', }); const {client_secret: clientSecret} = await res.json(); const {error} = await stripe.confirmPayment({ //`Elements` instance that was used to create the Payment Element elements, clientSecret, confirmParams: { return_url: 'https://example.com/order/123/complete', }, }); if (error) { // This point will only be reached if there is an immediate error when // confirming the payment. Show error to your customer (for example, payment // details incomplete) setErrorMessage(error.message); } else { // Your customer will be redirected to your `return_url`. For some payment // methods like iDEAL, your customer will be redirected to an intermediate // site first to authorize the payment, then redirected to the `return_url`. } }; return ( <form onSubmit={handleSubmit}> <PaymentElement /> <button type="submit" disabled={!stripe || !elements}> Pay </button> {/* Show error message to your customers */} {errorMessage && <div>{errorMessage}</div>} </form> ); }; const stripePromise = loadStripe('pk_test_6pRNASCoBOKtIshFeQd4XMUh'); const options = { mode: 'payment', amount: 1099, currency: 'usd', // Fully customizable with appearance API. appearance: { /*...*/ }, }; const App = () => ( <Elements stripe={stripePromise} options={options}> <CheckoutForm /> </Elements> ); ReactDOM.render(<App />, document.body); ``` 您可以閱讀[文件](https://github.com/stripe/react-stripe-js?tab=readme-ov-file#minimal-example)。 您幾乎可以整合任何東西。它有一個巨大的選項清單。 ![整合](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/67f3pb2i8xolt635rp2p.png) https://github.com/stripe --- 29. [Upscayl](https://github.com/upscayl/upscayl) - 開源 AI 影像升級器。 ---------------------------------------------------------------- ![高級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2c1837rev5jb260ro2sd.png) 適用於 Linux、MacOS 和 Windows 的免費開源 AI Image Upscaler 採用 Linux 優先概念建構。 它可能與全端無關,但它對於升級圖像很有用。 ![高級](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4qq1wm3wey3vihn9al4.png) 透過最先進的人工智慧,Upscayl 可以幫助您將低解析度影像變成高解析度。清脆又鋒利! https://github.com/upscayl/upscayl --- 30.[重新發送](https://github.com/resend)- 為開發人員提供的電子郵件 API。 ------------------------------------------------------- ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3auhh3hbxjmmzehe5v0.png) 您可以使用 React 建立和傳送電子郵件。 2023 年最受炒作的產品之一。 開始使用以下 npm 指令。 ``` npm install @react-email/components -E ``` 這是將其與 next.js 專案整合的方法。 ``` import { EmailTemplate } from '@/components/email-template'; import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); export async function POST() { const { data, error } = await resend.emails.send({ from: '[email protected]', to: '[email protected]', subject: 'Hello world', react: EmailTemplate({ firstName: 'John' }), }); if (error) { return Response.json({ error }); } return Response.json(data); } ``` 您可以閱讀[文件](https://resend.com/docs/introduction)。 ![重發](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rer9ym187e4i9l11afkg.png) 基本概念是一個簡單、優雅的介面,讓您可以在幾分鐘內開始發送電子郵件。它可以透過適用於您最喜歡的程式語言的 SDK 直接融入您的程式碼中。 https://github.com/resend --- 哇!如此長的專案清單。 我知道您有更多想法,分享它們,讓我們一起建造:D 如今建立全端應用程式並不難,但每個應用程式都可以透過有效地使用優秀的開源專案來解決任何問題來增加這一獨特因素。 例如,您可以建立一些提供通知或建立 UI 流來抓取資料的東西。 我希望其中一些內容對您的開發之旅有用。他們擁有一流的開發人員經驗;你可以依賴他們。 由於您將要建造東西,因此您可以在這裡找到一些[瘋狂的想法](https://github.com/florinpop17/app-ideas)。 祝你有美好的一天!直到下一次。 --- 原文出處:https://dev.to/copilotkit/im-building-a-full-stack-app-here-are-the-libraries-im-going-to-use-51nk

我如何建立 NotesGPT – 一個全端人工智慧語音筆記應用程式

上週,我推出了[notesGPT](https://usenotesgpt.com/) ,這是一款免費開源語音記事應用程式,上週迄今為止已有[35,000 名訪客](https://twitter.com/nutlope/status/1760053364791050285)、7,000 名用戶和超過 1,000 名 GitHub star。它允許您錄製語音筆記,使用[Whisper](https://github.com/openai/whisper)進行轉錄,並透過[Together](https://together.ai/)使用 Mixtral 來提取操作項並將其顯示在操作項視圖中。它也是[完全開源的](https://github.com/nutlope/notesgpt),配備了身份驗證、儲存、向量搜尋、操作項,並且在行動裝置上完全響應,易於使用。 我將向您詳細介紹我是如何建造它的。 架構和技術堆疊 ------- 這是架構的快速圖表。我們將更深入地討論每個部分,並同時展示程式碼範例。 ![架構圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjl3i4bu23fn0pabldsw.png) 這是我使用的整體技術堆疊: - 資料庫和雲端函數的[convex](https://convex.dev/) - Next.js [App Router](https://nextjs.org/docs/app)框架 - [複製](https://replicate.com/)Whisper 轉錄 - LLM 與[JSON 模式](https://docs.together.ai/docs/json-mode)的[Mixtral](https://mistral.ai/news/mixtral-of-experts/) - [Together.ai](http://Together.ai)用於推理和嵌入 - 用於儲存語音註釋的[凸檔存儲](https://docs.convex.dev/file-storage) - [凸向量搜尋](https://docs.convex.dev/vector-search)用於向量搜尋 - 負責使用者身份驗證的[職員](https://clerk.dev/) - [Tailwind CSS](https://tailwindcss.com/)樣式 登陸頁面 ---- 該應用程式的第一部分是您導航到notesGPT 時看到的登入頁面。 ![NotesGPT 的登陸頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hfscmudh4l33oab3azw.png) 用戶首先看到的是這個登陸頁面,它與應用程式的其餘部分一起使用 Next.js 和 Tailwind CSS 進行樣式建立。我喜歡使用 Next.js,因為它可以輕鬆啟動 Web 應用程式並編寫 React 程式碼。 Tailwind CSS 也很棒,因為它允許您在網頁上快速迭代,同時與 JSX 保持在同一檔案中。 與 Clerk 和 Convex 進行身份驗證 ----------------------- 當使用者點擊主頁上的任一按鈕時,他們將被導向到登入畫面。這是由 Clerk 提供支援的,這是一個與 Convex 很好整合的簡單身份驗證解決方案,我們將在整個後端使用它,包括雲端功能、資料庫、儲存和向量搜尋。 ![認證頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02khgd6f2jfew1w7dufn.png) Clerk 和 Convex 都很容易設定。您只需在這兩個服務上建立一個帳戶,安裝它們的 npm 庫,執行`npx convex dev`來設定您的凸資料夾,然後建立一個如下所示的`ConvexProvider.ts`檔案來包裝您的應用程式。 ``` 'use client'; import { ReactNode } from 'react'; import { ConvexReactClient } from 'convex/react'; import { ConvexProviderWithClerk } from 'convex/react-clerk'; import { ClerkProvider, useAuth } from '@clerk/nextjs'; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export default function ConvexClientProvider({ children, }: { children: ReactNode; }) { return ( <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!} > <ConvexProviderWithClerk client={convex} useAuth={useAuth}> {children} </ConvexProviderWithClerk> </ClerkProvider> ); } ``` 請查看[Convex Quickstart](https://docs.convex.dev/quickstart/nextjs)和[Convex Clerk](https://docs.convex.dev/auth/clerk) auth 部分以了解更多詳細資訊。 設定我們的架構 ------- 您可以在有或沒有模式的情況下使用 Convex。就我而言,我知道資料的結構並想要定義它,所以我在下面這樣做了。這也為您提供了一個非常好的類型安全 API,可以在與資料庫互動時使用。我們定義兩個表格-一個用於儲存所有語音註解資訊的`notes`表和用於提取的操作專案的`actionItems`表。我們還將定義索引,以便能夠透過`userId`和`noteId`快速查詢資料。 ``` import { defineSchema, defineTable } from 'convex/server'; import { v } from 'convex/values'; export default defineSchema({ notes: defineTable({ userId: v.string(), audioFileId: v.string(), audioFileUrl: v.string(), title: v.optional(v.string()), transcription: v.optional(v.string()), summary: v.optional(v.string()), embedding: v.optional(v.array(v.float64())), generatingTranscript: v.boolean(), generatingTitle: v.boolean(), generatingActionItems: v.boolean(), }) .index('by_userId', ['userId']) .vectorIndex('by_embedding', { vectorField: 'embedding', dimensions: 768, filterFields: ['userId'], }), actionItems: defineTable({ noteId: v.id('notes'), userId: v.string(), task: v.string(), }) .index('by_noteId', ['noteId']) .index('by_userId', ['userId']), }); ``` 儀表板 --- 現在我們已經有了後端和身份驗證設定以及模式,我們可以看看如何獲取資料。登入應用程式後,用戶可以查看其儀表板,其中列出了他們錄製的所有語音筆記。 ![儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6u9f1b60kgfp4txbszur.png) 為此,我們首先在凸資料夾中定義一個查詢,該查詢使用 auth 接收`userId` ,驗證其有效,並傳回與使用者的`userId`相符的所有註解。 ``` export const getNotes = queryWithUser({ args: {}, handler: async (ctx, args) => { const userId = ctx.userId; if (userId === undefined) { return null; } const notes = await ctx.db .query('notes') .withIndex('by_userId', (q) => q.eq('userId', userId)) .collect(); const results = Promise.all( notes.map(async (note) => { const count = ( await ctx.db .query('actionItems') .withIndex('by_noteId', (q) => q.eq('noteId', note._id)) .collect() ).length; return { count, ...note, }; }), ); return results; }, }); ``` 之後,我們可以透過凸提供的函數使用使用者的驗證令牌來呼叫此`getNotes`查詢,以在儀表板中顯示所有使用者的註解。我們使用伺服器端渲染在伺服器上取得此資料,然後將其傳遞到`<DashboardHomePage />`客戶端元件。這也確保了客戶端上的資料也保持最新。 ``` import { api } from '@/convex/_generated/api'; import { preloadQuery } from 'convex/nextjs'; import DashboardHomePage from './dashboard'; import { getAuthToken } from '../auth'; const ServerDashboardHomePage = async () => { const token = await getAuthToken(); const preloadedNotes = await preloadQuery(api.notes.getNotes, {}, { token }); return <DashboardHomePage preloadedNotes={preloadedNotes} />; }; export default ServerDashboardHomePage; ``` 錄製語音筆記 ------ 最初,使用者的儀表板上不會有任何語音註釋,因此他們可以點擊「錄製新語音註釋」按鈕來錄製。他們將看到以下螢幕,允許他們進行錄製。 ![錄製語音筆記頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3lm22akd3zanf3ar0za.png) 這將使用本機瀏覽器 API 錄製語音筆記,將檔案保存在 Convex 檔案儲存中,然後透過 Replicate 將其傳送至 Whisper 進行轉錄。我們要做的第一件事是在凸資料夾中定義一個`createNote`突變,它將接收此記錄,在凸資料庫中保存一些訊息,然後呼叫耳語操作。 ``` export const createNote = mutationWithUser({ args: { storageId: v.id('_storage'), }, handler: async (ctx, { storageId }) => { const userId = ctx.userId; let fileUrl = (await ctx.storage.getUrl(storageId)) as string; const noteId = await ctx.db.insert('notes', { userId, audioFileId: storageId, audioFileUrl: fileUrl, generatingTranscript: true, generatingTitle: true, generatingActionItems: true, }); await ctx.scheduler.runAfter(0, internal.whisper.chat, { fileUrl, id: noteId, }); return noteId; }, }); ``` 耳語動作如下圖所示。它使用 Replicate 作為 Whisper 的託管提供者。 ``` export const chat = internalAction({ args: { fileUrl: v.string(), id: v.id('notes'), }, handler: async (ctx, args) => { const replicateOutput = (await replicate.run( 'openai/whisper:4d50797290df275329f202e48c76360b3f22b08d28c196cbc54600319435f8d2', { input: { audio: args.fileUrl, model: 'large-v3', translate: false, temperature: 0, transcription: 'plain text', suppress_tokens: '-1', logprob_threshold: -1, no_speech_threshold: 0.6, condition_on_previous_text: true, compression_ratio_threshold: 2.4, temperature_increment_on_fallback: 0.2, }, }, )) as whisperOutput; const transcript = replicateOutput.transcription || 'error'; await ctx.runMutation(internal.whisper.saveTranscript, { id: args.id, transcript, }); }, }); ``` 此外,所有這些檔案都可以在 Convex 儀表板的「檔案」下看到。 ![凸形儀表板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz51ysreunwsk52tqjr9.png) 生成行動專案 ------ 使用者完成語音記錄並透過耳語進行轉錄後,輸出將傳遞到 Together AI 中。我們同時顯示此加載畫面。 ![頁面載入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rcr80meap2xql9nrzlf.png) 我們首先定義一個我們希望輸出所在的模式。然後,我們將此模式傳遞到 Together.ai 上託管的 Mixtral 模型中,並提示辨識語音註釋的摘要、文字記錄,並根據成績單。然後我們將所有這些資訊保存到 Convex 資料庫中。為此,我們在凸資料夾中建立一個凸動作。 ``` // convex/together.ts const NoteSchema = z.object({ title: z .string() .describe('Short descriptive title of what the voice message is about'), summary: z .string() .describe( 'A short summary in the first person point of view of the person recording the voice message', ) .max(500), actionItems: z .array(z.string()) .describe( 'A list of action items from the voice note, short and to the point. Make sure all action item lists are fully resolved if they are nested', ), }); export const chat = internalAction({ args: { id: v.id('notes'), transcript: v.string(), }, handler: async (ctx, args) => { const { transcript } = args; const extract = await client.chat.completions.create({ messages: [ { role: 'system', content: 'The following is a transcript of a voice message. Extract a title, summary, and action items from it and answer in JSON in this format: {title: string, summary: string, actionItems: [string, string, ...]}', }, { role: 'user', content: transcript }, ], model: 'mistralai/Mixtral-8x7B-Instruct-v0.1', response_model: { schema: NoteSchema, name: 'SummarizeNotes' }, max_tokens: 1000, temperature: 0.6, max_retries: 3, }); const { title, summary, actionItems } = extract; await ctx.runMutation(internal.together.saveSummary, { id: args.id, summary, actionItems, title, }); }); ``` 當 Together.ai 做出回應時,我們會看到最終畫面,使用者可以在左側的記錄和摘要之間切換,並查看並勾選右側的操作專案。 ![完整語音筆記頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnd6j68hgusa0aj2buhv.png) 向量搜尋 ---- 該應用程式的最後一部分是向量搜尋。我們使用 Together.ai 嵌入來嵌入文字記錄,並使人們可以根據文字記錄的語義在儀表板中進行搜尋。 我們透過在凸資料夾中建立一個`similarNotes`操作來實現此目的,該操作接受使用者的搜尋查詢,為其產生嵌入,並找到要在頁面上顯示的最相似的註釋。 ``` export const similarNotes = actionWithUser({ args: { searchQuery: v.string(), }, handler: async (ctx, args): Promise<SearchResult[]> => { // 1. Create the embedding const getEmbedding = await togetherai.embeddings.create({ input: [args.searchQuery.replace('/n', ' ')], model: 'togethercomputer/m2-bert-80M-32k-retrieval', }); const embedding = getEmbedding.data[0].embedding; // 2. Then search for similar notes const results = await ctx.vectorSearch('notes', 'by_embedding', { vector: embedding, limit: 16, filter: (q) => q.eq('userId', ctx.userId), // Only search my notes. }); return results.map((r) => ({ id: r._id, score: r._score, })); }, }); ``` 結論 -- 就像這樣,我們建立了一個可投入生產的全端人工智慧應用程式,配備身份驗證、資料庫、儲存和 API。請隨意查看[notesGPT,](https://usenotesgpt.com/)以從您的筆記或[GitHub 儲存庫](https://github.com/nutlope/notesGPT)產生操作專案以供參考。如果您有任何疑問,[請私訊我](twitter.com/nutlope),我將非常樂意回答! --- 原文出處:https://dev.to/nutlope/how-i-built-notesgpt-a-full-stack-ai-voice-note-app-265o

軟體工程師面試學習指南

[本·羅戈揚](https://www.linkedin.com/in/benjaminrogojan/) 軟體工程面試與其他技術面試一樣,需要充分的準備。有許多主題需要涵蓋,以確保您準備好應對有關演算法、資料結構、設計、最佳化以及不斷增長的主題的連續問題。 因此,我在最後一輪面試中建立了一個清單,涵蓋了許多熱門話題。 為了幫助您追蹤進度,我們針對下面列出的相同問題編制了一份全面的清單; [該列表可以在這裡找到](https://docs.google.com/spreadsheets/d/19hSRrL4l3gRiJ5ucH9q4iwFo2QHgic9gGMNUrcn1mm0/edit?usp=sharing)。 ### **聆聽經典熱身** 1. [fizzbuzz](https://www.hackerrank.com/challenges/fizzbuzz/problem) 2. [子陣列和等於 K](https://leetcode.com/problems/subarray-sum-equals-k/) 3. [陣列:左旋轉](https://www.hackerrank.com/challenges/ctci-array-left-rotation/problem?h_l=interview&playlist_slugs%5B%5D=interview-preparation-kit&playlist_slugs%5B%5D=arrays) 4. [字串:製作字謎詞](https://www.hackerrank.com/challenges/ctci-making-anagrams/problem?h_l=interview&playlist_slugs%5B%5D=interview-preparation-kit&playlist_slugs%5B%5D=strings) 5. [第 N 次斐波那契數](https://www.algoexpert.io/questions/Nth%20Fibonacci) 你怎麼做的?花點時間對這些經典作品進行評價。我們在面試過程中的某個時刻被問到了其中大部分問題,而且通常是在早期就被問到的淘汰式問題。它們通常與演算法和資料結構關係不大,但仍然需要對循環和陣列有很好的理解(是的,陣列是一種資料結構)。 ### **演算法和資料結構** #### **預習問題** 在觀看有關資料結構和演算法的影片內容之前,請考慮嘗試以下這些問題。看看你能否回答他們。這將幫助您知道要關注什麼。 1. [985. 查詢後偶數之和](https://leetcode.com/problems/sum-of-even-numbers-after-queries/) 2. [657. 機器人回歸原點](https://leetcode.com/problems/robot-return-to-origin/) 3. [961. 2N 陣列中的 N 重複元素](https://leetcode.com/problems/n-repeated-element-in-size-2n-array/) 4. [110.平衡二元樹](https://leetcode.com/problems/balanced-binary-tree/) 5. [3. 最長無重複字元的子字串](https://leetcode.com/problems/longest-substring-without-repeating-characters/) 6. [19. 從清單結尾刪除第 N 個節點](https://leetcode.com/problems/remove-nth-node-from-end-of-list/) 7. [23. 合併 k 個排序列表](https://leetcode.com/problems/merge-k-sorted-lists/) 8. [31. 下一個排列](https://leetcode.com/problems/next-permutation/) ### 演算法和資料結構影片 #### 資料結構 ![](https://cdn-images-1.medium.com/max/1600/1*Dyu63sMUVL-gYEZISOE2BQ.jpeg) 1. [資料結構與演算法 #1 --- 什麼是資料結構?](https://www.youtube.com/watch?v=bum_19loj9A) --- 影片 2. [多調光](https://archive.org/details/0102WhatYouShouldKnow/02_05-multidimensionalArrays.mp4)--- 影片 3. [動態陣列](https://www.coursera.org/learn/data-structures/lecture/EwbnV/dynamic-arrays)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 4. [調整陣列大小](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 5. [資料結構:鍊錶](https://youtu.be/njTh_OwMljA)--- 影片 6. [核心鍊錶與陣列](https://www.coursera.org/learn/data-structures-optimizing-performance/lecture/rjBs9/core-linked-lists-vs-arrays)--- 影片 7. [指針到指針](https://www.eskimo.com/~scs/cclass/int/sx8.html)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 8. [資料結構:樹](https://youtu.be/oSWTXtMglKE)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 9. [資料結構:堆](https://youtu.be/t0Cq6tVNRBA)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 10. [資料結構:哈希表](https://youtu.be/shs0KM3wKv8)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 11. [電話簿問題](https://www.coursera.org/learn/data-structures/lecture/NYZZP/phone-book-problem)---影片 12. [資料結構:堆疊和佇列](https://youtu.be/wjI1WNcIntg)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 13. [使用堆疊後進先出](https://archive.org/details/0102WhatYouShouldKnow/05_01-usingStacksForLast-inFirst-out.mp4)--- 影片 14. [資料結構:計算機科學速成課程#14](https://youtu.be/DuDz6B4cqVc)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 15. [資料結構:嘗試](https://www.youtube.com/watch?v=zIjfhVPRZCg)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 #### 演算法 ![](https://cdn-images-1.medium.com/max/1600/1*bPpvELo9_QqQsDz7CSbwXQ.gif) 1. [演算法:圖搜尋、DFS 和 BFS](https://www.youtube.com/watch?v=zaBhtODEL0w&list=PLX6IKgS15Ue02WDPRCmYKuZicQHit9kFt)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 2. [BFS(廣度優先搜尋)和DFS(深度優先搜尋)](https://www.youtube.com/watch?v=uWL6FJhq5fM) --- 影片 3. [演算法:二分查找](https://youtu.be/P3YID7liBug)---影片 4. [二元搜尋樹回顧](https://www.youtube.com/watch?v=x6At0nzX92o&index=1&list=PLA5Lqm4uh9Bbq-E0ZnqTIa8LRaL77ica6)--- 影片 5. [用於面試的 Python 演算法](https://www.youtube.com/watch?v=p65AHm9MX80)[ ](https://archive.org/details/0102WhatYouShouldKnow/03_01-resizableArrays.mp4)--- 影片 6. [演算法:遞歸](https://youtu.be/KEEKn7Me-ms)---影片 7. [演算法:冒泡排序](https://youtu.be/6Gv8vg0kcHc)--- 影片 8. [演算法:歸併排序](https://youtu.be/KF2j-9iSf4Q)--- 影片 9. [演算法:快速排序](https://youtu.be/SLauY6PpjW4)---影片 ### **大 O 表示法** 1. [大 O 表示法與時間複雜度簡介(資料結構與演算法 #7)](https://www.youtube.com/watch?v=D6xkbGLQesk) --- 影片 2. [哈佛 CS50 --- 漸近符號](https://www.youtube.com/watch?v=iOq5kSKqeR4)--- 影片 3. [演算法複雜度分析的簡單介紹](http://discrete.gr/complexity/)--- Post 4. [備忘錄](http://bigocheatsheet.com/)--- 帖子 ### **動態規劃** 1. [動態規劃(像程式設計師一樣思考)---影片](https://www.youtube.com/watch?v=iv_yHjmkv4I) 2. [演算法:記憶化與動態規劃 --- 影片](https://www.youtube.com/watch?v=P8Xa2BitN3I&t=13s) 3. 6 [.006:動態規劃 I:斐波那契、最短路徑 --- 影片](https://www.youtube.com/watch?v=OQ5jsbhAv_M&t=7s) 4. [6.006:動態規劃 II:文字對齊、二十一點 --- 影片](https://www.youtube.com/watch?v=ENyox7kNKeY&t=4s) 5. [動態規劃 --- 帖子](https://www.topcoder.com/community/competitive-programming/tutorials/dynamic-programming-from-novice-to-advanced/) ### **字串操作** 1. [編碼面試問答:最長的連續字元](https://www.youtube.com/watch?v=qRNB8CV3_LU)--- 影片 2. [Sedgewick --- 子字串搜尋](https://www.coursera.org/learn/algorithms-part2/home/week/4)--- 影片 #### 面試問題演練 1. [谷歌編碼面試 --- 普世價值樹問題](https://www.youtube.com/watch?v=7HgsS8bRvjo)--- 影片 2. [Google 編碼面試問題和答案 #1:第一個重複出現的字元](https://www.youtube.com/watch?v=GJdiM-muYqc)--- 影片 3. [在二元搜尋樹中找到最小和最大元素](https://www.youtube.com/watch?v=Ut90klNN264&index=30&list=PL2_aWCzGMAwI3W_JlcBbtYTwiQSsOTa6P)--- 影片 4. [求二元樹的高度](https://www.youtube.com/watch?v=_pnqMz5nrRs&list=PL2_aWCzGMAwI3W_JlcBbtYTwiQSsOTa6P&index=31)--- 影片 5. [檢查二元樹是否為二元搜尋樹](https://www.youtube.com/watch?v=yEwSGhSsT0U&index=35&list=PL2_aWCzGMAwI3W_JlcBbtYTwiQSsOTa6P)--- 影片 6. [什麼是尾遞歸?為什麼這麼糟?](https://www.quora.com/What-is-tail-recursion-Why-is-it-so-bad) --- 影片 ### **學後問題** 現在您已經學習了一些,並觀看了一些影片,讓我們嘗試更多問題! 1. [越大越好](https://www.hackerrank.com/challenges/bigger-is-greater/problem) 2. [6.之字折線轉換](https://leetcode.com/problems/zigzag-conversion/) 3. [7. 反轉整數](https://leetcode.com/problems/reverse-integer/) 4. [40. 組合和 II](https://leetcode.com/problems/combination-sum-ii/) 5. [43. 字串相乘](https://leetcode.com/problems/multiply-strings/) 6. [拉里的陣列](https://www.hackerrank.com/challenges/larrys-array/problem) 7. [短回文](https://www.hackerrank.com/challenges/short-palindrome/problem) 8. [65. 有效號碼](https://leetcode.com/problems/valid-number/) 9. [越大越好](https://www.hackerrank.com/challenges/bigger-is-greater/problem) 10. [完整計數排序](https://www.hackerrank.com/challenges/countingsort4/problem) 11. [莉莉的家庭作業](https://www.hackerrank.com/challenges/lilys-homework/problem) 12. [普通孩子](https://www.hackerrank.com/challenges/common-child/problem) 13. [459. 重複子字串模式](https://leetcode.com/problems/repeated-substring-pattern/) 14. [27. 刪除元素](https://leetcode.com/problems/remove-element/) 15. [450. 刪除 BST 中的節點](https://leetcode.com/problems/delete-node-in-a-bst/) 16. [659. 將陣列分割成連續的子序列](https://leetcode.com/problems/split-array-into-consecutive-subsequences/) 17. [最大值有界的子陣列數](https://leetcode.com/problems/number-of-subarrays-with-bounded-maximum) 18. [組合和 IV](https://leetcode.com/problems/combination-sum-iv) 19. [買賣股票的最佳時機(有冷卻時間)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown) 20. [最長重複字元替換](https://leetcode.com/problems/longest-repeating-character-replacement) 21. [成對交換節點](https://leetcode.com/problems/swap-nodes-in-pairs) 22. [二元樹右側視圖](https://leetcode.com/problems/binary-tree-right-side-view) 23. [展平嵌套列表迭代器](https://leetcode.com/problems/flatten-nested-list-iterator) 24. [二元樹層次順序遍歷](https://leetcode.com/problems/binary-tree-level-order-traversal) 25. [二元搜尋樹迭代器](https://leetcode.com/problems/binary-search-tree-iterator) 26. [對鏈最大長度](https://leetcode.com/problems/maximum-length-of-pair-chain) 27. [將鍊錶拆分為多個部分](https://leetcode.com/problems/split-linked-list-in-parts) ### **操作性程式設計問題** 有些公司不會問你演算法問題。相反,他們可能會更專注於實施和營運問題。這些通常更小眾,涉及實際問題,例如循環資料和執行某種任務。這些類型的問題通常不需要太多練習,因為更多的是了解陣列和 HashMap 等基本概念並追蹤您對它們所做的事情。 1. [袋鼠問題](https://www.hackerrank.com/challenges/kangaroo/problem) 2. [打破記錄](https://www.hackerrank.com/challenges/breaking-best-and-worst-records/problem) 3. [查找字串](https://www.hackerrank.com/challenges/find-a-string/problem)[迭代器](https://www.hackerrank.com/challenges/itertools-permutations/problem) 4. [不知道!](https://www.hackerrank.com/challenges/no-idea/problem) 5. [程式設計師的日子](https://www.hackerrank.com/challenges/day-of-the-programmer/problem) 6. [排行榜](https://www.hackerrank.com/challenges/climbing-the-leaderboard/problem) 7. [詞序](https://www.hackerrank.com/challenges/word-order/problem) 8. [夏洛克和方塊](https://www.hackerrank.com/challenges/sherlock-and-squares/problem) 9. [均衡陣列](https://www.hackerrank.com/challenges/equality-in-a-array/problem) 10. [蘋果和橘子](https://www.hackerrank.com/challenges/apple-and-orange/problem) 11. [更多操作風格問題](https://www.hackerrank.com/domains/python) ### **系統設計影片** 系統設計問題是至關重要的問題,表明您不僅僅是一名編碼員。身為工程師,您需要能夠思考大局。某些服務屬於哪裡,您需要什麼樣的伺服器,您將如何管理流量等等。所有這些想法都表明您能夠設計軟體,而不僅僅是編寫人們告訴您編寫的程式碼。 1. [停車場系統](https://youtu.be/DSGsa0pu8-k)---影片 2. [什麼是應用程式](https://www.youtube.com/watch?v=vvhC64hQZMk)--- 影片 3. [優步設計](https://youtu.be/umWABit-wbk)---影片 4. [Instagram](https://www.youtube.com/watch?v=QmX2NPkJTKg) --- 影片 5. [Tinder 服務](https://www.youtube.com/watch?v=xQnIN9bW0og)--- 影片 ### **作業系統** 作業系統問題比較少見,但是對執行緒、調度、記憶體等概念有紮實的理解是有好處的,即使只是基本的理解。當被問到進程和線程有什麼區別而不知道答案時,這是非常尷尬的。 1. [常見作業系統面試問題](https://www.geeksforgeeks.org/commonly-asked-operating-systems-interview-questions-set-1/) 2. [什麼是翻譯後備緩衝區?](https://www.geeksforgeeks.org/operating-system-translation-lookaside-buffer-tlb/) 3. [為什麼循環法可以避免優先反轉問題?](https://leetcode.com/discuss/interview-question/operating-system/220604/Why-does-Round-Robin-avoid-the-Priority-Inversion-Problem) 4. [中斷與系統呼叫---檔案系統中的「inode」是什麼?](https://leetcode.com/discuss/interview-question/operating-system/124838/Interrupt-Vs-System-Call) 5. [作業系統面試題目及答案 --- 第一部分](https://www.youtube.com/watch?v=b18X4uOKjHs) 6. [什麼是內核 --- Gary 解釋](https://www.youtube.com/watch?v=mycVSMyShk8) 7. [循環演算法教程(CPU調度)](https://www.youtube.com/watch?v=aWlQYllBZDs) 8. [LRU 快取的魔力(Google 開發 100 天)](https://www.youtube.com/watch?v=R5ON3iwx78M) --- 影片 9. [MIT 6.004 L15:記憶體層次結構](https://www.youtube.com/watch?v=vjYF_fAZI5E&list=PLrRW1w6CGAcXbMtDFj205vALOGmiRc82-&index=24)--- 影片 10. [中斷](https://www.youtube.com/watch?v=uFKi2-J-6II&list=PLCiOXwirraUCBE9i_ukL8_Kfg6XNv7Se8&index=3)---影片 11. [調度](https://www.youtube.com/watch?v=-Gu5mYdKbu4&index=4&list=PLCiOXwirraUCBE9i_ukL8_Kfg6XNv7Se8)---影片 ### **執行緒數** ![](https://cdn-images-1.medium.com/max/1600/1*uSHd3KSxg363C1frMv2KwQ.png) 1. [用戶級線程與內核級線程](https://leetcode.com/discuss/interview-question/operating-system/124631/User-Level-thread-Vs-Kernel-Level-thread) 2. [行程和線程簡介](https://www.youtube.com/watch?v=exbKr6fnoUw)--- 影片 3. [進程和線程之間的區別 --- 佐治亞理工學院 --- 高級操作系統](https://www.youtube.com/watch?v=O3EyzlZxx3g&t=11s)--- 影片 4. [分叉和多線程之間的區別](https://leetcode.com/discuss/interview-question/operating-system/125024/Difference-between-forking-and-multithreading) ### **物件導向** ![](https://cdn-images-1.medium.com/max/1600/1*8LHiDwWhrU4iegYiNnKX_A.png) 與作業系統類似,並不是每次面試都會問你有關物件導向程式設計的問題,但你永遠不知道。您需要確保記住計算機 162 課程中的基礎知識。 1. [Java 程式設計教學 --- 49 --- 繼承](https://www.youtube.com/watch?v=9JpNY-XAseg)--- 影片 2. [Java 程式設計教學 --- 55 --- 多態性簡介](https://www.youtube.com/watch?v=0xw06loTm1k)--- 影片 3. [Java 程式設計教學 --- 58 --- 抽象類別與具體類別](https://www.youtube.com/watch?v=TyPNvt6Zg8c)--- 影片 4. [Java 程式設計教學 --- 57 --- 重寫規則](https://www.youtube.com/watch?v=zN9pKULyoj4&t=3s)--- 影片 5. [Java 程式設計教學 --- 59 --- 保存物件的類](https://www.youtube.com/watch?v=slY5Ag7IjM0) 6. [物件導向程式設計](https://www.youtube.com/watch?v=lbXsrHGhBAU)---影片 ### **設計模式** 如果您像我們一樣,我們不會被教導所有各種設計模式。因此,最好了解它們的工作原理以及使用它們的原因。一些面試問題可以很簡單,例如“為什麼要使用工廠類?” 1. [工廠設計模式](https://www.youtube.com/watch?v=ub0DXaeV6hA)---影片 2. [觀察者設計模式](https://youtu.be/wiQdrH2YpT4)---影片 3. [適配器設計模式](https://www.youtube.com/watch?v=qG286LQM6BU&list=PLF206E906175C7E07&index=13)---影片 4. [立面設計模式](https://www.youtube.com/watch?v=B1Y8fcYrz5o&list=PLF206E906175C7E07&index=14)---影片 5. [責任鏈設計模式](https://www.youtube.com/watch?v=jDX6x8qmjbA&list=PLF206E906175C7E07&index=22)---影片 6. [解譯器設計模式](https://www.youtube.com/watch?v=6CVymSJQuJE&list=PLF206E906175C7E07&index=23)---影片 7. [單例設計模式教學](https://www.youtube.com/watch?v=NZaXM67fxbs&list=PLF206E906175C7E07&index=7)--- 影片 8. [第 6 章(第 1 部分)--- 模式(影片)](https://youtu.be/LAP2A80Ajrg?list=PLJ9pm_Rc9HesnkwKlal_buSIHA-jTZMpO&t=3344) --- 影片 9. [Head First 設計模式](https://www.amazon.com/Head-First-Design-Patterns-Freeman/dp/0596007124)--- 影片 ### SQL 這是最後一節。你們中的許多人可能不會被問到那麼多 SQL 問題。不過,我始終認為放在後口袋是件好事。 #### SQL --- 問題 1. [262. 行程和用戶](https://leetcode.com/problems/trips-and-users/) 2. [601. 體育場人流量](https://leetcode.com/problems/human-traffic-of-stadium/) 3. [185. 部門前三薪資](https://leetcode.com/problems/department-top-three-salaries/) 4. [626.交換席位](https://leetcode.com/problems/exchange-seats/) 5. [駭客排名報告](https://www.hackerrank.com/challenges/the-report/problem) 6. [177.第N最高薪水](https://leetcode.com/problems/nth-highest-salary/) 7. [對稱對](https://www.hackerrank.com/challenges/symmetric-pairs/problem) 8. [職業](https://www.hackerrank.com/challenges/placements/problem)[安置](https://www.hackerrank.com/challenges/occupations/problem) 9. [奧利凡德的庫存](https://www.hackerrank.com/challenges/harry-potter-and-wands/problem) #### SQL --- 影片 1. [IQ15:6 個 SQL 查詢面試問題](https://www.youtube.com/watch?v=uAWWhEA57bE)--- 影片 2. [了解 ROW\_NUMBER 和分析函數](https://www.youtube.com/watch?v=QFj-hZi8MKk)--- 影片 3. [分析函數的高級實現](https://www.youtube.com/watch?v=G3kYPzLWtpo&t=4s)--- 影片 4. [分析函數的高級實現第 2 部分](https://www.youtube.com/watch?v=XecU6Ieyu-4&t=54s)--- 影片 5. [聰明的貓頭鷹 SQL 影片](https://www.youtube.com/watch?v=2-1XQHAgDsM&list=PL6EDEB03D20332309)--- 影片 #### SQL 後問題 1. [二元樹節點](https://www.hackerrank.com/challenges/binary-search-tree-1/problem) 2. [氣象觀測站 18](https://www.hackerrank.com/challenges/weather-observation-station-18/problem) 3. [挑戰](https://www.hackerrank.com/challenges/challenges/problem)[列印素數](https://www.hackerrank.com/challenges/print-prime-numbers/problem) 4. [595.大國](https://leetcode.com/problems/big-countries/) 5. [626.交換席位](https://leetcode.com/problems/exchange-seats/) 6. [SQL 面試問題:3 個技術篩選練習(針對資料分析師)](https://data36.com/sql-interview-questions-tech-screening-data-analysts/) 面試可能會很困難,因為你會覺得自己沒有任何進展。擁有本學習指南將幫助您追蹤您的進度並讓您更好地了解自己的表現! 祝你好運! 另外,如果您想閱讀/觀看更多精彩的貼文或影片: [在 SaturnCloud 上使用 Jupyter Notebook 連接到大查詢第 2 部分](https://www.youtube.com/watch?v=O1cBN5gJtdw&t=15s) [身為資料科學家你應該閱讀的三本書](https://www.coriers.com/three-books-you-must-read-if-you-want-a-career-as-a-data-scientist/) [Hadoop 與關聯式資料庫](http://www.acheronanalytics.com/acheron-blog/hadoop-vs-relational-databases) [算法如何變得不道德和有偏見](http://www.acheronanalytics.com/acheron-blog/how-do-machines-learn-bias-data-science) [如何改善資料驅動策略](http://www.acheronanalytics.com/acheron-blog/how-to-improve-your-data-driven-strategy) [如何開發穩健演算法](https://medium.com/better-programming/how-to-develop-a-robust-algorithm-c38e08f32201) [資料科學家必須具備的 4 項技能](https://www.theseattledataguy.com/4-skills-data-scientist-must-have/) [SQL 最佳實踐 - 設計 ETL 影片](http://www.acheronanalytics.com/acheron-blog/sql-best-practices-designing-an-etl-video) --- 原文出處:https://dev.to/seattledataguy/the-interview-study-guide-for-software-engineers-764

Next.js 14 使用瀏覽器爬蟲,進行即時資料抓取的預訂應用程式

目錄 == - [介紹](#introduction) - [技術堆疊](#tech-stack) - [特徵](#features) - [設定 Next.js 應用程式](#step-1-setting-up-the-nextjs-application) - [安裝所需的套件](#step-2-installing-required-packages) - [設定 Redis 連接](#step-3-setting-up-redis-connection) - [配置 BullMQ 佇列](#step-4-configuring-bullmq-queue) - [Next.js 儀器設置](#step-5-nextjs-instrumentation-setup) - [設定 Bright Data 的抓取瀏覽器](#step-6-setting-up-bright-datas-scraping-browser) - [Bright Data 的抓取瀏覽器是什麼?](#what-is-bright-datas-scraping-browser) - [設定 Bright Data 抓取瀏覽器的步驟](#steps-to-set-up-bright-datas-scraping-browser) - [使用 Puppeteer 實作抓取邏輯](#implementing-the-scraping-logic-with-puppeteer) - [航班搜尋功能](#flight-search-feature) - [顯示航班搜尋結果](#displaying-flight-search-results) - [探索完整的指南和程式碼庫](#discover-the-complete-guide-and-codebase) - [在 YouTube 上觀看詳細說明](#watch-the-detailed-explanation-on-youtube) - [在 GitHub 上探索完整程式碼](#explore-the-full-code-on-github) - [結論](#conclusion) 介紹 == 在不斷發展的 Web 開發領域,有效收集、處理和顯示外部來源資料的能力變得越來越有價值。無論是市場研究、競爭分析或客戶洞察,網路抓取在釋放網路資料的巨大潛力方面都發揮著至關重要的作用。 這篇部落格文章介紹了建立強大的 Next.js 應用程式的綜合指南,該應用程式旨在從領先的旅行搜尋引擎之一 Kayak 抓取航班資料。透過利用 Next.js 的強大功能以及 BullMQ、Redis 和 Puppeteer 等現代技術。 技術堆疊 ==== - [Next.js](https://nextjs.org/docs) - [順風CSS](https://tailwindcss.com/docs) - [下一個介面](https://nextui.org/docs) - [健康)狀況](https://zustand.surge.sh/) - [條紋](https://stripe.com/docs) - [Bright Data 的抓取瀏覽器](https://brdta.com/kishansheth21) - [打字稿](https://www.typescriptlang.org/docs) - [雷迪斯](https://redis.io/documentation) - [BullMQ](https://docs.bullmq.io/) - [傀儡師](https://pptr.dev/) - [智威湯遜](https://jwt.io/introduction) - [阿克西奧斯](https://axios-http.com/docs/intro) - [PostgreSQL](https://www.postgresql.org/docs) - [棱鏡](https://www.prisma.io/docs) 特徵 == - 🚀 帶有 Tailwind CSS 的 Next.js 14 應用程式目錄 - 體驗由最新 Next.js 14 提供支援的時尚現代的 UI,並使用 Tailwind CSS 進行設計,以實現完美的外觀和感覺。 - 🔗 API 路由和伺服器操作 - 深入研究與 Next.js 14 的 API 路由和伺服器操作的無縫後端集成,確保高效的資料處理和伺服器端邏輯執行。 - 🕷 使用 Puppeteer Redis 和 BullMQ 進行抓取 - 利用 Puppeteer 的強大功能進行進階 Web 抓取,並使用 Redis 和 BullMQ 管理佇列和作業以實現強大的後端操作。 - 🔑 用於身份驗證和授權的 JWT 令牌 - 使用 JWT 令牌保護您的應用程式,為整個平台提供可靠的身份驗證和授權方法。 - 💳 支付網關 Stripe - 整合 Stripe 進行無縫支付處理,為預訂旅行、航班和飯店提供安全、輕鬆的交易。 - ✈️ 使用 Stripe 支付網關預訂旅行、航班和飯店 - 使用我們的 Stripe 支援的支付系統,讓您的旅遊預訂體驗變得輕鬆。 - 📊 從多個網站抓取即時資料 - 從多個來源抓取即時資料,保持領先,讓您的應用程式更新最新資訊。 - 💾 使用 Prisma 將抓取的資料儲存在 PostgreSQL 中 - 利用 PostgreSQL 和 Prisma 高效儲存和管理抓取的資料,確保可靠性和速度。 - 🔄 用於狀態管理的 Zustand - 透過 Zustand 簡化狀態邏輯並增強效能,在您的應用程式中享受流暢且可管理的狀態管理。 - 😈 該應用程式的最佳功能 - 使用 Bright Data 的抓取瀏覽器抓取不可抓取的資料。 ![抓取瀏覽器迷因](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ytki1wpsbvluk1qkpo.jpg) Bright Data的抓取瀏覽器為我們提供了自動驗證碼解決功能,可以幫助我們抓取不可抓取的資料。 第 1 步:設定 Next.js 應用程式 --------------------- 1. **建立 Next.js 應用程式**:首先建立一個新的 Next.js 應用程式(如果您還沒有)。您可以透過在終端機中執行以下命令來完成此操作: ``` npx create-next-app@latest booking-app ``` 2. **導航到您的應用程式目錄**:變更為您新建立的應用程式目錄: ``` cd booking-app ``` 步驟2:安裝所需的軟體包 ------------ 您需要安裝多個軟體包,包括 Redis、BullMQ 和 Puppeteer Core。執行以下命令來安裝它們: ``` npm install ioredis bullmq puppeteer-core ``` - `ioredis`是 Node.js 的強大 Redis 用戶端,支援與 Redis 進行通訊。 - `bullmq`以 Redis 作為後端來管理作業和訊息佇列。 - `puppeteer-core`可讓您控制外部瀏覽器以進行抓取。 步驟3:設定Redis連接 ------------- 在適當的目錄(例如`lib/` )中建立一個檔案(例如`redis.js` )來配置 Redis 連線: ``` // lib/redis.js import Redis from 'ioredis'; // Use REDIS_URL from environment or fallback to localhost const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379'; const connection = new Redis(REDIS_URL); export { connection }; ``` 步驟4:配置BullMQ佇列 -------------- 透過在 Redis 配置所在的相同目錄中建立另一個檔案(例如, `queue.js` )來設定 BullMQ 佇列: ``` // lib/queue.js import { Queue } from 'bullmq'; import { connection } from './redis'; export const importQueue = new Queue('importQueue', { connection, defaultJobOptions: { attempts: 2, backoff: { type: 'exponential', delay: 5000, }, }, }); ``` 第 5 步:Next.js 儀器設置 ------------------ Next.js 允許偵測,可以在 Next.js 配置中啟用。您還需要建立一個用於作業處理的工作文件。 1.**在 Next.js 中啟用 Instrumentation** :將以下內容新增至`next.config.js`以啟用 Instrumentation: ``` // next.config.js module.exports = { experimental: { instrumentationHook: true, }, }; ``` 2.**建立用於作業處理的 Worker** :在您的應用程式中,建立一個檔案 ( `instrumentation.js` ) 來處理作業處理。該工作人員將使用 Puppeteer 來執行抓取任務: ``` // instrumentation.js export const register = async () => { if (process.env.NEXT_RUNTIME === 'nodejs') { const { Worker } = await import('bullmq'); const puppeteer = await import('puppeteer-core'); const { connection } = await import('./lib/redis'); const { importQueue } = await import('./lib/queue'); new Worker('importQueue', async (job) => { // Job processing logic with Puppeteer goes here }, { connection, concurrency: 10, removeOnComplete: { count: 1000 }, removeOnFail: { count: 5000 }, }); } }; ``` 第 6 步:設定 Bright Data 的抓取瀏覽器 --------------------------- 在設定 Bright 資料抓取瀏覽器之前,我們先來談談什麼是抓取瀏覽器。 ### Bright Data 的抓取瀏覽器是什麼? Bright Data 的抓取瀏覽器是一款用於自動網頁抓取的尖端工具,旨在與 Puppeteer、Playwright 和 Selenium 無縫整合。它提供了一套網站解鎖功能,包括代理輪換、驗證碼解決等,以提高抓取效率。它非常適合需要互動的複雜網頁抓取,透過在 Bright Data 基礎架構上託管無限的瀏覽器會話來實現可擴展性。如欲了解更多詳情,請造訪[光明資料](https://brdta.com/kishansheth21)。 ![明亮的資料抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c70ochb1lgdrusgicwz4.png) <a id="steps-to-set-up-bright-datas-scraping-browser"></a> #### 第 1 步:導覽至 Bright Data 網站 首先造訪[Brightdata.com](https://brdta.com/kishansheth21) 。這是您存取 Bright Data 提供的豐富網頁抓取資源和工具的入口。 ![光明資料首頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/afgkpgytcytoqfuq0h8w.png) #### 第 2 步:建立帳戶 造訪 Bright Data 網站後,註冊並建立一個新帳戶。系統將提示您輸入基本資訊以啟動並執行您的帳戶。 ![登入/註冊 Bright Data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ffha75szs9tubh8kra9j.png) #### 第 3 步:選擇您的產品 在產品選擇頁面上,尋找代理商和抓取基礎設施產品。本產品專為滿足您的網路抓取需求而設計,提供強大的資料擷取工具和功能。 ![光明資料產品](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b058azlmjv30po6289fh.png) #### 第 4 步:新增代理 在「代理程式和抓取基礎設施」頁面中,您會找到一個「新增按鈕」。點擊此按鈕開始將新的抓取瀏覽器新增到您的工具包的過程。 ![新代理](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jscxsyt9yess1nvzi4z.png) #### 第五步:選擇抓取瀏覽器 將出現一個下拉列表,您應該從中選擇抓取瀏覽器選項。這告訴 Bright Data 您打算設定一個新的抓取瀏覽器環境。 ![選擇抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i483ipx7spwne65c6tep.png) #### 第 6 步:為您的抓取瀏覽器命名 為您的新抓取瀏覽器指定一個唯一的名稱。這有助於稍後辨識和管理它,特別是如果您計劃對不同的抓取專案使用多個瀏覽器。 ![抓取瀏覽器名稱](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0qnitec7s7gki7t3826.png) #### 步驟7:新增瀏覽器 命名您的瀏覽器後,按一下「新增」按鈕。此操作完成了新的抓取瀏覽器的建立。 ![新增抓取瀏覽器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ao6n1edyyx2no621nh01.png) #### 第 8 步:查看您的抓取瀏覽器詳細訊息 新增抓取瀏覽器後,您將被導向到一個頁面,您可以在其中查看新建立的抓取瀏覽器的所有詳細資訊。這些資訊對於整合和使用至關重要。 ![抓取瀏覽器詳細訊息](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ev33mbgznevn6h9p60g6.png) #### 第 9 步:存取程式碼和整合範例 尋找“查看程式碼和整合範例”按鈕。點擊此按鈕將為您提供如何跨多種程式語言和程式庫整合和使用抓取瀏覽器的全面視圖。對於希望自訂抓取設定的開發人員來說,此資源非常寶貴。 ![程式碼和整合範例按鈕](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30araqzko585yzmhrumd.png) #### 第 10 步:整合您的抓取瀏覽器 最後,複製 SRS\_WS\_ENDPOINT 變數。這是一條關鍵訊息,您需要將其整合到原始程式碼中,以便您的應用程式能夠與您剛剛設定的抓取瀏覽器進行通訊。 ![抓取瀏覽器端點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqhpglz1lngobguk2nu4.png) 透過遵循這些詳細步驟,您已在 Bright Data 平台中成功建立了一個抓取瀏覽器,準備好處理您的網頁抓取任務。請記住,Bright Data 提供廣泛的文件和支持,幫助您最大限度地提高抓取專案的效率和效果。無論您是在收集市場情報、進行研究還是監控競爭格局,新設定的抓取瀏覽器都是資料收集庫中的強大工具。 ### 第 7 步:使用 Puppeteer 實作抓取邏輯 從我們上次設定用於抓取航班資料的 Next.js 應用程式的地方開始,下一個關鍵步驟是實現實際的抓取邏輯。此過程涉及利用 Puppeteer 連接到瀏覽器實例、導航到目標 URL(在我們的範例中為 Kayak)並抓取必要的飛行資料。提供的程式碼片段概述了實現此目標的複雜方法,與我們先前建立的 BullMQ 工作設定無縫整合。讓我們分解這個抓取邏輯的元件,並了解它如何適合我們的應用程式。 #### 建立與瀏覽器的連接 我們抓取過程的第一步是透過 Puppeteer 建立與瀏覽器的連線。這是透過利用`puppeteer.connect`方法來完成的,該方法使用 WebSocket 端點 ( `SBR_WS_ENDPOINT` ) 連接到現有的瀏覽器實例。此環境變數應設定為您正在使用的抓取瀏覽器服務的 WebSocket URL,例如 Bright Data: ``` const browser = await puppeteer.connect({ browserWSEndpoint: SBR_WS_ENDPOINT, }); ``` #### 開啟新頁面並導航到目標 URL 連線後,我們在瀏覽器中建立一個新頁面並導航到作業資料中指定的目標 URL。此 URL 是我們打算從中抓取航班資料的特定 Kayak 搜尋結果頁面: ``` const page = await browser.newPage(); await page.goto(job.data.url); ``` #### 抓取航班資料 我們邏輯的核心在於從頁面中抓取航班資料。我們透過使用`page.evaluate`來實現這一點,這是一種 Puppeteer 方法,允許我們在瀏覽器上下文中執行腳本。在此腳本中,我們等待必要的元素加載,然後繼續收集航班資訊: - **Flight Selector** :我們以`.nrc6-wrapper`類別為目標元素,其中包含航班詳細資訊。 - **資料擷取**:對於每個航班元素,我們提取詳細訊息,例如航空公司徽標、出發和到達時間、航班持續時間、航空公司名稱和價格。出發和到達時間經過清理,以刪除最後不必要的數值,確保我們準確地捕捉時間。 - **價格處理**:價格在刪除所有非數字字元後提取為整數,確保其可用於數值運算或比較。 擷取的資料被建構成飛行物件陣列,每個物件都包含上述詳細資訊: ``` const scrappedFlights = await page.evaluate(async () => { // Data extraction logic const flights = []; // Process each flight element // ... return flights; }); ``` #### 錯誤處理和清理 我們的抓取邏輯被包裝在一個 try-catch 區塊中,以在抓取過程中優雅地處理任何潛在的錯誤。無論結果如何,我們都會確保瀏覽器在finally區塊中正確關閉,從而保持資源效率並防止潛在的記憶體洩漏: ``` try { // Scraping logic } catch (error) { console.log({ error }); } finally { await browser.close(); console.log("Browser closed successfully."); } ``` #### 整個程式碼 ``` const SBR_WS_ENDPOINT = process.env.SBR_WS_ENDPOINT; export const register = async () => { if (process.env.NEXT_RUNTIME === "nodejs") { const { Worker } = await import("bullmq"); const puppeteer = await import("puppeteer"); const { connection } = await import("./lib/redis"); const { importQueue } = await import("./lib/queue"); new Worker( "importQueue", async (job) => { const browser = await puppeteer.connect({ browserWSEndpoint: SBR_WS_ENDPOINT, }); try { const page = await browser.newPage(); console.log("in flight scraping"); console.log("Connected! Navigating to " + job.data.url); await page.goto(job.data.url); console.log("Navigated! Scraping page content..."); const scrappedFlights = await page.evaluate(async () => { await new Promise((resolve) => setTimeout(resolve, 5000)); const flights = []; const flightSelectors = document.querySelectorAll(".nrc6-wrapper"); flightSelectors.forEach((flightElement) => { const airlineLogo = flightElement.querySelector("img")?.src || ""; const [rawDepartureTime, rawArrivalTime] = ( flightElement.querySelector(".vmXl")?.innerText || "" ).split(" – "); // Function to extract time and remove numeric values at the end const extractTime = (rawTime: string): string => { const timeWithoutNumbers = rawTime .replace(/[0-9+\s]+$/, "") .trim(); return timeWithoutNumbers; }; const departureTime = extractTime(rawDepartureTime); const arrivalTime = extractTime(rawArrivalTime); const flightDuration = ( flightElement.querySelector(".xdW8")?.children[0]?.innerText || "" ).trim(); const airlineName = ( flightElement.querySelector(".VY2U")?.children[1]?.innerText || "" ).trim(); // Extract price const price = parseInt( ( flightElement.querySelector(".f8F1-price-text")?.innerText || "" ) .replace(/[^\d]/g, "") .trim(), 10 ); flights.push({ airlineLogo, departureTime, arrivalTime, flightDuration, airlineName, price, }); }); return flights; }); } catch (error) { console.log({ error }); } finally { await browser.close(); console.log("Browser closed successfully."); } }, { connection, concurrency: 10, removeOnComplete: { count: 1000 }, removeOnFail: { count: 5000 }, } ); } }; ``` ### 步驟8:航班搜尋功能 基於我們的航班資料抓取功能,讓我們將全面的航班搜尋功能整合到我們的 Next.js 應用程式中。此功能將為使用者提供一個動態介面,透過指定出發地、目的地和日期來搜尋航班。利用強大的 Next.js 框架以及現代 UI 庫和狀態管理,我們建立了引人入勝且響應迅速的航班搜尋體驗。 #### 航班搜尋功能的關鍵組成部分 1. **動態城市選擇**:此功能包括來源和目的地輸入的自動完成功能,由預先定義的城市機場程式碼清單提供支援。當使用者輸入時,應用程式會過濾並顯示匹配的城市,透過更輕鬆地尋找和選擇機場來增強用戶體驗。 2. **日期選擇**:使用者可以透過日期輸入選擇預期的航班日期,為規劃旅行提供彈性。 3. **抓取狀態監控**:啟動抓取作業後,應用程式透過定期 API 呼叫來監控作業的狀態。這種非同步檢查允許應用程式使用抓取過程的狀態更新 UI,確保使用者了解進度和結果。 #### 航班搜尋元件的完整程式碼 ``` "use client"; import { useAppStore } from "@/store"; import { USER_API_ROUTES } from "@/utils/api-routes"; import { cityAirportCode } from "@/utils/city-airport-codes"; import { Button, Input, Listbox, ListboxItem } from "@nextui-org/react"; import axios from "axios"; import Image from "next/image"; import { useRouter } from "next/navigation"; import React, { useEffect, useRef, useState } from "react"; import { FaCalendarAlt, FaSearch } from "react-icons/fa"; const SearchFlights = () => { const router = useRouter(); const { setScraping, setScrapingType, setScrappedFlights } = useAppStore(); const [loadingJobId, setLoadingJobId] = useState<number | undefined>(undefined); const [source, setSource] = useState(""); const [sourceOptions, setSourceOptions] = useState< { city: string; code: string; }[] >([]); const [destination, setDestination] = useState(""); const [destinationOptions, setDestinationOptions] = useState< { city: string; code: string; }[] >([]); const [flightDate, setFlightDate] = useState(() => { const today = new Date(); return today.toISOString().split("T")[0]; }); const handleSourceChange = (query: string) => { const matchingCities = Object.entries(cityAirportCode) .filter(([, city]) => city.toLowerCase().includes(query.toLowerCase())) .map(([code, city]) => ({ code, city })) .splice(0, 5); setSourceOptions(matchingCities); }; const destinationChange = (query: string) => { const matchingCities = Object.entries(cityAirportCode) .filter(([, city]) => city.toLowerCase().includes(query.toLowerCase())) .map(([code, city]) => ({ code, city })) .splice(0, 5); setDestinationOptions(matchingCities); }; const startScraping = async () => { if (source && destination && flightDate) { const data = await axios.get(`${USER_API_ROUTES.FLIGHT_SCRAPE}?source=${source}&destination=${destination}&date=${flightDate}`); if (data.data.id) { setLoadingJobId(data.data.id); setScraping(true); setScrapingType("flight"); } } }; useEffect(() => { if (loadingJobId) { const checkIfJobCompleted = async () => { try { const response = await axios.get(`${USER_API_ROUTES.FLIGHT_SCRAPE_STATUS}?jobId=${loadingJobId}`); if (response.data.status) { set ScrappedFlights(response.data.flights); clearInterval(jobIntervalRef.current); setScraping(false); setScrapingType(undefined); router.push(`/flights?data=${flightDate}`); } } catch (error) { console.log(error); } }; jobIntervalRef.current = setInterval(checkIfJobCompleted, 3000); } return () => clearInterval(jobIntervalRef.current); }, [loadingJobId]); return ( <div className="h-[90vh] flex items-center justify-center"> <div className="absolute left-0 top-0 h-[100vh] w-[100vw] max-w-[100vw] overflow-hidden overflow-x-hidden"> <Image src="/flight-search.png" fill alt="Search" /> </div> <div className="absolute h-[50vh] w-[60vw] flex flex-col gap-5"> {/* UI and functionality for flight search */} </div> </div> ); }; export default SearchFlights; ``` ### 步驟9:航班搜尋頁面UI ![航班搜尋頁面](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54bkhpea27fkzg4vlur1.png) ### 顯示航班搜尋結果 成功抓取飛行資料後,下一個關鍵步驟是以使用者友善的方式將這些結果呈現給使用者。 Next.js 應用程式中的 Flights 元件就是為此目的而設計的。 ``` "use client"; import { useAppStore } from "@/store"; import { USER_API_ROUTES } from "@/utils/api-routes"; import { Button } from "@nextui-org/react"; import axios from "axios"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; import React from "react"; import { FaChevronLeft } from "react-icons/fa"; import { MdOutlineFlight } from "react-icons/md"; const Flights = () => { const router = useRouter(); const searchParams = useSearchParams(); const date = searchParams.get("date"); const { scrappedFlights, userInfo } = useAppStore(); const getRandomNumber = () => Math.floor(Math.random() * 41); const bookFLight = async (flightId: number) => {}; return ( <div className="m-10 px-[20vw] min-h-[80vh]"> <Button className="my-5" variant="shadow" color="primary" size="lg" onClick={() => router.push("/search-flights")} > <FaChevronLeft /> Go Back </Button> <div className="flex-col flex gap-5"> {scrappedFlights.length === 0 && ( <div className="flex items-center justify-center py-5 px-10 mt-10 rounded-lg text-red-500 bg-red-100 font-medium"> No Flights found. </div> )} {scrappedFlights.map((flight: any) => { const seatsLeft = getRandomNumber(); return ( <div key={flight.id} className="grid grid-cols-12 border bg-gray-200 rounded-xl font-medium drop-shadow-md" > <div className="col-span-9 bg-white rounded-l-xl p-10 flex flex-col gap-5"> <div className="grid grid-cols-4 gap-4"> <div className="flex flex-col gap-3 font-medium"> <div> <div className="relative w-20 h-16"> <Image src={flight.logo} alt="airline name" fill /> </div> </div> <div>{flight.name}</div> </div> <div className="col-span-3 flex justify-between"> <div className="flex flex-col gap-2"> <div className="text-blue-600">From</div> <div> <span className="text-3xl"> <strong>{flight.departureTime}</strong> </span> </div> <div>{flight.from}</div> </div> <div className="flex flex-col items-center justify-center gap-2"> <div className="bg-violet-100 w-max p-3 text-4xl text-blue-600 rounded-full"> <MdOutlineFlight /> </div> <div> <span className="text-lg"> <strong>Non-stop</strong> </span> </div> <div>{flight.duration}</div> </div> <div className="flex flex-col gap-2"> <div className="text-blue-600">To</div> <div> <span className="text-3xl"> <strong>{flight.arrivalTime}</strong> </span> </div> <div>{flight.to}</div> </div> </div> </div> <div className="flex justify-center gap-10 bg-violet-100 p-3 rounded-lg"> <div className="flex"> <span>Airplane  </span> <span className="text-blue-600 font-semibold"> Boeing 787 </span> </div> <div className="flex"> <span>Travel Class:  </span> <span className="text-blue-600 font-semibold">Economy</span> </div> </div> <div className="flex justify-between font-medium"> <div> Refundable <span className="text-blue-600"> $5 ecash</span> </div> <div className={`${ seatsLeft > 20 ? "text-green-500" : "text-red-500" }`} > Only {seatsLeft} Seats Left </div> <div className="cursor-pointer">Flight Details</div> </div> </div> <div className="col-span-3 bg-violet-100 rounded-r-xl h-full flex flex-col items-center justify-center gap-5"> <div> <div> <span className="line-through font-light"> ${flight.price + 140} </span> </div> <div className="flex items-center gap-2"> <span className="text-5xl font-bold">${flight.price}</span> <span className="text-blue-600">20% OFF</span> </div> </div> <Button variant="ghost" radius="full" size="lg" color="primary" onClick={() => { if (userInfo) bookFLight(flight.id); }} > {userInfo ? "Book Now" : "Login to Book"} </Button> </div> </div> ); })} </div> </div> ); }; export default Flights; ``` #### 航班搜尋結果 ![航班搜尋結果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxufusoyrf6hgtrrcmsw.png) ### 探索完整的指南和程式碼庫 上面共享的部分和程式碼片段僅代表使用 Next.js 建立強大的航班資料抓取和搜尋應用程式所需的完整功能和程式碼的一小部分。為了掌握這個專案的全部內容,包括高級功能、優化和最佳實踐,我邀請您更深入地研究我的線上綜合資源。 #### 在 YouTube 上觀看詳細說明 有關引導您完成此應用程式的開發過程、編碼細微差別和功能的逐步影片指南,請觀看我的 YouTube 影片。本教程旨在讓您更深入地了解這些概念,讓您按照自己的步調進行操作並獲得對 Next.js 應用程式開發的寶貴見解。 https://www.youtube.com/watch?v=ZWVhk0fxHM0 #### 在 GitHub 上探索完整程式碼 如果您渴望探索完整的程式碼,請造訪我的 GitHub 儲存庫。在那裡,您將找到完整的程式碼庫,包括讓該應用程式在您自己的電腦上執行所需的所有元件、實用程式和設定說明。 https://github.com/koolkishan/nextjs-travel-planner ### 結論 使用 Next.js 建立飛行資料抓取和搜尋工具等綜合應用程式展示了現代 Web 開發工具和框架的強大功能和多功能性。無論您是希望提高技能的經驗豐富的開發人員,還是渴望深入 Web 開發的初學者,這些資源都是為您的旅程量身定制的。在 YouTube 上觀看詳細教程,在 GitHub 上探索完整程式碼,並加入對話以增強您的開發專業知識並為充滿活力的開發者社群做出貢獻。 --- 原文出處:https://dev.to/kishansheth/nextjs-14-booking-app-with-live-data-scraping-using-scraping-browser-610

6 個 JavaScript 控制台方法,例如泰勒絲民間傳說歌詞

--- 標題:6 個 JavaScript 控制台方法,如泰勒絲民間傳說歌詞 發表:真實 描述:了解六種鮮為人知的 JavaScript 控制台方法,這些方法與泰勒絲 (Taylor Swift) 最新專輯《Followre》中的歌詞類似。 標籤: javascript、webdev、除錯、生產力 封面圖片:https://twilio-cms-prod.s3.amazonaws.com/images/ts\_AMVvYGp.width-1616.png --- 這篇部落格文章是為[Twilio](https://www.twilio.com/)撰寫的,[最初發佈在 Twilio 部落格上](https://www.twilio.com/blog/js-console-methods-like-taylor-swift-folklore-lyrics)。 如果您進行 Web 開發,您可能至少使用過一次`console.log` (或超過一千次...誰在數?),因為這是最好的偵錯方法!但您知道還有其他控制台方法嗎? Taylor Swift 的最新專輯《Followre》 充滿了沉思的隱喻、典故和象徵意義,這篇文章將其中一些歌詞比作 6 個鮮為人知的 JavaScript `console`方法。 ### 控制台到底是什麼? `console`是一個[全域物件](https://developer.mozilla.org/en-US/docs/Glossary/Global_object),允許開發人員存取偵錯控制台。它有大量的方法,可以更輕鬆地記錄語句、變數、函數、錯誤等——天哪! #### 6種類似民間傳說歌詞的控制台方法 #### *1. console.log = "但這會很有趣 // 如果你是那個人"* `console.log`是最常用的方法。用於通用日誌記錄,它在 Web 控制台中顯示傳遞給它的訊息。你知道你可以用 CSS 來裝飾它嗎? ``` console.log("%cWARNING: you will be obsessed with folklore", "font: 2em sans-serif; color: yellow; background-color: red;"); ``` ![輸出console.log css](https://twilio-cms-prod.s3.amazonaws.com/images/Yz6zzAsmqMlZsQPirgqJZ1U1MWR_pPvMUExuwPavf-Orm.width-1000.png) `Log`簡單、可靠,並且可以完成工作,但它被過度使用,吸引了類似`console`方法的所有註意力,這些方法做得更多。如果`Log`是您需要的一種或唯一的控制台方法,那麼它會很有趣 - 但正如本文將要展示的,您將透過其他`console`方法獲得更多樂趣! #### *2. console.table = "我是一個鏡球 // 今晚我會向你展示你自己的每個版本"* `table`方法接受一個物件或陣列並將輸入記錄為表格,使其看起來更乾淨:它就像`log`的更好版本。與鏡像球一樣, `table`可以透過接受可選參數`columns`來選擇要顯示的列子集來顯示不同版本的輸入。 陣列中的每個元素(如果資料是物件,則為每個可枚舉屬性)將是表中的一行。下面的 JavaScript 程式碼有一個物件,您可以看到最初使用 log 的輸出。 ``` function Album(name, year, numSongs) { this.name = name; this.year = year; this.numSongs = numSongs; } const fearless = new Album("Fearless", 2008, 13); const speakNow = new Album("Speak Now", 2010, 19); const folklore = new Album("folklore", 2020, 16); console.log([fearless, speakNow, folklore]); ``` ![物件的console.log](https://twilio-cms-prod.s3.amazonaws.com/images/4w8wxLh0fE0C0YQ1-lO-MdqRmLx_QjFj-ng-UcbM-C8Q2.width-1000.png) 這很好,但是給定陣列時`table`的輸出看起來更好: ``` console.table([fearless, speakNow, folklore]); ``` ![物件的console.table](https://twilio-cms-prod.s3.amazonaws.com/images/MqAnPKv4jrbZVF4Z7-vf37jkzC6N4ppYKS_-pFH0eXuov.width-1000.png) 接受`columns`參數,如`console.table([fearless, speakNow, folklore], ["name"]);`會顯示: ![帶有額外列參數的 console.table](https://twilio-cms-prod.s3.amazonaws.com/images/UTQQlcite2SYJZMXWuKc_p0yetEku7jPZLGtsmBc5aTGY.width-1000.png) 您也可以傳遞它(而不是`name` ) `year`或`numSongs` - 就像鏡子球一樣,表格可以向您顯示其輸入的每個版本! #### *3. console.assert =“如果你從不流血,你就永遠不會成長”* `console.assert(expression, message)`僅在表達式為 false 時才列印。泰勒絲 (Taylor Swift) 在歌曲*《The 1》*中的歌詞「如果你從不流血,你就永遠不會成長」也同意這一點——如果你從不流血,或者失敗,或者有時不正確,你就永遠不會成長。 `assert`表明,透過錯誤,您可以成長為一名開發人員,因為您可以修復錯誤,控制台會透過將斷言變成漂亮的紅色來幫助您。 ``` const numFolkloreSongs = 16; const num1989Songs = 13; console.assert(numFolkloreSongs > num1989Songs, 'folklore has more songs than 1989'); //won't run console.assert(num1989Songs + 3 > numFolkloreSongs, 'the number of songs on 1989 + 3 is not greater than the number of folklore songs'); ``` ![斷言失敗](https://twilio-cms-prod.s3.amazonaws.com/images/6kyCsGK8UWI0nEEgZ703RHjPu67krRq-OCmTgkDuiJtYw.width-1000.png) #### *4. console.time/console.timeEnd = "時間,神秘的時間/切開我,然後治癒我。"* `console.time()`建立一個計時器來查看某些操作需要多長時間。它可以採用名稱或標籤的可選參數來區分網頁上最多 10,000 個計時器。 `console.timeEnd()`停止計時器,並在控制台中顯示結果。 時間可能很艱難——它可以讓你心碎,但它也可以治癒你,讓你感覺更好。 ``` console.time('sms timer'); let x = 0; while (x < 3) { console.log("They told me all of my cages were mental/So I got wasted like all my potential"); x+=1; } console.timeEnd('sms timer'); ``` ![控制台時間](https://twilio-cms-prod.s3.amazonaws.com/images/C0ExSxgVLyLR6uuPM911Lhw64QJ3qLZXDWKbrLdJ5FwnD.width-1000.png) 如果沒有標籤傳遞給`console.time()` ,它將記錄 default 而不是*sms time* 。 #### *5. console.clear:“如果我對你來說已經死了,為什麼你還在守靈?”* `console.clear`簡短、甜蜜、簡潔。它會清除控制台,並且在某些環境中,可能會列印諸如“控制台已清除”之類的訊息。 歌詞“如果我對你來說已經死了,為什麼你還在守靈?”是憂鬱的,但也有一些尖銳的味道:它非常適合當你想要結束對話時,就像`clear`一樣,你可以重新開始,重新開始。 #### *6. console.group/console.groupEnd ="想到一直以來有一條看不見的繩子把你和我綁在一起,這不是很美好嗎?"* `console.group`表示內聯訊息群組的開始, `console.groupEnd`標記其結束。如果群組包含日誌,它們將作為一個群組列印,因此格式更清晰,您可以更輕鬆地了解群組包含的內容。 就像有一些看不見的字串(或`console`命令)將群組中的專案捆綁在一起。 ``` console.group("folklore"); console.log("the 1"); console.log("cardigan"); console.log("the last great american dynasty"); console.log("invisible string"); console.log("my tears ricochet"); console.groupEnd(); console.log("outside"); ``` ![團體](https://twilio-cms-prod.s3.amazonaws.com/images/JiqlDPAtBRz7G_ExfwKLEhsOXwAMZKIRS-Bv3YaL9SKGb.width-1000.png) #### 控制台的下一步是什麼? ![tswift 太棒了 gif](https://twilio-cms-prod.s3.amazonaws.com/original_images/tsgif.gif) 還有許多其他控制台方法未包含在此處(部分原因是它們與 Taylor Swift 歌詞的關係不大。)有關控制台方法的更多訊息,[請查看 Mozilla 開發者網絡有關 Web 技術的文件](https://developer.mozilla.org/en-US/docs/Web/API/console)。讓我知道您最喜歡或最不喜歡的民俗歌曲在線或在評論中! 1. 推特: [@lizziepika](https://twitter.com/lizziepika) 2. GitHub:[伊莉莎白西格爾](https://github.com/elizabethsiegle) 3. 電子郵件:[email protected] --- 原文出處:https://dev.to/twilio/6-javascript-console-methods-like-taylor-swift-folklore-lyrics-3h0k

為初學者到專家提供的 101 個 Bash 指令和提示

> **2019 年 9 月 25 日更新:**感謝[ラナ・kuaru](https://twitter.com/rana_kualu)的辛勤工作,本文現已提供日文版。請點擊下面的連結查看他們的工作。如果您知道本文被翻譯成其他語言,請告訴我,我會將其發佈在這裡。 [🇯🇵 閱讀日語](https://qiita.com/rana_kualu/items/7b62898d373901466f5c) > **2019 年 7 月 8 日更新:**我最近發現大約兩年前發佈在法語留言板上的[這篇非常相似的文章](https://bookmarks.ecyseo.net/?EAWvDw)。如果您有興趣學習一些 shell 命令——並且您*會說 français* ,那麼它是對我下面的文章的一個很好的補充。 直到大約一年前,我幾乎只在 macOS 和 Ubuntu 作業系統中工作。在這兩個作業系統上, `bash`是我的預設 shell。在過去的六、七年裡,我對`bash`工作原理有了大致的了解,並想為那些剛入門的人概述一些更常見/有用的命令。如果您認為您了解有關`bash`所有訊息,請無論如何看看下面的內容 - 我已經提供了一些提示和您可能忘記的標誌的提醒,這可以讓您的工作更輕鬆一些。 下面的命令或多或少以敘述風格排列,因此如果您剛開始使用`bash` ,您可以從頭到尾完成操作。事情到最後通常會變得不那麼常見並且變得更加困難。 <a name="toc"></a> 目錄 -- - [基礎](#the-basics) ``` - [First Commands, Navigating the Filesystem](#first-commands) ``` ``` - [`pwd / ls / cd`](#pwd-ls-cd) ``` ``` - [`; / && / &`](#semicolon-andand-and) ``` ``` - [Getting Help](#getting-help) ``` ``` - [`-h`](#minus-h) ``` ``` - [`man`](#man) ``` ``` - [Viewing and Editing Files](#viewing-and-editing-files) ``` ``` - [`head / tail / cat / less`](#head-tail-cat-less) ``` ``` - [`nano / nedit`](#nano-nedit) ``` ``` - [Creating and Deleting Files and Directories](#creating-and-deleting-files) ``` ``` - [`touch`](#touch) ``` ``` - [`mkdir / rm / rmdir`](#mkdir-rm-rmdir) ``` ``` - [Moving and Copying Files, Making Links, Command History](#moving-and-copying-files) ``` ``` - [`mv / cp / ln`](#mv-cp-ln) ``` ``` - [Command History](#command-history) ``` ``` - [Directory Trees, Disk Usage, and Processes](#directory-trees-disk-usage-processes) ``` ``` - [`mkdir –p / tree`](#mkdir--p-tree) ``` ``` - [`df / du / ps`](#df-du-ps) ``` ``` - [Miscellaneous](#basic-misc) ``` ``` - [`passwd / logout / exit`](#passwd-logout-exit) ``` ``` - [`clear / *`](#clear-glob) ``` - [中間的](#intermediate) ``` - [Disk, Memory, and Processor Usage](#disk-memory-processor) ``` ``` - [`ncdu`](#ncdu) ``` ``` - [`top / htop`](#top-htop) ``` ``` - [REPLs and Software Versions](#REPLs-software-versions) ``` ``` - [REPLs](#REPLs) ``` ``` - [`-version / --version / -v`](#version) ``` ``` - [Environment Variables and Aliases](#env-vars-aliases) ``` ``` - [Environment Variables](#env-vars) ``` ``` - [Aliases](#aliases) ``` ``` - [Basic `bash` Scripting](#basic-bash-scripting) ``` ``` - [`bash` Scripts](#bash-scripts) ``` ``` - [Custom Prompt and `ls`](#custom-prompt-ls) ``` ``` - [Config Files](#config-files) ``` ``` - [Config Files / `.bashrc`](#config-bashrc) ``` ``` - [Types of Shells](#types-of-shells) ``` ``` - [Finding Things](#finding-things) ``` ``` - [`whereis / which / whatis`](#whereis-which-whatis) ``` ``` - [`locate / find`](#locate-find) ``` ``` - [Downloading Things](#downloading-things) ``` ``` - [`ping / wget / curl`](#ping-wget-curl) ``` ``` - [`apt / gunzip / tar / gzip`](#apt-gunzip-tar-gzip) ``` ``` - [Redirecting Input and Output](#redirecting-io) ``` ``` - [`| / > / < / echo / printf`](#pipe-gt-lt-echo-printf) ``` ``` - [`0 / 1 / 2 / tee`](#std-tee) ``` - [先進的](#advanced) ``` - [Superuser](#superuser) ``` ``` - [`sudo / su`](#sudo-su) ``` ``` - [`!!`](#click-click) ``` ``` - [File Permissions](#file-permissions) ``` ``` - [File Permissions](#file-permissions-sub) ``` ``` - [`chmod / chown`](#chmod-chown) ``` ``` - [User and Group Management](#users-groups) ``` ``` - [Users](#users) ``` ``` - [Groups](#groups) ``` ``` - [Text Processing](#text-processing) ``` ``` - [`uniq / sort / diff / cmp`](#uniq-sort-diff-cmp) ``` ``` - [`cut / sed`](#cut-sed) ``` ``` - [Pattern Matching](#pattern-matching) ``` ``` - [`grep`](#grep) ``` ``` - [`awk`](#awk) ``` ``` - [Copying Files Over `ssh`](#ssh) ``` ``` - [`ssh / scp`](#ssh-scp) ``` ``` - [`rsync`](#rsync) ``` ``` - [Long-Running Processes](#long-running-processes) ``` ``` - [`yes / nohup / ps / kill`](#yes-nohup-ps-kill) ``` ``` - [`cron / crontab / >>`](#cron) ``` ``` - [Miscellaneous](#advanced-misc) ``` ``` - [`pushd / popd`](#pushd-popd) ``` ``` - [`xdg-open`](#xdg-open) ``` ``` - [`xargs`](#xargs) ``` - [獎勵:有趣但大多無用的東西](#bonus) ``` - [`w / write / wall / lynx`](#w-write-wall-lynx) ``` ``` - [`nautilus / date / cal / bc`](#nautilus-date-cal-bc) ``` --- <a name="the-basics"></a> 基礎 == <a name="first-commands"></a> 第一個指令,瀏覽檔案系統 ------------ 現代檔案系統具有目錄(資料夾)樹,其中目錄要么是*根目錄*(沒有父目錄),要么是*子目錄*(包含在單一其他目錄中,我們稱之為“父目錄”)。向後遍歷檔案樹(從子目錄到父目錄)將始終到達根目錄。有些檔案系統有多個根目錄(如 Windows 的磁碟機: `C:\` 、 `A:\`等),但 Unix 和類別 Unix 系統只有一個名為`\`的根目錄。 <a name="pwd-ls-cd"></a> ### `pwd / ls / cd` [\[ 返回目錄 \]](#toc) 在檔案系統中工作時,使用者始終*在*某個目錄中工作,我們稱之為當前目錄或*工作目錄*。使用`pwd`列印使用者的工作目錄: ``` [ andrew@pc01 ~ ]$ pwd /home/andrew ``` 使用`ls`列出該目錄的內容(檔案和/或子目錄等): ``` [ andrew@pc01 ~ ]$ ls Git TEST jdoc test test.file ``` > **獎金:** > > 使用`ls -a`顯示隱藏(“點”)文件 > > 使用`ls -l`顯示文件詳細訊息 > > 組合多個標誌,如`ls -l -a` > > 有時您可以連結諸如`ls -la`之類的標誌,而不是`ls -l -a` 使用`cd`更改到不同的目錄(更改目錄): ``` [ andrew@pc01 ~ ]$ cd TEST/ [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST [ andrew@pc01 TEST ]$ cd A [ andrew@pc01 A ]$ pwd /home/andrew/TEST/A ``` `cd ..`是「 `cd`到父目錄」的簡寫: ``` [ andrew@pc01 A ]$ cd .. [ andrew@pc01 TEST ]$ pwd /home/andrew/TEST ``` `cd ~`或只是`cd`是「 `cd`到我的主目錄」的簡寫(通常`/home/username`或類似的東西): ``` [ andrew@pc01 TEST ]$ cd [ andrew@pc01 ~ ]$ pwd /home/andrew ``` > **獎金:** > > `cd ~user`表示「 `cd`到`user`的主目錄 > > 您可以使用`cd ../..`等跳轉多個目錄等級。 > > 使用`cd -`返回到最近的目錄 > > `.`是「此目錄」的簡寫,因此`cd .`不會做太多事情 <a name="semicolon-andand-and"></a> ### `; / && / &` [\[ 返回目錄 \]](#toc) 我們在命令列中輸入的內容稱為*命令*,它們總是執行儲存在電腦上某處的一些機器碼。有時這個機器碼是一個內建的Linux命令,有時它是一個應用程式,有時它是你自己寫的一些程式碼。有時,我們會想依序執行一個指令。為此,我們可以使用`;` (分號): ``` [ andrew@pc01 ~ ]$ ls; pwd Git TEST jdoc test test.file /home/andrew ``` 上面的分號表示我首先 ( `ls` ) 列出工作目錄的內容,然後 ( `pwd` ) 列印其位置。連結命令的另一個有用工具是`&&` 。使用`&&`時,如果左側命令失敗,則右側命令將不會執行。 `;`和`&&`都可以在同一行中多次使用: ``` # whoops! I made a typo here! [ andrew@pc01 ~ ]$ cd /Giit/Parser && pwd && ls && cd -bash: cd: /Giit/Parser: No such file or directory # the first command passes now, so the following commands are run [ andrew@pc01 ~ ]$ cd Git/Parser/ && pwd && ls && cd /home/andrew/Git/Parser README.md doc.sh pom.xml resource run.sh shell.sh source src target ``` ....但是與`;` ,即使第一個命令失敗,第二個命令也會執行: ``` # pwd and ls still run, even though the cd command failed [ andrew@pc01 ~ ]$ cd /Giit/Parser ; pwd ; ls -bash: cd: /Giit/Parser: No such file or directory /home/andrew Git TEST jdoc test test.file ``` `&`看起來與`&&`類似,但實際上實現了完全不同的功能。通常,當您執行長時間執行的命令時,命令列將等待該命令完成,然後才允許您輸入另一個命令。在命令後面加上`&`可以防止這種情況發生,並允許您在舊命令仍在執行時執行新命令: ``` [ andrew@pc01 ~ ]$ cd Git/Parser && mvn package & cd [1] 9263 ``` > **額外的好處:**當我們在命令後使用`&`來「隱藏」它時,我們說該作業(或「進程」;這些術語或多或少可以互換)是「後台的」。若要查看目前正在執行的背景作業,請使用`jobs`指令: > ````bash \[ andrew@pc01 ~ \]$ 職位 \[1\]+ 執行 cd Git/Parser/ && mvn package & ``` <a name="getting-help"></a> ## Getting Help <a name="minus-h"></a> ### `-h` [[ Back to Table of Contents ]](#toc) Type `-h` or `--help` after almost any command to bring up a help menu for that command: ``` \[ andrew@pc01 ~ \]$ du --help 用法:你\[選項\]...\[檔案\]... 或: du \[選項\]... --files0-from=F 對目錄遞歸地總結文件集的磁碟使用情況。 長期權的強制性參數對於短期權也是強制性的。 -0, --null 以 NUL 結束每個輸出行,而不是換行符 -a, --all 計算所有檔案的寫入計數,而不僅僅是目錄 ``` --apparent-size print apparent sizes, rather than disk usage; although ``` ``` the apparent size is usually smaller, it may be ``` ``` larger due to holes in ('sparse') files, internal ``` ``` fragmentation, indirect blocks, and the like ``` -B, --block-size=SIZE 在列印前按 SIZE 縮放大小;例如, ``` '-BM' prints sizes in units of 1,048,576 bytes; ``` ``` see SIZE format below ``` … ``` <a name="man"></a> ### `man` [[ Back to Table of Contents ]](#toc) Type `man` before almost any command to bring up a manual for that command (quit `man` with `q`): ``` LS(1) 使用者指令 LS(1) 姓名 ``` ls - list directory contents ``` 概要 ``` ls [OPTION]... [FILE]... ``` 描述 ``` List information about the FILEs (the current directory by default). ``` ``` Sort entries alphabetically if none of -cftuvSUX nor --sort is speci- ``` ``` fied. ``` ``` Mandatory arguments to long options are mandatory for short options ``` ``` too. ``` … ``` <a name="viewing-and-editing-files"></a> ## Viewing and Editing Files <a name="head-tail-cat-less"></a> ### `head / tail / cat / less` [[ Back to Table of Contents ]](#toc) `head` outputs the first few lines of a file. The `-n` flag specifies the number of lines to show (the default is 10): ``` 列印前三行 ===== \[ andrew@pc01 ~ \]$ 頭 -n 3 c 這 文件 有 ``` `tail` outputs the last few lines of a file. You can get the last `n` lines (like above), or you can get the end of the file beginning from the `N`-th line with `tail -n +N`: ``` 從第 4 行開始列印文件末尾 ============== \[ andrew@pc01 ~ \]$ tail -n +4 c 確切地 六 線 ``` `cat` concatenates a list of files and sends them to the standard output stream (usually the terminal). `cat` can be used with just a single file, or multiple files, and is often used to quickly view them. (**Be warned**: if you use `cat` in this way, you may be accused of a [_Useless Use of Cat_ (UUOC)](http://bit.ly/2SPHE4V), but it's not that big of a deal, so don't worry too much about it.) ``` \[ andrew@pc01 ~ \]$ 貓 a 歸檔一個 \[ andrew@pc01 ~ \]$ 貓 ab 歸檔一個 文件b ``` `less` is another tool for quickly viewing a file -- it opens up a `vim`-like read-only window. (Yes, there is a command called `more`, but `less` -- unintuitively -- offers a superset of the functionality of `more` and is recommended over it.) Learn more (or less?) about [less](http://man7.org/linux/man-pages/man1/less.1.html) and [more](http://man7.org/linux/man-pages/man1/more.1.html) at their `man` pages. <a name="nano-nedit"></a> ### `nano / nedit` [[ Back to Table of Contents ]](#toc) `nano` is a minimalistic command-line text editor. It's a great editor for beginners or people who don't want to learn a million shortcuts. It was more than sufficient for me for the first few years of my coding career (I'm only now starting to look into more powerful editors, mainly because defining your own syntax highlighting in `nano` can be a bit of a pain.) `nedit` is a small graphical editor, it opens up an X Window and allows point-and-click editing, drag-and-drop, syntax highlighting and more. I use `nedit` sometimes when I want to make small changes to a script and re-run it over and over. Other common CLI (command-line interface) / GUI (graphical user interface) editors include `emacs`, `vi`, `vim`, `gedit`, Notepad++, Atom, and lots more. Some cool ones that I've played around with (and can endorse) include Micro, Light Table, and VS Code. All modern editors offer basic conveniences like search and replace, syntax highlighting, and so on. `vi(m)` and `emacs` have more features than `nano` and `nedit`, but they have a much steeper learning curve. Try a few different editors out and find one that works for you! <a name="creating-and-deleting-files"></a> ## Creating and Deleting Files and Directories <a name="touch"></a> ### `touch` [[ Back to Table of Contents ]](#toc) `touch` was created to modify file timestamps, but it can also be used to quickly create an empty file. You can create a new file by opening it with a text editor, like `nano`: ``` \[ andrew@pc01 前 \]$ ls \[ andrew@pc01 ex \]$ 奈米 a ``` _...editing file..._ ``` \[ andrew@pc01 前 \]$ ls A ``` ...or by simply using `touch`: ``` \[ andrew@pc01 ex \]$ touch b && ls ab ``` > **Bonus**: > > Background a process with \^z (Ctrl+z) > > ```bash > [ andrew@pc01 ex ]$ nano a > ``` > > _...editing file, then hit \^z..._ > > ```bash > Use fg to return to nano > > [1]+ Stopped nano a > [ andrew@pc01 ex ]$ fg > ``` > > _...editing file again..._ --- > **Double Bonus:** > > Kill the current (foreground) process by pressing \^c (Ctrl+c) while it’s running > > Kill a background process with `kill %N` where `N` is the job index shown by the `jobs` command <a name="mkdir-rm-rmdir"></a> ### `mkdir / rm / rmdir` [[ Back to Table of Contents ]](#toc) `mkdir` is used to create new, empty directories: ``` \[ andrew@pc01 ex \]$ ls && mkdir c && ls ab ABC ``` You can remove any file with `rm` -- but be careful, this is non-recoverable! ``` \[ andrew@pc01 ex \]$ rm a && ls 西元前 ``` You can add an _"are you sure?"_ prompt with the `-i` flag: ``` \[ andrew@pc01 前 \]$ rm -ib rm:刪除常規空文件“b”? y ``` Remove an empty directory with `rmdir`. If you `ls -a` in an empty directory, you should only see a reference to the directory itself (`.`) and a reference to its parent directory (`..`): ``` \[ andrew@pc01 ex \]$ rmdir c && ls -a 。 .. ``` `rmdir` removes empty directories only: ``` \[ andrew@pc01 ex \]$ cd .. && ls 測試/ \*.txt 0.txt 1.txt a a.txt bc \[ andrew@pc01 ~ \]$ rmdir 測試/ rmdir:無法刪除“test/”:目錄不為空 ``` ...but you can remove a directory -- and all of its contents -- with `rm -rf` (`-r` = recursive, `-f` = force): ``` \[ andrew@pc01 ~ \]$ rm –rf 測試 ``` <a name="moving-and-copying-files"></a> ## Moving and Copying Files, Making Links, Command History <a name="mv-cp-ln"></a> ### `mv / cp / ln` [[ Back to Table of Contents ]](#toc) `mv` moves / renames a file. You can `mv` a file to a new directory and keep the same file name or `mv` a file to a "new file" (rename it): ``` \[ andrew@pc01 ex \]$ ls && mv ae && ls A B C D BCDE ``` `cp` copies a file: ``` \[ andrew@pc01 ex \]$ cp e e2 && ls BCDE E2 ``` `ln` creates a hard link to a file: ``` ln 的第一個參數是 TARGET,第二個參數是 NEW LINK ================================= \[ andrew@pc01 ex \]$ ln bf && ls bcde e2 f ``` `ln -s` creates a soft link to a file: ``` \[ andrew@pc01 ex \]$ ln -sbg && ls BCDE E2 FG ``` Hard links reference the same actual bytes in memory which contain a file, while soft links refer to the original file name, which itself points to those bytes. [You can read more about soft vs. hard links here.](http://bit.ly/2D0W8cN) <a name="command-history"></a> ### Command History [[ Back to Table of Contents ]](#toc) `bash` has two big features to help you complete and re-run commands, the first is _tab completion_. Simply type the first part of a command, hit the \<tab\> key, and let the terminal guess what you're trying to do: ``` \[ andrew@pc01 目錄 \]$ ls 另一個長檔名 這是一個長檔名 一個新檔名 \[ andrew@pc01 目錄 \]$ ls t ``` _...hit the TAB key after typing `ls t` and the command is completed..._ ``` \[ andrew@pc01 dir \]$ ls 這是檔名 這是長檔名 ``` You may have to hit \<TAB\> multiple times if there's an ambiguity: ``` \[ andrew@pc01 目錄 \]$ ls a \[ andrew@pc01 目錄 \]$ ls an 一個新檔名另一個長檔名 ``` `bash` keeps a short history of the commands you've typed previously and lets you search through those commands by typing \^r (Ctrl+r): ``` \[ andrew@pc01 目錄 \] ``` _...hit \^r (Ctrl+r) to search the command history..._ ``` (反向搜尋)``: ``` _...type 'anew' and the last command containing this is found..._ ``` (reverse-i-search)`anew': 觸碰新檔名 ``` <a name="directory-trees-disk-usage-processes"></a> ## Directory Trees, Disk Usage, and Processes <a name="mkdir--p-tree"></a> ### `mkdir –p / tree` [[ Back to Table of Contents ]](#toc) `mkdir`, by default, only makes a single directory. This means that if, for instance, directory `d/e` doesn't exist, then `d/e/f` can't be made with `mkdir` by itself: ``` \[ andrew@pc01 ex \]$ ls && mkdir d/e/f ABC mkdir:無法建立目錄「d/e/f」:沒有這樣的檔案或目錄 ``` But if we pass the `-p` flag to `mkdir`, it will make all directories in the path if they don't already exist: ``` \[ andrew@pc01 ex \]$ mkdir -pd/e/f && ls A B C D ``` `tree` can help you better visualise a directory's structure by printing a nicely-formatted directory tree. By default, it prints the entire tree structure (beginning with the specified directory), but you can restrict it to a certain number of levels with the `-L` flag: ``` \[ andrew@pc01 前 \]$ 樹 -L 2 。 |-- 一個 |-- b |-- c `--d ``` `--e ``` 3個目錄,2個文件 ``` You can hide empty directories in `tree`'s output with `--prune`. Note that this also removes "recursively empty" directories, or directories which aren't empty _per se_, but which contain only other empty directories, or other recursively empty directories: ``` \[ andrew@pc01 ex \]$ 樹 --prune 。 |-- 一個 `--b ``` <a name="df-du-ps"></a> ### `df / du / ps` [[ Back to Table of Contents ]](#toc) `df` is used to show how much space is taken up by files for the disks or your system (hard drives, etc.). ``` \[ andrew@pc01 前 \]$ df -h 已使用的檔案系統大小 可用 使用% 安裝於 udev 126G 0 126G 0% /dev tmpfs 26G 2.0G 24G 8% /執行 /dev/mapper/ubuntu--vg-root 1.6T 1.3T 252G 84% / … ``` In the above command, `-h` doesn't mean "help", but "human-readable". Some commands use this convention to display file / disk sizes with `K` for kilobytes, `G` for gigabytes, and so on, instead of writing out a gigantic integer number of bytes. `du` shows file space usage for a particular directory and its subdirectories. If you want to know how much space is free on a given hard drive, use `df`; if you want to know how much space a directory is taking up, use `du`: ``` \[ andrew@pc01 ex \]$ 你 4 ./d/e/f 8./d/e 12 ./天 4./c 20 . ``` `du` takes a `--max-depth=N` flag, which only shows directories `N` levels down (or fewer) from the specified directory: ``` \[ andrew@pc01 ex \]$ du -h --max-深度=1 12K./天 4.0K./c 20K。 ``` `ps` shows all of the user's currently-running processes (aka. jobs): ``` \[ andrew@pc01 前 \]$ ps PID TTY 時間 CMD 16642 分/15 00:00:00 ps 25409 點/15 00:00:00 重擊 ``` <a name="basic-misc"></a> ## Miscellaneous <a name="passwd-logout-exit"></a> ### `passwd / logout / exit` [[ Back to Table of Contents ]](#toc) Change your account password with `passwd`. It will ask for your current password for verification, then ask you to enter the new password twice, so you don't make any typos: ``` \[ andrew@pc01 目錄 \]$ 密碼 更改安德魯的密碼。 (目前)UNIX 密碼: 輸入新的 UNIX 密碼: 重新輸入新的 UNIX 密碼: passwd:密碼更新成功 ``` `logout` exits a shell you’ve logged in to (where you have a user account): ``` \[ andrew@pc01 目錄 \]$ 註銷 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` `exit` exits any kind of shell: ``` \[ andrew@pc01 ~ \]$ 退出 登出 ────────────────────────────────────────────────── ── ────────────────────────────── 會話已停止 ``` - Press <return> to exit tab ``` ``` - Press R to restart session ``` ``` - Press S to save terminal output to file ``` ``` <a name="clear-glob"></a> ### `clear / *` [[ Back to Table of Contents ]](#toc) Run `clear` to move the current terminal line to the top of the screen. This command just adds blank lines below the current prompt line. It's good for clearing your workspace. Use the glob (`*`, aka. Kleene Star, aka. wildcard) when looking for files. Notice the difference between the following two commands: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/ PArrayUtils.java PFile.java PSQLFile.java PWatchman.java PDateTimeUtils.java PFixedWidthFile.java PStringUtils.java PXSVFile.java PDelimitedFile.java PNode.java PTextFile.java Parser.java \[ andrew@pc01 ~ \]$ ls Git/Parser/source/PD\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` The glob can be used multiple times in a command and matches zero or more characers: ``` \[ andrew@pc01 ~ \]$ ls Git/Parser/source/P *D* m\* Git/Parser/source/PDateTimeUtils.java Git/Parser/source/PDelimitedFile.java ``` <a name="intermediate"></a> # Intermediate <a name="disk-memory-processor"></a> ## Disk, Memory, and Processor Usage <a name="ncdu"></a> ### `ncdu` [[ Back to Table of Contents ]](#toc) `ncdu` (NCurses Disk Usage) provides a navigable overview of file space usage, like an improved `du`. It opens a read-only `vim`-like window (press `q` to quit): ``` \[ andrew@pc01 ~ \]$ ncdu ncdu 1.11 ~ 使用箭頭鍵導航,按 ?求助 \--- /home/安德魯 ------------------------------------------- ------------------ 148.2 MiB \[##########\] /.m2 91.5 MiB \[######\] /.sbt 79.8 MiB \[######\] /.cache 64.9 MiB \[####\] /.ivy2 40.6 MiB \[##\] /.sdkman 30.2 MiB \[##\] /.local 27.4 MiB \[#\] /.mozilla 24.4 MiB \[#\] /.nanobackups 10.2 MiB \[ \] .confout3.txt ``` 8.4 MiB [ ] /.config ``` ``` 5.9 MiB [ ] /.nbi ``` ``` 5.8 MiB [ ] /.oh-my-zsh ``` ``` 4.3 MiB [ ] /Git ``` ``` 3.7 MiB [ ] /.myshell ``` ``` 1.7 MiB [ ] /jdoc ``` ``` 1.5 MiB [ ] .confout2.txt ``` ``` 1.5 MiB [ ] /.netbeans ``` ``` 1.1 MiB [ ] /.jenv ``` 564.0 KiB \[ \] /.rstudio-desktop 磁碟使用總量:552.7 MiB 表觀大小:523.6 MiB 專案:14618 ``` <a name="top-htop"></a> ### `top / htop` [[ Back to Table of Contents ]](#toc) `top` displays all currently-running processes and their owners, memory usage, and more. `htop` is an improved, interactive `top`. (Note: you can pass the `-u username` flag to restrict the displayed processes to only those owner by `username`.) ``` \[ andrew@pc01 ~ \]$ htop 1 \[ 0.0%\] 9 \[ 0.0%\] 17 \[ 0.0%\] 25 \[ 0.0%\] 2 \[ 0.0%\] 10 \[ 0.0%\] 18 \[ 0.0%\] 26 \[ 0.0%\] 3 \[ 0.0%\] 11 \[ 0.0%\] 19 \[ 0.0%\] 27 \[ 0.0%\] 4 \[ 0.0%\] 12 \[ 0.0%\] 20 \[ 0.0%\] 28 \[ 0.0%\] 5 \[ 0.0%\] 13 \[ 0.0%\] 21 \[| 1.3%\] 29 \[ 0.0%\] 6 \[ 0.0%\] 14 \[ 0.0%\] 22 \[ 0.0%\] 30 \[| 0.6%\] 7 \[ 0.0%\] 15 \[ 0.0%\] 23 \[ 0.0%\] 31 \[ 0.0%\] 8 \[ 0.0%\] 16 \[ 0.0%\] 24 \[ 0.0%\] 32 \[ 0.0%\] Mem\[|||||||||||||||||||1.42G/252G\] 任務:188、366 個; 1 執行 交換電壓\[| 2.47G/256G\]平均負載:0.00 0.00 0.00 ``` Uptime: 432 days(!), 00:03:55 ``` PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ 指令 9389 安德魯 20 0 23344 3848 2848 R 1.3 0.0 0:00.10 htop 10103 根 20 0 3216M 17896 2444 S 0.7 0.0 5h48:56 /usr/bin/dockerd ``` 1 root 20 0 181M 4604 2972 S 0.0 0.0 15:29.66 /lib/systemd/syst ``` 533 根 20 0 44676 6908 6716 S 0.0 0.0 11:19.77 /lib/systemd/syst 546 根 20 0 244M 0 0 S 0.0 0.0 0:01.39 /sbin/lvmetad -f 1526 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.00 /usr/sbin/ModemMa 1544 根 20 0 329M 2252 1916 S 0.0 0.0 0:00.06 /usr/sbin/ModemMa F1Help F2Setup F3SearchF4FilterF5Tree F6SortByF7Nice -F8Nice +F9Kill F10Quit ``` <a name="REPLs-software-versions"></a> ## REPLs and Software Versions <a name="REPLs"></a> ### REPLs [[ Back to Table of Contents ]](#toc) A **REPL** is a Read-Evaluate-Print Loop, similar to the command line, but usually used for particular programming languages. You can open the Python REPL with the `python` command (and quit with the `quit()` function): ``` \[ andrew@pc01 ~ \]$ python Python 3.5.2(默認,2018 年 11 月 12 日,13:43:14)... > > > 辭職() ``` Open the R REPL with the `R` command (and quit with the `q()` function): ``` \[ andrew@pc01 ~ \]$ R R版3.5.2(2018-12-20)--「蛋殼冰屋」... > q() 儲存工作區影像? \[是/否/c\]: 否 ``` Open the Scala REPL with the `scala` command (and quit with the `:quit` command): ``` \[ andrew@pc01 ~ \]$ scala 歡迎使用 Scala 2.11.12 ... 斯卡拉>:退出 ``` Open the Java REPL with the `jshell` command (and quit with the `/exit` command): ``` \[ andrew@pc01 ~ \]$ jshell |歡迎使用 JShell——版本 11.0.1 ... jshell> /退出 ``` Alternatively, you can exit any of these REPLs with \^d (Ctrl+d). \^d is the EOF (end of file) marker on Unix and signifies the end of input. <a name="version"></a> ### `-version / --version / -v` [[ Back to Table of Contents ]](#toc) Most commands and programs have a `-version` or `--version` flag which gives the software version of that command or program. Most applications make this information easily available: ``` \[ andrew@pc01 ~ \]$ ls --version ls (GNU coreutils) 8.25 ... \[ andrew@pc01 ~ \]$ ncdu -版本 NCDU 1.11 \[ andrew@pc01 ~ \]$ python --version Python 3.5.2 ``` ...but some are less intuitive: ``` \[ andrew@pc01 ~ \]$ sbt scalaVersion … \[資訊\]2.12.4 ``` Note that some programs use `-v` as a version flag, while others use `-v` to mean "verbose", which will run the application while printing lots of diagnostic or debugging information: ``` SCP(1) BSD 通用指令手冊 SCP(1) 姓名 ``` scp -- secure copy (remote file copy program) ``` … -v 詳細模式。導致 scp 和 ssh(1) 列印偵錯訊息 ``` about their progress. This is helpful in debugging connection, ``` ``` authentication, and configuration problems. ``` … ``` <a name="env-vars-aliases"></a> ## Environment Variables and Aliases <a name="env-vars"></a> ### Environment Variables [[ Back to Table of Contents ]](#toc) **Environment variables** (sometimes shortened to "env vars") are persistent variables that can be created and used within your `bash` shell. They are defined with an equals sign (`=`) and used with a dollar sign (`$`). You can see all currently-defined env vars with `printenv`: ``` \[ andrew@pc01 ~ \]$ printenv SPARK\_HOME=/usr/local/spark 術語=xterm … ``` Set a new environment variable with an `=` sign (don't put any spaces before or after the `=`, though!): ``` \[ andrew@pc01 ~ \]$ myvar=你好 ``` Print a specific env var to the terminal with `echo` and a preceding `$` sign: ``` \[ andrew@pc01 ~ \]$ echo $myvar 你好 ``` Environment variables which contain spaces or other whitespace should be surrounded by quotes (`"..."`). Note that reassigning a value to an env var overwrites it without warning: ``` \[ andrew@pc01 ~ \]$ myvar="你好,世界!" && 回顯 $myvar 你好世界! ``` Env vars can also be defined using the `export` command. When defined this way, they will also be available to sub-processes (commands called from this shell): ``` \[ andrew@pc01 ~ \]$ export myvar="另一" && echo $myvar 另一個 ``` You can unset an environment variable by leaving the right-hand side of the `=` blank or by using the `unset` command: ``` \[ andrew@pc01 ~ \]$ 取消設定 mynewvar \[ andrew@pc01 ~ \]$ echo $mynewvar ``` <a name="aliases"></a> ### Aliases [[ Back to Table of Contents ]](#toc) **Aliases** are similar to environment variables but are usually used in a different way -- to replace long commands with shorter ones: ``` \[ andrew@pc01 apidocs \]$ ls -l -a -h -t 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … \[ andrew@pc01 apidocs \]$ 別名 lc="ls -l -a -h -t" \[ andrew@pc01 apidocs \]$ lc 總計 220K drwxr-xr-x 5 安德魯 安德魯 4.0K 12 月 21 日 12:37 。 -rw-r--r-- 1 安德魯 安德魯 9.9K 十二月 21 12:37 help-doc.html -rw-r--r-- 1 安德魯 安德魯 4.5K 12 月 21 日 12:37 script.js … ``` You can remove an alias with `unalias`: ``` \[ andrew@pc01 apidocs \]$ unalias lc \[ andrew@pc01 apidocs \]$ lc 目前未安裝程式“lc”。 … ``` > **Bonus:** > > [Read about the subtle differences between environment variables and aliases here.](http://bit.ly/2TDG8Tx) > > [Some programs, like **git**, allow you to define aliases specifically for that software.](http://bit.ly/2TG8X1A) <a name="basic-bash-scripting"></a> ## Basic `bash` Scripting <a name="bash-scripts"></a> ### `bash` Scripts [[ Back to Table of Contents ]](#toc) `bash` scripts (usually ending in `.sh`) allow you to automate complicated processes, packaging them into reusable functions. A `bash` script can contain any number of normal shell commands: ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file && ls" > ex.sh ``` A shell script can be executed with the `source` command or the `sh` command: ``` \[ andrew@pc01 ~ \]$ 源 ex.sh 桌面 Git TEST c ex.sh 專案測試 桌面 Git TEST c ex.sh 檔案專案測試 ``` Shell scripts can be made executable with the `chmod` command (more on this later): ``` \[ andrew@pc01 ~ \]$ echo "ls && touch file2 && ls" > ex2.sh \[ andrew@pc01 ~ \]$ chmod +x ex2.sh ``` An executable shell script can be run by preceding it with `./`: ``` \[ andrew@pc01 ~ \]$ ./ex2.sh 桌面 Git TEST c ex.sh ex2.sh 檔案專案測試 桌面 Git TEST c ex.sh ex2.sh 檔案 file2 專案測試 ``` Long lines of code can be split by ending a command with `\`: ``` \[ andrew@pc01 ~ \]$ echo "for i in {1..3}; do echo \\ > \\"歡迎\\$i次\\";完成” > ex3.sh ``` Bash scripts can contain loops, functions, and more! ``` \[ andrew@pc01 ~ \]$ 源 ex3.sh 歡迎1次 歡迎2次 歡迎3次 ``` <a name="custom-prompt-ls"></a> ### Custom Prompt and `ls` [[ Back to Table of Contents ]](#toc) Bash scripting can make your life a whole lot easier and more colourful. [Check out this great bash scripting cheat sheet.](https://devhints.io/bash) `$PS1` (Prompt String 1) is the environment variable that defines your main shell prompt ([learn about the other prompts here](http://bit.ly/2SPgsmT)): ``` \[ andrew@pc01 ~ \]$ printf "%q" $PS1 $'\\n\\\[\\E\[1m\\\]\\\[\\E\[30m\\\]\\A'$'\\\[\\E\[37m\\\]|\\\[\\E\[36m\\\]\\u\\\[\\E\[37m \\\]@\\\[\\E\[34m\\\]\\h'$'\\\[\\E\[32m\\\]\\W\\\[\\E\[37m\\\]|'$'\\\[\\E(B\\E\[m\\\] ' ``` You can change your default prompt with the `export` command: ``` \[ andrew@pc01 ~ \]$ export PS1="\\n此處指令> " 此處指令> echo $PS1 \\n此處指令> ``` ...[you can add colours, too!](http://bit.ly/2TMbEit): ``` 此處指令> export PS1="\\e\[1;31m\\n程式碼: \\e\[39m" (這應該是紅色的,但在 Markdown 中可能不會這樣顯示) =============================== 程式碼:回顯$PS1 \\e\[1;31m\\n程式碼: \\e\[39m ``` You can also change the colours shown by `ls` by editing the `$LS_COLORS` environment variable: ``` (同樣,這些顏色可能不會出現在 Markdown 中) =========================== 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 程式碼:匯出 LS\_COLORS='di=31:fi=0:ln=96:or=31:mi=31:ex=92' 程式碼:ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="config-files"></a> ## Config Files <a name="config-bashrc"></a> ### Config Files / `.bashrc` [[ Back to Table of Contents ]](#toc) If you tried the commands in the last section and logged out and back in, you may have noticed that your changes disappeared. _config_ (configuration) files let you maintain settings for your shell or for a particular program every time you log in (or run that program). The main configuration file for a `bash` shell is the `~/.bashrc` file. Aliases, environment variables, and functions added to `~/.bashrc` will be available every time you log in. Commands in `~/.bashrc` will be run every time you log in. If you edit your `~/.bashrc` file, you can reload it without logging out by using the `source` command: ``` \[ andrew@pc01 ~ \]$ nano ~/.bashrc ``` _...add the line `echo “~/.bashrc loaded!”` to the top of the file_... ``` \[ andrew@pc01 ~ \]$ 源 ~/.bashrc ~/.bashrc 已載入! ``` _...log out and log back in..._ ``` 最後登入:2019 年 1 月 11 日星期五 10:29:07 從 111.11.11.111 ~/.bashrc 已加載! \[ 安德魯@pc01 ~ \] ``` <a name="types-of-shells"></a> ### Types of Shells [[ Back to Table of Contents ]](#toc) _Login_ shells are shells you log in to (where you have a username). _Interactive_ shells are shells which accept commands. Shells can be login and interactive, non-login and non-interactive, or any other combination. In addition to `~/.bashrc`, there are a few other scripts which are `sourced` by the shell automatically when you log in or log out. These are: - `/etc/profile` - `~/.bash_profile` - `~/.bash_login` - `~/.profile` - `~/.bash_logout` - `/etc/bash.bash_logout` Which of these scripts are sourced, and the order in which they're sourced, depend on the type of shell opened. See [the bash man page](https://linux.die.net/man/1/bash) and [these](http://bit.ly/2TGCwA8) Stack Overflow [posts](http://bit.ly/2TFHFsf) for more information. Note that `bash` scripts can `source` other scripts. For instance, in your `~/.bashrc`, you could include the line: ``` 來源~/.bashrc\_addl ``` ...which would also `source` that `.bashrc_addl` script. This file can contain its own aliases, functions, environment variables, and so on. It could, in turn, `source` other scripts, as well. (Be careful to avoid infinite loops of script-sourcing!) It may be helpful to split commands into different shell scripts based on functionality or machine type (Ubuntu vs. Red Hat vs. macOS), for example: - `~/.bash_ubuntu` -- configuration specific to Ubuntu-based machines - `~/.bashrc_styles` -- aesthetic settings, like `PS1` and `LS_COLORS` - `~/.bash_java` -- configuration specific to the Java language I try to keep separate `bash` files for aesthetic configurations and OS- or machine-specific code, and then I have one big `bash` file containing shortcuts, etc. that I use on every machine and every OS. Note that there are also _different shells_. `bash` is just one kind of shell (the "Bourne Again Shell"). Other common ones include `zsh`, `csh`, `fish`, and more. Play around with different shells and find one that's right for you, but be aware that this tutorial contains `bash` shell commands only and not everything listed here (maybe none of it) will be applicable to shells other than `bash`. <a name="finding-things"></a> ## Finding Things <a name="whereis-which-whatis"></a> ### `whereis / which / whatis` [[ Back to Table of Contents ]](#toc) `whereis` searches for "possibly useful" files related to a particular command. It will attempt to return the location of the binary (executable machine code), source (code source files), and `man` page for that command: ``` \[ andrew@pc01 ~ \]$ whereis ls ls: /bin/ls /usr/share/man/man1/ls.1.gz ``` `which` will only return the location of the binary (the command itself): ``` \[ andrew@pc01 ~ \]$ 其中 ls /bin/ls ``` `whatis` prints out the one-line description of a command from its `man` page: ``` \[ andrew@pc01 ~ \]$ 什麼是哪裡是哪個什麼是 whereis (1) - 尋找指令的二進位、原始檔和手冊頁文件 which (1) - 定位指令 Whatis (1) - 顯示一行手冊頁描述 ``` `which` is useful for finding the "original version" of a command which may be hidden by an alias: ``` \[ andrew@pc01 ~ \]$ 別名 ls="ls -l" “original” ls 已被上面定義的別名“隱藏” =========================== \[ andrew@pc01 ~ \]$ ls 總計 36 drwxr-xr-x 2 安德魯 andrew 4096 Jan 9 14:47 桌面 drwxr-xr-x 4 安德魯 安德魯 4096 十二月 6 10:43 Git … 但我們仍然可以使用返回的位置來呼叫「原始」ls ======================= \[ andrew@pc01 ~ \]$ /bin/ls 桌面 Git TEST c ex.sh ex2.sh ex3.sh 檔案 file2 專案測試 ``` <a name="locate-find"></a> ### `locate / find` [[ Back to Table of Contents ]](#toc) `locate` finds a file anywhere on the system by referring to a semi-regularly-updated cached list of files: ``` \[ andrew@pc01 ~ \]$ 找到 README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.jenv/README.md /home/andrew/.myshell/README.md … ``` Because it's just searching a list, `locate` is usually faster than the alternative, `find`. `find` iterates through the file system to find the file you're looking for. Because it's actually looking at the files which _currently_ exist on the system, though, it will always return an up-to-date list of files, which is not necessarily true with `locate`. ``` \[ andrew@pc01 ~ \]$ find ~/ -iname "README.md" /home/andrew/.jenv/README.md /home/andrew/.config/micro/plugins/gotham-colors/README.md /home/andrew/.oh-my-zsh/plugins/ant/README.md … ``` `find` was written for the very first version of Unix in 1971, and is therefore much more widely available than `locate`, which was added to GNU in 1994. `find` has many more features than `locate`, and can search by file age, size, ownership, type, timestamp, permissions, depth within the file system; `find` can search using regular expressions, execute commands on files it finds, and more. When you need a fast (but possibly outdated) list of files, or you’re not sure what directory a particular file is in, use `locate`. When you need an accurate file list, maybe based on something other than the files’ names, and you need to do something with those files, use `find`. <a name="downloading-things"></a> ## Downloading Things <a name="ping-wget-curl"></a> ### `ping / wget / curl` [[ Back to Table of Contents ]](#toc) `ping` attempts to open a line of communication with a network host. Mainly, it's used to check whether or not your Internet connection is down: ``` \[ andrew@pc01 ~ \]$ ping google.com PING google.com (74.125.193.100) 56(84) 位元組資料。 使用 32 位元組資料 Ping 74.125.193.100: 來自 74.125.193.100 的回覆:位元組=32 時間<1ms TTL=64 … ``` `wget` is used to easily download a file from the Internet: ``` \[ andrew@pc01 ~ \]$ wget \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso ``` `curl` can be used just like `wget` (don’t forget the `--output` flag): ``` \[ andrew@pc01 ~ \]$ 捲曲 \\ > http://releases.ubuntu.com/18.10/ubuntu-18.10-desktop-amd64.iso \\ > \--輸出ubuntu.iso ``` `curl` and `wget` have their own strengths and weaknesses. `curl` supports many more protocols and is more widely available than `wget`; `curl` can also send data, while `wget` can only receive data. `wget` can download files recursively, while `curl` cannot. In general, I use `wget` when I need to download things from the Internet. I don’t often need to send data using `curl`, but it’s good to be aware of it for the rare occasion that you do. <a name="apt-gunzip-tar-gzip"></a> ### `apt / gunzip / tar / gzip` [[ Back to Table of Contents ]](#toc) Debian-descended Linux distributions have a fantastic package management tool called `apt`. It can be used to install, upgrade, or delete software on your machine. To search `apt` for a particular piece of software, use `apt search`, and install it with `apt install`: ``` \[ andrew@pc01 ~ \]$ apt 搜尋漂白位 ...bleachbit/bionic、bionic 2.0-2 全部 從系統中刪除不需要的文件 您需要“sudo”來安裝軟體 ============== \[ andrew@pc01 ~ \]$ sudo apt installbleachbit ``` Linux software often comes packaged in `.tar.gz` ("tarball") files: ``` \[ andrew@pc01 ~ \]$ wget \\ > https://github.com/atom/atom/releases/download/v1.35.0-beta0/atom-amd64.tar.gz ``` ...these types of files can be unzipped with `gunzip`: ``` \[ andrew@pc01 ~ \]$gunzipatom-amd64.tar.gz && ls 原子 amd64.tar ``` A `.tar.gz` file will be `gunzip`-ped to a `.tar` file, which can be extracted to a directory of files using `tar -xf` (`-x` for "extract", `-f` to specify the file to "untar"): ``` \[ andrew@pc01 ~ \]$ tar -xfatom-amd64.tar && mv \\ 原子-beta-1.35.0-beta0-amd64 原子 && ls 原子atom-amd64.tar ``` To go in the reverse direction, you can create (`-c`) a tar file from a directory and zip it (or unzip it, as appropriate) with `-z`: ``` \[ andrew@pc01 ~ \]$ tar -zcf 壓縮.tar.gz 原子 && ls 原子atom-amd64.tar壓縮.tar.gz ``` `.tar` files can also be zipped with `gzip`: ``` \[ andrew@pc01 ~ \]$ gzipatom-amd64.tar && ls 原子 原子-amd64.tar.gz 壓縮.tar.gz ``` <a name="redirecting-io"></a> ## Redirecting Input and Output <a name="pipe-gt-lt-echo-printf"></a> ### `| / > / < / echo / printf` [[ Back to Table of Contents ]](#toc) By default, shell commands read their input from the standard input stream (aka. stdin or 0) and write to the standard output stream (aka. stdout or 1), unless there’s an error, which is written to the standard error stream (aka. stderr or 2). `echo` writes text to stdout by default, which in most cases will simply print it to the terminal: ``` \[ andrew@pc01 ~ \]$ 回顯“你好” 你好 ``` The pipe operator, `|`, redirects the output of the first command to the input of the second command: ``` 'wc'(字數)傳回檔案中的行數、字數、位元組數 ======================== \[ andrew@pc01 ~ \]$ echo "範例文件" |廁所 ``` 1 2 17 ``` ``` `>` redirects output from stdout to a particular location ``` \[ andrew@pc01 ~ \]$ echo "test" > 文件 && 頭文件 測試 ``` `printf` is an improved `echo`, allowing formatting and escape sequences: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" 1 3 2 ``` `<` gets input from a particular location, rather than stdin: ``` 'sort' 依字母/數字順序對檔案的行進行排序 ======================== \[ andrew@pc01 ~ \]$ sort <(printf "1\\n3\\n2") 1 2 3 ``` Rather than a [UUOC](#viewing-and-editing-files), the recommended way to send the contents of a file to a command is to use `<`. Note that this causes data to "flow" right-to-left on the command line, rather than (the perhaps more natural, for English-speakers) left-to-right: ``` \[ andrew@pc01 ~ \]$ printf "1\\n3\\n2" > 文件 && 排序 < 文件 1 2 3 ``` <a name="std-tee"></a> ### `0 / 1 / 2 / tee` [[ Back to Table of Contents ]](#toc) 0, 1, and 2 are the standard input, output, and error streams, respectively. Input and output streams can be redirected with the `|`, `>`, and `<` operators mentioned previously, but stdin, stdout, and stderr can also be manipulated directly using their numeric identifiers: Write to stdout or stderr with `>&1` or `>&2`: ``` \[ andrew@pc01 ~ \]$ 貓測試 回顯“標準輸出”>&1 回顯“標準錯誤”>&2 ``` By default, stdout and stderr both print output to the terminal: ``` \[ andrew@pc01 ~ \]$ ./測試 標準錯誤 標準輸出 ``` Redirect stdout to `/dev/null` (only print output sent to stderr): ``` \[ andrew@pc01 ~ \]$ ./test 1>/dev/null 標準錯誤 ``` Redirect stderr to `/dev/null` (only print output sent to stdout): ``` \[ andrew@pc01 ~ \]$ ./test 2>/dev/null 標準輸出 ``` Redirect all output to `/dev/null` (print nothing): ``` \[ andrew@pc01 ~ \]$ ./test &>/dev/null ``` Send output to stdout and any number of additional locations with `tee`: ``` \[ andrew@pc01 ~ \]$ ls && echo "測試" | tee 文件1 文件2 文件3 && ls 文件0 測試 文件0 文件1 文件2 文件3 ``` <a name="advanced"></a> # Advanced <a name="superuser"></a> ## Superuser <a name="sudo-su"></a> ### `sudo / su` [[ Back to Table of Contents ]](#toc) You can check what your username is with `whoami`: ``` \[ andrew@pc01 abc \]$ whoami 安德魯 ``` ...and run a command as another user with `sudo -u username` (you will need that user's password): ``` \[ andrew@pc01 abc \]$ sudo -u 測試觸摸 def && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def ``` If `–u` is not provided, the default user is the superuser (usually called "root"), with unlimited permissions: ``` \[ andrew@pc01 abc \]$ sudo touch ghi && ls -l 總計 0 -rw-r--r-- 1 次測試 0 Jan 11 20:05 def -rw-r--r-- 1 root root 0 Jan 11 20:14 ghi ``` Use `su` to become another user temporarily (and `exit` to switch back): ``` \[ andrew@pc01 abc \]$ su 測試 密碼: test@pc01:/home/andrew/abc$ whoami 測試 test@pc01:/home/andrew/abc$ 退出 出口 \[ andrew@pc01 abc \]$ whoami 安德魯 ``` [Learn more about the differences between `sudo` and `su` here.](http://bit.ly/2SKQH77) <a name="click-click"></a> ### `!!` [[ Back to Table of Contents ]](#toc) The superuser (usually "root") is the only person who can install software, create users, and so on. Sometimes it's easy to forget that, and you may get an error: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? ``` You could retype the command and add `sudo` at the front of it (run it as the superuser): ``` \[ andrew@pc01 ~ \]$ sudo apt install ruby 正在閱讀包裝清單... ``` Or, you could use the `!!` shortcut, which retains the previous command: ``` \[ andrew@pc01 ~ \]$ apt 安裝 ruby E:無法開啟鎖定檔案 /var/lib/dpkg/lock-frontend - 開啟(13:權限被拒絕) E: 無法取得 dpkg 前端鎖定 (/var/lib/dpkg/lock-frontend),您是 root 嗎? \[ andrew@pc01 ~ \]$ sudo !! sudo apt 安裝 ruby 正在閱讀包裝清單... ``` By default, running a command with `sudo` (and correctly entering the password) allows the user to run superuser commands for the next 15 minutes. Once those 15 minutes are up, the user will again be prompted to enter the superuser password if they try to run a restricted command. <a name="file-permissions"></a> ## File Permissions <a name="file-permissions-sub"></a> ### File Permissions [[ Back to Table of Contents ]](#toc) Files may be able to be read (`r`), written to (`w`), and/or executed (`x`) by different users or groups of users, or not at all. File permissions can be seen with the `ls -l` command and are represented by 10 characters: ``` \[ andrew@pc01 ~ \]$ ls -lh 總計 8 drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxr-xr-x 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rw-r--r-- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The first character of each line represents the type of file, (`d` = directory, `l` = link, `-` = regular file, and so on); then there are three groups of three characters which represent the permissions held by the user (u) who owns the file, the permissions held by the group (g) which owns the file, and the permissions held any other (o) users. (The number which follows this string of characters is the number of links in the file system to that file (4 or 1 above).) `r` means that person / those people have read permission, `w` is write permission, `x` is execute permission. If a directory is “executable”, that means it can be opened and its contents can be listed. These three permissions are often represented with a single three-digit number, where, if `x` is enabled, the number is incremented by 1, if `w` is enabled, the number is incremented by 2, and if `r` is enabled, the number is incremented by 4. Note that these are equivalent to binary digits (`r-x` -> `101` -> `5`, for example). So the above three files have permissions of 755, 755, and 644, respectively. The next two strings in each list are the name of the owner (`andrew`, in this case) and the group of the owner (also `andrew`, in this case). Then comes the size of the file, its most recent modification time, and its name. The `–h` flag makes the output human readable (i.e. printing `4.0K` instead of `4096` bytes). <a name="chmod-chown"></a> ### `chmod / chown` [[ Back to Table of Contents ]](#toc) File permissions can be modified with `chmod` by setting the access bits: ``` \[ andrew@pc01 ~ \]$ chmod 777 測試 && chmod 000 tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -rwxrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 \---------- 1 安德魯 安德魯 0 一月 11 16:34 tist ``` ...or by adding (`+`) or removing (`-`) `r`, `w`, and `x` permissions with flags: ``` \[ andrew@pc01 ~ \]$ chmod +rwx Tist && chmod -w 測試 && ls -lh chmod:測試:新權限是 r-xrwxrwx,而不是 r-xr-xr-x 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 -r-xrwxrwx 1 安德魯 安德魯 40 Jan 11 16:16 測試 -rwxr-xr-x 1 安德魯 安德魯 0 一月 11 16:34 tist ``` The user who owns a file can be changed with `chown`: ``` \[ andrew@pc01 ~ \]$ sudo chown 碼頭測試 ``` The group which owns a file can be changed with `chgrp`: ``` \[ andrew@pc01 ~ \]$ sudo chgrp hadoop tit && ls -lh 總計 8.0K drwxr-xr-x 4 安德魯 安德魯 4.0K 1 月 4 日 19:37 品嚐 \-----w--w- 1 瑪麗娜·安德魯 2011 年 1 月 40 日 16:16 測試 -rwxr-xr-x 1 安德魯 hadoop 0 一月 11 16:34 tist ``` <a name="users-groups"></a> ## User and Group Management <a name="users"></a> ### Users [[ Back to Table of Contents ]](#toc) `users` shows all users currently logged in. Note that a user can be logged in multiple times if -- for instance -- they're connected via multiple `ssh` sessions. ``` \[ andrew@pc01 ~ \]$ 用戶 安德魯·科林·科林·科林·科林·科林·克里希納·克里希納 ``` To see all users (even those not logged in), check `/etc/passwd`. (**WARNING**: do not modify this file! You can corrupt your user accounts and make it impossible to log in to your system.) ``` \[ andrew@pc01 ~ \]$ alias au="cut -d: -f1 /etc/passwd \\ > |排序| uniq”&& au \_易於 一個廣告 安德魯... ``` Add a user with `useradd`: ``` \[ andrew@pc01 ~ \]$ sudo useradd aardvark && au \_易於 土豚 一個廣告... ``` Delete a user with `userdel`: ``` \[ andrew@pc01 ~ \]$ sudo userdel aardvark && au \_易於 一個廣告 安德魯... ``` [Change a user’s default shell, username, password, or group membership with `usermod`.](http://bit.ly/2D4upIg) <a name="groups"></a> ### Groups [[ Back to Table of Contents ]](#toc) `groups` shows all of the groups of which the current user is a member: ``` \[ andrew@pc01 ~ \]$ 組 andrew adm cdrom sudo dial plugdev lpadmin sambashare hadoop ``` To see all groups on the system, check `/etc/group`. (**DO NOT MODIFY** this file unless you know what you are doing.) ``` \[ andrew@pc01 ~ \]$ alias ag=“cut -d: -f1 /etc/group \\ > |排序”&& ag 管理員 一個廣告 安德魯... ``` Add a group with `groupadd`: ``` \[ andrew@pc01 ~ \]$ sudo groupadd aardvark && ag 土豚 管理員 一個廣告... ``` Delete a group with `groupdel`: ``` \[ andrew@pc01 ~ \]$ sudo groupdel aardvark && ag 管理員 一個廣告 安德魯... ``` [Change a group’s name, ID number, or password with `groupmod`.](https://linux.die.net/man/8/groupmod) <a name="text-processing"></a> ## Text Processing <a name="uniq-sort-diff-cmp"></a> ### `uniq / sort / diff / cmp` [[ Back to Table of Contents ]](#toc) `uniq` can print unique lines (default) or repeated lines: ``` \[ andrew@pc01 man \]$ printf "1\\n2\\n2" > a && \\ > printf "1\\n3\\n2" > b \[ andrew@pc01 人 \]$ uniq a 1 2 ``` `sort` will sort lines alphabetically / numerically: ``` \[ andrew@pc01 man \]$ 排序 b 1 2 3 ``` `diff` will report which lines differ between two files: ``` \[ andrew@pc01 人 \]$ diff ab 2c2 < 2 --- > 3 ``` `cmp` reports which bytes differ between two files: ``` \[andrew@pc01 人\]$ cmp ab ab 不同:字元 3,第 2 行 ``` <a name="cut-sed"></a> ### `cut / sed` [[ Back to Table of Contents ]](#toc) `cut` is usually used to cut a line into sections on some delimiter (good for CSV processing). `-d` specifies the delimiter and `-f` specifies the field index to print (starting with 1 for the first field): ``` \[ andrew@pc01 人 \]$ printf "137.99.234.23" > c \[ andrew@pc01 man \]$ cut -d'.' c-f1 137 ``` `sed` is commonly used to replace a string with another string in a file: ``` \[ andrew@pc01 man \]$ echo "舊" | sed s/舊/新/ 新的 ``` ...but `sed` is an extremely powerful utility, and cannot be properly summarised here. It’s actually Turing-complete, so it can do anything that any other programming language can do. `sed` can find and replace based on regular expressions, selectively print lines of a file which match or contain a certain pattern, edit text files in-place and non-interactively, and much more. A few good tutorials on `sed` include: - [https://www.tutorialspoint.com/sed/](https://www.tutorialspoint.com/sed/) - [http://www.grymoire.com/Unix/Sed.html](http://www.grymoire.com/Unix/Sed.html) - [https://www.computerhope.com/unix/used.htm](https://www.computerhope.com/unix/used.htm) <a name="pattern-matching"></a> ## Pattern Matching <a name="grep"></a> ### `grep` [[ Back to Table of Contents ]](#toc) The name `grep` comes from `g`/`re`/`p` (search `g`lobally for a `r`egular `e`xpression and `p`rint it); it’s used for finding text in files. `grep` is used to find lines of a file which match some pattern: ``` \[ andrew@pc01 ~ \]$ grep -e " *.fi.* " /etc/profile /etc/profile:Bourne shell 的系統範圍 .profile 檔案 (sh(1)) =================================================== ``` # The file bash.bashrc already sets the default PS1. ``` ``` fi ``` ``` fi ``` … ``` ...or contain some word: ``` \[ andrew@pc01 ~ \]$ grep "andrew" /etc/passwd 安德魯:x:1000:1000:安德魯,,,:/home/andrew:/bin/bash ``` `grep` is usually the go-to choice for simply finding matching lines in a file, if you’re planning on allowing some other program to handle those lines (or if you just want to view them). `grep` allows for (`-E`) use of extended regular expressions, (`-F`) matching any one of multiple strings at once, and (`-r`) recursively searching files within a directory. These flags used to be implemented as separate commands (`egrep`, `fgrep`, and `rgrep`, respectively), but those commands are now deprecated. > **Bonus**: [see the origins of the names of a few famous `bash` commands](https://kb.iu.edu/d/abnd) <a name="awk"></a> ### `awk` [[ Back to Table of Contents ]](#toc) `awk` is a pattern-matching language built around reading and manipulating delimited data files, like CSV files. As a rule of thumb, `grep` is good for finding strings and patterns in files, `sed` is good for one-to-one replacement of strings in files, and `awk` is good for extracting strings and patterns from files and analysing them. As an example of what `awk` can do, here’s a file containing two columns of data: ``` \[ andrew@pc01 ~ \]$ printf "A 10\\nB 20\\nC 60" > 文件 ``` Loop over the lines, add the number to sum, increment count, print the average: ``` \[ andrew@pc01 ~ \]$ awk 'BEGIN {sum=0;計數=0; OFS=“”} {sum+=$2; count++} END {print "平均值:", sum/count}' 文件 平均:30 ``` `sed` and `awk` are both Turing-complete languages. There have been multiple books written about each of them. They can be extremely useful with pattern matching and text processing. I really don’t have enough space here to do either of them justice. Go read more about them! > **Bonus**: [learn about some of the differences between `sed`, `grep`, and `awk`](http://bit.ly/2AI3IaN) <a name="ssh"></a> ## Copying Files Over `ssh` <a name="ssh-scp"></a> ### `ssh / scp` [[ Back to Table of Contents ]](#toc) `ssh` is how Unix-based machines connect to each other over a network: ``` \[ andrew@pc01 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 12:30:52,來自 137.xxx.xxx.199 ``` Notice that my prompt has changed as I’m now on a different machine: ``` \[ andrew@pc02 ~ \]$ 退出 登出 與 137.xxx.xxx.89 的連線已關閉。 ``` Create a file on machine 1: ``` \[ andrew@pc01 ~ \]$ echo "你好" > 你好 ``` Copy it to machine 2 using `scp` (secure copy; note that `scp` uses `–P` for a port #, `ssh` uses `–p`) ``` \[ andrew@pc01 ~ \]$ scp –P你好安德魯@137.xxx.xxx.89:~ 你好 100% 0 0.0KB/秒 00:00 ``` `ssh` into machine 2: ``` \[ andrew@pc02 ~ \]$ ssh –p安德魯@137.xxx.xxx.89 上次登入:2019 年 1 月 11 日星期五 22:47:37,來自 137.xxx.xxx.79 ``` The file’s there! ``` \[ andrew@pc02 ~ \]$ ls 你好多xargs \[ andrew@pc02 ~ \]$ 貓你好 你好 ``` <a name="rsync"></a> ### `rsync` [[ Back to Table of Contents ]](#toc) `rsync` is a file-copying tool which minimises the amount of data copied by looking for deltas (changes) between files. Suppose we have two directories: `d`, with one file, and `s`, with two files: ``` \[ andrew@pc01 d \]$ ls && ls ../s f0 f0 f1 ``` Sync the directories (copying only missing data) with `rsync`: ``` \[ andrew@pc01 d \]$ rsync -off ../s/\* . 正在發送增量文件列表... ``` `d` now contains all files that `s` contains: ``` \[ andrew@pc01 d \]$ ls f0 f1 ``` `rsync` can be performed over `ssh` as well: ``` \[ andrew@pc02 r \]$ ls \[ andrew@pc02 r \]$ rsync -avz -e "ssh -p “ [email protected]:~/s/\* 。 接收增量檔案列表 f0 f1 發送 62 位元組 接收 150 位元組 141.33 位元組/秒 總大小為 0 加速率為 0.00 \[ andrew@pc02 r \]$ ls f0 f1 ``` <a name="long-running-processes"></a> ## Long-Running Processes <a name="yes-nohup-ps-kill"></a> ### `yes / nohup / ps / kill` [[ Back to Table of Contents ]](#toc) Sometimes, `ssh` connections can disconnect due to network or hardware problems. Any processes initialized through that connection will be “hung up” and terminate. Running a command with `nohup` insures that the command will not be hung up if the shell is closed or if the network connection fails. Run `yes` (continually outputs "y" until it’s killed) with `nohup`: ``` \[ andrew@pc01 ~ \]$ nohup 是 & \[1\]13173 ``` `ps` shows a list of the current user’s processes (note PID number 13173): ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' 13173 分/10 00:00:12 是 ``` _...log out and log back into this shell..._ The process has disappeared from `ps`! ``` \[ andrew@pc01 ~ \]$ ps | sed -n '/是/p' ``` But it still appears in `top` and `htop` output: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' 13173 安德魯 20 0 4372 704 636 D 25.0 0.0 0:35.99 是 ``` Kill this process with `-9` followed by its process ID (PID) number: ``` \[ andrew@pc01 ~ \]$ 殺死 -9 13173 ``` It no longer appears in `top`, because it’s been killed: ``` \[ andrew@pc01 ~ \]$ 頂部 -bn 1 | sed -n '/是/p' ``` <a name="cron"></a> ### `cron / crontab / >>` [[ Back to Table of Contents ]](#toc) `cron` provides an easy way of automating regular, scheduled tasks. You can edit your `cron` jobs with `crontab –e` (opens a text editor). Append the line: ``` - - - - - 日期 >> ~/datefile.txt ``` This will run the `date` command every minute, appending (with the `>>` operator) the output to a file: ``` \[ andrew@pc02 ~ \]$ head ~/datefile.txt 2019 年 1 月 12 日星期六 14:37:01 GMT 2019 年 1 月 12 日星期六

S.O.L.I.D:提升編碼技能的 5 個黃金法則

在軟體開發領域,這個以其多樣化和強烈持有觀點而聞名的領域,很少有實踐能夠像 SOLID 原則那樣達成共識,作為成為更好的軟體工程師的保證途徑。 Robert C. Martin 在 2000 年代初期正式製定的 5 條黃金法則極大地影響了軟體開發產業,並為更好的程式碼品質和決策過程製定了新標準,至今仍具有相關性。 ![堅實的原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2mdlic5qf7f0ws36owf.jpg) SOLID 原則是專門為支援 OOP(物件導向程式設計)範例而設計的。因此,本文是為希望提高開發技能並編寫更優雅、可維護和可擴展程式碼的 OOP 開發人員而設計的。 這裡使用的語言是 TypeScript,遵循常見的跨語言 OOP 概念。需要基本的 OOP 知識。 --- 1. S = 單一職責原則(SRP) ------------------ 單一職責原則 (SRP) 是五個 SOLID 原則之一,它規定每個類別應該只有一個職責,以保持有意義的關注點分離。 此模式是一種稱為「上帝物件」的常見反模式的解決方案,「上帝物件」只是指承擔太多職責的類別或物件,使其難以理解、測試和維護。 遵循 SRP 規則有助於使程式碼元件可重複使用、鬆散耦合且易於理解。讓我們探討這項原則,展示 SRP 違規情況和解決方案。 ### 全域宣告 ``` enum Color { BLUE = 'blue', GREEN = 'green', RED = 'red' } enum Size { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large' } class Product { private _name: string; private _color: Color; private _size: Size; constructor (name: string, color: Color, size: Size) { this._name = name; this._color = color; this._size = size; } public get name(): string { return this._name; } public get color(): Color { return this._color; } public get size(): Size { return this._size; } } ``` ### 違反 在下面的程式碼中, `ProductManager`類別負責**products 的建立和存儲**,違反了單一職責原則。 ``` class ProductManager { private _products: Product[] = []; createProduct (name: string, color: Color, size: Size): Product { return new Product(name, color, size); } storeProduct (product: Product): void { this._products.push(product); } getProducts (): Product[] { return this._products; } } const productManager: ProductManager = new ProductManager(); const product: Product = productManager.createProduct('Product 1', Color.BLUE, Size.LARGE); productManager.storeProduct(product); const allProducts: Product[] = productManager.getProducts(); ``` ### 解決 將產品建立和儲存的處理分離到兩個不同的類別可以減少`ProductManager`類別的職責數量。這種方法進一步模組化了程式碼並使其更易於維護。 ``` class ProductManager { createProduct (name: string, color: Color, size: Size): Product { return new Product(name, color, size); } } class ProductStorage { private _products: Product[] = []; storeProduct (product: Product): void { this._products.push(product); } getProducts (): Product[] { return this._products; } } ``` #### 用法: ``` const productManager: ProductManager = new ProductManager(); const productStorage: ProductStorage = new ProductStorage(); const product: Product = productManager.createProduct("Product 1", Color.BLUE, Size.LARGE); productStorage.storeProduct(product); const allProducts: Product[] = productStorage.getProducts(); ``` --- 2. O = 開閉原理 (OCP) ----------------- > “軟體實體應該對擴展開放,但對修改關閉” 開閉原則 (OCP) 是*「寫一次,寫得夠好以便可擴展,然後就忘記它」。* 這項原則的重要性取決於這樣一個事實:模組可能會根據新的需求不時發生變化。如果在模組編寫、測試並上傳到生產環境後出現新需求,則修改此模組通常是不好的做法,尤其是當其他模組依賴它時。為了防止這種情況,我們可以使用開閉原則。 ### 全域宣告 ``` enum Color { BLUE = 'blue', GREEN = 'green', RED = 'red' } enum Size { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large' } class Product { private _name: string; private _color: Color; private _size: Size; constructor (name: string, color: Color, size: Size) { this._name = name; this._color = color; this._size = size; } public get name(): string { return this._name; } public get color(): Color { return this._color; } public get size(): Size { return this._size; } } class Inventory { private _products: Product[] = []; public add(product: Product): void { this._products.push(product); } addArray(products: Product[]) { for (const product of products) { this.add(product); } } public get products(): Product[] { return this._products; } } ``` ### 違反 讓我們描述一個實作產品過濾類別的場景。讓我們加入按顏色過濾產品的功能。 ``` class ProductsFilter { byColor(inventory: Inventory, color: Color): Product[] { return inventory.products.filter(p => p.color === color); } } ``` 我們已經測試了此程式碼並將其部署到生產中。 幾天后,客戶請求一項新功能 - 也按大小過濾。然後我們修改該類別以支援新的要求。 **現在違反了開閉原則!** ``` class ProductsFilter { byColor(inventory: Inventory, color: Color): Product[] { return inventory.products.filter(p => p.color === color); } bySize(inventory: Inventory, size: Size): Product[] { return inventory.products.filter(p => p.size === size); } } ``` ### 解決 在不違反 OCP 的情況下實現過濾機制的正確方法應該使用「規範」類別。 ``` abstract class Specification { public abstract isValid(product: Product): boolean; } class ColorSpecification extends Specification { private _color: Color; constructor (color) { super(); this._color = color; } public isValid(product: Product): boolean { return product.color === this._color; } } class SizeSpecification extends Specification { private _size: Size; constructor (size) { super(); this._size = size; } public isValid(product: Product): boolean { return product.size === this._size; } } // A robust mechanism to allow different combinations of specifications class AndSpecification extends Specification { private _specifications: Specification[]; // "...rest" operator, groups the arguments into an array constructor ((...specifications): Specification[]) { super(); this._specifications = specifications; } public isValid (product: Product): boolean { return this._specifications.every(specification => specification.isValid(product)); } } class ProductsFilter { public filter (inventory: Inventory, specification: Specification): Product[] { return inventory.products.filter(product => specification.isValid(product)); } } ``` #### 用法: ``` const p1: Product = new Product('Apple', Color.GREEN, Size.LARGE); const p2: Product = new Product('Pear', Color.GREEN, Size.LARGE); const p3: Product = new Product('Grapes', Color.GREEN, Size.SMALL); const p4: Product = new Product('Blueberries', Color.BLUE, Size.LARGE); const p5: Product = new Product('Watermelon', Color.RED, Size.LARGE); const inventory: Inventory = new Inventory(); inventory.addArray([p1, p2, p3, p4, p5]); const greenColorSpec: ColorSpecification = new ColorSpecification(Color.GREEN); const largeSizeSpec: SizeSpecification = new SizeSpecification(Size.LARGE); const andSpec: AndSpecification = new AndSpecification(greenColorSpec, largeSizeSpec); const productsFilter: ProductsFilter = new ProductsFilter(); const filteredProducts: Product[] = productsFilter.filter(inventory, andSpec); // All large green products ``` 過濾機制現在是完全可擴展的。現有的類別不應該再被修改。 如果有新的過濾要求,我們只需建立一個新規範即可。或者如果需要更改規範組合,可以透過使用`AndSpecification`類別輕鬆完成。 --- 3. L=里氏替換原理(LSP) ---------------- 里氏替換原則(LSP)是軟體元件靈活性和穩健性的重要規則。它由 Barbara Liskov 提出,並成為 SOLID 原則的基本要素。 LSP 規定**超類別的物件應該可以用子類別的物件替換,而不影響程式的正確性。**換句話說,子類別應該擴展超類別的行為而不改變其原始功能。採用這種方法可以提高軟體元件的質量,確保可重複使用性並減少意外的副作用。 ### 違反 下面的範例說明了違反里氏替換原則 (LSP) 的場景。當`Rectangle`物件被`Square`物件取代時,透過檢查程序的行為可以觀察到這種違規的跡象。 #### 聲明: ``` class Rectangle { protected _width: number; protected _height: number; constructor (width: number, height: number) { this._width = width; this._height = height; } get width (): number { return this._width; } get height (): number { return this._height; } set width (width: number) { this._width = width; } set height (height: number) { this._height = height; } getArea (): number { return this._width * this._height; } } // A square is also rectangle class Square extends Rectangle { get width (): number { return this._width; } get height (): number { return this._height; } set height (height: number) { this._height = this._width = height; // Changing both width & height } set width (width: number) { this._width = this._height = width; // Changing both width & height } } function increaseRectangleWidth(rectangle: Rectangle, byAmount: number) { rectangle.width += byAmount; } ``` #### 用法: ``` const rectangle: Rectangle = new Rectangle(5, 5); const square: Square = new Square(5, 5); console.log(rectangle.getArea()); // Expected: 25, Got: 25 (V) console.log(square.getArea()); // Expected: 25, Got: 25 (V) // LSP Violation Indication: Can't replace object 'rectangle' (superclass) with 'square' (subclass) since the results would be different. increaseRectangleWidth(rectangle, 5); increaseRectangleWidth(square, 5); console.log(rectangle.getArea()); // Expected: 50, Got: 50 (V) // LSP Violation, increaseRectangleWidth() changed both width and height of the square, unexpected behavior. console.log(square.getArea()); //Expected: 50, Got: 100 (X) ``` ### 解決 重構的程式碼現在遵循 LSP,確保超類別`Shape`的物件可以替換為子類別`Rectangle`和`Square`的物件,而不會影響計算面積的正確性,也不會引入任何改變程式行為的不必要的副作用。 #### 聲明: ``` abstract class Shape { public abstract getArea(): number; } class Rectangle extends Shape { private _width: number; private _height: number; constructor (width: number, height: number) { super(); this._width = width; this._height = height; } getArea (): number { return this._width * this._height; } } class Square extends Shape { private _side: number; constructor (side: number) { super(); this._side = side; } getArea (): number { return this._side * this._side; } } function displayArea (shape: Shape): void { console.log(shape.getArea()); } ``` #### 用法: ``` const rectangle: Rectangle = new Rectangle(5, 10); const square: Square = new Square(5); // The rectangle's area is correctly calculated displayArea(rectangle); // Expected: 50, Got: 50 (V) // The square's area is correctly calculated displayArea(square); // Expected: 25, Got: 25 (V) ``` --- 4. I = 介面隔離原則 (ISP) ------------------- 介面隔離原則 (ISP) 強調建立特定於客戶端的介面而不是一刀切的重要性。 這種方法根據客戶的需求集中類,消除了類別必須實現它實際上不使用或不需要的方法的情況。 透過應用介面隔離原則,軟體系統可以以更靈活、易於理解和易於重構的方式建構。讓我們來看一個例子。 ### 違反 這裡違反了 ISP 規則,因為`Robot`必須實現完全沒有必要的`eat()`函數。 ``` interface Worker { work(): void; eat(): void; } class Developer implements Worker { public work(): void { console.log('Coding..'); } public eat(): void { console.log('Eating..'); } } class Robot implements Worker { public work(): void { console.log('Building a car..'); } // ISP Violation: Robot is forced to implement this function even when unnecessary public eat(): void { throw new Error('Cannot eat!'); } } ``` ### 解決 下面的範例代表了我們之前遇到的問題的解決方案。現在,介面更加簡潔且更加特定於客戶端,允許客戶端類別僅實現與其相關的方法。 ``` interface Workable { work(): void; } interface Eatable { eat(): void; } class Developer implements Workable, Eatable { public work(): void { console.log('Coding..'); } public eat(): void { console.log('Eating...'); } } class Robot implements Workable { public work(): void { console.log('Building a car..'); } // No need to implement eat(), adhering ISP. } ``` #### ISP 前後: ![重構前後的介面隔離原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szh5wkpkc30z4t91j5oy.png) --- 5. D = 依賴倒置原理(DIP) ------------------ 依賴倒置原則(DIP)是最終的SOLID原則,重點是透過使用抽象來減少低層模組(例如資料讀取/寫入)和高層模組(執行關鍵操作)之間的耦合。 DIP 對於設計能夠適應變化、模組化且易於更新的軟體至關重要。 **DIP 的關鍵準則是:** 1. **高層模組不應該依賴低層模組。兩者都應該依賴抽象。**這意味著應用程式的功能不應該依賴特定的實現,以便使系統更加靈活並且更容易更新或替換低階實現。 2. **抽像不應該依賴細節。細節應該取決於抽象。**這鼓勵設計專注於實際需要什麼操作,而不是如何實現這些操作。 ### 違反 讓我們來看一個展示依賴倒置原則 (DIP) 違規的範例。 `MessageProcessor` (高階模組)緊密耦合並直接依賴`FileLogger` (低階模組),違反了原則,因為它不依賴抽象層,而是依賴特定的類別實作。 **額外獎勵:**這也違反了開閉原則(OCP)。如果我們想要更改日誌記錄機制以寫入資料庫而不是文件,我們將被迫直接修改`MessageProcessor`函數。 ``` import fs from 'fs'; // Low Level Module class FileLogger { logMessage(message: string): void { fs.writeFileSync('somefile.txt', message); } } // High Level Module class MessageProcessor { // DIP Violation: This high-level module is is tightly coupled with the low-level module (FileLogger), making the system less flexible and harder to maintain or extend. private logger = new FileLogger(); processMessage(message: string): void { this.logger.logMessage(message); } } ``` ### 解決 以下重構的程式碼表示為了遵守依賴倒置原則 (DIP) 所需進行的變更。與前面的範例相反,高階類別`MessageProcessor`持有特定低階類別`FileLogger`的私有屬性,現在它持有`Logger`類型的私有屬性(表示抽象層的介面)。 這種更好的方法減少了類別之間的依賴關係,從而使程式碼更具可擴展性和可維護性。 #### 聲明: ``` import fs from 'fs'; // Abstraction Layer interface Logger { logMessage(message: string): void; } // Low Level Module #1 class FileLogger implements Logger { logMessage(message: string): void { fs.writeFileSync('somefile.txt', message); } } // Low Level Module #2 class ConsoleLogger implements Logger { logMessage(message: string): void { console.log(message); } } // High Level Module class MessageProcessor { // Resolved: The high level module is now loosely coupled with the low level logger modules. private _logger: Logger; constructor (logger: Logger) { this._logger = logger; } processMessage (message: string): void { this._logger.logMessage(message); } } ``` #### 用法: ``` const fileLogger = new FileLogger(); const consoleLogger = new ConsoleLogger(); // Now the logging mechanism can be easily replaced const messageProcessor = new MessageProcessor(consoleLogger); messageProcessor.processMessage('Hello'); ``` #### DIP 之前和之後: ![重構前後的依賴倒置原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kju8qfpie60b104vuz90.png) 結論 -- 透過遵循 SOLID 原則,開發人員在開發或維護任何規模的軟體系統時,可以避免常見的陷阱,例如緊密耦合、缺乏靈活性、程式碼可重複使用性差以及一般維護困難。掌握這些原則是成為更好的軟體工程師的又一步。 --- 原文出處:https://dev.to/idanref/solid-the-5-golden-rules-to-level-up-your-coding-skills-2p82

使用 VS Code 在 JavaScript 專案中設定 ESLINT

*ESLINT* :你有沒有想過ESLINT 是什麼,當我第一次聽說ESLINT 時,我很好奇它到底是怎麼回事,從那時起我就一直在我的專案中使用它,儘管一開始我錯誤地使用了它,那就是為什麼我發布這篇文章是為了讓人們能夠正確理解。但在深入探討之前,讓我先快速解釋一下什麼是 ESLINT 和 VS Code。 **ESLINT**是用於 Javascript 和 JSX 的可插入 linting 實用程序,它有助於發現可能的錯誤。 **VS Code**是頂級的開發編輯器之一,它由 Microsoft 開發和維護,它有助於提高生產力,並且還具有許多功能,我要強調的功能之一是擴充。擴充功能是 VS Code 中的外部套件,可讓您擴展編輯器的功能 你可以從他們的官方網站下載 VS Code [VS Code Download](https://code.visualstudio.com/) **注意:***我不會深入研究 VS Code。本文中有關 VS Code 的所有內容都只與 ESLINT 相關*。 **腳步**: - 建立一個 JavaScript 專案 - 在 VS Code 編輯器中安裝 eslint 作為擴展 - 使用 npm 將 eslint 安裝為全域包 - 在您的 javascript 專案中初始化 eslint - 修改專案中的 eslint 設定檔。 讓我們使用`npm init --yes`建立一個簡單的 javascript 專案 ![alt text](https://thepracticaldev.s3.amazonaws.com/i/tebfsgkfr3k3h4bl7zvr.PNG "簡單的專案") 操作成功後,它將建立一個*package.json*文件,該文件將管理我們專案的所有配置。 讓我們嘗試在 VS Code 編輯器上安裝 ESLINT 擴充 ![alt text](https://thepracticaldev.s3.amazonaws.com/i/9rmkgbk7nio6ravjm0rx.PNG "ESLINT安裝過程") 一旦我們在 VS Code 編輯器上安裝了 eslint 擴展,然後使用下面的程式碼透過 npm 將 eslint 安裝為全域包 ``` npm install eslint -g ``` 您需要在專案中初始化 eslint,以便可以利用 eslint 的強大功能。從您的根專案輸入以下程式碼來初始化 eslint ``` eslint --init ``` 在初始化期間 eslint 會問你一些問題,更像是設定你的設定檔。 - **您想如何使用 ESLint?** ``` * __To check syntax only__ => it helps you correct your syntax and make sure it conform to standard. ``` ``` * __To check syntax and find problems__ => to help you check for syntax correctness and also help to find any problems in your code base ``` ``` * __To check syntax, find problems, and enforce code style___ => to help you check for syntax, find problem and enforce style, enforcing style means to conforms to a particular coding standard such as Airbnb, Google and other Standard coding style. But I always go for the last option the one with syntax, find problems and enforce code style ``` - **您的專案使用什麼類型的模組?** ``` * __Javascript module (import/export)__ => if your project has babel installed then you definitely need to choose this option. If you are working on a project such as React, Vue, Angular e.t.c they all use babel so you need choose this option. ``` ``` * __CommonJS (require/exports)__ => this option is meant for commonJS that has nothing to do with babel, maybe your nodejs project and any other javascript project ``` - **您的專案使用哪個框架?** ``` * __React__ => if you are using react in/for your project then this option is for you ``` ``` * __Vue__ => if you are using Vue in/for your project then this option is for you ``` ``` * __None of these__ => if you are using neither React or Vue in your project choose this option ``` - **你的程式碼在哪裡執行?** ``` * __Browser__ => if your project runs on browser e.g React, Angular, Vue e.t.c then go for this option ``` ``` * __Node__ => if your project is a node based then gladly choose this option ``` - **您希望如何為您的專案定義風格?** ``` * __Use a popular style guide__ => This allows you to choose from set of popular style such as Airbnb,Standard and Google style guide, it is advisable to choose this option in order for you to follow popular and most used style guide and i will be choosen this option in this post. ``` ``` * Answer questions about your style: _This is for custom style guide_ ``` ``` * Inspect your JavaScript file(s).: _custom style guide_ ``` - **您希望設定檔採用什麼格式?** ``` * __Javascript__ => whether you want your eslint config file to be in *.js* file ``` ``` * __YAML__ => whether you want your eslint config file to be in *.yaml* file ``` ``` * __JSON__ => whether you want your eslint config file to be in *.json* file ``` 您可以選擇此部分中的任何選項 選擇首選設定檔類型後,它將提示您安裝所有必要的依賴項。成功安裝所有必要的依賴項後,它將產生一個帶有“.eslintrc”.“js/json/yaml”的設定檔。 **如下所示的設定檔範例** ![alt text](https://thepracticaldev.s3.amazonaws.com/i/sqyim5m8qoet5lx4bu8o.PNG "eslint設定檔鏡像") 下面是一個小動畫圖像,顯示 VS Code 如何與 eslint 配合使用來通知您 javascript 專案中的錯誤 ![alt text](https://cdn-images-1.medium.com/max/800/1*udUEME0YgHCXqD4pjMxpUA.gif "eslint設定檔鏡像") **在專案中設定 ESLINT 規則** 在專案中定義 ESLINT 規則會告知 eslint 您要新增或刪除的規則類型。您可以在設定檔的規則部分修改/設定規則 要設定的規則範例是 ``` "rules" : { no-console: 0; no-empty: 0; no-irregular-whitespace:0; } ``` 您可以定義盡可能多的規則,您可以在其官方文件[ESLINT Rules Documentation](https://eslint.org/docs/rules/)上閱讀有關 ESLINT 規則的更多訊息 最後,我將向您展示如何將 eslint 連結到 javascript 專案編譯器/轉譯器 以下步驟 - 前往`package.json`文件,在文件的腳本段中加入以下內容 ``` script:{ "lint":"eslint" } ``` **注意:** *“lint”只是一個普通單詞,您可以使用任何您喜歡的單詞* 然後在你的根專案中你可以執行你的 linting 腳本 ``` npm run lint ``` > ESLINT 有助於提高工作效率,根據標準編寫程式碼,並在您的程式碼庫違反樣式指南規則時標記錯誤。透過這篇文章,您應該能夠將 ESLINT 整合到您的 Javascript 專案中。 --- 原文出處:https://dev.to/devdammak/setting-up-eslint-in-your-javascript-project-with-vs-code-2amf

如何編寫有用的 commit 訊息(我的提交訊息範本)

我們都曾在某個時刻經歷過有關 git commit 訊息的混亂。 ![XKCD - 隨著專案的拖延,我的 git 提交訊息的資訊量越來越少](https://thepracticaldev.s3.amazonaws.com/i/xn60rbbj7353tb5gtkh1.png) 我也不例外。我的部落格的提交訊息如下所示: ``` fixxxx stuff post post post post posts mmm posts front maddy Add chris oliver add syntax article add git patch article fix video video arty art art Fix links oops ``` 因為我部落格的 git 歷史記錄只有我自己看過,所以沒關係。我已經接受了我永遠無法在我的部落格中充分利用 git 的事實,而且我完全同意這一點。 不幸的是,有些人對待有多個貢獻者的真實專案就像我對待我的部落格一樣。我發現這種做法是無知而不是懶惰的結果。因此,我將分享一些關於如何在實際專案中使用提交訊息的技巧。 ### 為什麼要關心? 😤[我不在乎,跳到模板!](#the-final-template) 🚀 Git 是一個強大的工具,即使您只使用它來保存程式碼更改歷史記錄並且不利用其最強大的功能。 然而,你會發現你挖掘得越深,git 就會變得越強大。您還會發現 git 的許多最有用的功能都是在提交訊息有用的假設下運作的。 想想你上次使用`git blame`是什麼時候。如果您發現一條提交訊息,上面`fixed a bad bug` ,這會有幫助嗎?可能不是,您可能試圖找到有關您正在處理的程式碼的更多上下文;你需要解釋什麼以及為什麼。 Git 提交訊息必須包含每次更改背後的內容和原因,以便明天勇敢的 git 探索者能夠進入提交作者的頭腦。如果提交訊息不包含該訊息,為什麼還要寫一個?提交訊息只有在對將來某個時候試圖理解更改的人有用時才有用。 為了建立一個良好的提交訊息的模板,我將把提交訊息分成幾個部分。 ### 首先,主題行 在提交訊息中,第一行(有時稱為主題行)應與正文隔離。理想情況下,這一行總結了提交中所做的更改。 當我寫主題行時,我嘗試完成這句話,“這次提交將......” 例如,我可能會編寫一個主題行,內容類似於`Remove unused, commented code` 。這可以很好地結束我的句子:“此提交將刪除未使用的帶註釋的程式碼。” 在設定主題行格式時,需要記住一兩個規則。 首先,主題行中的第一個字元應大寫;這只是一個常見的約定。根據我的經驗,它還使閱讀一長串的單行提交清單變得更加容易。 其次,您的提交訊息不應超過五十個字元。這是因為 GitHub 等工具會將該行截斷為 50 個字元。因此,為了讓其他人能夠有效地瀏覽和理解你的主題行,你應該嘗試用五十個字符來總結整個變化。 我的提交訊息範本的第一行如下所示: `Summarize the change in less than 50 characters` ### 接下來,第一個正文“段落” 在某些提交中,主題行足以傳達整個想法。例如,如果您的提交將`Add a comma to the README` ,您可能不需要自己解釋。 然而,在大多數提交中,您的更改可能會受益於一些額外的上下文。我們不希望未來的開發人員在嘗試理解更改背後的原因時錯過上下文。 這就是訊息正文發揮作用的地方。我將正文分為“段落”,這些“段落”只是鬆散定義的由空格分隔的文字字串。它們可以是要點、句子或其他東西;重要的是它們從冷開始就易於閱讀和理解。 過去,我通常使用提交訊息正文的第一段來解釋我所做的事情。這些天,我已經不再關注*「什麼」* ,而是開始記錄*「為什麼」* 。 [Ben Orenstein 最近改變了我對提交訊息格式的看法](https://twitter.com/r00k/status/1175100703829909505?s=20): {% 推特 1175100703829909505 %} 因此,在這種情況下,我們想要引導我們做出改變*的原因*。 這是一個例子: ``` Refactor the coupon UI Because: - The old UI code is fairly slow - There were a few unused dependencies - The old UI has aged poorly ``` 這些「段落」的偉大之處在於只有一個格式規則:72 個字元換行。這更多的是一種遺留傳統,而不是任何實質的東西。然而,主要原因是這允許 git 縮進一些空間(假設最大字元限制為 80)。我建議遵循這條規則,儘管它並不總是嚴格必要的。 這是到目前為止的提交訊息範本: ``` Summarize the change in less than 50 characters Because: - Explain the reasons you made this change - Make a new bullet for each reason - Each line should be under 72 characters ``` ### 現在是第二正文“段落” 既然我們已經總結了更改並分享了進行更改的原因,那麼以較長的形式準確地解釋我們所做的事情可能是謹慎的做法。 我用第二個「段落」來更詳細地解釋我在更改中所做的事情,例如: ``` Refactor the coupon UI Because: - The old UI code is fairly slow - There were a few unused dependencies - The old UI has aged poorly I thought it was necessary to remove some of the old coupon UI code. Unfortunately, it has aged pretty poorly, and I think this refactor makes the code much easier to support in the long-run. Primarily, this commit improves the performance of the coupon component. It also removes some unused dependencies. ``` 提交正文的這一部分應該比 50 個字元的摘要更深入地解釋所做的事情。格式由您決定(只要您以 72 個字元換行即可)。 這是更新後的模板: ``` Summarize the change in less than 50 characters Because: - Explain the reasons you made this change - Make a new bullet for each reason - Each line should be under 72 characters Explain exactly what was done in this commit with more depth than the 50 character subject line. Remember to wrap at 72 characters! ``` ### 其他部分:附加註釋和合著者 此時,我們正在編寫有效且連貫的提交訊息。然而,有時提交訊息需要一些額外的註釋。這可以在最後一節中完成。 例如: ``` Refactor the coupon UI Because: - The old UI code is fairly slow - There were a few unused dependencies - The old UI has aged poorly I thought it was necessary to remove some of the old coupon UI code. Unfortunately, it has aged pretty poorly, and I think this refactor makes the code much easier to support in the long-run. Primarily, this commit improves the performance of the coupon component. It also removes some unused dependencies. These changes should resolve issue #1337. This commit removed the left-pad dependency, so please stop using it! Co-authored-by: nspinazz89 <[email protected]> ``` 在這個例子中我能夠: - 參考相關問題 - 加入一行以警告我刪除了依賴項 - 包含與我一起參與該提交的人員的引用 此時,任何查看此提交訊息的人都會知道: 1. 做了什麼一目了然 2. 為什麼需要改變 3. 有關已完成操作的詳細訊息 4. 有關變更的任何有用的詳細訊息 這使得我們的提交訊息對我們未來的自己和任何其他需要理解我們程式碼的開發人員來說更加有用。 即使您不同意我編寫提交訊息的方法,也很難否認我們必須編寫提交訊息,以便其他開發人員在閱讀我們的程式碼時能夠進入我們的視野。 我認為大多數人都同意“好”程式碼的標誌是可維護性,您可以透過編寫有助於其他人理解甚至將來更改您的程式碼的提交訊息來增強程式碼的可維護性。 ### 最終模板 ``` Summarize the change in less than 50 characters Because: - Explain the reasons you made this change - Make a new bullet for each reason - Each line should be under 72 characters Explain exactly what was done in this commit with more depth than the 50 character subject line. Remember to wrap at 72 characters! Include any additional notes, relevant links, or co-authors. ``` ### 還有更多... 這些天我寫了[很多文章](https://jh.codes),我經營一個[播客](https://www.devpath.fm),並且我已經開始發送一份關於我聽到的所有精彩故事的[時事通訊摘要](https://pages.convertkit.com/674caf55d4/f30f7753a7)。 您還可以在[Twitter](https://twitter.com/jakeherrington)上關注我,我在那裡製作一些愚蠢的表情包並談論如何成為開發人員。 --- 原文出處:https://dev.to/jacobherrington/how-to-write-useful-commit-messages-my-commit-message-template-20n9

😻建立您自己的 CLI 版本的 MonkeyType 🙈

長話短說 ---- 在這個易於理解的教程中,您將學習如何在幾分鐘內建立自己的 MonkeyType CLI 版本。 😎 **您將學到什麼:✨** - 使用 Pythoncurses 模組建立具有**WPM**和**Accuracy**支援的強大打字 CLI 應用程式。 您準備好成為 CLI MonkeyTyper 了嗎? 😉 無論這是您的第一個 CLI 應用程式還是第 n 個應用程式。請隨意跟隨。 ![猴子在筆記型電腦上打字](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unzjcx4ues1ot7m4h4hu.gif) --- 設定環境🙈 ----- > ℹ️不需要設定虛擬環境,我們不會使用任何外部相依性。 建立一個資料夾來保存專案的所有原始程式碼: ``` mkdir cli-monkeytype-python cd cli-monkeytype-python ``` 建立兩個新文件,我們將在其中編寫程式: ``` touch main.py typing_test.py ``` `main.py`檔案將作為我們應用程式的起點,而`typing_test.py`檔案將保存程式的所有邏輯。 > ℹ️對於Linux或Mac用戶,您不需要下載任何依賴項,我們將主要使用**curses** 、 **time**和**random**模組,這些模組都包含在Python標準庫中。 ⚠️**注意** > Windows 使用者可能必須安裝curses,因為它不包含在Windows 的Python 標準庫中。在繼續下一步之前,請確保已安裝它。 --- 讓我們來寫程式吧🐵 --------- > 💡 我們將在本節中研究應用程式的方法、大綱和實際編碼部分。 😵‍💫 ### 方法和概要👀 我們將在這裡採取不同的方法,而不是將所有程式碼都塞在`main`文件中。我們將把程式碼分成不同文件中的類別。 將有一個單獨的文件,其中包含一個負責封裝與打字測試相關的所有邏輯的類別。在主文件中,我們將呼叫此類的方法。聽起來,對吧?讓我們開始吧。 🚀 這是我們類別的骨架以及我們將要處理的所有方法。 ``` class TypingTest: def __init__(self, stdscr): pass def get_line_to_type(self): pass def display_wpm(self): pass def display_accuracy(self): pass def display_typed_chars(self): pass def display_details(self): pass def test_accuracy(self): pass def test_wpm(self): pass ``` 所有函數名稱都應該是不言自明的。如果您需要協助理解每個函數的作用,即使在查看了此大綱之後,為什麼還要閱讀這篇文章?只是開玩笑\*不是真的\*。 😏 > 🥱 這是一個適合初學者的應用程式。別擔心,一起編碼吧。 ### 真正的樂趣開始了! ![表演時間 GIF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i0r36i2t9mrygd14wtop.gif) 我們將從導入模組並編寫`__init__`方法開始。這將初始化程式執行所需的所有術語。 ``` import curses import random import time class TypingTest: def __init__(self, stdscr): self.stdscr = stdscr self.to_type_text = self.get_line_to_type() self.user_typed_text = [] self.wpm = 0 self.start_time = time.time() # Initialize color pairs curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) # --SNIP-- ``` `stdscr`用於控制終端螢幕,對於建立使用者可以看到其擊鍵的基於文字的使用者介面至關重要。 ⌨️ `get_line_to_type`方法取得一行文字供使用者鍵入。該文字儲存在`self.to_type_text`變數中。當他們鍵入時,他們輸入的字元將保存在`self.user_typed_text`清單中。我們使用列表是因為當使用者更正錯誤輸入的字元時,彈出最後一項會更容易。 初始每分鐘字數 (WPM) 分數設定為 0,我們記錄測驗的開始時間。我們也初始化了一些**顏色對**,根據它們是否正確來指示字元上的顏色。稍後,我們將根據使用者打字所需的時間來計算**WPM** 。 現在,新增以下功能的程式碼 > ℹ️ 確保在專案根目錄中建立一個名為`typing_texts.txt`的新文件,其中包含幾行文字。參考:[點這裡](https://github.com/shricodev/blogs/blob/main/cli-monkeytype-python/typing_texts.txt)。 ``` # --SNIP-- def get_line_to_type(self): with open("typing_texts.txt", "r", encoding="utf-8") as file: lines = file.readlines() return random.choice(lines).strip() def display_wpm(self): self.stdscr.addstr(1, 0, f"WPM: {self.wpm}", curses.color_pair(3)) def display_accuracy(self): self.stdscr.addstr( 2, 0, f"Accuracy: {self.test_accuracy()}%", curses.color_pair(3), ) def display_typed_chars(self): for i, char in enumerate(self.user_typed_text): correct_character = self.to_type_text[i] # Use color pair 1 if correct, else color pair 2. color = 1 if char == correct_character else 2 self.stdscr.addstr(0, i, char, curses.color_pair(color)) def display_details(self): self.stdscr.addstr(self.to_type_text) self.display_wpm() self.display_accuracy() self.display_typed_chars() # --SNIP-- ``` 讓我總結一下這些方法,它們非常簡單: 🎯 `get_line_to_type(self)` :從名為「typing\_texts.txt」的檔案中擷取刪除了尾隨空格的隨機行。 🎯 `display_wpm(self)` :當使用者鍵入時在螢幕上的第一行顯示 WPM。 🎯 `display_accuracy(self)` :在螢幕上第 2 行顯示**準確率百分比。**準確率由我們即將編寫的`test_accuracy()`方法計算。 🎯 `display_typed_chars(self)` :顯示使用者在螢幕上輸入的字符,突出顯示一個顏色對(顏色 1)中的正確字符和另一個顏色對(顏色 2)中的錯誤字符。 🎯 `display_details(self)` :它本質上是一個輔助函數,幫助顯示上面所有顯示函數的內容。 好的,現在我們已經編寫了這些顯示方法,讓我們實現實際的邏輯來測試準確性和 WPM 本身。 新增以下程式碼行: ``` # --SNIP-- def test_accuracy(self): total_characters = min(len(self.user_typed_text), len(self.to_type_text)) # If there are no typed chars, show accuracy 0. if total_characters == 0: return 0.0 matching_characters = 0 for current_char, target_char in zip(self.user_typed_text, self.to_type_text): if current_char == target_char: matching_characters += 1 matching_percentage = (matching_characters / total_characters) * 100 return matching_percentage def test_wpm(self): # getkey method by default is blocking. # We do not want to wait until the user types each char to check WPM. # Else the entire logic will be faulty. self.stdscr.nodelay(True) while True: # Since we have nodelay = True, if not using max(), # users might end up with time.time() equal to start_time, # resulting in 0 and potentially causing a zero-divisible error in the below line. time_elapsed = max(time.time() - self.start_time, 1) # Considering the average word length in English is 5 characters self.wpm = round((len(self.user_typed_text) / (time_elapsed / 60)) / 5) self.stdscr.clear() self.display_details() self.stdscr.refresh() # Exit the loop when the user types in the total length of the text. if len(self.user_typed_text) == len(self.to_type_text): self.stdscr.nodelay(False) break # We have `nodelay = True`, so we don't want to wait for the keystroke. # If we do not get a key, it will throw an exception # in the below lines when accessing the key. try: key = self.stdscr.getkey() except Exception: continue # Check if the key is a single character before using ord() if isinstance(key, str) and len(key) == 1: if ord(key) == 27: # ASCII value for ESC break # If the user has not typed anything reset to the current time if not self.user_typed_text: self.start_time = time.time() if key in ("KEY_BACKSPACE", "\b", "\x7f"): if len(self.user_typed_text) > 0: self.user_typed_text.pop() elif len(self.user_typed_text) < len(self.to_type_text): self.user_typed_text.append(key) ``` 🎯 `test_accuracy(self)` :透過將使用者輸入的字元與目標文字進行比較,計算並返回打字準確度(以百分比形式)。如果字元匹配,則將匹配字元的計數加1。最後,計算匹配的百分比。 🎯 `test_wpm(self)` :計算每分鐘字數(WPM)並即時更新顯示。我們用一個**公式**來計算WPM,這不是我想出來的,我從網路複製的。它追蹤使用者輸入的內容,處理退格鍵,並在使用者完成輸入目標文字或按**ESC**時停止。 偉大的!這就是我們的**TypingTest**類別。 🎉 > ✅ 我們編寫程式碼的方式可以幫助我們輕鬆地將程式碼匯入到任何未來的專案中,並使維護變得更加容易。 是時候測試我們的實作了。 🙈 在`main.py`檔案中,加入以下程式碼行: ``` from curses import wrapper from typing_test import TypingTest def main(stdscr): stdscr.clear() stdscr.addstr("Welcome to the typing speed test") stdscr.addstr("\nPress any key to continue!") while True: typing_test = TypingTest(stdscr) stdscr.getkey() typing_test.test_wpm() stdscr.addstr( 3, 0, "Congratulations! You have completed the test! Press any key to continue...", ) stdscr.nodelay(False) key = stdscr.getkey() # Check if the key is a single character before using ord() if isinstance(key, str) and len(key) == 1: if ord(key) == 27: # ASCII value for ESC break if __name__ == "__main__": wrapper(main) ``` > 💡 注意:我們從curses `wrapper`方法內的main 函數,該函數處理curses 模組的初始化和清理。 在 main 中,我們建立**TypingTest**類別的實例並在無限循環中執行測試,這讓使用者可以繼續執行測試,直到他們決定按**ESC**退出。 讓我們看看它的實際效果。 🔥 ![打字測試演示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0eregzw35f8yq66blja.gif) > 🫵 如果你已經做到了這一步,我想指派一個小任務給你。目前,我們正在從文件中隨機選擇文字進行輸入。我希望您從網路上**抓取**輸入文字並使用該內容。請隨意在我的儲存庫中開啟包含您的變更的拉取請求。 > 如果您需要幫助,我已經參與了一個類似的 Python 抓取專案。請隨意[檢查](https://github.com/shricodev/IPO-Monitor-NEPSE)一下。 --- **包起來!** 🐒 ---------- 到目前為止,您已經建立了一個 Python CLI 應用程式來測試您在終端機中的打字速度。 本文的記錄原始碼可在此處取得: https://github.com/shricodev/blogs/tree/main/cli-monkeytype-python 非常感謝您的閱讀! 🎉🫡 > 在下面的評論部分寫下你的想法。 👇 {% cta https://twitter.com/shricodev %} 在 Twitter 上追蹤我 🐥 {% endcta %} {% 嵌入 https://dev.to/shricodev %} --- 原文出處:https://dev.to/shricodev/build-your-own-cli-version-of-monkeytype-bm7

70 個 JavaScript 面試問題

嗨大家好,新年快樂:煙火::煙火::煙火:! ---------------------- 這是一篇很長的文章,所以請耐心聽我一秒鐘或一個小時。每個問題的每個答案都有一個向上箭頭**↑**連結,可讓您返回到問題列表,這樣您就不會浪費時間上下滾動。 ### 問題 - [1. `undefined`和`null`有什麼差別?](#1-whats-the-difference-between-undefined-and-null) - [2. &amp;&amp; 運算子的作用是什麼?](#2-what-does-the-ampamp-operator-do) - [3. || 是什麼意思?運營商做什麼?](#3-what-does-the-operator-do) - [4. 使用 + 或一元加運算子是將字串轉換為數字的最快方法嗎?](#4-is-using-the-or-unary-plus-operator-the-fastest-way-in-converting-a-string-to-a-number) - [5.什麼是DOM?](#5-what-is-the-dom) - [6.什麼是事件傳播?](#6-what-is-event-propagation) - [7.什麼是事件冒泡?](#7-whats-event-bubbling) - [8. 什麼是事件擷取?](#8-whats-event-capturing) - [9. `event.preventDefault()`和`event.stopPropagation()`方法有什麼差別?](#9-whats-the-difference-between-eventpreventdefault-and-eventstoppropagation-methods) - [10. 如何知道元素中是否使用了`event.preventDefault()`方法?](#10-how-to-know-if-the-eventpreventdefault-method-was-used-in-an-element) - [11. 為什麼這段程式碼 obj.someprop.x 會拋出錯誤?](#11-why-does-this-code-objsomepropx-throw-an-error) - \[12.什麼是`event.target` ?\](#12-什麼是 eventtarget- ) - [13.什麼是`event.currentTarget` ?](#13-what-is-eventcurrenttarget) - [14. `==`和`===`有什麼差別?](#14-whats-the-difference-between-and-) - [15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳 false?](#15-why-does-it-return-false-when-comparing-two-similar-objects-in-javascript) - [16. `!!`是什麼意思?運營商做什麼?](#16-what-does-the-operator-do) - [17. 如何計算一行中的多個表達式?](#17-how-to-evaluate-multiple-expressions-in-one-line) - [18.什麼是吊裝?](#18-what-is-hoisting) - [19.什麼是範圍?](#19-what-is-scope) - [20.什麼是閉包?](#20-what-are-closures) - [21. JavaScript 中的假值是什麼?](#21-what-are-the-falsy-values-in-javascript) - [22. 如何檢查一個值是否為假值?](#22-how-to-check-if-a-value-is-falsy) - [23. `"use strict"`有什麼作用?](#23-what-does-use-strict-do) - [24. JavaScript 中`this`的值是什麼?](#24-whats-the-value-of-this-in-javascript) - [25. 物件的`prototype`是什麼?](#25-what-is-the-prototype-of-an-object) - \[26.什麼是 IIFE,它有什麼用?\](#26-what-is-an-iife-what-is-the-use-of-it ) - [27. `Function.prototype.apply`方法有什麼用?](#27-what-is-the-use-functionprototypeapply-method) - [28. `Function.prototype.call`方法有什麼用?](#28-what-is-the-use-functionprototypecall-method) - [29. `Function.prototype.apply`和`Function.prototype.call`有什麼差別?](#29-whats-the-difference-between-functionprototypeapply-and-functionprototypecall) - [30. `Function.prototype.bind`的用法是什麼?](#30-what-is-the-usage-of-functionprototypebind) - \[31.什麼是函數式程式設計以及 JavaScript 的哪些特性使其成為函數式語言的候選者?\](#31-什麼是函數式程式設計和 javascript 的特性是什麼-使其成為函數式語言的候選者 ) - [32.什麼是高階函數?](#32-what-are-higher-order-functions) - [33.為什麼函數被稱為First-class Objects?](#33-why-are-functions-called-firstclass-objects) - \[34.手動實作`Array.prototype.map`方法。\](#34-手動實作 arrayprototypemap-method ) - [35. 手動實作`Array.prototype.filter`方法。](#35-implement-the-arrayprototypefilter-method-by-hand) - [36. 手動實作`Array.prototype.reduce`方法。](#36-implement-the-arrayprototypereduce-method-by-hand) - [37.什麼是`arguments`物件?](#37-what-is-the-arguments-object) - [38. 如何創造沒有**原型的**物件?](#38-how-to-create-an-object-without-a-prototype) - [39. 為什麼當你呼叫這個函數時,這段程式碼中的`b`會變成全域變數?](#39-why-does-b-in-this-code-become-a-global-variable-when-you-call-this-function) - [40.什麼是**ECMAScript** ?](#40-what-is-ecmascript) - [41. **ES6**或**ECMAScript 2015**有哪些新功能?](#41-what-are-the-new-features-in-es6-or-ecmascript-2015) - [42. `var` 、 `let`和`const`關鍵字有什麼差別?](#42-whats-the-difference-between-var-let-and-const-keywords) - [43. 什麼是**箭頭函數**?](#43-what-are-arrow-functions) - [44.什麼是**類別**?](#44-what-are-classes) - [45.什麼是**模板文字**?](#45-what-are-template-literals) - [46.什麼是**物件解構**?](#46-what-is-object-destructuring) - [47.什麼是`ES6 Modules` ?](#47-what-are-es6-modules) - [48.什麼是`Set`物件以及它如何運作?](#48-what-is-the-set-object-and-how-does-it-work) - [49. 什麼是回呼函數?](#49-what-is-a-callback-function) - [50. 什麼是**Promise** ?](#50-what-are-promises) - [51. 什麼是*async/await*以及它是如何運作的?](#51-what-is-asyncawait-and-how-does-it-work) - [52. **Spread 運算子**和**Rest 運算**子有什麼差別?](#52-whats-the-difference-between-spread-operator-and-rest-operator) - [53. 什麼是**預設參數**?](#53-what-are-default-parameters) - [54.什麼是**包裝物件**?](#54-what-are-wrapper-objects) - [55.**隱性強制**和**顯性**強制有什麼差別?](#55-what-is-the-difference-between-implicit-and-explicit-coercion) - [56. 什麼是`NaN` ?以及如何檢查值是否為`NaN` ?](#56-what-is-nan-and-how-to-check-if-a-value-is-nan) - [57. 如何檢查一個值是否為一個**陣列**?](#57-how-to-check-if-a-value-is-an-array) - [58. 如何在不使用`%`或模運算子的情況下檢查數字是否為偶數?](#58-how-to-check-if-a-number-is-even-without-using-the-or-modulo-operator) - [59. 如何檢查物件中是否存在某個屬性?](#59-how-to-check-if-a-certain-property-exists-in-an-object) - [60.什麼是**AJAX** ?](#60-what-is-ajax) - [61. JavaScript 中建立物件的方式有哪些?](#61-what-are-the-ways-of-making-objects-in-javascript) - [62. `Object.seal`和`Object.freeze`方法有什麼不同?](#62-whats-the-difference-between-objectseal-and-objectfreeze-methods) - [63. `in`運算子和物件中的`hasOwnProperty`方法有什麼差別?](#63-whats-the-difference-between-the-in-operator-and-the-hasownproperty-method-in-objects) - [64. JavaScript中處理**非同步程式碼的**方法有哪些?](#64-what-are-the-ways-to-deal-with-asynchronous-code-in-javasscript) - [65.**函數表達式**和**函數宣告**有什麼不同?](#65-whats-the-difference-between-a-function-expression-and-function-declaration) - \[66.一個函數有多少種*呼叫*方式?\]( 66-函數可以有多少種方式被呼叫) ================= - [67. 什麼是*記憶*,它有什麼用?](#67-what-is-memoization-and-whats-the-use-it) - [68. 實現記憶輔助功能。](#68-implement-a-memoization-helper-function) - [69. 為什麼`typeof null`回傳`object` ?如何檢查一個值是否為`null` ?](#69-why-does-typeof-null-return-object-how-to-check-if-a-value-is-null) - [`new`關鍵字有什麼作用?](#70-what-does-the-new-keyword-do) ### 1. `undefined`和`null`有什麼差別? [^](#the-questions "返回問題")在了解`undefined`和`null`之間的差異之前,我們必須先了解它們之間的相似之處。 - 它們屬於**JavaScript 的**7 種基本型別。 ``` let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint']; ``` - 它們是**虛假的**價值觀。使用`Boolean(value)`或`!!value`將其轉換為布林值時計算結果為 false 的值。 ``` console.log(!!null); //logs false console.log(!!undefined); //logs false console.log(Boolean(null)); //logs false console.log(Boolean(undefined)); //logs false ``` 好吧,我們來談談差異。 - `undefined`是尚未指派特定值的變數的預設值。或一個沒有**明確**回傳值的函數。 `console.log(1)` 。或物件中不存在的屬性。 JavaScript 引擎為我們完成了**指派**`undefined`值的任務。 ``` let _thisIsUndefined; const doNothing = () => {}; const someObj = { a : "ay", b : "bee", c : "si" }; console.log(_thisIsUndefined); //logs undefined console.log(doNothing()); //logs undefined console.log(someObj["d"]); //logs undefined ``` - `null`是**「代表無值的值」** 。 `null`是已**明確**定義給變數的值。在此範例中,當`fs.readFile`方法未引發錯誤時,我們得到`null`值。 ``` fs.readFile('path/to/file', (e,data) => { console.log(e); //it logs null when no error occurred if(e){ console.log(e); } console.log(data); }); ``` 當比較`null`和`undefined`時,使用`==`時我們得到`true` ,使用`===`時得到`false` 。您可以[在此處](#14-whats-the-difference-between-and-)閱讀原因。 ``` console.log(null == undefined); // logs true console.log(null === undefined); // logs false ``` ### 2. `&&`運算子的作用是什麼? [^](#the-questions "返回問題") `&&`或**邏輯 AND**運算子在其運算元中尋找第一個*假*表達式並傳回它,如果沒有找到任何*假*表達式,則傳回最後一個表達式。它採用短路來防止不必要的工作。在我的一個專案中關閉資料庫連線時,我在`catch`區塊中使用了它。 ``` console.log(false && 1 && []); //logs false console.log(" " && true && 5); //logs 5 ``` 使用**if**語句。 ``` const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { if (conMobile) { conMobile.release(); } } }); ``` 使用**&amp;&amp;**運算子。 ``` const router: Router = Router(); router.get('/endpoint', (req: Request, res: Response) => { let conMobile: PoolConnection; try { //do some db operations } catch (e) { conMobile && conMobile.release() } }); ``` ### 3. `||`是什麼意思?運營商做什麼? [↑](#the-questions "返回問題") `||` or**邏輯 OR**運算子尋找其運算元中的第一個*真值*表達式並傳回它。這也採用短路來防止不必要的工作。在**ES6預設函數參數**被支援之前,它被用來初始化函數中的預設參數值。 ``` console.log(null || 1 || undefined); //logs 1 function logName(name) { var n = name || "Mark"; console.log(n); } logName(); //logs "Mark" ``` ### 4. 使用**+**或一元加運算子是將字串轉換為數字的最快方法嗎? [^](#the-questions "返回問題")根據[MDN 文件,](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus) `+`是將字串轉換為數字的最快方法,因為如果該值已經是數字,它不會對該值執行任何操作。 ### 5.什麼是**DOM** ? [^](#the-questions "返回問題") **DOM**代表**文件物件模型,**是 HTML 和 XML 文件的介面 ( **API** )。當瀏覽器第一次讀取(*解析*)我們的 HTML 文件時,它會建立一個大物件,一個基於 HTML 文件的非常大的物件,這就是**DOM** 。它是根據 HTML 文件建模的樹狀結構。 **DOM**用於互動和修改**DOM 結構**或特定元素或節點。 想像一下,如果我們有這樣的 HTML 結構。 ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document Object Model</title> </head> <body> <div> <p> <span></span> </p> <label></label> <input> </div> </body> </html> ``` 等效的**DOM**應該是這樣的。 ![DOM 等效項](https://thepracticaldev.s3.amazonaws.com/i/mbqphfbjfie45ynj0teo.png) **JavaScript**中的`document`物件代表**DOM** 。它為我們提供了許多方法,我們可以用來選擇元素來更新元素內容等等。 ### 6.什麼是**事件傳播**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**冒泡階段**,**事件**向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達`window` ,而在**捕獲階段**,事件從`window`開始向下到達觸發的元素事件或`<a href="#12-what-is-eventtarget-">event.target</a>` 。 **事件傳播**分為**三個**階段。 1. [捕獲階段](#8-whats-event-capturing)-事件從`window`開始,然後向下到達每個元素,直到到達目標元素。 2. [目標階段](#12-what-is-eventtarget-)– 事件已到達目標元素。 3. [冒泡階段](#7-whats-event-bubbling)-事件從目標元素冒起,然後向上移動到每個元素,直到到達`window` 。 ![事件傳播](https://thepracticaldev.s3.amazonaws.com/i/hjayqa99iejfhbsujlqd.png) ### 7.什麼是**事件冒泡**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**冒泡階段**,**事件**向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達`window` 。 如果我們有一個像這樣的範例標記。 ``` <div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div> ``` 還有我們的js程式碼。 ``` function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }); addEvent(parent, 'click', function (e) { console.log('parent'); }); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }); addEvent(document, 'click', function (e) { console.log('document'); }); addEvent('html', 'click', function (e) { console.log('html'); }) addEvent(window, 'click', function (e) { console.log('window'); }) }); ``` `addEventListener`方法有第三個可選參數**useCapture ,**預設值為`false`事件將在**冒泡階段**發生,如果為`true` ,事件將在**捕獲階段**發生。如果我們點擊`child`元素,它會分別在**控制台**上記錄`child` 、 `parent`元素、 `grandparent` 、 `html` 、 `document`和`window` 。這就是**事件冒泡**。 ### 8. 什麼是**事件擷取**? [↑](#the-questions "返回問題")當某個**事件**發生在**DOM**元素上時,該**事件**並非完全發生在該元素上。在**捕獲階段**,事件從`window`開始一直到觸發事件的元素。 如果我們有一個像這樣的範例標記。 ``` <div class="grandparent"> <div class="parent"> <div class="child">1</div> </div> </div> ``` 還有我們的js程式碼。 ``` function addEvent(el, event, callback, isCapture = false) { if (!el || !event || !callback || typeof callback !== 'function') return; if (typeof el === 'string') { el = document.querySelector(el); }; el.addEventListener(event, callback, isCapture); } addEvent(document, 'DOMContentLoaded', () => { const child = document.querySelector('.child'); const parent = document.querySelector('.parent'); const grandparent = document.querySelector('.grandparent'); addEvent(child, 'click', function (e) { console.log('child'); }, true); addEvent(parent, 'click', function (e) { console.log('parent'); }, true); addEvent(grandparent, 'click', function (e) { console.log('grandparent'); }, true); addEvent(document, 'click', function (e) { console.log('document'); }, true); addEvent('html', 'click', function (e) { console.log('html'); }, true) addEvent(window, 'click', function (e) { console.log('window'); }, true) }); ``` `addEventListener`方法有第三個可選參數**useCapture ,**預設值為`false`事件將在**冒泡階段**發生,如果為`true` ,事件將在**捕獲階段**發生。如果我們點擊`child`元素,它會分別在**控制台**上記錄`window` 、 `document` 、 `html` 、 `grandparent` 、 `parent`和`child` 。這就是**事件捕獲**。 ### 9. `event.preventDefault()`和`event.stopPropagation()`方法有什麼差別? [↑](#the-questions "返回問題") `event.preventDefault()`方法**阻止**元素的預設行為。如果在`form`元素中使用,它**會阻止**其提交。如果在`anchor`元素中使用,它**會阻止**其導航。如果在`contextmenu`中使用,它**會阻止**其顯示或顯示。而`event.stopPropagation()`方法會停止事件的傳播或停止事件在[冒泡](#7-whats-event-bubbling)或[捕獲](#8-whats-event-capturing)階段發生。 ### 10. 如何知道元素中是否使用了`event.preventDefault()`方法? [↑](#the-questions "返回問題")我們可以使用事件物件中的`event.defaultPrevented`屬性。它傳回一個`boolean` ,指示是否在特定元素中呼叫了`event.preventDefault()` 。 ### 11. 為什麼這段程式碼`obj.someprop.x`會拋出錯誤? ``` const obj = {}; console.log(obj.someprop.x); ``` [^](#the-questions "返回問題")顯然,由於我們嘗試存取 a 的原因,這會引發錯誤 `someprop`屬性中的`x`屬性具有`undefined`值。請記住,物件中的**屬性**本身並不存在,且其**原型**具有預設值`undefined`且`undefined`沒有屬性`x` 。 ### 12.什麼是**event.target** ? [↑](#the-questions "返回問題")最簡單來說, **event.target**是**發生**事件的元素或**觸發**事件的元素。 HTML 標記範例。 ``` <div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div> ``` JavaScript 範例。 ``` function clickFunc(event) { console.log(event.target); } ``` 如果您單擊按鈕,它會記錄**按鈕**標記,即使我們將事件附加在最外部的`div`上,它也會始終記錄**按鈕**,因此我們可以得出結論, **event.target**是觸發事件的元素。 ### 13.什麼是**event.currentTarget** ? [↑](#the-questions "返回問題") **event.currentTarget**是我們**明確**附加事件處理程序的元素。 複製**問題 12**中的標記。 HTML 標記範例。 ``` <div onclick="clickFunc(event)" style="text-align: center;margin:15px; border:1px solid red;border-radius:3px;"> <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;"> <div style="margin:25px;border:1px solid skyblue;border-radius:3px;"> <button style="margin:10px"> Button </button> </div> </div> </div> ``` 並且稍微改變我們的**JS** 。 ``` function clickFunc(event) { console.log(event.currentTarget); } ``` 如果您按一下該按鈕,即使我們按一下該按鈕,它也會記錄最外層的**div**標記。在此範例中,我們可以得出結論, **event.currentTarget**是我們附加事件處理程序的元素。 ### 14. `==`和`===`有什麼差別? [^](#the-questions "返回問題") `==` \_\_(抽象相等)\_\_ 和`===` \_\_(嚴格相等)\_\_ 之間的區別在於`==`在*強制轉換*後按**值**進行比較,而`===`在不進行*強制轉換的*情況下按**值**和**類型**進行比較。 讓我們更深入地研究`==` 。那麼首先我們來談談*強制*。 *強制轉換*是將一個值轉換為另一種類型的過程。在本例中, `==`進行*隱式強制轉換*。在比較兩個值之前, `==`需要執行一些條件。 假設我們必須比較`x == y`值。 1. 如果`x`和`y`具有相同的類型。 然後將它們與`===`運算子進行比較。 2. 如果`x`為`null`且`y` `undefined` ,則傳回`true` 。 3. 如果`x` `undefined`且`y`為`null`則傳回`true` 。 4. 如果`x`是`number`類型, `y`是`string`類型 然後回傳`x == toNumber(y)` 。 5. 如果`x`是`string`類型, `y`是`number`類型 然後返回`toNumber(x) == y` 。 6. 如果`x`是`boolean`類型 然後返回`toNumber(x) == y` 。 7. 如果`y`是`boolean`類型 然後回傳`x == toNumber(y)` 。 8. 如果`x`是`string` 、 `symbol`或`number`且`y`是 type `object` 然後回傳`x == toPrimitive(y)` 。 9. 如果`x`是`object`且`x`是`string` 、 `symbol` 然後返回`toPrimitive(x) == y` 。 10. 返回`false` 。 **注意:** `toPrimitive`首先使用物件中的`valueOf`方法,然後使用`toString`方法來取得該物件的原始值。 讓我們舉個例子。 | `x` | `y` | `x == y` | | ------------- |:-------------:| ----------------: | | `5` | `5` | `true` | | `1` | `'1'` | `true` | | `null` | `undefined` | `true` | | `0` | `false` | `true` | | `'1,2'` | `[1,2]` | `true` | | `'[object Object]'` | `{}` | `true` | 這些範例都傳回`true` 。 **第一個範例**屬於**條件一**,因為`x`和`y`具有相同的類型和值。 **第二個範例**轉到**條件四,**在比較之前將`y`轉換為`number` 。 **第三個例子**涉及**條件二**。 **第四個範例**轉到**條件七,**因為`y`是`boolean` 。 **第五個範例**適用於**條件八**。使用`toString()`方法將陣列轉換為`string` ,該方法傳回`1,2` 。 **最後一個例子**適用於**條件十**。使用傳回`[object Object]`的`toString()`方法將該物件轉換為`string` 。 | `x` | `y` | `x === y` | | ------------- |:-------------:| ----------------: | | `5` | `5` | `true` | | `1` | `'1'` | `false` | | `null` | `undefined` | `false` | | `0` | `false` | `false` | | `'1,2'` | `[1,2]` | `false` | | `'[object Object]'` | `{}` | `false` | 如果我們使用`===`運算符,則除第一個範例之外的所有比較都將傳回`false` ,因為它們不具有相同的類型,而第一個範例將傳回`true` ,因為兩者俱有相同的類型和值。 ### 15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳**false** ? [^](#the-questions "返回問題")假設我們有下面的例子。 ``` let a = { a: 1 }; let b = { a: 1 }; let c = a; console.log(a === b); // logs false even though they have the same property console.log(a === c); // logs true hmm ``` **JavaScript**以不同的方式比較*物件*和*基元*。在*基元*中,它透過**值**來比較它們,而在*物件*中,它透過**引用**或**儲存變數的記憶體位址**來比較它們。這就是為什麼第一個`console.log`語句回傳`false`而第二個`console.log`語句回傳`true`的原因。 `a`和`c`有相同的引用,而`a`和`b`則不同。 ### 16. **!!**是什麼意思?運營商做什麼? [↑](#the-questions "返回問題")**雙非**運算子或**!!**將右側的值強制轉換為布林值。基本上,這是一種將值轉換為布林值的奇特方法。 ``` console.log(!!null); //logs false console.log(!!undefined); //logs false console.log(!!''); //logs false console.log(!!0); //logs false console.log(!!NaN); //logs false console.log(!!' '); //logs true console.log(!!{}); //logs true console.log(!![]); //logs true console.log(!!1); //logs true console.log(!![].length); //logs false ``` ### 17. 如何計算一行中的多個表達式? [↑](#the-questions "返回問題")我們可以使用`,`或逗號運算子來計算一行中的多個表達式。它從左到右計算並傳回右側最後一項或最後一個操作數的值。 ``` let x = 5; x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10); function addFive(num) { return num + 5; } ``` 如果記錄`x`的值,它將是**27** 。首先,我們**增加**x 的值,它將是**6** ,然後我們呼叫函數`addFive(6)`並將 6 作為參數傳遞,並將結果分配給`x` , `x`的新值將是**11** 。之後,我們將`x`的當前值乘以**2**並將其分配給`x` , `x`的更新值將是**22** 。然後,我們將`x`的當前值減去 5 並將結果指派給`x` ,更新後的值將是**17** 。最後,我們將`x`的值增加 10 並將更新後的值指派給`x` ,現在`x`的值將是**27** 。 ### 18.什麼是**吊裝**? [^](#the-questions "返回問題")**提升**是一個術語,用於描述將*變數*和*函數*移動到其*(全域或函數)*作用域的頂部(即我們定義該變數或函數的位置)。 要理解**提升**,我必須解釋*執行上下文*。 **執行上下文**是目前正在執行的「程式碼環境」。**執行上下文**有兩個階段*:編譯*和*執行*。 **編譯**- 在此階段,它獲取所有*函數聲明*並將它們*提升*到作用域的頂部,以便我們稍後可以引用它們並獲取所有*變數聲明***(使用 var 關鍵字聲明)** ,並將它們*提升*並給它們一個默認值*未定義*的 . **執行**- 在此階段,它將值指派給先前*提升的*變數,並*執行*或*呼叫*函數**(物件中的方法)** 。 **注意:**只有使用*var*關鍵字宣告的**函數宣告**和變數才會*被提升*,而不是**函數表達式**或**箭頭函數**、 `let`和`const`關鍵字。 好吧,假設我們在下面的*全域範圍*內有一個範例程式碼。 ``` console.log(y); y = 1; console.log(y); console.log(greet("Mark")); function greet(name){ return 'Hello ' + name + '!'; } var y; ``` 此程式碼記錄`undefined` , `1` , `Hello Mark!`分別。 所以*編譯*階段看起來像這樣。 ``` function greet(name) { return 'Hello ' + name + '!'; } var y; //implicit "undefined" assignment //waiting for "compilation" phase to finish //then start "execution" phase /* console.log(y); y = 1; console.log(y); console.log(greet("Mark")); */ ``` 出於範例目的,我對變數和*函數呼叫*的*賦值*進行了評論。 *編譯*階段完成後,它開始*執行*階段,呼叫方法並向變數賦值。 ``` function greet(name) { return 'Hello ' + name + '!'; } var y; //start "execution" phase console.log(y); y = 1; console.log(y); console.log(greet("Mark")); ``` ### 19.什麼是**範圍**? [↑](#the-questions "返回問題") JavaScript 中的**作用域**是我們可以有效存取變數或函數的**區域**。 JavaScript 有三種類型的作用域。**全域作用域**、**函數作用域**和**區塊作用域(ES6)** 。 - **全域作用域**- 在全域命名空間中宣告的變數或函數位於全域作用域中,因此可以在程式碼中的任何位置存取。 ``` //global namespace var g = "global"; function globalFunc(){ function innerFunc(){ console.log(g); // can access "g" because "g" is a global variable } innerFunc(); } ``` - **函數作用域**- 函數內聲明的變數、函數和參數可以在該函數內部存取,但不能在函數外部存取。 ``` function myFavoriteFunc(a) { if (true) { var b = "Hello " + a; } return b; } myFavoriteFunc("World"); console.log(a); // Throws a ReferenceError "a" is not defined console.log(b); // does not continue here ``` - **區塊作用域**- 在區塊`{}`內宣告的變數**( `let` 、 `const` )**只能在區塊內存取。 ``` function testBlock(){ if(true){ let z = 5; } return z; } testBlock(); // Throws a ReferenceError "z" is not defined ``` **範圍**也是一組查找變數的規則。如果一個變數在**當前作用域中**不存在,它會在**外部作用域中查找**並蒐索該變數,如果不存在,它會再次**查找,**直到到達**全域作用域。**如果該變數存在,那麼我們可以使用它,如果不存在,我們可以使用它來拋出錯誤。它搜尋**最近的**變數,一旦找到它就停止**搜尋**或**尋找**。這稱為**作用域鏈**。 ``` /* Scope Chain Inside inner function perspective inner's scope -> outer's scope -> global's scope */ //Global Scope var variable1 = "Comrades"; var variable2 = "Sayonara"; function outer(){ //outer's scope var variable1 = "World"; function inner(){ //inner's scope var variable2 = "Hello"; console.log(variable2 + " " + variable1); } inner(); } outer(); // logs Hello World // because (variable2 = "Hello") and (variable1 = "World") are the nearest // variables inside inner's scope. ``` ![範圍](https://thepracticaldev.s3.amazonaws.com/i/l81b3nmdonimex0qsgyr.png) ### 20.什麼是**閉包**? [^](#the-questions "返回問題")這可能是所有這些問題中最難的問題,因為**閉包**是一個有爭議的話題。那我就從我的理解來解釋。 **閉包**只是函數在宣告時記住其當前作用域、其父函數作用域、其父函數的父函數作用域上的變數和參數的引用的能力,直到在**作用域鏈**的幫助下到達全域作用域。基本上它是聲明函數時建立的**作用域**。 例子是解釋閉包的好方法。 ``` //Global's Scope var globalVar = "abc"; function a(){ //testClosures's Scope console.log(globalVar); } a(); //logs "abc" /* Scope Chain Inside a function perspective a's scope -> global's scope */ ``` 在此範例中,當我們宣告`a`函數時**,全域**作用域是`a's`*閉包*的一部分。 ![a的閉包](https://thepracticaldev.s3.amazonaws.com/i/teatokuw4xvgtlzbzhn8.png) 變數`globalVar`在影像中沒有值的原因是該變數的值可以根據我們呼叫`a`**位置**和**時間**而改變。 但在上面的範例中, `globalVar`變數的值為**abc** 。 好吧,讓我們來看一個複雜的例子。 ``` var globalVar = "global"; var outerVar = "outer" function outerFunc(outerParam) { function innerFunc(innerParam) { console.log(globalVar, outerParam, innerParam); } return innerFunc; } const x = outerFunc(outerVar); outerVar = "outer-2"; globalVar = "guess" x("inner"); ``` ![複雜的](https://thepracticaldev.s3.amazonaws.com/i/e4hxm7zvz8eun2ppenwp.png) 這將列印“猜測外部內部”。對此的解釋是,當我們呼叫`outerFunc`函數並將`innerFunc`函數的回傳值指派給變數`x`時,即使我們將新值**outer-2**指派給`outerVar`變數, `outerParam`也會具有**outer**值,因為 重新分配發生在呼叫`outer`函數之後,當我們呼叫`outerFunc`函數時,它會在**作用域鏈**中尋找`outerVar`的值,而`outerVar`的值為**「outer」** 。現在,當我們呼叫引用了`innerFunc`的`x`變數時, `innerParam`的值為**inner,**因為這是我們在呼叫中傳遞的值,而`globalVar`變數的值為**猜測**,因為在呼叫`x`變數之前,我們為`globalVar`分配了一個新值,並且在呼叫`x`時**作用域鏈**中`globalVar`的值是**猜測**。 我們有一個例子來示範沒有正確理解閉包的問題。 ``` const arrFuncs = []; for(var i = 0; i < 5; i++){ arrFuncs.push(function (){ return i; }); } console.log(i); // i is 5 for (let i = 0; i < arrFuncs.length; i++) { console.log(arrFuncs[i]()); // all logs "5" } ``` 由於**Closures**的原因,此程式碼無法按我們的預期工作。 `var`關鍵字建立一個全域變數,當我們推送一個函數時 我們返回全域變數`i` 。因此,當我們在循環之後呼叫該陣列中的其中一個函數時,它會記錄`5` ,因為我們得到 `i`的目前值為`5` ,我們可以存取它,因為它是全域變數。因為**閉包**保留該變數的**引用,**而不是其建立時的**值**。我們可以使用**IIFES**或將`var`關鍵字變更為`let`來解決此問題,以實現區塊作用域。 ### 21. **JavaScript**中的**假**值是什麼? [↑](#the-questions "返回問題") ``` const falsyValues = ['', 0, null, undefined, NaN, false]; ``` **假**值是轉換為布林值時變成**false 的**值。 ### 22. 如何檢查一個值是否為**假值**? [↑](#the-questions "返回問題")使用**布林**函數或雙非運算符**[!!](#16-what-does-the-operator-do)** ### 23. `"use strict"`有什麼作用? [^](#the-questions "返回問題") `"use strict"`是**JavaScript**中的 ES5 功能,它使我們的程式碼在*函數*或*整個腳本*中處於**嚴格模式**。**嚴格模式**幫助我們避免程式碼早期出現**錯誤**並為其加入限制。 **嚴格模式**給我們的限制。 - 分配或存取未宣告的變數。 ``` function returnY(){ "use strict"; y = 123; return y; } ``` - 為唯讀或不可寫的全域變數賦值; ``` "use strict"; var NaN = NaN; var undefined = undefined; var Infinity = "and beyond"; ``` - 刪除不可刪除的屬性。 ``` "use strict"; const obj = {}; Object.defineProperty(obj, 'x', { value : '1' }); delete obj.x; ``` - 參數名稱重複。 ``` "use strict"; function someFunc(a, b, b, c){ } ``` - 使用**eval**函數建立變數。 ``` "use strict"; eval("var x = 1;"); console.log(x); //Throws a Reference Error x is not defined ``` - **該**值的預設值是`undefined` 。 ``` "use strict"; function showMeThis(){ return this; } showMeThis(); //returns undefined ``` **嚴格模式**的限制遠不止這些。 ### 24. JavaScript 中`this`的值是什麼? [↑](#the-questions "返回問題")基本上, `this`是指目前正在執行或呼叫函數的物件的值。我說**目前**是因為**它**的值會根據我們使用它的上下文和使用它的位置而改變。 ``` const carDetails = { name: "Ford Mustang", yearBought: 2005, getName(){ return this.name; }, isRegistered: true }; console.log(carDetails.getName()); // logs Ford Mustang ``` 這是我們通常所期望的,因為在**getName**方法中我們傳回`this.name` ,在此上下文中`this`指的是`carDetails`物件,該物件目前是正在執行的函數的「擁有者」物件。 好吧,讓我們加入一些程式碼讓它變得奇怪。在`console.log`語句下面加入這三行程式碼 ``` var name = "Ford Ranger"; var getCarName = carDetails.getName; console.log(getCarName()); // logs Ford Ranger ``` 第二個`console.log`語句印製了**「Ford Ranger」**一詞,這很奇怪,因為在我們的第一個`console.log`語句中它印了**「Ford Mustang」** 。原因是`getCarName`方法有一個不同的「擁有者」物件,即`window`物件。在全域作用域中使用`var`關鍵字聲明變數會在`window`物件中附加與變數同名的屬性。請記住,當未使用`"use strict"`時,全域範圍內的`this`指的是`window`物件。 ``` console.log(getCarName === window.getCarName); //logs true console.log(getCarName === this.getCarName); // logs true ``` 本例中的`this`和`window`指的是同一個物件。 解決此問題的一種方法是使用函數中的`<a href="#27-what-is-the-use-functionprototypeapply-method">apply</a>`和`<a href="#28-what-is-the-use-functionprototypecall-method">call</a>`方法。 ``` console.log(getCarName.apply(carDetails)); //logs Ford Mustang console.log(getCarName.call(carDetails)); //logs Ford Mustang ``` `apply`和`call`方法期望第一個參數是一個物件,該物件將是該函數內`this`的值。 **IIFE** (即**立即呼叫函數表達式)** 、在全域作用域中宣告的函數、物件內部方法中的**匿名函數**和內部函數都有一個指向**window**物件的預設**值**。 ``` (function (){ console.log(this); })(); //logs the "window" object function iHateThis(){ console.log(this); } iHateThis(); //logs the "window" object const myFavoriteObj = { guessThis(){ function getThis(){ console.log(this); } getThis(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; myFavoriteObj.guessThis(); //logs the "window" object myFavoriteObj.thisIsAnnoying(function (){ console.log(this); //logs the "window" object }); ``` 如果我們想要取得`myFavoriteObj`物件中的`name`屬性**(Marko Polo)**的值,有兩種方法可以解決這個問題。 首先,我們將`this`的值保存在變數中。 ``` const myFavoriteObj = { guessThis(){ const self = this; //saves the this value to the "self" variable function getName(){ console.log(self.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; ``` 在此圖像中,我們保存`this`的值,該值將是`myFavoriteObj`物件。所以我們可以在`getName`內部函數中存取它。 其次,我們使用**ES6[箭頭函數](#43-what-are-arrow-functions)**。 ``` const myFavoriteObj = { guessThis(){ const getName = () => { //copies the value of "this" outside of this arrow function console.log(this.name); } getName(); }, name: 'Marko Polo', thisIsAnnoying(callback){ callback(); } }; ``` [箭頭函數](#43-what-are-arrow-functions)沒有自己的`this` 。它複製封閉詞法範圍的`this`值,或複製`getName`內部函數外部的`this`值(即`myFavoriteObj`物件)。我們也可以根據[函數的呼叫方式](#66-how-many-ways-can-a-function-be-invoked)來決定`this`的值。 ### 25. 物件的`prototype`是什麼? [↑](#the-questions "返回問題")最簡單的`prototype`是一個物件的*藍圖*。如果目前物件中確實存在它,則將其用作**屬性**和**方法**的後備。這是在物件之間共享屬性和功能的方式。這是 JavaScript**原型繼承**的核心概念。 ``` const o = {}; console.log(o.toString()); // logs [object Object] ``` 即使`o.toString`方法不存在於`o`物件中,它也不會拋出錯誤,而是傳回字串`[object Object]` 。當物件中不存在屬性時,它會尋找其**原型**,如果仍然不存在,則會尋找**原型的原型**,依此類推,直到在**原型鏈**中找到具有相同屬性的屬性。**原型鏈**的末尾在**Object.prototype**之後為`null` 。 ``` console.log(o.toString === Object.prototype.toString); // logs true // which means we we're looking up the Prototype Chain and it reached // the Object.prototype and used the "toString" method. ``` ### 26. 什麼是**IIFE** ,它有什麼用? [^](#the-questions "返回問題") **IIFE**或**立即呼叫函數表達式**是在建立或宣告後將被呼叫或執行的函數。建立**IIFE**的語法是,我們將`function (){}`包裝在括號`()`或**分組運算**子內,以將函數視為表達式,然後用另一個括號`()`呼叫它。所以**IIFE**看起來像這樣`(function(){})()` 。 ``` (function () { }()); (function () { })(); (function named(params) { })(); (() => { })(); (function (global) { })(window); const utility = (function () { return { //utilities }; })(); ``` 這些範例都是有效的**IIFE** 。倒數第二個範例顯示我們可以將參數傳遞給**IIFE**函數。最後一個範例表明我們可以將**IIFE**的結果保存到變數中,以便稍後引用它。 **IIFE**的最佳用途是進行初始化設定功能,並避免與全域範圍內的其他變數**發生命名衝突**或污染全域名稱空間。讓我們舉個例子。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> ``` 假設我們有一個指向庫`somelibrary.js`的連結,該庫公開了我們可以在程式碼中使用的一些全域函數,但該庫有兩個我們不使用`createGraph`和`drawGraph`方法,因為這些方法中有錯誤。我們想要實作我們自己的`createGraph`和`drawGraph`方法。 - 解決這個問題的一種方法是改變腳本的結構。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } </script> ``` 當我們使用這個解決方案時,我們將覆蓋庫為我們提供的這兩種方法。 - 解決這個問題的另一種方法是更改我們自己的輔助函數的名稱。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> function myCreateGraph() { // createGraph logic here } function myDrawGraph() { // drawGraph logic here } </script> ``` 當我們使用此解決方案時,我們還將這些函數呼叫更改為新函數名稱。 - 另一種方法是使用**IIFE** 。 ``` <script src="https://cdnurl.com/somelibrary.js"></script> <script> const graphUtility = (function () { function createGraph() { // createGraph logic here } function drawGraph() { // drawGraph logic here } return { createGraph, drawGraph } })(); </script> ``` 在此解決方案中,我們建立一個實用程式變數,它是**IIFE**的結果,它傳回一個包含`createGraph`和`drawGraph`兩個方法的物件。 **IIFE**解決的另一個問題就是這個例子。 ``` var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { li[i].addEventListener('click', function (e) { console.log(i); }) } ``` 假設我們有一個`ul`元素,其類別為**list-group** ,並且它有 5 個`li`子元素。當我們**點擊**單一`li`元素時,我們希望`console.log` `i`的值。 但我們想要的程式碼中的行為不起作用。相反,它會在對`li`元素的任何**點擊**中記錄`5` 。我們遇到的問題是由於**閉包的**工作方式造成的。**閉包**只是函數記住其當前作用域、其父函數作用域和全域作用域中的變數引用的能力。當我們在全域範圍內使用`var`關鍵字聲明變數時,顯然我們正在建立一個全域變數`i` 。因此,當我們單擊`li`元素時,它會記錄**5** ,因為這是我們稍後在回調函數中引用它時的`i`值。 - 解決這個問題的一種方法是**IIFE** 。 ``` var li = document.querySelectorAll('.list-group > li'); for (var i = 0, len = li.length; i < len; i++) { (function (currentIndex) { li[currentIndex].addEventListener('click', function (e) { console.log(currentIndex); }) })(i); } ``` 這個解決方案之所以有效,是因為**IIFE**為每次迭代建立一個新範圍,並且我們捕獲`i`的值並將其傳遞到`currentIndex`參數中,因此當我們呼叫**IIFE**時,每次迭代的`currentIndex`值都是不同的。 ### 27. `Function.prototype.apply`方法有什麼用? [^](#the-questions "返回問題") `apply`呼叫一個函數,在呼叫時指定`this`或該函數的「所有者」物件。 ``` const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.apply(details); // returns 'Hello World!' ``` 這個方法的工作方式類似於`<a href="#28-what-is-the-use-functionprototypecall-method">Function.prototype.call</a>`唯一的差異是我們傳遞參數的方式。在`apply`中,我們將參數作為陣列傳遞。 ``` const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.apply(person, ['Hello']); // returns "Hello Marko Polo!" ``` ### 28. `Function.prototype.call`方法有什麼用? [^](#the-questions "返回問題")此`call`呼叫一個函數,指定呼叫時該函數的`this`或「擁有者」物件。 ``` const details = { message: 'Hello World!' }; function getMessage(){ return this.message; } getMessage.call(details); // returns 'Hello World!' ``` 這個方法的工作方式類似於`<a href="#27-what-is-the-use-functionprototypeapply-method">Function.prototype.apply</a>`唯一的差異是我們傳遞參數的方式。在`call`中,我們直接傳遞參數,對於每個參數`,`用逗號分隔它們。 ``` const person = { name: "Marko Polo" }; function greeting(greetingMessage) { return `${greetingMessage} ${this.name}`; } greeting.call(person, 'Hello'); // returns "Hello Marko Polo!" ``` ### 29. `Function.prototype.apply`和`Function.prototype.call`有什麼差別? [↑](#the-questions "返回問題") `apply`和`call`之間的唯一區別是我們如何在被呼叫的函數中傳遞**參數**。在`apply`中,我們將參數作為**陣列**傳遞,而在`call`中,我們直接在參數列表中傳遞參數。 ``` const obj1 = { result:0 }; const obj2 = { result:0 }; function reduceAdd(){ let result = 0; for(let i = 0, len = arguments.length; i < len; i++){ result += arguments[i]; } this.result = result; } reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // returns 15 reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15 ``` ### 30. `Function.prototype.bind`的用法是什麼? [↑](#the-questions "返回問題") `bind`方法傳回一個新*綁定的*函數 到特定的`this`值或“所有者”物件,因此我們可以稍後在程式碼中使用它。 `call` 、 `apply`方法立即呼叫函數,而不是像`bind`方法那樣傳回一個新函數。 ``` import React from 'react'; class MyComponent extends React.Component { constructor(props){ super(props); this.state = { value : "" } this.handleChange = this.handleChange.bind(this); // Binds the "handleChange" method to the "MyComponent" component } handleChange(e){ //do something amazing here } render(){ return ( <> <input type={this.props.type} value={this.state.value} onChange={this.handleChange} /> </> ) } } ``` ### 31.什麼是**函數式程式設計**? **JavaScript**的哪些特性使其成為**函數式語言**的候選者? [^](#the-questions "返回問題")**函數式程式設計**是一種**聲明式**程式設計範式或模式,它介紹如何**使用表達式來**計算值而不改變傳遞給它的參數的函數來建立應用程式。 JavaScript**陣列**具有**map** 、 **filter** 、 **reduce**方法,這些方法是函數式程式設計世界中最著名的函數,因為它們非常有用,而且它們不會改變或改變陣列,這使得這些函數變得**純粹**,並且JavaScript 支援**閉包**和**高階函數**,它們是**函數式程式語言**的一個特徵。 - **map**方法建立一個新陣列,其中包含對陣列中每個元素呼叫提供的回調函數的結果。 ``` const words = ["Functional", "Procedural", "Object-Oriented"]; const wordsLength = words.map(word => word.length); ``` - **filter**方法會建立一個新陣列,其中包含透過回調函數中測試的所有元素。 ``` const data = [ { name: 'Mark', isRegistered: true }, { name: 'Mary', isRegistered: false }, { name: 'Mae', isRegistered: true } ]; const registeredUsers = data.filter(user => user.isRegistered); ``` - **reduce**方法對累加器和陣列中的每個元素(從左到右)套用函數,將其減少為單一值。 ``` const strs = ["I", " ", "am", " ", "Iron", " ", "Man"]; const result = strs.reduce((acc, currentStr) => acc + currentStr, ""); ``` ### 32.什麼是**高階函數**? [^](#the-questions "返回問題")**高階函數**是可以傳回函數或接收具有函數值的一個或多個參數的函數。 ``` function higherOrderFunction(param,callback){ return callback(param); } ``` ### 33.為什麼函數被稱為**First-class Objects** ? [^](#the-questions "返回問題") JavaScript 中的 \_\_Functions\_\_ 是**一流物件**,因為它們被視為該語言中的任何其他值。它們可以分配給**變數**,可以是稱為**方法的物件的屬性**,可以是**陣列中的專案**,可以**作為參數傳遞給函數**,也可以**作為函數的值返回**。函數與**JavaScript**中任何其他值之間的唯一區別是**函數**可以被呼叫。 ### 34. 手動實作`Array.prototype.map`方法。 [↑](#the-questions "返回問題") ``` function map(arr, mapCallback) { // First, we check if the parameters passed are right. if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { return []; } else { let result = []; // We're making a results array every time we call this function // because we don't want to mutate the original array. for (let i = 0, len = arr.length; i < len; i++) { result.push(mapCallback(arr[i], i, arr)); // push the result of the mapCallback in the 'result' array } return result; // return the result array } } ``` 正如`Array.prototype.map`方法的MDN描述。 **map() 方法建立一個新陣列,其中包含對呼叫陣列中的每個元素呼叫所提供函數的結果。** ### 35. 手動實作`Array.prototype.filter`方法。 [↑](#the-questions "返回問題") ``` function filter(arr, filterCallback) { // First, we check if the parameters passed are right. if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') { return []; } else { let result = []; // We're making a results array every time we call this function // because we don't want to mutate the original array. for (let i = 0, len = arr.length; i < len; i++) { // check if the return value of the filterCallback is true or "truthy" if (filterCallback(arr[i], i, arr)) { // push the current item in the 'result' array if the condition is true result.push(arr[i]); } } return result; // return the result array } } ``` 正如`Array.prototype.filter`方法的 MDN 描述。 **filter() 方法建立一個新陣列,其中包含透過所提供函數實現的測試的所有元素。** ### 36. 手動實作`Array.prototype.reduce`方法。 [↑](#the-questions "返回問題") ``js 函數reduce(arr,reduceCallback,initialValue){ // 首先,我們檢查傳遞的參數是否正確。 if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') { ``` return []; ``` } 別的 { ``` // If no initialValue has been passed to the function we're gonna use the ``` ``` let hasInitialValue = initialValue !== undefined; ``` ``` let value = hasInitialValue ? initialValue : arr[0]; ``` ``` // first array item as the initialValue ``` ``` // Then we're gonna start looping at index 1 if there is no ``` ``` // initialValue has been passed to the function else we start at 0 if ``` ``` // there is an initialValue. ``` ``` for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) { ``` ``` // Then for every iteration we assign the result of the ``` ``` // reduceCallback to the variable value. ``` ``` value = reduceCallback(value, arr[i], i, arr); ``` ``` } ``` ``` return value; ``` } } ``` As the MDN description of the <code>Array.prototype.reduce</code> method. __The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.__ ###37. What is the __arguments__ object? [&uarr;](#the-questions "Back To Questions") The __arguments__ object is a collection of parameter values pass in a function. It's an __Array-like__ object because it has a __length__ property and we can access individual values using array indexing notation <code>arguments[1]</code> but it does not have the built-in methods in an array <code>forEach</code>,<code>reduce</code>,<code>filter</code> and <code>map</code>. It helps us know the number of arguments pass in a function. We can convert the <code>arguments</code> object into an array using the <code>Array.prototype.slice</code>. ``` 函數一(){ 返回 Array.prototype.slice.call(參數); } ``` Note: __the <code>arguments</code> object does not work on ES6 arrow functions.__ ``` 函數一(){ 返回參數; } 常數二 = 函數 () { 返回參數; } 常量三 = 函數三() { 返回參數; } const 四 = () =&gt; 參數; 四(); // 拋出錯誤 - 參數未定義 ``` When we invoke the function <code>four</code> it throws a <code>ReferenceError: arguments is not defined</code> error. We can solve this problem if your enviroment supports the __rest syntax__. ``` const 四 = (...args) =&gt; args; ``` This puts all parameter values in an array automatically. ###38. How to create an object without a __prototype__? [&uarr;](#the-questions "Back To Questions") We can create an object without a _prototype_ using the <code>Object.create</code> method. ``` 常數 o1 = {}; console.log(o1.toString()); // Logs \[object Object\] 取得此方法到Object.prototype const o2 = Object.create(null); // 第一個參數是物件「o2」的原型,在此 // case 將為 null 指定我們不需要任何原型 console.log(o2.toString()); // 拋出錯誤 o2.toString 不是函數 ``` ###39. Why does <code>b</code> in this code become a global variable when you call this function? [&uarr;](#the-questions "Back To Questions") ``` 函數 myFunc() { 令a = b = 0; } myFunc(); ``` The reason for this is that __assignment operator__ or __=__ has right-to-left __associativity__ or __evaluation__. What this means is that when multiple assignment operators appear in a single expression they evaluated from right to left. So our code becomes likes this. ``` 函數 myFunc() { 令 a = (b = 0); } myFunc(); ``` First, the expression <code>b = 0</code> evaluated and in this example <code>b</code> is not declared. So, The JS Engine makes a global variable <code>b</code> outside this function after that the return value of the expression <code>b = 0</code> would be 0 and it's assigned to the new local variable <code>a</code> with a <code>let</code> keyword. We can solve this problem by declaring the variables first before assigning them with value. ``` 函數 myFunc() { 令 a,b; a = b = 0; } myFunc(); ``` ###40. <div id="ecmascript">What is __ECMAScript__</div>? [&uarr;](#the-questions "Back To Questions") __ECMAScript__ is a standard for making scripting languages which means that __JavaScript__ follows the specification changes in __ECMAScript__ standard because it is the __blueprint__ of __JavaScript__. ###41. What are the new features in __ES6__ or __ECMAScript 2015__? [&uarr;](#the-questions "Back To Questions") * [Arrow Functions](#43-what-are-arrow-functions) * [Classes](#44-what-are-classes) * [Template Strings](#45-what-are-template-literals) * __Enhanced Object literals__ * [Object Destructuring](#46-what-is-object-destructuring) * [Promises](#50-what-are-promises) * __Generators__ * [Modules](#47-what-are-es6-modules) * Symbol * __Proxies__ * [Sets](#48-what-is-the-set-object-and-how-does-it-work) * [Default Function parameters](#53-what-are-default-parameters) * [Rest and Spread](#52-whats-the-difference-between-spread-operator-and-rest-operator) * [Block Scoping with <code>let</code> and <code>const</code>](#42-whats-the-difference-between-var-let-and-const-keywords) ###42. What's the difference between <code>var</code>, <code>let</code> and <code>const</code> keywords? [&uarr;](#the-questions "Back To Questions") Variables declared with <code>var</code> keyword are _function scoped_. What this means that variables can be accessed across that function even if we declare that variable inside a block. ``` 函數給MeX(showX) { 如果(顯示X){ ``` var x = 5; ``` } 返回x; } console.log(giveMeX(false)); console.log(giveMeX(true)); ``` The first <code>console.log</code> statement logs <code>undefined</code> and the second <code>5</code>. We can access the <code>x</code> variable due to the reason that it gets _hoisted_ at the top of the function scope. So our function code is intepreted like this. ``` 函數給MeX(showX) { 變數 x; // 有一個預設值未定義 如果(顯示X){ ``` x = 5; ``` } 返回x; } ``` If you are wondering why it logs <code>undefined</code> in the first <code>console.log</code> statement remember variables declared without an initial value has a default value of <code>undefined</code>. Variables declared with <code>let</code> and <code>const</code> keyword are _block scoped_. What this means that variable can only be accessed on that block <code>{}</code> on where we declare it. ``` 函數給MeX(showX) { 如果(顯示X){ ``` let x = 5; ``` } 返回x; } 函數給MeY(顯示Y){ 如果(顯示Y){ ``` let y = 5; ``` } 返回y; } ``` If we call this functions with an argument of <code>false</code> it throws a <code>Reference Error</code> because we can't access the <code>x</code> and <code>y</code> variables outside that block and those variables are not _hoisted_. There is also a difference between <code>let</code> and <code>const</code> we can assign new values using <code>let</code> but we can't in <code>const</code> but <code>const</code> are mutable meaning. What this means is if the value that we assign to a <code>const</code> is an object we can change the values of those properties but can't reassign a new value to that variable. ###43. What are __Arrow functions__? [&uarr;](#the-questions "Back To Questions") __Arrow Functions__ are a new way of making functions in JavaScript. __Arrow Functions__ takes a little time in making functions and has a cleaner syntax than a __function expression__ because we omit the <code>function</code> keyword in making them. ``` //ES5版本 var getCurrentDate = 函數 (){ 返回新日期(); } //ES6版本 const getCurrentDate = () =&gt; new Date(); ``` In this example, in the ES5 Version have <code>function(){}</code> declaration and <code>return</code> keyword needed to make a function and return a value respectively. In the __Arrow Function__ version we only need the <code>()</code> parentheses and we don't need a <code>return</code> statement because __Arrow Functions__ have a implicit return if we have only one expression or value to return. ``` //ES5版本 函數問候(名稱){ return '你好' + 名字 + '!'; } //ES6版本 const 問候 = (name) =&gt; `Hello ${name}` ; constgreet2 = 名稱 =&gt; `Hello ${name}` ; ``` We can also parameters in __Arrow functions__ the same as the __function expressions__ and __function declarations__. If we have one parameter in an __Arrow Function__ we can omit the parentheses it is also valid. ``` const getArgs = () =&gt; 參數 const getArgs2 = (...休息) =&gt; 休息 ``` __Arrow functions__ don't have access to the <code>arguments</code> object. So calling the first <code>getArgs</code> func will throw an Error. Instead we can use the __rest parameters__ to get all the arguments passed in an arrow function. ``` 常量資料 = { 結果:0, 數字:\[1,2,3,4,5\], 計算結果() { ``` // "this" here refers to the "data" object ``` ``` const addAll = () => { ``` ``` // arrow functions "copies" the "this" value of ``` ``` // the lexical enclosing function ``` ``` return this.nums.reduce((total, cur) => total + cur, 0) ``` ``` }; ``` ``` this.result = addAll(); ``` } }; ``` __Arrow functions__ don't have their own <code>this</code> value. It captures or gets the <code>this</code> value of lexically enclosing function or in this example, the <code>addAll</code> function copies the <code>this</code> value of the <code>computeResult</code> method and if we declare an arrow function in the global scope the value of <code>this</code> would be the <code>window</code> object. ###44. What are __Classes__? [&uarr;](#the-questions "Back To Questions") __Classes__ is the new way of writing _constructor functions_ in __JavaScript__. It is _syntactic sugar_ for using _constructor functions_, it still uses __prototypes__ and __Prototype-Based Inheritance__ under the hood. ``` //ES5版本 函數人(名字,姓氏,年齡,地址){ ``` this.firstName = firstName; ``` ``` this.lastName = lastName; ``` ``` this.age = age; ``` ``` this.address = address; ``` } Person.self = 函數(){ ``` return this; ``` } Person.prototype.toString = function(){ ``` return "[object Person]"; ``` } Person.prototype.getFullName = function (){ ``` return this.firstName + " " + this.lastName; ``` } //ES6版本 類人{ ``` constructor(firstName, lastName, age, address){ ``` ``` this.lastName = lastName; ``` ``` this.firstName = firstName; ``` ``` this.age = age; ``` ``` this.address = address; ``` ``` } ``` ``` static self() { ``` ``` return this; ``` ``` } ``` ``` toString(){ ``` ``` return "[object Person]"; ``` ``` } ``` ``` getFullName(){ ``` ``` return `${this.firstName} ${this.lastName}`; ``` ``` } ``` } ``` __Overriding Methods__ and __Inheriting from another class__. ``` //ES5版本 Employee.prototype = Object.create(Person.prototype); 函數 Employee(名字, 姓氏, 年齡, 地址, 職位名稱, 開始年份) { Person.call(this, 名字, 姓氏, 年齡, 地址); this.jobTitle = jobTitle; this.yearStarted = YearStarted; } Employee.prototype.describe = function () { return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}` ; } Employee.prototype.toString = function () { 返回“\[物件員工\]”; } //ES6版本 class Employee extends Person { //繼承自「Person」類 建構函數(名字,姓氏,年齡,地址,工作標題,開始年份){ ``` super(firstName, lastName, age, address); ``` ``` this.jobTitle = jobTitle; ``` ``` this.yearStarted = yearStarted; ``` } 描述() { ``` return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`; ``` } toString() { // 重寫「Person」的「toString」方法 ``` return "[object Employee]"; ``` } } ``` So how do we know that it uses _prototypes_ under the hood? ``` 類別東西{ } 函數 AnotherSomething(){ } const as = new AnotherSomething(); const s = new Something(); console.log(typeof Something); // 記錄“函數” console.log(AnotherSomething 類型); // 記錄“函數” console.log(as.toString()); // 記錄“\[物件物件\]” console.log(as.toString()); // 記錄“\[物件物件\]” console.log(as.toString === Object.prototype.toString); console.log(s.toString === Object.prototype.toString); // 兩個日誌都回傳 true 表示我們仍在使用 // 底層原型,因為 Object.prototype 是 // 原型鏈的最後一部分和“Something” // 和「AnotherSomething」都繼承自Object.prototype ``` ###45. What are __Template Literals__? [&uarr;](#the-questions "Back To Questions") __Template Literals__ are a new way of making __strings__ in JavaScript. We can make __Template Literal__ by using the backtick or back-quote symbol. ``` //ES5版本 vargreet = '嗨,我是馬克'; //ES6版本 讓問候 = `Hi I'm Mark` ; ``` In the ES5 version, we need to escape the <code>'</code> using the <code>\\</code> to _escape_ the normal functionality of that symbol which in this case is to finish that string value. In Template Literals, we don't need to do that. ``` //ES5版本 var 最後一個字 = '\\n' - ' 在' - '我\\n' - '鋼鐵人\\n'; //ES6版本 讓最後一個單字=` ``` I ``` ``` Am ``` 鋼鐵人 `; ``` In the ES5 version, we need to add this <code>\n</code> to have a new line in our string. In Template Literals, we don't need to do that. ``` //ES5版本 函數問候(名稱){ return '你好' + 名字 + '!'; } //ES6版本 const 問候 = 名稱 =&gt; { 返回`Hello ${name} !` ; } ``` In the ES5 version, If we need to add an expression or value in a string we need to use the <code>+</code> or string concatenation operator. In Template Literals, we can embed an expression using <code>${expr}</code> which makes it cleaner than the ES5 version. ###46. What is __Object Destructuring__? [&uarr;](#the-questions "Back To Questions") __Object Destructuring__ is a new and cleaner way of __getting__ or __extracting__ values from an object or an array. Suppose we have an object that looks like this. ``` 常量僱員 = { 名字:“馬可”, 姓氏:“波羅”, 職位:“軟體開發人員”, 聘用年份:2017 }; ``` The old way of getting properties from an object is we make a variable t

我們透過 1 個月的地下室開發所取得的成就

管理社區是一項艱鉅的工作,但經過幾年的努力,它就變得容易了。這是我們社區的第一個月,我想向您展示我們迄今為止所取得的成就。 如果您打算經營一個社區甚至加入一個社區,本系列將以[地下室開發人員](https://discord.gg/basementdevs)為例向您展示如何衡量社區的健康狀況。 目錄 -- - [一、整體成就](#1-general-achievements) - [2. 人口統計](#1-demographics) ``` * [2.1 New Members](#21-new-members) ``` ``` * [2.2 Members Activation](#22-members-activation) ``` ``` * [2.3 Member Visits & Comunicators](#23-member-visits-amp-comunicators) ``` - [3. 活動](#3-events) - [4. 合作夥伴](#4-partnerships) - [5. 發佈內容](#5-published-content) - [6. 現在怎麼辦?](#6-what-now) 一、整體成就 ------ 首先,我要感謝加入社群的幾位讀者。現在我們有幾個波蘭人每天在那裡參與活動。 > 僅供參考:社群正在尋找像您這樣的成員,所以請盡快加入我們! :D ![每週會議最多有 71 名成員](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0zly7fwr9rjijsuz832.jpg) 我們最新的每週例會人數達到了 71 人的高峰。 自從我們建立社群以來,我們的每週會議一直是討論新機會、專案和其他機會,並聽取成員意見的地方。 &lt; 視訊控制樣式=“高度:100%;”&gt; 您的瀏覽器不支援影片標籤。 說到社群回饋,我們的大多數成員都是想要習慣英語的巴西人,因此社群中製作的第一個專案是一個覆蓋層,用於將我們在每週會議中所說的所有內容從英語翻譯成巴西葡萄牙語!為[Pantotone](https://github.com/Pantotone)乾杯,這個神奇工具的創造者! ![Github組織](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv2brtina8dtrd1ifqb0.png) 此外,由於我們開始建立專案,因此建立了一個新的 GitHub 組織來託管與我們社區相關的所有專案。不要忘記[關注我們的社區](https://github.com/basementdevs)以獲取最新的專案更新! 2. 人口統計 ------- 當我們談論成長時,我們談論的是數字!以下是**1 月 14 日至 2 月 12 日**的回顧。 ### 2.1 新成員 自 1 月 15 日起,我們的會員人數增加了一倍!目前我們的伺服器上有 2197 名活躍會員,其中許多人每天都在不同風格的語音頻道(小組或一對一)和文字頻道中練習英語。 ![2k 會員峰值圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1dqorxb9zx3gl86iug7.png) ### 2.2 會員激活 我們本月將研究的指標之一是新成員的“激活”,即他們了解社區的含義以及為什麼它非常適合他們。 ![上個月新成員互動圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmjkzxh5d2te1gzmbl1y.png) 我們在第一個月做得很好,你可以看到圖表直接到底部,但這只是因為上週我們在巴西舉辦了**狂歡**節,所以這是預料之中的,我們的巴西開發商值得參加這個令人驚嘆的活動和一個愉快的假期。 ### 2.3 會員來訪及溝通 ![本月會員存取量圖表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xs7vtuw8fyd1h5ldfbq.png) 我們的平均溝通者數量或參與度是 8%。我們的目標是到 3 月底達到 15%。我們要怎麼做呢?我還不知道,但有了這份每週活動列表,這應該是個不錯的選擇。 3. 活動 ----- 活動是我們社區的核心,上個月舉辦了 16 場預先安排的活動,分為 5 個模式: - **地下室會議:**我們的每週會議於每週二晚上 07:00 BRT 舉行。其目的是分享我們社區的計劃、宣布新的合作夥伴關係、向成員通報最新訊息,當然還有練習英語。 - **和雷納託一起練習:**每週三,我們的社區都會聚在一起和雷納託一起練習英語。在這次練習中,我們的成員會得到一個主題,每個人都與雷納托就這個主題進行對話,總是引入新單字來擴大社群的詞彙量。 - **與JP一起旅行:**在本次會議中,我們的會員將與JP一起計劃他們的夢想旅行,討論費用、參觀地點、當地美食等等!快來和我們一起規劃您的旅程。 - **與李奧納多一起練習:**每週四早上,我們的社區都會與李奧納多一起參加英語練習課程。參與者被分配一個主題並與李奧納多對話,融入新單字建立詞彙量。 - **Coding Dojo:**二月初,我們推出了 Basement Coding Dojo,這是一項社群成員在模擬衝刺期間在開發團隊中扮演角色的活動。我們的第一個 Dojo 開發團隊由[Gabriel](https://github.com/gvieiragoulart) 、 [Lorena](https://github.com/Lorenalgm) 、 [Lucas](https://github.com/lucas-pace) 、 [Pedro](https://github.com/pedrovian4) 、 [Renato](https://github.com/reenatoteixeira)和[Yuri](https://github.com/yurastico)組成。在一個月的時間裡,團隊的目標是建立一個簡單但完整的應用程式,從事設計、後端和前端工作,參加每週會議並與開源專案互動。您可以在[此存儲庫](https://github.com/basementdevs/english-coding-dojo)中找到有關此事件的更多資訊。 4. 合作夥伴 ------- 現在我們最大的目標是找到HR公司甚至是正在積極招募開發人員的公司。 這個月,我們的目標是建立一個人口統計資料,提供更多有關有趣事物的訊息,為我們的會員帶來這些機會,例如:資歷、堆疊等。到目前為止,我們已經取得了一些成果,但還沒有什麼意義,因為這對我們來說是一種新方法。 ![使用者早期人口統計](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxll9ud9we8y6uo1miry.png) 如果您的公司有空缺職位,請發送電子郵件至 [email protected],我會將其轉發給我們最好的會員進行申請! 5. 發佈內容 ------- 上個月,我們社群的成員在這個平台上發表的文章獲得了多項認可。我們想重點介紹以下文章,這些文章是其發布週中最相關的文章(前 7 名)。 {% 嵌入 https://dev.to/danielhe4rt/database-101-how-to-model-leaderboards-for-1m-players-game-2pfa %} 「在他的『Database 101』系列的最新一期中,@danielhe4rt 探索了為擁有100 萬玩家的遊戲排行榜建模的複雜藝術。本文借鑒開源節奏遊戲YARG 的個人經驗並利用ScyllaDB 的效率,深入研究了查詢驅動的資料建模和寬列資料庫概念,為製作高性能排行榜提供了全面的指南。” {% 嵌入 https://dev.to/reenatoteixeira/everything-that-you-need-to-know-about-git-2440 %} 「透過這份全面的初學者指南,探索GIT 的程式碼版本控制世界!這篇突破性文章由首次DEV 貢獻者@reenatoteixeira 撰寫,精心解開了GIT 的所有要點,確保學習之旅順利進行。具有清晰的解釋和詳細的步驟-通過逐步指導,您將掌握 GIT 的藝術,為您提供無縫的版本控制功能。” {% 嵌入 https://dev.to/kecbm/staircase-detail-112g %} 透過我們的貢獻者@kebcm 建立的這個精彩的分步教程,了解如何為初學者在編程中建立樓梯!樓梯是由一系列台階形成的視覺表示,每個台階都由 # 字元表示。探索如何建立列印自訂尺寸樓梯的函數。 {% 嵌入 https://dev.to/anthonyvii/be-a-better-developer-with-these-git-good-practices-2dim %} 如果您是開發人員,您可能會經常使用 Git。無論是單獨還是團隊開發,這對於應用程式開發都至關重要。但許多人都在與混亂的儲存庫和不清楚的提交訊息/分支名稱作鬥爭。學好 Git 並堅持最佳實踐是職業發展的關鍵之一。 6. 現在怎麼辦? --------- 到目前為止,我們的社區正在以一種我們意想不到的方式發展,但仍然以健康的方式發展。許多以前從未嘗試過**技術英語**的人已經邁出了練習的第一步,並開始**看到新的可能性**,這就是我們需要的燃料。 如果您想成為這一變革之旅的一部分,請加入我們的[Discord 伺服器](https://discord.gg/basementdevs),並且不要忘記喜歡和評論本文! 歡迎任何讓我們的社區變得更好的建議,好嗎?保持水分,下個月見! > 順便說一句,**雷納托**和**安東尼**在這份報告中幫助我乾杯:D --- 原文出處:https://dev.to/danielhe4rt/what-weve-achieved-with-1-month-of-basement-devs-3pec

高階端對端 DevOps 專案:使用 Terraform、Helm、Jenkins 和 ArgoCD 將微服務應用程式部署到 AWS EKS(第一部分)

DevOps 是 IT 產業中一個快速發展的領域。作為一名 DevOps 工程師,跟上開發空間以避免落後至關重要。 GitOPs 是該領域已經發展成熟的流行範例。 **GitOps**是一種 DevOps 框架或實踐,透過它,我們使 Git 儲存庫成為單一事實來源,同時將 CI/CD 和版本控制應用於基礎設施自動化。 [紅帽](https://www.redhat.com/en/topics/devops/what-is-gitops)將其定義為「使用 Git 儲存庫作為單一事實來源來交付基礎設施即程式碼」。 另一方面, **[DevSecOps](https://aws.amazon.com/what-is/devsecops/#:~:text=DevSecOps%20is%20the%20practice%20of,is%20both%20efficient%20and%20secure.)**是 DevOps 的新改進版本,它在 SDLC(軟體開發生命週期)中灌輸安全工具和控制措施。 devsecops 方法的主要目標是“將安全性左移”,即安全性應該從一開始就成為開發生命週期的一部分,而不是事後才想到。 在本專案指南中,我們將應用 GitOps 實踐,同時實作包含許多工具的高階端對端 DevSecOps 管道。 專案概況 ==== 這是一個由兩部分組成的專案。在第一部分中,我們將設定執行 CI 管道的 EC2 執行個體。 **若要了解如何使用 jenkins 建立標準的持續整合管道,請按[此處](https://dev.to/kelvinskell/a-practical-guide-to-building-a-standard-continuous-integration-pipeline-with-jenkins-2kp9)。** 在第二部分中,我們將設定 EKS 叢集、ArgoCD 用於持續交付,並配置 Prometheus 和 Grafana 用於應用程式監控。 在這個專案中,我們將涵蓋以下內容: **- 基礎架構即程式碼:**我們將使用 terraform 來設定我們的 EC2 執行個體以及 EKS 叢集。 **- Jenkins 伺服器設定:**在 Jenkins 伺服器上安裝和設定基本工具,包括 Jenkins 本身、Docker、OWASP 相依性檢查、Sonarqube 和 Trivy。 **- EKS 叢集部署:**利用 Terraform 建立 Amazon EKS 叢集,這是 AWS 上的託管 Kubernetes 服務。 **- 負載平衡器配置:**為 EKS 叢集配置 AWS 應用程式負載平衡器 (ALB)。 **- ArgoCD 安裝:**安裝並設定 ArgoCD 以實現持續交付和 GitOps。 **- Sonarqube 整合:**整合 Sonarqube 以在 DevSecOps 管道中進行程式碼品質分析。 **- 監控設定:**使用 Helm、Prometheus 和 Grafana 實現對 EKS 叢集的監控。 **- ArgoCD應用程式部署:**使用ArgoCD部署微服務應用程式,包括資料庫和入口元件。 第一部分:設定 CI 管道 ============= **- 第 1 步:設定 EC2 執行個體** 克隆[Git 儲存庫](https://github.com/Kelvinskell/microservices-devops-1)。 `cd`進入 terraform 目錄。 執行`terraform init` ,然後執行`terraform plan`以查看建議的基礎架構變更。 執行`terraform apply`來實作這些變更並配置實例。 此實例使用使用者資料進行引導,一旦配置完畢,將自動安裝 jenkins、sonarqube、trivy 和 docker。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7bpfkke1mp7k5xnvdhjw.png) **- 步驟2:修改應用程式程式碼** 這是一個簡單但關鍵的步驟。在您剛剛複製的儲存庫中包含的**Jenkinsfile**中,您必須將所有出現的「 **kelvinskell** 」變更為您的 DockerHub 使用者名稱。如果您想自己實施這個專案,這是非常有必要的。 **- 第 3 步:設定 Jenkins 伺服器** - 在瀏覽器上登入jenkins伺服器。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cryly0tf1nzxwbdlha9o.png) - 安裝建議的插件並完成註冊。 - 前往“管理Jenkins”、“插件”,然後安裝以下插件:Docker、Docker Commons、Docker pipeline、SonarQube Scanner、Sonar 品質門、SSH2 Easy、OWASP 依賴項檢查、OWASP Markup Formatter 插件、GitHub API pluin 和GitHub pipeline插件。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r12a2xjz5p9svyii685e.png) - 設定工具:前往 Dashborad > 管理 jenkins > 工具 **git安裝** ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qvhas5tzgkmdbokqkz2a.png) **聲納掃描器安裝** ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oil2k0fymyutyttuqljn.png) **依賴性檢查** ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8xvuiz5hxndem72wce9j.png) **Docker安裝** ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6dgt502xm5qxpmgpeof5.png) **- 第 4 步:配置 SonarQube** - 在瀏覽器上,連接到連接埠 9000 上的 Jenkins 伺服器 IP 位址並登入伺服器。 預設使用者名稱和密碼是“admin”。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v6f84jf2phosumnfnt4v.png) 登入後,按一下“手動”。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dz4r3d83eitw5hbyb0x8.png) 請按照上圖中的說明操作,然後按一下「設定」。 **注意:**您的專案金鑰必須完全是**newsread-microservices-application** 。這樣,您就不必編輯 Jenkinsfile。 - 選擇**“With Jenkins”**並選擇 GitHub 作為 DevOps 平台。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4bnnbea6j43tvj0h3ljw.png) - 點擊**“配置分析”** ,在步驟3中,複製“sonar.projectKey”,稍後您將需要它。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdcus7h1uzotllmnnuq6.png) - 點選「帳戶」>「我的帳戶」>「產生令牌」。 為其命名並點擊“生成”。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tz5qe7ooadml77g0cu4.png) - 前往“管理 Jenkins”>“憑證” - 選擇 Secret tex 並貼上您剛剛複製的令牌。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz59t3tbuhckci2fxgt0.png) - 現在前往 Jenkins 儀表板 > 設定 Jenkins > 系統 > Sonarqube 伺服器 > 新增 Sonarqube - 將其命名為“SonarQube Server”,輸入秘密令牌的伺服器 URL 和憑證 ID。 請注意,我們的伺服器 url 是 localhost,因為 SonarQube 與 jenkins 託管在同一台伺服器上。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a770dmbbjbbbg66pbft.png) - 點選“儲存”。 **- 第 5 步:整合您的 DockerHub 憑證** 此階段對於 Jenkins 存取您的 DockerHub 帳戶至關重要。 - 前往“管理 Jenkins”>“憑證”>“新增憑證” ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0oi45zq6c0wmtl08u53v.png) **- 第 6 步:設定 Jenkins 管道** - 從 Jenkins 的儀表板中,按一下「新專案」並建立管道作業。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wxwu61m452794k2crxqp.png) - 在“建置觸發器”下,選擇“觸發遠端建置”。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gqu6svazhn7zjyotm9yk.png) 在「身份驗證令牌」方塊下設定秘密令牌。我們將在建立 GitHub Webhook 時使用它。 - 在管道下,確保參數設定如下: - 定義:來自 SCM 的管道腳本 - SCM:設定您的 SCM。確保只建立您的主分支。例如,如果您的主分支名為“main”,請將“\*/main”放在要建置的分支下。 - 腳本路徑:Jenkinsfile ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqy8e5uicfpwm523701p.png) **注意:**您必須將我的[儲存庫](https://github.com/Kelvinskell/microservices-devops-1)分叉到您自己的 GitHub 帳戶。這是您存取儲存庫並能夠對其進行配置所必需的。 完成此操作後,建立 GitHub 個人存取權杖。 我們將使用 GitHub PAT 從 Jenkins 向我們的儲存庫進行身份驗證。 - 連接到 EC2 實例,切換到 jenkins 用戶,建立 SSH 金鑰對。公鑰將作為您的 PAT 上傳到 GitHub,而私鑰將加入到我們的 Jenkins 配置中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dy0fk403lqv50eeh1nd.png) - 返回 Jenkins 伺服器,點擊“新增憑證” ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9t7mfdbdun64k7n80m6.png) 錯誤訊息現已消失。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8cwoepv5sgkzaq16u5f4.png) - 點選“儲存”。 **- 第 7 步:建立 GitHub WebHook** 這對於遠端觸發我們的詹金斯建置是必要的。 - 前往儲存庫的 GitHub Webhook 建立頁面並輸入以下資訊: URL:輸入以下 URL,根據需要替換 \*\*\* 之間的值: ``` ***JENKINS_SERVER_URL***/job/***JENKINS_JOB_NAME***/build?token=***JENKINS_BUILD_TRIGGER_TOKEN*** ``` ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0qb14f6bp78i03f2jbs9.png) **- 第 8 步:執行管道** 現在,我們已經完成了該管道的配置。是時候檢驗我們的工作了。 您可以透過進行變更並推送到 GitHub 儲存庫來觸發管道。如果正確配置了 Web hook 觸發器,這將自動啟動管道。 或者,您只需點擊“立即建置”即可執行管道。 如果一切都按預期配置,您將得到以下輸出: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kzesc1d3m2wtebg5z9al.png) 結論 -- 我們現在已經結束了這個專案的第一部分。在第一部分中,我們配置並設定了持續整合管道。第二部分將涉及使用 ArgoCD 實施 GitOps。 我們將使用 terraform 配置 EKS 集群,然後使用 ArgoCD 持續部署到 EKS 集群。 這裡的想法是,您可以讓單獨的團隊管理流程的兩個部分 - 持續整合和持續部署。從而進一步解耦和簡化整個過程,同時使用 Git 作為我們的單一事實來源。 **PS:**我願意接受遠距 DevOps、雲端和科技寫作機會。 在[LinkedIn](https://linkedin.com/in/kelvin-onuchukwu-3460871a1)上與我聯絡。 --- 原文出處:https://dev.to/kelvinskell/advanced-end-to-end-devops-project-deploying-a-microservices-app-to-aws-eks-using-terraform-helm-jenkins-and-argocd-part-i-3a53

Git 最佳實踐:成為更好的開發人員

如果您是開發人員,您可能每天都會使用名為 Git 的版本控制系統。無論是團隊工作還是個人工作,該工具的使用對於應用程式的開發過程都至關重要。然而,通常會遇到混亂的儲存庫、提交的訊息不明確、無法傳達有用資訊以及濫用分支等問題。對於想要在就業市場中脫穎而出的人來說,了解如何正確使用 Git 並遵循良好實踐至關重要。 --- 目錄 -- 1. [Git 分支的命名約定](#naming-conventions) 2. [分支名稱約定前綴](#branch-names) 3. [提交訊息](#commit-message) 4. [常規提交](#conventional-commits) --- Git 分支的命名約定<a id="naming-conventions"></a> ------------------------------------------ 當我們使用程式碼版本控制時,我們應該遵循的主要良好實踐之一是為分支、提交、拉取請求等使用清晰且描述性的名稱。確保所有團隊成員的簡潔工作流程至關重要。除了提高生產力之外,記錄專案的開發過程還可以簡化團隊合作。透過遵循這些做法,您很快就會看到好處。 基於此,社群建立了一個您可以在專案中遵循的分支命名約定。以下專案的使用是可選的,但它們可以幫助提高您的開發技能。 **1.小寫:**分支名稱不要使用大寫字母,堅持小寫; **2. 連字符分隔:**如果您的分支名稱由多個單字組成,請用連字符分隔它們。遵循烤肉串慣例。避免 PascalCase、camelCase 或 Snake\_case; **3. (az, 0-9):**分支名稱中僅使用字母數字字元和連字符。避免使用任何非字母數字字元; **4. 請不要使用連續的連字號(--)。**這種做法可能會令人困惑。例如,如果您有分支類型(例如功能、錯誤修復、修補程式等),請改用斜線 (/); **5. 避免以連字符結尾分支名稱**。這是沒有意義的,因為連字符分隔單詞,並且末尾沒有單詞可以分隔; **6. 這個做法最重要:**使用描述性的、簡潔的、清晰的名稱來解釋分支上做了什麼; **分支名稱錯誤** - `fixSidebar` - `feature-new-sidebar-` - `FeatureNewSidebar` - `feat_add_sidebar` **好聽的支行名字** - 功能/新側邊欄 - 新增側邊欄 - 修補程式/取得歷史資料上的間隔查詢參數 --- 分支名稱約定前綴<a id="branch-names"></a> --------------------------------- 有時分支的目的並不明確。它可以是新功能、錯誤修復、文件更新或其他任何內容。為了解決這個問題,通常的做法是在分支名稱上使用前綴來快速解釋分支的用途。 - **feature:**它傳達了將要開發的新功能。例如, `feature/add-filters` ; - **發布:**用於準備新版本。前綴`release/`通常用於在合併來自分支主伺服器的新更新以建立版本之前執行諸如上次觸及和修訂之類的任務。例如, `release/v3.3.1-beta` ; - **bugfix:**它表示您正在解決程式碼中的錯誤,並且它通常與問題相關。例如, `bugfix/sign-in-flow` ; - **hotfix:**與bugfix類似,但它與修復生產環境中存在的嚴重錯誤有關。例如, `hotfix/cors-error` ; - **docs:**寫一些文件。例如, `docs/quick-start` ; 如果您正在使用任務管理工作流程,例如 Jira、Trello、ClickUp 或任何可以建立使用者故事的類似工具,則每張卡片都有一個關聯的編號。因此,通常在分行名稱的前綴上使用這些卡號。例如: - `feature/T-531-add-sidebar` - `docs/T-789-update-readme` - `hotfix/T-142-security-path` --- 提交訊息<a id="commit-message"></a> ------------------------------- 讓我們來談談提交訊息。不幸的是,很容易找到帶有“加入了很多東西”或“皮卡丘,我選擇你”之類的提交訊息的專案......是的,我曾經發現一個專案,其中提交訊息與神奇寶貝戰鬥有關。 提交訊息在開發過程中非常重要。創造一段美好的歷史將在你的旅程中給你很多幫助。與分支一樣,提交也有社區建立的約定,您可以在下面了解: - 提交訊息包含三個重要部分:主題、說明和頁腳。提交的主題是必要的,它定義了提交的目的。描述(正文)用於為提交的目的提供額外的上下文和解釋。最後是頁腳,通常用於元資料,例如分配提交。雖然同時使用描述和頁腳被認為是一種很好的做法,但這不是必需的。 - **在主題行中使用祈使語氣。**例如: > `Add README.md` ✅; > `Added README.md` ❌; > `Adding README.md` ❌; - **主題行的第一個字母大寫。**例如: > `Add user authentication` ✅; > `add user authentication` ❌; - **不要以句號結束主題行。**例如: > `Update unit tests` ✅; > `Update unit tests.` ❌; - 主題行限制在**50個字元**以內,即清晰、簡潔; - 將正文包裹在**72 個字元**處,並將主題與**空白行**分隔開; - 如果您的提交正文有多個段落,請**使用空白行將它們分開**; - 如有必要,使用**要點**而不是僅使用段落; --- 常規提交<a id="conventional-commits"></a> ------------------------------------- > “常規提交規範是基於提交訊息的輕量級約定。它提供了一組簡單的規則來建立明確提交歷史記錄。” 以下引用來自Conventional Commit 的官方網站。該規範是社區中提交訊息最常使用的約定。 ### 結構 ``` <type>[optional scope]: <description> [optional body] [optional footer(s)] ``` ### 提交類型 我們要研究的第一個結構是提交類型。它提供了有關此提交中所做操作的清晰上下文。您可以在下面看到提交類型清單以及何時使用它們: - **feat:**引進新功能; - **fix:**修復軟體錯誤; - **refactor:**用於程式碼更改,保留其整體功能; - **chore:**不影響生產程式碼的更新,涉及工具、配置或程式庫調整; - **docs:**文件文件的新增或修改; - **perf:**程式碼更改提高效能; - **style:**與程式碼呈現相關的調整,例如格式和空白; - **test:**測試的包含或修正; - **build:**影響建置系統或外部依賴項的修改; - **ci:** CI 設定檔和腳本的更改; - **env:**描述 CI 流程中設定檔的調整或加入,例如容器配置參數。 ### 範圍 範圍是一種結構,可以在提交類型之後加入以提供額外的上下文資訊: - `fix(ui): resolve issue with button alignment` - `feat(auth): implement user authentication` ### 身體 提交訊息的正文提供了提交引入的更改的詳細說明。它通常會加入在主題行後面的空白行之後。 **例子:** ``` Add new functionality to handle user authentication. This commit introduces a new module to manage user authentication. It includes functions for user login, registration, and password recovery. ``` ### 頁尾 提交訊息的頁腳用於提供與提交相關的附加資訊。這可以包括諸如誰審查或批准了變更之類的詳細資訊。 **例子:** ``` Signed-off-by: John <[email protected]> Reviewed-by: Anthony <[email protected]> ``` ### 突破性變革 指示提交包含可能導致相容性問題或需要修改相關程式碼的重大變更。您可以在頁腳中新增`BREAKING CHANGE`或包含`!`在類型/範圍之後。 ### 使用常規提交的提交範例 ``` chore: add commitlint and husky chore(eslint): enforce the use of double quotes in JSX refactor: type refactoring feat: add axios and data handling feat(page/home): create next routing chore!: drop support for Node 18 ``` **包含主題、正文和頁尾:** ``` feat: add function to convert colors in hexadecimal to rgba Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s. Reviewed-by: 2 Refs: #345 ``` --- 參考 -- - <https://www.conventionalcommits.org> - <https://medium.com/@abhay.pixolo/naming-conventions-for-git-branches-a-cheatsheet-8549feca2534> - <https://se-education.org/guides/conventions/git.html> - <https://cbea.ms/git-commit/> https://blog.geekhunter.com.br/o-que-e-commit-e-como-usar-commits-semanticos/ --- 原文出處:https://dev.to/anthonyvii/be-a-better-developer-with-these-git-good-practices-2dim

如何將 Google Gemini 與 Node.js 結合使用

介紹 -- 過去一年,生成式人工智慧一直是科技領域的熱門話題。每個人都在使用它來建造很酷的專案。谷歌有自己的生成人工智慧,稱為 Gemini。 最近,Google 為 Gemini 開發者推出了 API。它附帶了幾個庫和框架,開發人員可以使用它們將其合併到他們的應用程式中。 在本文中,我們將建立一個簡單的 Node.js 應用程式並將 Google Gemini 整合到其中。我們將使用[**Google Gemini SDK**](https://www.npmjs.com/package/@google/generative-ai) 。 那麼,事不宜遲,讓我們開始吧! 什麼是雙子座? ------- ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lky153eb6l4thz5a246n.png) Google Gemini 是由 Google AI 開發的強大且多方面的 AI 模型。 Gemini 不僅處理文字;也處理文字。它可以理解和操作各種格式,如程式碼、音訊、圖像和視訊。這為您的 Node.js 專案帶來了令人興奮的可能性。 專案設定: ----- ### **1.建立Node.js專案:** 要啟動我們的專案,我們需要設定 Node.js 環境。那麼,讓我們建立一個節點專案。在終端機中執行以下命令。 ``` npm init ``` 這將初始化一個新的 Node.js 專案。 ### 2.安裝依賴項: 現在,我們將安裝專案所需的依賴項。 ``` npm install express body-parser @google/generative-ai dotenv ``` 這將安裝以下軟體包: - express:流行的 Node.js Web 框架 - body-parser:用來解析請求體的中介軟體 - @google/generative-ai:用於存取 Gemini 模型的套件 - dotenv:從 .env 檔案載入環境變數 ### 3.**設定環境變數:** 接下來,我們將建立一個`.env`資料夾來安全地儲存 API 憑證等敏感資訊。 ``` //.env API_KEY=YOUR_API_KEY PORT=3000 ``` ### 4.**取得API金鑰:** 在使用 Gemini 之前,我們需要從 Google Developers Console 設定 API 憑證。為此,我們需要註冊 Google 帳戶並建立 API 金鑰。 登入後,前往<https://makersuite.google.com/app/apikey> 。我們會得到這樣的結果: ![Google AI Studio 控制台的圖片](https://cdn.hashnode.com/res/hashnode/image/upload/v1707836987343/d339372d-195e-47f7-80a0-dc33fef00428.png) 然後我們將點擊“建立 API 金鑰”按鈕。這將產生一個唯一的 API 金鑰,我們將使用它來驗證對 Google Generative AI API 的請求。 > 要測試您的 API,您可以執行以下 Curl 命令: > > ```javascript > 捲曲\\ > -H '內容類型:application/json' \\ > -d '{"contents":\[{"parts":\[{"text":"寫一個關於魔法背包的故事"}\]}\]}' \\ > -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR\_API\_KEY > ```` > > 將`YOUR_API_KEY`替換為我們先前獲得的實際 API 金鑰。 取得 API 金鑰後,我們將使用 API 金鑰更新`.env`檔。 ### 5. 建立 Express 伺服器: 現在,我們將在根目錄中建立一個`index.js`檔案並設定一個基本的express 伺服器。請看下面的程式碼: ``` const express = require("express"); const dotenv = require("dotenv"); dotenv.config(); const app = express(); const port = process.env.PORT; app.get("/", (req, res) => { res.send("Hello World"); }); app.listen(port, () => { console.log(`Server running on port ${port}`); }); ``` 在這裡,我們使用“dotenv”套件從`.env`檔案存取連接埠號碼。 在專案的頂部,我們使用`dotenv.config()`載入環境變數,使其可以在整個檔案中存取。 ### 6. 執行專案: 在此步驟中,我們將向`package.json`檔案新增一個啟動腳本,以輕鬆執行我們的專案。 因此,將以下腳本新增至 package.json 檔案中。 ``` "scripts": { "start": "node index.js" } ``` package.json 檔案應如下所示: ![package.json 文件](https://cdn.hashnode.com/res/hashnode/image/upload/v1707982485800/c23cbb23-68c6-4f6b-942d-dad0dfe9c3fb.png) 要檢查一切是否正常,讓我們使用以下命令執行該專案: ``` npm run start ``` 這將啟動 Express 伺服器。現在如果我們造訪這個 URL <http://localhost:3000/>我們會得到: ![http://localhost:3000/ 的圖片](https://cdn.hashnode.com/res/hashnode/image/upload/v1707838639217/c4d08730-7534-4ad5-a0fd-5962d3eb7cc6.png) 驚人的!專案設定已完成並且執行完美。接下來,我們將在下一節中將 Gemini 加入我們的專案中 新增Google雙子座: ------------ ### 1. 設定路由和中介軟體: 要將 Gemini 新增至我們的專案中,我們將建立一個`/generate`路由,以便與 Gemini AI 進行通訊。 為此,將以下程式碼新增至`index.js`檔案。 ``` const bodyParser = require("body-parser"); const { generateResponse } = require("./controllers/index.js"); //middleware to parse the body content to JSON app.use(bodyParser.json()); app.post("/generate", generateResponse); ``` 在這裡,我們使用`body-parser`中間件將內容解析為 JSON 格式。 ### 2.設定Google Generative AI: 現在,我們將建立一個控制器資料夾,並在其中建立一個`index.js`檔案。在這裡,我們將建立一個新的控制器函數來處理上面程式碼中聲明的生成路由。 ``` const { GoogleGenerativeAI } = require("@google/generative-ai"); const dotenv = require("dotenv"); dotenv.config(); // GoogleGenerativeAI required config const configuration = new GoogleGenerativeAI(process.env.API_KEY); // Model initialization const modelId = "gemini-pro"; const model = configuration.getGenerativeModel({ model: modelId }); ``` 在這裡,我們透過傳遞環境變數中的 API 金鑰來為 Google Generative AI API 建立一個配置物件。 然後,我們透過向配置物件的`getGenerativeModel`方法提供模型 ID(“gemini-pro”)來初始化模型。 > #### **型號配置:** > > 我們也可以依照自己的方便配置模型參數 > > 這些參數值控制模型如何產生回應。 > > 例子: > > ```javascript > 常量產生配置 = { > 停止序列:\[“紅色”\], > 最大輸出令牌:200, > 溫度:0.9, > 頂部P:0.1, > 頂級K:16, > }; > > const model = configuration.getGenerativeModel({ model: modelId, GenerationConfig }); > ```` > #### **安全設定:** > > 我們可以使用安全設定來防止有害的反應。預設情況下,安全性設定配置為阻止在各個維度上具有中等到高可能性不安全的內容。 > > 這是一個例子: > > ```javascript > const { HarmBlockThreshold, HarmCategory } = require("@google/generative-ai"); > > 常量安全設定 = \[ > { > ``` > category: HarmCategory.HARM_CATEGORY_HARASSMENT, > > ``` > ``` > threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH, > > ``` > }, > { > ``` > category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, > > ``` > ``` > threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE, > > ``` > }, > \]; > > const model = genAI.getGenerativeModel({ model: "MODEL\_NAME", safetySettings }); > ```` > > 透過這些安全設置,我們可以透過最大限度地減少有害內容生成的可能性來增強安全性。 ### 3. 管理對話歷史記錄: 為了追蹤對話歷史記錄,我們建立了一個陣列`history`並將其從控制器檔案中匯出: ``` export const history = []; ``` ### 4.**實現控制器功能:** 現在,我們將編寫一個控制器函數`generateResponse`來處理產生路由(/generate)並產生對使用者請求的回應。 ``` /** * Generates a response based on the given prompt. * @param {Object} req - The request object. * @param {Object} res - The response object. * @returns {Promise} - A promise that resolves when the response is sent. */ export const generateResponse = async (req, res) => { try { const { prompt } = req.body; const result = await model.generateContent(prompt); const response = await result.response; const text = response.text(); console.log(text); history.push(text); console.log(history); res.send({ response: text }); } catch (err) { console.error(err); res.status(500).json({ message: "Internal server error" }); } }; ``` 在這裡,我們從請求正文中獲取提示,並使用`model.generateContent`方法根據提示產生回應。 為了追蹤響應,我們將響應推送到歷史陣列。 ### 5. 查看回覆紀錄: 現在,我們將建立一條路線來檢查我們的回應歷史記錄。該端點傳回`history`陣列。 將簡單程式碼加入`./index.js`資料夾中。 ``` app.get("/generate", (req, res) => { res.send(history); }); ``` 我們就完成了! ### 6.執行專案: 現在,我們必須檢查我們的應用程式是否正常運作! 讓我們使用以下命令來執行我們的專案: ``` npm run start ``` ![端子輸出](https://cdn.hashnode.com/res/hashnode/image/upload/v1707855196139/694e7c44-39c4-4ee7-8080-51e0a429c8ec.png) 沒有錯誤!感謝上帝! :) 它運作正常。 ### 7. 檢查功能 接下來,我們將使用 Postman 發出 Post 請求來驗證我們的控制器功能。 我們將使用以下 JSON 負載向<http://localhost:3000/generate>發送 POST 請求: ``` { "prompt": "Write 3 Javascript Tips for Beginners" } ``` ![郵差控制台輸出](https://cdn.hashnode.com/res/hashnode/image/upload/v1707855502196/bb379294-e966-4fa1-b08d-057f852b8c1a.png) 我們得到了回應: ``` { "response": "1. **Use console.log() for Debugging:**\n - console.log() is a useful tool for debugging your JavaScript code. It allows you to inspect the values of variables and expressions, and to see how your code is executing. This can be especially helpful when you encounter errors or unexpected behavior in your program.\n\n2. **Learn the Basics of Data Types:**\n - JavaScript has several built-in data types, including strings, numbers, booleans, and objects. Understanding the properties and behaviors of each data type is crucial for writing effective code. For instance, strings can be manipulated using string methods, while numbers can be used in mathematical operations.\n\n3. **Use Strict Mode:**\n - Strict mode is a way to opt-in to a restricted and secure subset of JavaScript. It helps you to write more secure and reliable code, as it throws errors for common mistakes that would otherwise go unnoticed in regular JavaScript mode. To enable strict mode, simply add \"use strict;\" at the beginning of your JavaScript file or module." } ``` ![郵差控制台輸出](https://cdn.hashnode.com/res/hashnode/image/upload/v1707855825387/a186b78f-e6d9-4197-8b00-ce55766a2e16.png) 偉大的!我們的 Gemini AI 整合正在按預期工作! 此外,我們可以造訪[http://localhost:3000/generate 的](http://localhost:3000/generate)歷史記錄端點來查看對話歷史記錄。 這樣,我們就將 Gemini AI 整合到了 Node.js 應用程式中。在接下來的文章中,我們將探索 Gemini AI 的更多用例。 到那時,請繼續關注! 結論 -- 如果您發現這篇部落格文章有幫助,請考慮與可能受益的其他人分享。您也可以關注我,以了解更多有關 Javascript、React 和其他 Web 開發主題的內容。 要贊助我的工作,請存取: [Arindam 的贊助頁面](https://arindam1729.hashnode.dev/sponsor)並探索各種贊助選項。 在[Twitter](https://twitter.com/intent/follow?screen_name=Arindam_1729) 、 [LinkedIn](https://www.linkedin.com/in/arindam2004/) 、 [Youtube](https://www.youtube.com/channel/@Arindam_1729)和[GitHub](https://github.com/Arindam200)上與我聯絡。 感謝您的閱讀:) ![謝謝](https://cdn.hashnode.com/res/hashnode/image/upload/v1707859424336/0c24ca09-aebb-4e5a-9a59-065ed5a8a9c8.png) --- 原文出處:https://dev.to/arindam_1729/how-to-use-google-gemini-with-nodejs-2d39

只需 4 小時即可製作您的作品集?這是可能的。

你能在 4 小時內完成你的作品集嗎? 聽起來很瘋狂,對吧? > 您的作品集是一個展示您最好的作品、個性和才能的網站。您可以被雇用,人們可以更多地了解您。 建立一個好的投資組合需要遵循各種規則,而條件取決於您的職業。例如,使用者體驗設計師與軟體開發人員的情況可能完全不同。 ### 我認為一個好的作品集應該是什麼樣的。 對我來說,這三件事對於一個好的作品集是必不可少的。 應該很**簡單**。**在職的**。**專業的**。 - 存取作品集的人的體驗應該很好。 - 此人應該能夠與您聯繫(通常是聯絡表單)。 - 作品集應該有一個故事和一點個人風格。 - 它應該放在網域上以提高可信度。 - 它不應該有太多會破壞這一刻的花俏動畫。 ### 為什麼你應該製作一個投資組合? 擁有作品集是一種常見的做法,尤其是當您有多個東西要展示時。 > 並非每個人都有時間或資源來製作完全適合自己需求的獨特投資組合。 嗯,你不應該。 在這個不斷發展的世界中沒有必要。 沒有非常容易使用的程式碼工具。 但是你能在一天甚至一小時內製作出作品集嗎? 我們無法製作最好的作品集,但我們可以製作一個效果良好的體面作品集。 如果您不喜歡這個也沒關係,因為我知道製作具有自己獨特風格的作品集需要花費很多時間。 每個人都有自己的經歷,每個人都試圖根據我們多年來所學到的知識互相幫助。 一點注意 ---- 我是使用者體驗法則的忠實粉絲,知道如何才能做到優秀。 我不會介紹像 Bubble、Framer、Wix 和 Squarespace 這樣的無程式碼工具,因為它們已經很流行了。 --- 我管理多個社群檔案,因此我總是糾結於將觀眾引導至哪些社群媒體。 大多數人都在努力擁有多個配置文件,您一定聽說過`linkfree`或`bento` 。 您不僅僅是一系列關於您的連結。 你必須講述你的故事。這個故事很重要。 作品集是實現這一目標的最佳方式,因此這裡有一些工具,對於想要快速獲得具有良好使用者體驗的作品集的人來說將很有幫助。 您可以使用每個選項連接到您的網域。 我敢打賭這篇文章會讓你大吃一驚。 那我們就開始吧。 --- 1.[聚合工作](https://www.polywork.com/invite/anmol_baranwal-voltorb) ---------------------------------------------------------------- Polywork 是 2022 年最好的產品之一。 它可以在幾秒鐘內將您的 LinkedIn 個人資料轉換為個人網站。 我是 Polywork 的忠實粉絲,因為製作獨特風格的作品集是多麼容易。它有一個圖形編輯器,可以讓你建立部分,你也可以在後台使用人工智慧。 相信我,這就是您所需要的一切。 ![聚合工作](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7gvnoaurlktbmk3gg11.png) 最棒的是它是免費的,您可以以大約 96 美元購買一年。考慮到您將透過高級版本解鎖所有新`fonts` 、 `layouts`和`colors` ,這是一個相當不錯的交易。 這是來自其中一個模板。酷吧 :D ![範本](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drzgta1xnkhg7qjntb6c.png) 在免費版本中,您將獲得一個按鈕來隨機化您的作品集(不同的主題和佈局)。一定要嘗試! 您可以加入這樣的個人化連結。 ![連結](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gyyjdpibakzqineuc2yw.png) 您可以建立亮點以及「關於我」、「社交連結」和個人化「標題」等部分。此外,還有超過 1,000 種針對不同個人資料量身定制的徽章供您選擇。 ![徽章](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcidgo1nwlz5ztcecu36.png) 開箱即用的是,您可以控制您的`social card` 、 `metadata` ,甚至頁面`title` 。這使您可以完全控制,使您的投資組合看起來真實。 ![社群卡](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3uqmx55nzui9arj3e36.png) 有很多話要說,但最方便的功能是有人可以多快與您聯繫。 就好像在短短幾秒鐘內,讓我們面對現實吧,這正是每個投資組合所需要的。 ![接觸](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1ei275dt79noxxvzx9b.png) 您可以查看我的[Polywork 個人資料](https://www.polywork.com/anmolbaranwal)作為樣本。 ![輪廓](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t209dwrmvsxjheiof1o5.png) 關於 Polywork 的最佳簡介之一來自 Craftwork 的共同創辦人[Mike Bifulco](https://www.polywork.com/irreverentmike) 。一探究竟! > 在 Polywork 上創造一個好的作品集甚至不需要超過 4 小時。 2.[來自珍珠](https://peerlist.io/anmolbaranwal/signup) -------------------------------------------------- 我加入 Peerlist 已經幾個月了。我也是他們的測試版行動用戶之一。 除了可以使用同行清單做的其他事情之外,您還可以在網域上製作良好的投資組合。 讓我們討論其中的一些。 ![來自珍珠](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k04zxgouxo1s9i2nbcl4.png) ![來自珍珠](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1doko89swvblfy6z736k.png) 您可以在一個設定檔中包含所有內容。 - GitHub 個人資料、貢獻和固定儲存庫 - DEV + Hashnode + Medium 部落格文章 - YouTube影片 - 子堆疊 + RSS 來源 - Codeforces 和其他編碼設定文件 - 您甚至可以從 Product Hunt 加入您的狩獵或製造商產品 基本上一切:D ![工作](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1gzcfg2leqfhywwab97.png) ![專案](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w422mys01m11kpsjafy7.png) 對等列表上還有一個集合的概念。我在其他地方見過這些,但是將這些放在您的個人資料中確實很棒。 您可以在一個地方新增社交資料卡。 以及您推薦的影片、書籍、播客或自訂內容。 ![收藏品](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pzc67pxqkymprmtl3slr.png) 您甚至可以找到工作並通過您的簡歷和經驗進行驗證。 ![已驗證](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvje9g9alac8lrfs3xdw.png) 您還可以做的另一件事是為某人提供擔保,這可以提高可信度並讓您在個人資料中具有明顯的優勢。它與LinkedIn上的推薦類似,但他們在卡片上做了一些創意。 ![擔保](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/98y314zrn6vvlqhusyp2.png) 您可以免費將您的個人資料連接到您的自訂網域。所以,這就是你的投資組合的樣子。 ![自訂域](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myy4f4xvayyccai07ea5.png) 順便說一句,如果您想知道 -&gt; 您的社交連結將位於底部。 您甚至可以讓您的專案在同行清單中受到關注:D 牽涉的事情很多,我無法一一列舉。 我將在後面的文章中介紹 Peerlist 中令人興奮的內容。 請注意! 3.[展示](https://www.showwcase.com/) ---------------------------------- Showwcase 的主要目標是建立一個強大的開發者社群。 Showwcase 是我幾個月前使用的第一個。 ![展示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tt07du3co8831ltgj7cn.png) 一種簡單而強大的方式來展示您的故事。 最獨特的功能之一是您可以與 Stripe 連接,並且可以從付費追蹤者那裡賺錢。多麼酷啊? 這些是您可以在自訂部分之外製作的一些部分。 ![部分](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ka3xcr5xr6hrqqtuvyvu.png) 您可以輕鬆加入社交連結並查看一些很棒的社區,例如`Open Source` 、 `Hackathons` 、 `Internships` 、 `Portfolios`等等。 ![社群連結](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5vjm3quo7his881j768.png) 您可以獲得推薦,甚至可以加入合作者,這是一件很划算的事情。我從一開始就對此感到驚訝。 ![合作者](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mp5j7vc81r58kzap2tvu.png) 您可以使用您的個人連結產生並製作適合 ATS 的簡歷。 例如,您可以[在這裡](https://www.showwcase.com/anmol-baranwal/resume/ats)找到我的。它使用網站資訊。順便說一下,我的個人資料沒有更新:D 這個社群非常龐大,我已經加入 Showwcase 幾個月了。 ![輪廓](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwovaoiz0dmghy701o2q.png) 您可以在[anmol-baranwal.showwcase.com](https://anmol-baranwal.showwcase.com/)查看我的展示個人資料 請參閱 Showwcase 執行長[Rong](https://www.showwcase.com/tianrongliew)的簡介。 您可以在 Showwcase 進行招募和受聘。 我不喜歡簡歷功能,該功能只是從個人資料中收集資料。有履歷會更有利。 但 Showwcase 已經提供了超出您所需的功能。 4.[閱讀](https://read.cv/) ------------------------ 這是製作作品集的另一種方式。我喜歡 Read 的使用者介面。 最好的事情是有人可以隨時隨地與您聯繫,並且很容易找到帶有甜蜜表情符號的聯繫按鈕。它實際上鼓勵人們使用該聯繫部分。 ![Read.cv 橫幅](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztq28e74gm2uthszqwzm.png) 您可以在 ReadCV 上查看[Edmundo Santos](https://read.cv/edmundo)的個人資料。非常棒。 ![輪廓](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gj2w3o4rdm3wc10vubo.png) 有各種各樣的部分,如社交聯繫、經驗、副專案、演講、寫作等等。 ![社群連結](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iqykez5hsoao2d68zixl.png) 您可以加入的部分數量令人難以置信。它為您在各個部分提供了很大的靈活性。 ![部分](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ft1zlea3csz7rcwmh2rm.png) 當然,還有職位列表和自訂網域,您可以將您的個人資料連結到其中。 佈局是固定的,但足以讓您入門。 我見過很多偉大的人使用“閱讀”作為他們的首選個人資料! --- 這些是您可以使用的一些很棒的資源。 - [超過 25 個帶有程式碼的令人驚嘆的作品集](https://dev.to/anmolbaranwal/stunning-portfolios-that-will-blow-your-mind-fuel-your-creativity-226o) - [無需編碼即可建立令人驚嘆的網站的 20 個工具](https://dev.to/anmolbaranwal/20-game-changing-website-builders-to-build-classy-websites-without-coding-4ll7) - [開發人員的最小組合模板](https://github.com/cobiwave/simplefolio)- GitHub 上有 13k Stars --- 當您將個人資料連接到網域時,沒有人有時間查看您是否自己製作了作品集。只要它對你有用,那就太棒了。 你覺得有用嗎? 除了 ReadCV(新手)之外,我已經嘗試了這些方法好幾個月了,直到那時我才讓你知道。 順便說一句,這些都沒有贊助。我希望如此。哈哈! 如果你堅持使用那些簡單、無聊的工具,你就錯過了。在這個科技世界中,您可以毫無麻煩地建立您的投資組合。 程式設計師會否認建立這樣的作品集行不通,但這不是真的,如果它對你有用那就太好了。 我已經完成了 5000 多個小時的編程,我可以這麼說。 任何好的東西都比糟糕的投資組合好。 正如我在一開始所說的,如果您不想透過程式設計來建立原始作品集也沒關係,因為只有開發人員才應該使用程式碼來完成它。 您還需要什麼? 匆忙!建立您的作品集並向世界展示:D 你認為哪個最好?我喜歡 Polywork &amp; Read。兩者都是我在列表中使用最少的:D 我很高興我終於寫了這篇文章。 我透過深入研究並分享我的經驗來撰寫內容。您可以透過[在 GitHub 上贊助](https://github.com/sponsors/Anmol-Baranwal)我來支持我。 請在[GitHub](https://github.com/Anmol-Baranwal)和[Twitter](https://twitter.com/Anmol_Codes)上關注我:) > 如果您熱衷於贊助這篇文章,請給我發訊息 [email protected] 或在 Twitter 上聯繫我! 🚀 {% 嵌入 https://github.com/Anmol-Baranwal %} 多寫,多啟發。 ![結束 GIF 揮手告別](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ylsck6b9c7ei6makpqd.gif) --- 原文出處:https://dev.to/anmolbaranwal/make-your-portfolio-in-just-4-hours-its-possible-2p3

100 多個帶有原始程式碼的 JavaScript 專案

您是否正在尋找**最好的 JavaScript 專案**來透過原始程式碼增加您的 JavaScript 知識? 在這篇文章中,我分享了 100 個最佳 JavaScript 教學。有各種各樣的專案,例如初學者 JavaScript 專案、中級 JavaScript 專案和高級 JavaScript 專案。 因此,無論您對 JavaScript 有什麼樣的了解,這些專案都會對您有所幫助。 🥳🥳 JavaScript 專案 ------------- JavaScript 是用於 Web 開發的最受歡迎的程式語言之一。它用途廣泛、功能強大,可用於建立各種專案,從簡單的腳本到複雜的 Web 應用程式。 如果您希望提高您的 JavaScript 技能或向潛在雇主展示您的能力,那麼**使用原始程式碼開發 JavaScript 專案**是一個很好的方法。 這裡我給了每個專案的源碼連結。因此,您可以自己練習這些**JavaScript 專案**。 😇 ![成為 JS 專家](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6gnnsmqtf4irq895e0s.jpg) 最佳 JavaScript 專案創意 ------------------ 讓我們來看看前 100 個 JavaScript 專案並討論如何建立它們。 ### 適合初學者的 JavaScript 專案 如果您是一位想要深入編碼世界或尋求擴展技能的初學者,那麼從**簡單的 JavaScript 專案**開始可能是寓教於樂的絕佳方式。這個初學者級 JavaScript 專案一定會對您有幫助。 #### 圖像顏色選擇器 Javascript ![圖像顏色選擇器 Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zswnpz4eqwkbyzlvxsm0.jpg) JavaScript 中的圖像顏色選擇器是一個**簡單的 JavaScript 專案**,可讓使用者直接從圖像中選擇顏色。 它使用戶能夠以互動方式從網頁上顯示的圖像中選取顏色,從而簡化諸如顏色匹配、顏色採樣或從圖像中提取顏色資訊等任務。 [預覽和程式碼](https://codingartistweb.com/2022/11/image-color-picker-javascript/) #### 數字時鐘 JavaScript ![數字時鐘 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9ychtonhcv3ccallsze.jpg) JavaScript 中的數位時鐘是一個基於 Web 的應用程式或 JavaScript 專案,它以數位格式在網頁上顯示當前時間。 這是一個常見且簡單的專案,展示如何使用 JavaScript 在網路上建立動態和互動式內容。 [預覽和程式碼](https://dev.to/nehasoni__/digital-clock-using-javascript-2648) #### 使用 JavaScript 的秒錶 ![使用 JavaScript 的秒錶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3qac5fgivbu54ee4ngy7.jpg) 使用 JavaScript 的秒錶是一個基本的 JavaScript 專案,可讓使用者測量經過的時間。它通常由一個用於開始計時的開始按鈕、一個用於暫停計時器的停止按鈕以及一個用於清除已用時間並重新開始的重置按鈕組成。 [預覽和程式碼](https://dev.to/shantanu_jana/create-a-simple-stopwatch-using-javascript-3eoo) #### RGB 顏色滑桿 JavaScript ![RGB 顏色滑桿 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6pvy2tq5yz6owpj1jqgj.jpg) JavaScript 中的 RGB 顏色滑桿是一個**適合初學者的完美 JavaScript 專案**,它允許使用者透過調整顏色的紅色、綠色和藍色 (RGB) 分量的值來選擇顏色。 此互動式元件通常由三個滑桿或輸入欄位(每個顏色通道一個)以及一個預覽區域組成,該區域根據所選 RGB 值顯示結果顏色。 [預覽和程式碼](https://codingartistweb.com/2023/05/rgb-color-slider-javascript/) #### JavaScript 年齡計算器 ![JavaScript 年齡計算器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g8nuylom621dansflni0.jpg) JavaScript 年齡計算器是適合初學者的簡單 JavaScript 專案,它根據出生日期和當前日期計算一個人的年齡。它通常以生日的形式獲取用戶的輸入,然後計算並顯示他們的年齡。 [預覽和程式碼](https://dev.to/code_mystery/javascript-age-calculator-calculate-age-from-date-of-birth-o9b) #### 懸停時影像縮放 ![懸停時影像縮放](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llrsmmgwkwjo0feoslk5.jpg) JavaScript 中的懸停圖像縮放功能可讓使用者將遊標懸停在圖像上時放大圖像。 此效果可讓使用者更仔細地查看圖像中的細節,從而增強使用者體驗,而無需使用者點擊其他控製或與其他控制項進行互動。 [預覽和程式碼](https://dev.to/shantanu_jana/image-zoom-on-hover-using-javascript-code-demo-328g) #### OTP 生成器 JavaScript ![OTP 生成器 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ad6rb29svfif5b0zg39j.jpg) JavaScript 中的 OTP(一次性密碼)產生器是一種產生唯一程式碼的工具,該程式碼通常由數字或字母數字字元組成,該程式碼一次性使用或在有限時間內有效。 OTP 通常用於 Web 應用程式中的使用者驗證、雙重認證 (2FA) 和帳戶驗證流程。 [預覽和程式碼](https://codingartistweb.com/2023/12/otp-generator/) #### JavaScript 手電筒效果 ![JavaScript 手電筒效果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4gyd41fpahtqfvc860h9.jpg) JavaScript 手電筒效果模擬手電筒或聚光燈在網頁上產生的照明。它是一種互動式視覺效果,透過將注意力集中在頁面的特定元素或區域來增強使用者體驗。 實現手電筒效果通常涉及在網頁上建立遮罩層,並允許使用者使用滑鼠或觸控輸入來移動手電筒。 [預覽和程式碼](https://groundtutorial.com/flashlight-effect-with-html-css-javascript/) #### 影像手風琴 ![影像手風琴](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ziojqmkvy1lbnzf1u7gx.jpg) JavaScript 中的圖像手風琴是一種使用者介面元素,它以垂直或水平堆疊的形式顯示一組圖像,允許用戶展開和折疊單個圖像以顯示與每個圖像相關的更多細節或資訊。 這種互動創造了一種視覺上吸引人的方式來呈現圖像集合,同時節省網頁空間。 [預覽和程式碼](https://groundtutorial.com/image-accordion-using-html-css-javascript/) #### 拖放元素 ![拖放元素](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iohtqrsk79qw7lva8ngx.jpg) JavaScript 拖放功能可讓使用者透過點擊網頁元素並將其拖曳到頁面上的不同位置來與它們互動。 此功能通常在 Web 應用程式中用於執行諸如重新排序清單、在容器之間移動元素或建立互動式使用者介面等任務。 [預覽和程式碼](https://groundtutorial.com/draggable-element-javascript/) #### 隨機數產生器 ![隨機數產生器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpezbhvuioe4hpd87yly.jpg) JavaScript 中的隨機數產生器(RNG)是一種產生指定範圍內或具有特定特徵的隨機數的工具。隨機數通常用於遊戲、模擬、密碼學和統計分析等應用。 JavaScript提供了用於生成隨機數的內建函數和方法,可以根據應用程式的要求進行自訂。 [預覽和程式碼](https://foolishdeveloper.com/random-password-generator-with-javascript/) #### 使用 JavaScript 的小費計算器 ![使用 HTML、CSS 的小費計算器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mcqubvfib3v63y5vhlu.jpg) [預覽和程式碼](https://groundtutorial.com/tip-calculator-html-css-javascript/) #### HTML、CSS、JS 中的雙範圍滑桿 ![HTML、CSS 中的雙範圍滑桿](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6knk161ja08o9t3bsh3.jpg) [預覽和程式碼](https://groundtutorial.com/double-range-slider-in-html-css-javascript/) #### 使用 JavaScript 的倒數計時器 ![使用 JavaScript 的倒數計時器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdi6x6fqxrv8q0o4acs3.jpg) [預覽和程式碼](https://dev.to/shantanu_jana/how-to-build-a-countdown-timer-using-javascript-4f4h) #### 使用 JavaScript 的漸層顏色產生器 ![使用 JavaScript 的漸層顏色產生器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pocfjwy56br7v0zg03kn.jpg) [預覽和程式碼](https://dev.to/shantanu_jana/gradient-color-generator-with-javascript-html-5e3p) #### 使用 Javascript 懸停時圖像縮放 ![使用 Javascript 懸停時圖像縮放](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p50dvkjpswitvvoglt26.jpg) [預覽和程式碼](https://dev.to/shantanu_jana/image-zoom-on-hover-using-javascript-code-demo-328g) ### 中階 JavaScript 專案 對於中級 JavaScript 開發人員來說,有許多專案提供了擴展技能、探索新技術和深入研究 Web 開發概念的機會。以下是為中級開發人員量身定制的 JavaScript 專案想法清單: #### 自訂遊標 ![自訂遊標](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3hg920h1fv61et73v6na.jpg) JavaScript 中的自訂遊標是指以自訂設計的圖形或 HTML 元素取代預設遊標外觀(例如箭頭或手形指標)的能力。這使得網頁開發人員能夠建立與網站主題或品牌相符的獨特且具有視覺吸引力的遊標效果。 [預覽和程式碼](https://foolishdeveloper.com/simple-mouse-cursor-effects-using-javascript-free-code/) #### 回文檢查器應用程式 ![回文檢查器應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eioy07s621eqjc1e5fi5.jpg) JavaScript 中的回文檢查器應用程式是一個簡單的 Web 應用程式,允許使用者輸入一串字元並檢查它是否是回文。 回文是單字、片語、數字或其他字元序列,無論空格、標點符號和大小寫,向前和向後讀起來都相同。 [預覽和程式碼](https://groundtutorial.com/how-to-check-palindrome-in-javascript/) #### 滑動圖像拼圖 Javascript ![滑動圖像拼圖 Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t3gn4vl9mvapb5b3cvot.jpg) JavaScript 中的滑動圖像拼圖是一種經典遊戲,其中圖像被分成較小的圖塊,玩家的目標是透過滑動它們來重新排列圖塊以形成原始圖像。遊戲提供了娛樂性和挑戰性的體驗,同時也考驗玩家解決問題的能力和空間推理能力。 [預覽和程式碼](https://groundtutorial.com/image-puzzle-game-javascript/) #### 使用 Javascript 的貨幣轉換器 ![使用 Javascript 的貨幣轉換器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41ncmzvsq5pj9a6vbjbn.jpg) 使用 JavaScript 的貨幣轉換器是一個 Web 應用程式,允許用戶根據當前匯率將價值從一種貨幣轉換為另一種貨幣。 用戶通常輸入他們想要轉換的金額,選擇來源貨幣,然後選擇目標貨幣。然後,應用程式從 API 檢索最新的匯率並相應地計算換算後的金額。 [預覽和程式碼](https://codingartistweb.com/2023/03/currency-converter-with-javascript/) #### 猜顏色遊戲Javascript ![猜顏色遊戲Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wp4a678c6zsw05ttmd9i.jpg) JavaScript 中的顏色猜測遊戲是一種互動式 Web 應用程式,玩家會看到目標顏色,並且必須猜測該顏色的 RGB 或十六進位程式碼。遊戲通常會提供線索來幫助玩家做出有根據的猜測,例如在正方形中顯示顏色或提供有關顏色成分(紅色、綠色、藍色)或亮度的提示。 [預覽和程式碼](https://groundtutorial.com/color-guessing-game-javascript/) #### 拖放可排序列表 ![拖放可排序列表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgyicbi03b7fac9cje89.jpg) JavaScript 中的拖放可排序清單是一個使用者介面元件,可讓使用者透過將清單中的專案拖曳到新位置來重新排序。 此功能通常在 Web 應用程式中用於執行諸如重新排列待辦事項清單中的專案、對圖庫中的圖像進行排序或在文件管理器中組織文件等任務。 [預覽和程式碼](https://codingartistweb.com/2023/02/drag-and-drop-sortable-list-javascript/) #### 觸碰並拖曳影像滑桿 ![觸碰並拖曳影像滑桿](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4idy4zthbl0v6wv4nf6.jpg) JavaScript 中的觸控和拖曳圖像滑桿是一個使用者介面元件,可讓使用者水平滑動或拖曳圖像以在圖像庫中導航。這種類型的滑桿針對智慧型手機和平板電腦等支援觸控的裝置進行了最佳化,但也可以與桌面瀏覽器上基於滑鼠的互動無縫協作。 [預覽和程式碼](https://groundtutorial.com/touch-image-slider-using-html-css-javascript/) #### 偵測網路速度 JavaScript ![偵測網路速度 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/twmtmhtmia47dzd9mjr5.jpg) 在 JavaScript 中偵測網路速度涉及測量從伺服器下載已知大小的檔案所需的時間。有多種技術可以實現此目的,包括使用 XMLHttpRequest、fetch API 或 HTML5 功能(例如網路資訊 API)。 [預覽和程式碼](https://foolishdeveloper.com/detect-internet-speed-javascript/) #### JavaScript 模因應用程式 ![JavaScript 模因應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j2q1nbc4ckpwemkok3hx.jpg) JavaScript Memes 應用程式是一個 Web 應用程式,允許使用者瀏覽、搜尋和查看 memes。它通常從 API 或資料庫中獲取模因,並將其顯示在用戶友好的介面中,通常具有分頁、排序、過濾和社交共享選項等功能。用戶可以與迷因互動,例如喜歡或分享它們,並且可以上傳自己的迷因。 [預覽和程式碼](https://codingartistweb.com/2022/10/memes-app-html-css-javascript/) #### 多個骰子滾軸 ![多個骰子滾軸](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmfp7t045awkyniij91a.jpg) JavaScript 中的多個骰子滾輪是一個允許使用者滾動多個不同類型的骰子(例如,d4、d6、d8、d10、d12、d20)並顯示結果的工具。 它通常用於《龍與地下城》等桌上角色扮演遊戲 (RPG) 中,或用於各種遊戲或教育目的。 [預覽和程式碼](https://codingartistweb.com/2023/10/multiple-dice-roller/) #### 密碼強度背景 ![密碼強度背景](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hkeyw0wr9bc3ajry59f.jpg) JavaScript 中的密碼強度背景是一項功能,當使用者在輸入欄位中鍵入密碼時,可以提供使用者有關密碼強度的視覺回饋。 此回饋通常是透過更改輸入欄位的背景顏色來提供的,以根據預先定義的標準指示密碼是弱、中等還是強。 [預覽和程式碼](https://codingartistweb.com/2023/10/password-strength-background/) #### 自訂滑鼠懸停效果 ![自訂滑鼠懸停效果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lm53lriqk83p9qbaa8eg.jpg) JavaScript 中的自訂滑鼠懸停效果是一種視覺效果,當使用者將滑鼠懸停在網頁上的元素上時,效果會變更該元素的外觀或行為。 這種效果可以透過使用 CSS 過渡或動畫結合 JavaScript 事件監聽器來偵測滑鼠懸停事件並觸發所需的效果來實現。 [預覽和程式碼](https://codingartistweb.com/2023/12/custom-mouse-hover-effect-with-javascript/) #### 文字相似度檢查器 ![文字相似度檢查器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4m8phfjm38cc0uug7tf.jpg) JavaScript 中的文字相似度檢查器是一種測量兩段文字或文件之間相似性的工具或應用程式。 文字相似度可以使用各種演算法和技術來計算,例如餘弦相似度、Jaccard 相似度、Levenshtein 距離或 TF-IDF(詞頻-逆文件頻率)。 [預覽和程式碼](https://codingartistweb.com/2023/09/text-similarity-checker/) #### 使用 JavaScript 的拋硬幣遊戲 ![使用 JavaScript 的拋硬幣遊戲](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gtd74ui4i8ds0q6k3b6o.jpg) [預覽和程式碼](https://dev.to/shantanu_jana/coin-toss-game-using-javascript-css-1cf0) #### Javascript 剪刀石頭布遊戲 ![Javascript 剪刀石頭布遊戲](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/if9zccyex7o9n0sfgpt7.jpg) [預覽和程式碼](https://groundtutorial.com/rock-paper-scissor-game-javascript/) #### JavaScript 中的右鍵上下文選單 ![JavaScript 中的右鍵上下文選單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ope2hkppwntnhnrgskf9.jpg) [預覽和程式碼](https://dev.to/shantanu_jana/custom-right-click-context-menu-in-javascript-4112) ### 高級 JavaScript 專案 高階 JavaScript 專案通常涉及建立複雜的 Web 應用程式或利用複雜框架、程式庫和 API 的軟體解決方案。 這些專案可能需要前端開發、後端開發或全端開發的專業知識。以下是高級 JavaScript 專案的一些範例: #### 帶有儲存的隨機報價產生器 ![帶有儲存的隨機報價產生器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/duvtugkuougrls6ylu6b.jpg) JavaScript 中帶有儲存功能的隨機報價產生器是一個 Web 應用程式,它向用戶顯示隨機報價,並允許他們保存自己喜歡的報價以供以後查看。 該應用程式通常從預先定義清單或外部 API 檢索報價,在網頁上動態顯示它們,並為使用者提供使用 localStorage 儲存和檢索所選報價的選項。 [預覽和程式碼](https://dev.to/nehasoni__/random-quote-generator-using-html-css-and-javascript-3gbp) #### 使用 JavaScript 的螢幕截圖應用程式 ![使用 JavaScript 的螢幕截圖應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8d0edi0hwddzum4k78ux.jpg) 使用 JavaScript 建立螢幕截圖擷取應用程式涉及利用瀏覽器功能來擷取目前網頁或頁面中特定元素的螢幕截圖。 雖然 JavaScript 本身無法直接截取螢幕截圖,但它可以與瀏覽器 API 互動以觸發螢幕截圖擷取功能。 [預覽和程式碼](https://codingartistweb.com/2023/05/screenshot-capture-app-using-javascript/) #### 警報應用程式 JavaScript ![警報應用程式 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bwr8w5254xt70mzcmtvi.jpg) JavaScript 中的鬧鐘應用程式是一個簡單的應用程式,可讓用戶設定鬧鐘並在特定時間接收通知或警報。 它通常涉及用戶互動來設定所需的鬧鐘時間,然後應用程式在背景執行,在達到設定時間時觸發通知。 [預覽和程式碼](https://www.codingnepalweb.com/simple-alarm-clock-html-javascript/) #### 文字轉語音轉換器 ![文字轉語音轉換器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ogf39oucpxjiof9uipjt.jpg) JavaScript 中的文字轉語音 (TTS) 轉換器是一種將書面文字轉換為口語單字的工具或應用程式。它利用瀏覽器 API 或第三方庫將文字合成為語音,並透過裝置的揚聲器或耳機播放。以下是使用 HTML、CSS 和 JavaScript 的文字轉語音轉換器的基本實作: [預覽和程式碼](https://dev.to/shantanu_jana/text-to-speech-converter-with-javascript-30oo) #### QR 碼產生器 JavaScript ![QR 碼產生器 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m0vcmr2mqoy700z80fcc.jpg) JavaScript 中的 QR 程式碼產生器是一種允許使用者在 Web 應用程式中動態建立快速回應 (QR) 程式碼的工具。 QR 碼是二維條碼,可以包含各種類型的訊息,例如 URL、文字、聯絡資訊或 Wi-Fi 憑證。 [預覽和程式碼](https://dev.to/murtuzaalisurti/how-to-make-a-qr-code-generator-using-vanilla-javascript-3cla) #### 測驗應用程式 JavaScript ![測驗應用程式 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aj3aaque8i3myerfuvtn.jpg) JavaScript 中的測驗應用程式是一個互動式 Web 應用程式,它向使用者提出一系列問題並允許他們選擇答案。回答完所有問題後,應用程式會評估答案並提供回饋,例如總分和正確答案。 [預覽和程式碼](https://codingartistweb.com/2022/06/quiz-app-with-javascript/) #### 使用 Javascript 的天氣應用程式 ![使用 Javascript 的天氣應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3wiz08coy82gig4xe6r.jpg) 使用 JavaScript 的天氣應用程式是一個 Web 應用程式,可為使用者提供特定位置的當前天氣資訊。它通常從天氣 API 檢索天氣資料、處理資料並將其顯示在使用者友好的介面中。 [預覽和程式碼](https://codingartistweb.com/2022/07/weather-app-with-javascript/) #### 自訂視訊播放器 ![自訂視訊播放器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xspo2vpcru8kjvpz6wye.jpg) JavaScript 中的自訂影片播放器是一個 Web JavaScript 專案,它提供用於播放影片內容的自訂使用者介面 (UI)。它通常包括播放、暫停、音量控制、播放速度調整、進度條、全螢幕模式和自訂樣式等功能。 [預覽和程式碼](https://codingartistweb.com/2022/07/custom-video-player-using-javascript/) #### 學生 JavaScript 專案 如果您是學生,那麼這些 JavaScript 專案將對您有很大幫助。 JavaScript 提供了適合不同技能水平的學生(從初學者到高級學習者)的各種專案。 這些專案不僅有助於強化基本概念,也提供 Web 開發的實務經驗。以下是一些**供學生使用的 javascript 專案想法**。 #### 星級評級 JavaScript ![星級評級 JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rm8udkic9kz59e4t528b.jpg) JavaScript 中的星級評級元件是一個學生導向的 JavaScript 專案,可讓使用者透過選擇代表不同滿意度或品質等級的星級來對專案進行評級。通常,它由一組水平排列的可點擊星形圖示組成,其中選定的星形突出顯示以指示使用者的評級。 [預覽和程式碼](https://dev.to/codingnepal/star-rating-system-in-html-css-javascript-97a) #### 五彩紙屑效果 Javascript ![五彩紙屑效果 Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rd6ph3ag1fb4rfnrcgrl.jpg) JavaScript 中的五彩紙屑效果是指一種圖形效果,其中彩色「五彩紙屑」(小紙片或其他材料)在螢幕上散佈或投擲,通常以節日或慶祝的方式進行。 [預覽和程式碼](https://codingartistweb.com/2022/11/confetti-effect-javascript/) #### 刮刮卡Javascript ![刮刮卡Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsaou1alqoca7ks3swe8.jpg) JavaScript 中的刮刮卡是一個**最好的 js 專案**,它模仿刮掉隱藏區域以顯示下面內容的體驗,類似於彩票或促銷卡。在 Web 開發中,這種效果通常使用 HTML5 畫布元素和 JavaScript 來處理互動來實作。 [預覽和程式碼](https://codingartistweb.com/2022/08/scratch-card-with-javascript/) #### 用 Javascript 進行西蒙遊戲 ![用 Javascript 進行西蒙遊戲](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ob7ng3dcqnvhpbv5z7u2.jpg) 西蒙遊戲是一款經典的記憶技巧電子遊戲。它涉及一個播放一系列音調和燈光的設備,然後玩家必須重複該序列。 在 Simon 遊戲的 JavaScript 實作中,您通常會建立一個使用者介面,其中的按鈕代表每種顏色,遊戲邏輯將涉及產生和顯示一系列顏色供玩家模仿。 [預覽和程式碼](https://dev.to/nanythery/coding-my-first-game-with-javascript-simon-says-60d) #### 自訂音樂播放器 Javascript ![自訂音樂播放器 Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yoexh51at6t9gbau64rb.jpg) 使用 JavaScript 建立自訂音樂播放器涉及建立允許使用者控制音訊播放的使用者介面。[預覽和程式碼](https://dev.to/codingnepal/create-custom-music-player-in-javascript-2367) #### 富文本編輯器 ![富文本編輯器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q7ajobdoysmcm3a8mok0.jpg) 富文本編輯器是一個元件,使用戶能夠使用類似於文字處理器的各種樣式和格式選項來編輯和格式化文字。這些編輯器通常提供粗體、斜體、底線、文字對齊、專案符號、編號清單等功能。 [預覽和程式碼](https://codingartistweb.com/2022/04/rich-text-editor-with-javascript/) > 如果您喜歡這些最佳 JavaScript 專案,那麼我將向此列表加入更多專案。請不要忘記喜歡分享和關注。 使用可用原始碼開始**JavaScript 專案**是增強程式設計技能和擴展知識的絕佳方法。透過剖析現有專案,您可以學習其他人的程式碼,了解不同的設計模式,並發現針對常見挑戰的創新解決方案。😊😊 希望您喜歡這些 JavaScript 專案。您將在我給出的源程式碼連結中獲得逐步說明。希望最佳 JavaScript 專案創意能幫助您增加 JavaScript 知識。 ![專案](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/urerlcgx7v4wkjxqvtmk.jpg) 因此,捲起袖子,深入研究原始碼,開始建立一些令人驚奇的東西! 🥳🥳 --- 原文出處:https://dev.to/shantanu_jana/100-javascript-projects-with-source-code-59lo