🔍 搜尋結果:result

🔍 搜尋結果:result

我如何建立 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

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

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

😻建立您自己的 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

如何將 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

為 Lambda 函數設定外觀的 5 種方法:DevTools 比較指南

長話短說 ---- 俗話說,給貓剝皮有多種方法…在科技界,給 Lambda 函數剝皮有 5 種方法 🤩 我們將比較 5 個開發工具 - ✅[翼](#1-wing) - ✅[刷子](#2-pulumi) - ✅ [AWS-CDK](#3-awscdk) - ✅ [Terraform 的 CDK](#4-cdk-for-terraform) - ✅[地形](#5-terraform) 介紹 -- 當開發人員試圖彌合開發和 DevOps 之間的差距時,我認為比較程式語言和 DevTools 會很有幫助。 讓我們從一個簡單函數的想法開始,該函數將文字檔案上傳到我們的雲端應用程式中的儲存桶。 下一步是展示實現這一目標的幾種方法。 **注意:**在雲端開發中,管理權限和儲存桶身分、打包執行時程式碼以及處理基礎架構和執行時的多個檔案會增加開發過程的複雜性。 ![讓我們開始吧](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kd69m1hntlzy1icrlba.gif) 讓我們深入研究一些程式碼! --- 1.[翼](https://github.com/winglang/wing) --------------------------------------- > [安裝 Wing](https://www.winglang.io/docs)後,讓我們建立一個檔案: `main.w` > 如果您不熟悉 Wing 程式語言,請查看[此處的](https://github.com/winglang/wing)**開源儲存庫** ``` bring cloud; let bucket = new cloud.Bucket(); new cloud.Function(inflight () => { bucket.put("hello.txt", "world!"); }); ``` **讓我們詳細分析一下上面程式碼中發生的情況。** > `bring cloud`是 Wing 的導入語法 > **建立一個雲端儲存桶:** `let bucket = new cloud.Bucket();`初始化一個新的雲端儲存桶實例。 > 在後端,Wing 平台在您的雲端供應商環境中配置一個新儲存桶。此桶用於儲存和檢索資料。 > **建立雲端函數:** `new cloud.Function(inflight () => { ... });`語句定義了一個新的雲函數。 > 該函數被觸發後,將執行其主體內定義的操作。 > `bucket.put("hello.txt", "world!");`上傳一個名為 hello.txt 的文件,其中包含內容世界!到之前建立的雲端儲存桶。 編譯並部署到 AWS ---------- - `wing compile --platform tf-aws main.w` - `terraform apply` 就是這樣,Wing 負責處理複雜性(權限、在執行時程式碼中獲取存儲桶身份、將執行時程式碼打包到存儲桶中、必須編寫多個文件 - 用於基礎設施和執行時)等。 更不用說它會產生 IAC(TF 或 CF),以及可以使用現有工具部署的 Javascript。 但在開發時,您可以使用本機模擬器獲得即時回饋並縮短迭代周期 ![翼控制台](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yea3ozudbqxbxr0hh1t5.gif) Wing 甚至還有一個[遊樂場](https://www.winglang.io/play/?code=YgByAGkAbgBnACAAYwBsAG8AdQBkADsACgAKAGwAZQB0ACAAYgB1AGMAawBlAHQAIAA9ACAAbgBlAHcAIABjAGwAbwB1AGQALgBCAHUAYwBrAGUAdAAoACkAOwAKAAoAbgBlAHcAIABjAGwAbwB1AGQALgBGAHUAbgBjAHQAaQBvAG4AKABpAG4AZgBsAGkAZwBoAHQAIAAoACkAIAA9AD4AIAB7AAoAIAAgAGIAdQBjAGsAZQB0AC4AcAB1AHQAKAAiAGgAZQBsAGwAbwAuAHQAeAB0ACIALAAgACIAdwBvAHIAbABkACEAIgApADsACgB9ACkAOwA%3D),您可以在瀏覽器中試用! 2.[刷子](https://www.pulumi.com) ------------------------------ > 步驟1:初始化一個新的Pulumi專案 ``` mkdir pulumi-s3-lambda-ts cd pulumi-s3-lambda-ts pulumi new aws-typescript ``` > 步驟 2. 編寫程式碼以將文字檔案上傳到 S3。 這將是您的專案結構。 ``` pulumi-s3-lambda-ts/ ├─ src/ │ ├─ index.ts # Pulumi infrastructure code │ └─ lambda/ │ └─ index.ts # Lambda function code to upload a file to S3 ├─ tsconfig.json # TypeScript configuration └─ package.json # Node.js project file with dependencies ``` 讓我們將此程式碼加入**index.ts** ``` import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws"; // Create an AWS S3 bucket const bucket = new aws.s3.Bucket("myBucket", { acl: "private", }); // IAM role for the Lambda function const lambdaRole = new aws.iam.Role("lambdaRole", { assumeRolePolicy: JSON.stringify({ Version: "2023-10-17", Statement: [{ Action: "sts:AssumeRole", Principal: { Service: "lambda.amazonaws.com", }, Effect: "Allow", Sid: "", }], }), }); // Attach the AWSLambdaBasicExecutionRole policy new aws.iam.RolePolicyAttachment("lambdaExecutionRole", { role: lambdaRole, policyArn: aws.iam.ManagedPolicy.AWSLambdaBasicExecutionRole, }); // Policy to allow Lambda function to access the S3 bucket const lambdaS3Policy = new aws.iam.Policy("lambdaS3Policy", { policy: bucket.arn.apply(arn => JSON.stringify({ Version: "2023-10-17", Statement: [{ Action: ["s3:PutObject", "s3:GetObject"], Resource: `${arn}/*`, Effect: "Allow", }], })), }); // Attach policy to Lambda role new aws.iam.RolePolicyAttachment("lambdaS3PolicyAttachment", { role: lambdaRole, policyArn: lambdaS3Policy.arn, }); // Lambda function const lambda = new aws.lambda.Function("myLambda", { code: new pulumi.asset.AssetArchive({ ".": new pulumi.asset.FileArchive("./src/lambda"), }), runtime: aws.lambda.Runtime.NodeJS12dX, role: lambdaRole.arn, handler: "index.handler", environment: { variables: { BUCKET_NAME: bucket.bucket, }, }, }); export const bucketName = bucket.id; export const lambdaArn = lambda.arn; ``` 接下來,為 Lambda 函數程式碼建立**lambda/index.ts**目錄: ``` import { S3 } from "aws-sdk"; const s3 = new S3(); export const handler = async (): Promise<void> => { const bucketName = process.env.BUCKET_NAME || ""; const fileName = "example.txt"; const content = "Hello, Pulumi!"; const params = { Bucket: bucketName, Key: fileName, Body: content, }; try { await s3.putObject(params).promise(); console.log(`File uploaded successfully at https://${bucketName}.s3.amazonaws.com/${fileName}`); } catch (err) { console.log(err); } }; ``` > 步驟 3:TypeScript 設定 (tsconfig.json) ``` { "compilerOptions": { "target": "ES2018", "module": "CommonJS", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "**/*.spec.ts"] } ``` **建立Pulumi專案後,會自動產生yaml檔案。** **pulumi.yaml** ``` name: s3-lambda-pulumi runtime: nodejs description: A simple example that uploads a file to an S3 bucket using a Lambda function template: config: aws:region: description: The AWS region to deploy into default: us-west-2 ``` 與 Pulumi 一起部署 ------------- 確保正確設定包含`index.js`檔案的`lambda`目錄。然後,執行以下命令來部署您的基礎架構: `pulumi up` --- 3. [AWS-CDK](https://aws.amazon.com/cdk) ---------------------------------------- > 步驟1:初始化一個新的CDK專案 ``` mkdir cdk-s3-lambda cd cdk-s3-lambda cdk init app --language=typescript ``` > 第 2 步:新增依賴項 ``` npm install @aws-cdk/aws-lambda @aws-cdk/aws-s3 ``` > 步驟 3:在 CDK 中定義 AWS 資源 文件: **index.js** ``` import * as cdk from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; export class CdkS3LambdaStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Create the S3 bucket const bucket = new s3.Bucket(this, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code }); // Define the Lambda function const lambdaFunction = new lambda.Function(this, 'MyLambda', { runtime: lambda.Runtime.NODEJS_14_X, // Define the runtime handler: 'index.handler', // Specifies the entry point code: lambda.Code.fromAsset('lambda'), // Directory containing your Lambda code environment: { BUCKET_NAME: bucket.bucketName, }, }); // Grant the Lambda function permissions to write to the S3 bucket bucket.grantWrite(lambdaFunction); } } ``` > 步驟 4:Lambda 函數程式碼 在 pulumi 目錄中建立與上面相同的檔案結構: **index.ts** ``` import { S3 } from 'aws-sdk'; const s3 = new S3(); exports.handler = async (event: any) => { const bucketName = process.env.BUCKET_NAME; const fileName = 'uploaded_file.txt'; const content = 'Hello, CDK! This file was uploaded by a Lambda function!'; try { const result = await s3.putObject({ Bucket: bucketName!, Key: fileName, Body: content, }).promise(); console.log(`File uploaded successfully: ${result}`); return { statusCode: 200, body: `File uploaded successfully: ${fileName}`, }; } catch (error) { console.log(error); return { statusCode: 500, body: `Failed to upload file: ${error}`, }; } }; ``` 部署 CDK 堆疊 --------- 首先,編譯 TypeScript 程式碼: `npm run build` ,然後 將您的 CDK 部署到 AWS: `cdk deploy` --- [4.Terraform 的 CDK](https://developer.hashicorp.com/terraform/cdktf) -------------------------------------------------------------------- > 步驟1:初始化一個新的CDKTF專案 ``` mkdir cdktf-s3-lambda-ts cd cdktf-s3-lambda-ts ``` 然後,使用 TypeScript 初始化一個新的 CDKTF 專案: ``` cdktf init --template="typescript" --local ``` > 步驟 2:安裝 AWS Provider 並新增相依性 ``` npm install @cdktf/provider-aws ``` > 第 3 步:定義基礎設施 編輯 main.ts 以定義 S3 儲存桶和 Lambda 函數: ``` import { Construct } from 'constructs'; import { App, TerraformStack } from 'cdktf'; import { AwsProvider, s3, lambdafunction, iam } from '@cdktf/provider-aws'; class MyStack extends TerraformStack { constructor(scope: Construct, id: string) { super(scope, id); new AwsProvider(this, 'aws', { region: 'us-west-2' }); // S3 bucket const bucket = new s3.S3Bucket(this, 'lambdaBucket', { bucketPrefix: 'cdktf-lambda-' }); // IAM role for Lambda const role = new iam.IamRole(this, 'lambdaRole', { name: 'lambda_execution_role', assumeRolePolicy: JSON.stringify({ Version: '2023-10-17', Statement: [{ Action: 'sts:AssumeRole', Principal: { Service: 'lambda.amazonaws.com' }, Effect: 'Allow', }], }), }); new iam.IamRolePolicyAttachment(this, 'lambdaPolicy', { role: role.name, policyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole', }); const lambdaFunction = new lambdafunction.LambdaFunction(this, 'MyLambda', { functionName: 'myLambdaFunction', handler: 'index.handler', role: role.arn, runtime: 'nodejs14.x', s3Bucket: bucket.bucket, // Assuming the Lambda code is uploaded to this bucket s3Key: 'lambda.zip', // Assuming the Lambda code zip file is named lambda.zip environment: { variables: { BUCKET_NAME: bucket.bucket, }, }, }); // Grant the Lambda function permissions to write to the S3 bucket new s3.S3BucketPolicy(this, 'BucketPolicy', { bucket: bucket.bucket, policy: bucket.bucket.apply(name => JSON.stringify({ Version: '2023-10-17', Statement: [{ Action: 's3:*', Resource: `arn:aws:s3:::${name}/*`, Effect: 'Allow', Principal: { AWS: role.arn, }, }], })), }); } } const app = new App(); new MyStack(app, 'cdktf-s3-lambda-ts'); app.synth(); ``` > 步驟 4:Lambda 函數程式碼 Lambda 函數程式碼應使用 TypeScript 編寫並編譯為 JavaScript,因為 AWS Lambda 本機執行 JavaScript。以下是您需要編譯和壓縮的 Lambda 函數的範例**index.ts** : ``` import { S3 } from 'aws-sdk'; const s3 = new S3(); exports.handler = async () => { const bucketName = process.env.BUCKET_NAME || ''; const content = 'Hello, CDKTF!'; const params = { Bucket: bucketName, Key: `upload-${Date.now()}.txt`, Body: content, }; try { await s3.putObject(params).promise(); return { statusCode: 200, body: 'File uploaded successfully' }; } catch (err) { console.error(err); return { statusCode: 500, body: 'Failed to upload file' }; } }; ``` 您需要將此 TypeScript 程式碼編譯為 JavaScript,對其進行壓縮,然後手動或使用腳本將其上傳到 S3 儲存桶。 確保 LambdaFunction 資源中的 s3Key 指向儲存桶中正確的 zip 檔案。 編譯和部署您的 CDKTF 專案 ---------------- 使用`npm run build`編譯專案 **生成 Terraform 配置文件** 執行`cdktf synth`指令。此命令執行您的 CDKTF 應用程式,該應用程式在`cdktf.out`目錄中產生 Terraform 設定檔( `*.tf.json`檔案): **部署您的基礎設施** `cdktf deploy` 5.[地形](https://developer.hashicorp.com/terraform) ------------------------------------------------- > 第 1 步:Terraform 設定 定義您的 AWS 供應商和 S3 儲存桶 使用以下內容建立名為**main.tf**的檔案: ``` provider "aws" { region = "us-west-2" # Choose your AWS region } resource "aws_s3_bucket" "lambda_bucket" { bucket_prefix = "lambda-upload-bucket-" acl = "private" } resource "aws_iam_role" "lambda_execution_role" { name = "lambda_execution_role" assume_role_policy = jsonencode({ Version = "2023-10-17" Statement = [ { Action = "sts:AssumeRole" Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }, ] }) } resource "aws_iam_policy" "lambda_s3_policy" { name = "lambda_s3_policy" description = "IAM policy for Lambda to access S3" policy = jsonencode({ Version = "2023-10-17" Statement = [ { Action = ["s3:PutObject", "s3:GetObject"], Effect = "Allow", Resource = "${aws_s3_bucket.lambda_bucket.arn}/*" }, ] }) } resource "aws_iam_role_policy_attachment" "lambda_s3_access" { role = aws_iam_role.lambda_execution_role.name policy_arn = aws_iam_policy.lambda_s3_policy.arn } resource "aws_lambda_function" "uploader_lambda" { function_name = "S3Uploader" s3_bucket = "YOUR_DEPLOYMENT_BUCKET_NAME" # Set your deployment bucket name here s3_key = "lambda.zip" # Upload your ZIP file to S3 and set its key here handler = "index.handler" role = aws_iam_role.lambda_execution_role.arn runtime = "nodejs14.x" environment { variables = { BUCKET_NAME = aws_s3_bucket.lambda_bucket.bucket } } } ``` > 步驟 2:Lambda 函數程式碼 (TypeScript) 為 Lambda 函數建立 TypeScript 檔案**index.ts** : ``` import { S3 } from 'aws-sdk'; const s3 = new S3(); exports.handler = async (event: any) => { const bucketName = process.env.BUCKET_NAME; const fileName = `uploaded-${Date.now()}.txt`; const content = 'Hello, Terraform and AWS Lambda!'; try { await s3.putObject({ Bucket: bucketName!, Key: fileName, Body: content, }).promise(); console.log('Upload successful'); return { statusCode: 200, body: JSON.stringify({ message: 'Upload successful' }), }; } catch (error) { console.error('Upload failed:', error); return { statusCode: 500, body: JSON.stringify({ message: 'Upload failed' }), }; } }; ``` 最後,將 Lambda 函數程式碼上傳到指定的 S3 儲存桶後,執行`terraform apply` 。 --- 我希望您喜歡在我們的雲端應用程式中編寫將文字檔案上傳到儲存桶的函數的五種簡單方法的比較。 正如您所看到的,除了一段程式碼之外,大多數程式碼都變得非常複雜。 結論! --- [![泰勒絲](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eawrorng82lqrl8lt54w.gif)](https://github.com/winglang/wing) 點擊圖片⬆️ ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/urh67eorvbn49cgbaae6.gif) > 如果您對 Wing 感興趣並且喜歡我們如何簡化雲端開發流程,請給我們一顆 ⭐ 顆星。 {% cta https://github.com/winglang/wing %} 請star ⭐ Wing {% endcta %} --- 原文出處:https://dev.to/winglang/5-ways-to-write-a-simple-function-in-your-cloud-app-1jgl

從 Next.js 到 Rails 再到 Elixir:我的 React.js 倦怠之旅

我自 2019 年以來一直是 Web 開發人員。我使用 React.js 和基於 React 的框架,如 Gatsby、Next、Remix、Astro 和 Hydrogen。我從來沒有對這些工具感到完全滿意,但是,作為一個深入 JS 生態系統的初學者,我從同行那裡聽到的都是這樣的話:「這就是方式,任何其他程式語言要么慢,要么老」。 ![就是這樣](https://media.giphy.com/media/stnjSj2vpLcM4rwmEH/giphy.gif) 結果,我習慣了巨大的複雜性:多個獨立的儲存庫、數千個函式庫和框架來實現簡單的事情、GraphQL、微服務、無伺服器、靜態網站產生、增量靜態再生、部分水化、 redux 、redux-thunk、babel、webpack、react 伺服器元件、伺服器操作等。這個清單還可以再持續 10 分鐘。 直到有一天我說**受夠了!** 讓我們來看看我慢慢發瘋的完整時間線。這需要一段時間,在閱讀長篇文章之前,請隨意煮點咖啡! --- ## 倦怠的時間表 ### [Gatsby.js](https://www.gatsbyjs.com/) 我記得完成我的訓練營並想:“我終於能夠建立我的作品集了!”,所以我做到了。只有一個小問題,我想在 Google 上建立索引,但是使用舊的「create-react-app」使這項任務幾乎不可能完成。很快我了解了 SEO 和 React 的水合循環,這讓我找到了這個問題的「解決方案」:Gatsby.js。靜態網站產生的想法對當時的我來說簡直是革命性的,畢竟沒有什麼比預先渲染 HTML 檔案更快了,對吧? 我決定透過閱讀文件來學習這個新框架,讓我告訴你,這**不是**一次有趣的體驗。我以前從未聽說過 GraphQL,顯然,您需要它來產生所有靜態檔案(到底是什麼???)。我問我的一些網友,很難學習這些過度設計的廢話是否正常,他們回答說「技能問題,再努力一點!」。於是我更加努力,終於學會了之後,我把我的個人網站移植到了Gatsby上。 ![再努力一點](https://media.giphy.com/media/gzRiZROEyDCznPofKj/giphy.gif) 我的大部分頁面都成功在 Google 上建立了索引,幾個月來,我對結果非常滿意。然後另一個問題出現了:我的**很多**開發者朋友開始說“Gatsby 死了!建立 Next 是為了簡化靜態站點生成並提供伺服器端渲染”。 ### [Next.js](https://nextjs.org/) 我快速瀏覽了 Next 文件並**立即**愛上了它。我能夠在沒有 GraphQL 的情況下用三分之一的程式碼做與 Gatsby 相同的事情!我再次將我的作品集移植到另一個框架:Next。 這次我確實有一次美好的經驗。部署到 Vercel 輕而易舉,「getStaticProps」和「getServerSideProps」功能很簡單,但功能非常強大,我可以選擇每個頁面的渲染樣式,整體來說非常靈活。 不幸的是,我透過慘痛的教訓學到了一些東西:在 JavaScript 生態系統中,所有美好的事情都會結束。 ### [混音](https://remix.run/) 我清楚記得 Remix 發佈時的情景。多名科技影響者開始發布有關它的內容(一如既往)。然而,當時我在主頁上看到它不支援靜態網站生成,只支援伺服器端渲染,所以我想「等一下,這些年來投資於 [JAMstack](https://jamstack.org/) 都被扔在這裡了嗎?不可能,這個框架不會長久」。然而,令我驚訝的是,Remix 不僅生存了下來,而且還被 Shopify 收購 https://shopify.engineering/remix-joins-shopify ,並成為 Next 的重要競爭對手。 幾個月過去了,我決定嘗試看看。我再一次感到驚訝,Remix 的主要座右銘是使用 Web 基礎知識,而不是像 Next 這樣過於複雜的快取系統。因此,在Remix 中編碼時,我腦中需要的思維模型要簡單10 倍:沒有全域狀態管理器,只需使用URL,更少的客戶端狀態,將所有邏輯移至伺服器,並使用cookie,無需使用完整堆疊中間的 REST API 非常簡單,只需將資料庫查詢移至「loader」函數即可。 ### 離開矩陣 ![離開矩陣](https://media.giphy.com/media/11e0gEWxYoSYTK/giphy.gif) 然後,突然間,真相呈現在我面前,我服下了紅色藥丸。我的腦海中開始浮現出多個問題:Remix 不就像所有其他「古老而無聊」的框架(如 Rails、Laravel 和 Django)一樣嗎?幾十年來,我們一直在使用伺服器端渲染進行全端 Web 開發,但 JavaScript 黑手黨集體認為這種方法是垃圾,將所有內容移至客戶端才是未來。難道同一個黑手黨認為 Rails 一直都是對的嗎?用 JS 框架做所有那些過度設計的怪物不是正確的舉動嗎?我開始質疑一切。這種「新」的 Web 開發方式更加簡單、快速。 ### 我已經完成了 Next 和 Vercel 我透過 [Next.js 應用程式路由器](https://nextjs.org/docs/app) 達到了臨界點。以下是 Vercel 向 Next 推送的所有錯誤的完整清單: - 曾經簡單的:「getStaticProps」和「getServerSideProps」函數現在變得複雜而麻煩。目前,沒有特定的位置來新增 API 呼叫或資料庫查詢,您可以將它們寫入任何您想要的位置!在多年前使用 PHP 犯了同樣的錯誤之後,我們開始再次將業務邏輯與 UI 混合。難道前端開發者不吸取過去的教訓嗎?如果我刪除按鈕會發生什麼事?這是否會破壞我的使用者身份驗證流程,因為資料庫呼叫位於其中?您的前端應該 100% 可廢棄且可更換。你相對於競爭對手的競爭優勢在於業務邏輯,它應該與 UI 層完全隔離。 ![可怕的 Next.js 程式碼](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kp41ds14loo21xgimcza.png) - 接下來是伺服器優先。這聽起來沒那麼糟吧?畢竟,這解決了 SEO 問題並立即向用戶展示新鮮內容。問題在於,大多數現有的 Next 程式碼庫都依賴客戶端程式庫,例如樣式元件和一些全域狀態管理器。這是什麼意思?隨著此類重大變化的不斷發生,您的應用程式將在幾週而不是幾年內變成遺留軟體。更多的時間花在保持所有依賴項最新上,而不是做重要的事情:發布功能。 - Vercel 從 Meta 聘請了多名 React 核心團隊成員。這帶來了嚴重的利益衝突,因為這些工程師現在(據稱)正在發布有利於 Next 的功能,而不是優先考慮那些可以幫助所有基於 React 的框架(如 Remix)的功能。 ![Vercel 正在破壞 React](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ye40ykjgrd3z10t5nx7.png) 我再也受不了了。我對自己說:你知道嗎?我厭倦了一遍又一遍地重新學習相同的框架,我完全不同意這種新的範式。 毫不奇怪,其他內容創作者也經歷了類似的情況: https://youtu.be/zkCBSz353fc?si=z3-FDVgcB3xfp06h https://youtu.be/Zt8mO_Aqzw8?si=10fy1d-ZoB7t3Uc_ --- ## 啟蒙之路 我非常累。在厭倦了所有的 React 工具後,我開始了尋找更簡單的 Web 框架的旅程。以下是我一直在尋找的先決條件: - 含電池 - 約定優於配置 - 良好的開發體驗 - 現代化且高性能的前端 我的第一個反應是查看 [Stack Overflow Survey 2023](https://survey.stackoverflow.co/2023/#section-most-popular-technologies-web-frameworks-and-technologies) 中的頂級框架。我立即從清單中刪除了所有與 JS、C# 和 Java 相關的內容。我從來沒有興趣學習後兩個,它們看起來醜陋且冗長。所以剩下的選項是:Laravel (PHP)、Django (Python)、Rails (Ruby) 和 Phoenix (Elixir)。 Python 是我在網路工程學位期間使用的語言,我獲得了非常愉快的體驗。 Django 似乎遵循約定優於配置的理念,但最終讓我放棄它的是沒有一個好的內建工具來在前端工作。論壇上的大多數人都說他們使用[HTMX](https://htmx.org/) 和[Alpine](https://alpinejs.dev/),但是,兩者都是您需要安裝的外部依賴項。 放棄Laravel 是非常困難的,因為它具有驚人的成本效益,有數百個官方軟體包可以處理新創公司可能需要的幾乎所有內容,例如託管、身份驗證、條紋支付等。對於前端,他們創造了[慣性。Node.js](https://inertiajs.com/),這是一種非常簡單而優雅的方式,可以在前端使用 React 的同時保持 Laravel 的高生產力和強大功能。百分之百誠實地說,我沒有選擇 Laravel 的唯一原因是 PHP 的語法,它看起來很難看,到處都是一堆 `$` 和 `->`。 ### Ruby on Rails Ruby on Rails 無需介紹。它是 Web 開發框架的元年,其革命性的「15 分鐘建立部落格」至今仍令人印象深刻。在我開始抱怨我發現的所有問題之前,讓我們先從好的方面開始。 與 Python 類似,Ruby 是一種可以向非技術人員展示的語言,他們會理解該軟體想要做什麼。它是**迄今為止**我見過的最容易閱讀和最美麗的語言。我很快就意識到,[編寫視覺上令人愉悅的程式碼](https://world.hey.com/dhh/a-writer-s-ruby-2050b634) 是Rails 團隊的首要任務,這對我來說來說是新的。 更不用說 Rails 幾乎發明了「包含電池」和「約定優於配置」的哲學,所以這不會是一個問題。在一份文件中,我提供了任何類型的 Web 應用程式所需的一切。 在前端,有 [Hotwire](https://hotwired.dev/),這是一種非常簡單且輕量級的方法,可以實現 SPA 框架提供的所有 UX 改進。我一直很好奇測試這個庫的極限,它看起來非常有前途。 好吧,Rails 在紙面上滿足了我想要的框架的所有先決條件。我們來試試吧!我在本地測試的第一件事是“railsscaffold”命令。我立即感到震驚。一個指令就能產生 CRUD 所需的一切?決不! ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58lbioexmot9412kojr5.png) 在 Node + React 領域,要實現相同的目標,我需要手動編寫所有程式碼(這裡沒有生成器)並安裝一堆程式庫,例如:Vite、prisma、express、react router、redux、redux-thunk 、 vitest、cypress 、react 測試庫、zod、typescript、eslint、prettier、1000 個不同的插件,甚至可能還有GraphQL 或tRPC。基本上就是一個已經有 900 個依賴項的 package.json。 在“railsscaffold”最初的震驚之後,當我從控制器打開程式碼時,我再次震驚了: ``` class ArticlesController < ApplicationController def index @articles = Article.all end def show @article = Article.find(params[:id]) end def new @article = Article.new end def create @article = Article.new(article_params) if @article.save redirect_to @article else render :new, status: :unprocessable_entity end end def edit @article = Article.find(params[:id]) end def update @article = Article.find(params[:id]) if @article.update(article_params) redirect_to @article else render :edit, status: :unprocessable_entity end end def destroy @article = Article.find(params[:id]) @article.destroy redirect_to root_path, status: :see_other end private def article_params params.require(:article).permit(:title, :body) end end ``` 這是所有後端程式碼嗎?只需幾行?這不可能!這非常簡單,看起來就像一個「低程式碼」工具。它簡單、優雅、可讀性極強,這是我們在 JS 領域很少見的。 好吧,好吧,你現在一定在想:「這個來自網路的瘋狂 React 開發者說他最終使用了 Elixir,所以 ruby 一定有問題!」。你是對的,我的匿名朋友,有些事情讓我很惱火,讓我們談談。 首先,我們需要解決房間裡的大象:從 React + Typescript 轉向動態類型語言並不容易。從我開始編寫程式碼的那一刻起,我的 VScode 上就沒有出現智慧感知或充滿程式碼建議的下拉式選單,我感到盲目和迷失。這是一種可怕的感覺,我可能會在函數名稱上輸入錯誤,直到網站投入使用時才意識到!我知道我們可以編寫測試,但這是我希望在 IDE 上立即辨識的錯誤類型,而不是在測試或部署期間辨識。 另一件我以為我會喜歡但最終討厭它的事情是:太多的魔法。在 Typescript 程式碼庫中,我可以點擊任何類別或函數的頂部,前往原始程式碼並查看其實作方式。在 Rails 上,我到底在哪裡進行驗證(例如)?我是否在控制器內建立私有函數?有專門的資料夾嗎?不,正確的位置是在模型內部。為什麼?因為這就是它的工作原理,所以您要么採用該約定,要么很難編寫 Ruby 程式碼。我根本無法對一切在幕後如何運作產生“直覺”,我必須盲目地相信維護者在組織一切方面做得很好。 為了解決我的挫折感,我開始寫前端程式碼。如何建立元件? [部分](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials)。如何定義該元件的 prop 類型?沒有辦法做到這一點,您需要打開它並直觀地查找其中的所有變數。做一些互動怎麼樣?建立國家?嗯,有帶有 [Stimulus](https://stimulus.hotwired.dev/) 的 Hotwire,但是正如您所看到的,您需要手動建立“重新渲染”功能,它沒有找到一種方法像React 這樣改變狀態後自動重新渲染頁面。 ``` // src/controllers/slideshow_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "slide" ] initialize() { this.index = 0 this.showCurrentSlide() } next() { this.index++ this.showCurrentSlide() } previous() { this.index-- this.showCurrentSlide() } showCurrentSlide() { this.slideTargets.forEach((element, index) => { element.hidden = index !== this.index }) } } ``` 我再一次感到沮喪。我非常接近找到完美的框架!如果 Rails 失敗,我想嘗試的下一個框架是什麼?靈丹妙藥。 ### 長生不老藥和鳳凰 我必須說實話,我已經沒有耐心了。我嘗試了多種不同的生態系統,我幾乎確信要堅持使用 Ruby on Rails,並放棄對完美的追求。直到我的 YouTube 推薦部分出現了一個影片: https://www.youtube.com/live/bfrzGXM-Z88?si=Xsa7yCKeVSY5R3sT 堅持,稍等!在這裡我們可以看到一位 React 開發人員說了很多關於函數式程式設計、Elixir 和 Phoenix Live View 的好話。也許我應該嘗試一下! 我做的第一件事就是打開Elixir 和Phoenix 的文件,我真的很喜歡這樣一個事實:所有包都使用[Hex Docs](https://hexdocs.pm/) 以相同的方式進行記錄,您只需要取得習慣於單一介面以學習新事物。 另一個好處是,您只需閱讀文件即可真正學習 Elixir,無需昂貴的課程!在其他所有生態系統中,我必須透過付費課程學習語言,然後透過閱讀文件來學習框架。 然後是時候開始編寫程式碼了。很快我就明白函數式程式設計與 OOP 有很大不同。我們來做一個小小的比較: ``` // JS const obj = {name: "daniel"} obj.age = 25 // result: obj = {name: "daniel", age: 25} ``` ``` # Elixir obj = %{name: "daniel"} obj = Map.put(obj, :age, 25) # result: obj = %{name: "daniel", age: 25} ``` 或者您可以使用管道運算子透過更簡單的語法實現相同的效果: ``` # Elixir with pipe operator obj = %{name: "daniel"} |> Map.put(:age, 25) # result: obj = %{name: "daniel", age: 25} ``` 最初,您可能會發現它的可讀性較差且更複雜,但我保證隨著時間的推移它會變得有意義!嗯,至少對我來說是這樣。身為 React 開發人員,我已經習慣了到處都可以看到多個函數,甚至前端元件也是函數!更不用說建立類別有時被 JavaScript 黑手黨視為一種程式碼味道。我的大腦已經針對這種新範式進行了“塑造”,這對我來說很自然。自從我在大學獲得網路工程學位以來,我上過幾門關於物件導向程式設計的課程,但它從來沒有「受歡迎」。我無法將複雜的問題建模為類別和物件。隨著時間的推移,使用多個函數來「改變」一個變數是我在腦海中建模的方式。 主要框架怎麼樣?包含鳳凰電池嗎?約定優於配置? **是的!** 老實說,生態系統與 Rails 不在同一水平,但已經達到了 95%。除非您需要非常具體的功能,Phoenix 都能滿足您的需求。 我幾乎被 Elixir 迷住了,我的清單中缺少兩件事:良好的開發人員體驗和現代/高效能的前端程式碼。 José Valim 宣布他正在嘗試為該語言加入類型,但 Elixir 目前還沒有這些類型,所以我很擔心。如何在沒有類型的情況下獲得智能感知和自動完成?很快我發現這些功能不一定相關。在 VScode 上安裝 [ElixirLS 擴充功能](https://marketplace.visualstudio.com/items?itemName=JakeBecker.elixir-ls) 後,我感到很驚訝。可以在隨機資料夾的隨機模組內定義函數,將其導入其他位置,並取得它的智慧感知和文件!我從靜態類型語言中獲得了這些好處,而無需編寫類型的麻煩,簡直太棒了! https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/ 我對前端的最後一個擔憂是由 Phoenix [Live View](https://hexdocs.pm/phoenix_live_view/welcome.html) 解決的。在程式碼方面,這正是文件主頁中讓我信服的部分: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5sjzj90khytebnk523fm.png) 您可以為每個元件定義“props”,如果類型不匹配,您的 IDE 中會出現錯誤,就像 React 一樣!感人的! 使用者體驗怎麼樣?每當使用者點擊連結時是否會載入整個頁面?一定不行!即時視圖與客戶端建立 WebSocket 連接,然後每次頁面轉換只是透過 Websocket 進行內容交換,不會發出新的 HTTP 請求。此外,所有狀態都在伺服器端進行管理,這意味著 Trello 等豐富的用戶體驗過去由於加載過多的 JavaScript 而在客戶端非常卡頓,現在變得非常快! Elixir 處理所有複雜的狀態邏輯並將頁面的更新部分傳送到前端。看看這裡的完整解釋: https://youtu.be/wrmVk2czqMg?si=ZoWAlPjQC-svmV3Y 由於我們使用 WebSocket 來建立 UI,因此建立像 Twitter 這樣的「即時」應用程式只需要幾行程式碼! https://youtu.be/MZvmYaFkNJI?si=gAow6oIjgf8_OTkg ## 結論 可以肯定地說,「完美的技術堆疊」並不存在。解決所有問題的靈丹妙藥是我們在腦中創造的幻覺,以不斷尋找和建構最優化的工具。 然而,在個人層面上,完美的堆疊確實存在。因為每個開發人員都有偏好,您可以輕鬆找到適合您標準的工具。如果你有和我類似的旅程,完美的可能就是長生不老藥和鳳凰!所以試試看吧,也許你會像我現在一樣喜歡它。 如果您讀到了這篇文章的結尾,那您就太棒了!非常感謝您抽出寶貴的時間,希望我能為您的職業生涯帶來一些價值。 ![結束](https://media.giphy.com/media/lD76yTC5zxZPG/giphy.gif) --- 原文出處:https://dev.to/danielbergholz/from-nextjs-to-rails-then-elixir-my-journey-through-reactjs-burnout-h8d

Docker 絕對初學者

Docker 是一個工具,允許開發人員將他們的應用程式及其所有依賴項打包到一個容器中。然後,這個容器就可以輕鬆地在任何安裝了 Docker 的機器上傳輸和執行,而不必擔心環境的差異。這就像是打包和執行軟體的標準化方式。 **容器是什麼?** ![Docker 容器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imsgbstga86vnxjwgebr.png) 容器就像一個小包,其中包含程式執行所需的一切,可以輕鬆地在不同電腦上移動和執行,而不會造成任何麻煩。 最酷的部分是這個迷你電腦(容器)就像一個披著斗篷的超級英雄。它可以在任何電腦上執行,無論它們有多麼不同,因為它自帶特殊的環境。這是一種保持軟體井然有序的方式,並確保它無論在哪裡都能以相同的方式運作。 ![容器](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1z5zcued8ya2onerpzwt.png) **為什麼我們需要 Docker?** 1. **一致性:** Docker 確保軟體在您的電腦、您朋友的電腦或任何電腦上以相同的方式運作。它使事情保持一致。 2. **可移植性:** 您可以將您的軟體及其朋友打包到 Docker 容器中,並且它可以移動到任何地方。這就像將您的遊戲及其所有規則放在手提箱中並在朋友家中玩。 3. **隔離:** Docker 容器就像小氣泡。氣泡內發生的事只會留在氣泡內。這意味著容器中的一個程式不會幹擾容器外的另一個程式。 4. **效率:** Docker有助於節省電腦資源。您可以讓許多容器在同一台電腦上執行,而不會相互妨礙,而不是讓一整台電腦只用於一個程式。 5. **速度:** Docker 讓啟動、停止和共享軟體變得快速、輕鬆。這就像打開和關閉遊戲機一樣 - 快速而簡單。 **什麼是 Docker 映像?** ![Docker 映像](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/817t1rsad728snnighkj.png) Docker 映像像是程式及其運作所需的所有內容的快照。它是一個打包版本,包括程式碼、工具和設置,就像包含所有成分的餅乾食譜的快照一樣。 **圖像是配方,容器是當您按照該配方實際製作和執行程序時所得到的。** **一些基本的 Docker 命令。** ![基本 Docker 指令](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xq1pwt8896lvster3ppl.png) 1. **`docker執行nginx`** - 此命令告訴 Docker 使用「nginx」映像執行容器。這就像告訴 Docker 啟動一個預製程式的新實例(nginx,它是一個 Web 伺服器)。 2. **`docker ps`** - 顯示正在執行的容器的清單。這就像檢查當前正在執行哪些程式。 3. **`docker ps -a`** - 顯示所有容器的列表,包括已停止的容器。這就像檢查您執行過的所有程式的歷史記錄。 4. **`docker stopsilly_sammet'** - 停止名為「silly_sammet」的正在運作的容器。這就像關閉當前正在執行的程式。 5. **`docker rmsilly_sammet'** - 刪除名為「silly_sammet」的已停止容器。這就像丟掉你不再需要的程式的指令一樣。 6. **`docker 映像`** - 列出您擁有的所有 Docker 映像。這就像查看您可以執行的所有不同程式的選單一樣。 7. **`docker rmi nginx`** - 刪除“nginx”圖像。這就像刪除您不想再使用的程式的配方。 8. **`docker拉nginx`** - 從網路下載「nginx」映像。這就像從食譜中獲取新食譜一樣。 9. **`docker 執行 ubuntu sleep 5`** - 使用「ubuntu」映像檔執行容器並使其休眠 5 秒。這就像啟動一個程序,只是等待一小會兒,然後就停止了。 10. **`docker exectracted_mcclintock cat /etc/hosts`** - 在名為「distracted_mcclintock」的正在執行的容器內執行命令。這就像在食譜書中偷看特定頁面一樣。 11. **`docker run -d kodekloud/simple-webapp`** - 從「kodekloud/simple-webapp」鏡像以分離模式執行容器。這就像啟動一個程式並讓它在背景執行。 12. **`docker Attach a043d`** - 將您的終端附加到 ID 為「a043d」的正在執行的容器。這就像跳入正在執行的程式來查看發生了什麼。 **一些 Docker 概念:** 1. **使用標籤執行:** - 標籤就像程式的版本。它指定您要執行哪個版本。 - 範例程式碼:`docker run nginx:latest` - 這將執行最新版本的 Nginx 程式。 2. **使用標準輸入執行:** - STDIN 就像在鍵盤上打字一樣。有些程式需要您的輸入。 - 範例程式碼:`docker run -i -t ubuntu` - 這會在 Ubuntu 容器內執行互動終端,讓您可以鍵入命令。 3. **使用連接埠映射執行:** - 連接埠就像門。程式使用它們與外界進行通訊。 - 範例程式碼:`docker run -p 8080:80 nginx` - 這將執行 Nginx,並打開電腦連接埠 8080 上的門,將其連接到容器的連接埠 80。 4. **使用磁碟區映射執行:** - 磁碟區就像共用資料夾。它們讓您可以將東西存放在容器之外。 - 範例程式碼:`docker run -v /your/local/folder:/container/folder nginx` - 這將執行 Nginx 並將電腦上的資料夾連接到容器內的資料夾。 5. **檢查容器:** - 檢查就像仔細檢查正在執行的程式。 - 範例程式碼:`docker檢查container_name` - 這為您提供有關正在執行或已停止的容器的詳細資訊。 6. **容器日誌:** - 日誌就像日記。他們記錄程式正在做什麼。 - 範例程式碼:“docker 日誌容器名稱” - 這會向您顯示特定容器的日誌或活動。 ##環境變數 環境變數就像程式用來尋找重要資訊的便利筆記,有點像是程式可以理解和更好工作的秘密訊息! 1. **Python腳本(app.py)中的環境變數:** - 假設您有一個用 Python 寫的程式 (app.py)。您可能想要在不更改程式碼的情況下自訂它。您可以使用環境變數。 - 範例程式碼(app.py): ``` import os app_color = os.getenv("APP_COLOR", "default_color") print(f"The app color is {app_color}") ``` - 正常運作腳本:`python app.py` - 以特定顏色執行:`export APP_COLOR=blue; python 應用程式.py` 2. **在 Docker 中使用 ENV 變數:** - Docker 容器也可以使用環境變數。這就像是向容器內的程式發出指令。 - 範例程式碼: - `docker run -e APP_COLOR=green simple-webapp-color` - 這會執行 Docker 容器(`simple-webapp-color`)並將環境變數 `APP_COLOR` 設為「綠色」。 3. **檢查環境變數:** - 有時,您會想要檢查正在執行的容器正在使用哪些環境變數。 - 範例程式碼:`docker檢查blissful_hopper` - 此命令提供有關名為“blissful_hopper”的容器的詳細訊息,包括其環境變數。 簡單來說,環境變數就像程式(或 Docker 容器)可以讀取以了解如何行為的小註釋。您可以在執行程式之前設定這些註釋,程式將使用它們來自訂自身。第二個範例中的「export」指令就像在執行程式之前寫一條註釋,告訴它如何運作。 “docker Inspect”指令就像是在容器內部查看它有什麼註解。 ## Docker 映像 **Docker 檔案:** Dockerfile 就像是 Docker 建立映像的一組指令。這就像是烤蛋糕的食譜。 ``` # Use the Ubuntu base image FROM Ubuntu # Update apt repository RUN apt-get update # Install dependencies using apt RUN apt-get install -y python # Install Python dependencies using pip RUN pip install flask RUN pip install flask-mysql # Copy source code to /opt folder COPY . /opt/source-code # Set the working directory WORKDIR /opt/source-code # Specify entry point to run the web server ENTRYPOINT ["flask", "run"] ``` **建立自己的圖像的步驟:** 1. 使用上述內容建立一個名為「Dockerfile」的檔案。 2. 將其保存在與原始碼相同的目錄中。 **建置 Docker 映像:** 在終端機中執行以下命令: ``` docker build -t your-image-name . ``` 此命令告訴 Docker 使用目前目錄中的 Dockerfile (`.`) 建置映像,並使用您選擇的名稱對其進行標記 (`-t your-image-name`)。 **分層架構:** ![分層架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i91e79dg61wnbbfp62m.png) - 將 Docker 映像視為一個分層蛋糕。 Dockerfile 中的每個指令都會在映像上新增一層。 - 圖層可重複使用。如果您變更程式碼中的某些內容,Docker 只會重建受影響的層,從而提高效率。 **Docker 建置輸出:** - 當您建置映像檔時,Docker 會顯示流程中的每個步驟。如果發生故障,它會給您錯誤訊息。 **你可以容器化什麼?** - 幾乎所有東西!應用程式、服務、資料庫、網站,基本上任何軟體都可以容器化。 - 這就像將您的軟體放入一個盒子中,以便它可以在任何地方執行而不會造成麻煩。 ## 什麼是 Docker CMD 與 ENTRYPOINT **Docker 中的`CMD`:** - 將 CMD 視為啟動容器時程式執行的預設操作。 - 這就像說,“嘿,當你執行這個容器時,默認執行此操作。” - 範例:`CMD ["flask", "run"]` 表示當容器啟動時,它會自動執行 Flask Web 伺服器。 **CMD 範例:** ``` FROM alpine CMD ["sleep", "5"] ``` 在此範例中,當您使用此映像執行容器時,它會自動休眠 5 秒。 **Docker 中的`ENTRYPOINT`:** - 將 ENTRYPOINT 視為容器所做的主要事情。就好像boss的命令一樣。 - 它設定一個預設應用程式在容器啟動時執行,但您仍然可以根據需要覆蓋它。 - 範例:`ENTRYPOINT ["flask", "run"]` 表示容器主要用於執行 Flask Web 伺服器,但如果需要,您仍可新增更多指令。 **入口點範例:** ``` FROM alpine ENTRYPOINT ["sleep"] CMD ["5"] ``` 在這裡,主要目的是睡眠,如果您願意,您仍然可以覆蓋睡眠持續時間。 在這兩種情況下,容器在啟動時只會休眠幾秒鐘。主要區別在於如何提供參數以及它們是否可以輕鬆覆蓋。 CMD 就像在說,“這是默認要做的事情”,而 ENTRYPOINT 就像在說,“這是主要要做的事情,但如果你願意,你可以稍微調整一下。”它們都有助於定義容器啟動時執行的操作。 ## Docker 中的網路: Docker 網路幫助容器(程式)相互通信,確保它們可以順利地協同工作。 **預設網路:** - Docker 建立預設網路供容器通訊。 - 範例程式碼:`docker run ubuntu --network=host` - 這使用主機網路執行 Ubuntu 容器,這意味著它與主機共享網路命名空間。 **使用者定義的網路:** - 您可以建立自己的網路以更好地組織和控制。 - 範例程式碼: ``` docker network create --driver=bridge --subnet=182.18.0.0/16 custom-isolated-network ``` - 這將建立一個名為「custom-isolated-network」的使用者定義的橋接網絡,具有特定的子網。 **上市網路:** - 您可以查看您擁有的所有網路。 - 範例程式碼:`docker network ls` **檢查網路:** - 您可以檢查特定網路的詳細資訊。 - 範例程式碼:`docker網路檢查blissful_hopper` - 這顯示有關名為「blissful_hopper」的網路的詳細資訊。 **嵌入式 DNS:** - Docker 有一個內建的 DNS 系統,供容器透過名稱相互查找。 - 範例程式碼:`mysql.connect(mysql)` - 這可能是程式碼中的一行,其中名為「mysql」的服務使用 Docker 的 DNS 連接到另一個名為「mysql」的服務。 ## Docker 儲存: ![Docker 儲存](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7i54a6m0o1tb0812xbjk.jpg) Docker 儲存就像使用容器時決定將資料保存在哪裡一樣。您可以將它們保留在容器內,使用磁碟區在容器之間共用它們,或將它們儲存在容器外部以妥善保管。 **Docker中的檔案系統:** - Docker 使用分層架構來建立映像。 Dockerfile 中的每個指令都會在檔案系統中新增一個新圖層。 ``` # Dockerfile FROM Ubuntu RUN apt-get update && apt-get install -y python RUN pip install flask flask-mysql COPY . /opt/source-code WORKDIR /opt/source-code ENTRYPOINT ["flask", "run"] ``` - Dockerfile 中的層: - 第 1 層:Ubuntu 基礎層 - 第 2 層:apt 軟體包的更改 - 第 3 層:pip 套件的變化 - 第 4 層:原始碼 - 第 5 層:使用「flask」指令更新入口點 - 第 6 層:容器層 **影像圖層:** - 當您建立 Docker 映像時,它由唯讀層組成。每一層代表影像的變化或加入。 - 第 1 層:Ubuntu 基礎層 - 第 2 層:apt 軟體包的更改 - 第 3 層:pip 套件的變化 - 第 4 層:原始碼 - 第 5 層:使用「flask」指令更新入口點 ``` # Build the Docker image docker build -t mmumshad/my-custom-app . ``` **容器層:** - 當您執行 Docker 容器時,會在唯讀映像層上方新增一個讀寫層。該層特定於正在執行的容器。 - 第 6 層. 容器層 ``` # Run the Docker container docker run mmumshad/my-custom-app ``` **數量:** - 卷是一種在容器外部保存資料的方法。它們就像外部記憶體。 ``` # Create a Docker volume docker volume create data_volume # Use the volume in a container docker run -v data_volume:/var/mysql mysql ``` - 您也可以使用「-v」將特定目錄從主機掛載到容器: ``` # Mount a host directory to a container directory docker run -v /path/on/host:/var/mysql/mysql -d mysql ``` - docker run --mount 指令用於將主機上的特定目錄或檔案掛載到正在執行的 Docker 容器中。 ``` docker run --mount type=bind,source=/mysql,target=/var/mysql mysql ``` **儲存驅動程式:** - Docker 使用儲存驅動程式來管理資料的儲存和存取方式。一些常見的儲存驅動程式包括 AUFS、ZFS、BTRFS、Device Mapper、Overlay 和 Overlay2。 [在 Docker 管理資料](https://docs.docker.com/storage/) [關於儲存驅動程式](https://docs.docker.com/storage/storagedriver/) [卷](https://docs.docker.com/storage/volumes/) ## Docker 組合 ![Docker Compose](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjyro6o2844s2or1b83c.jpeg) Docker Compose 是一個方便的工具,可幫助您輕鬆執行和連接不同的軟體服務,就好像它們都是同一事件的一部分一樣。 **Docker Compose 基礎:** 1. **執行單一容器:** - 通常,您可以像這樣執行單獨的 Docker 容器: ``` docker run mmumshad/simple-webapp docker run mongodb docker run redis:alpine docker run ansible ``` 2. **Docker 撰寫文件(`docker-compose.yml`):** - Docker Compose 允許您在一個簡單的檔案中定義所有這些服務: ``` # docker-compose.yml version: '3' services: web: image: 'mmumshad/simple-webapp' database: image: 'mongodb' messaging: image: 'redis:alpine' orchestration: image: 'ansible' ``` - 此檔案描述您要執行的服務(「web」、「database」、「messaging」、「orchestration」)、它們各自的映像以及任何其他配置。 3. **使用 Docker Compose 執行:** - 要一起啟動所有這些服務: ``` docker-compose up ``` - Docker Compose 負責啟動「docker-compose.yml」檔案中定義的所有容器。 4. **使用 Docker Compose 建置:** - 您也可以使用 Docker Compose 建置映像: ``` docker-compose build ``` - 此指令建置「docker-compose.yml」檔案中指定的映像。 **執行連結容器:** - 如果您要透過連結執行單一容器: ``` docker run -d --name redis redis docker run --name voting-app -p 5000:80 --link redis:redis voting-app docker run --name result-app -p 5001:80 --link db:db result-app docker run -d --name worker --link db:db --link redis:redis worker ``` - 在 Docker 中撰寫: ``` # docker-compose.yml version: '3' services: vote: image: 'voting-app' ports: - '5000:80' links: - 'redis:redis' result: image: 'result-app' ports: - '5001:80' links: - 'db:db' worker: image: 'worker' links: - 'db:db' - 'redis:redis' db: image: 'db' redis: image: 'redis' ``` Docker Compose 可讓您在單一檔案中描述整個應用程式堆疊,從而輕鬆管理、執行和連接不同的服務。這就像在一份計劃中寫下活動的所有任務,然後 Docker Compose 為您處理設定。 [Docker Compose 概述](https://docs.docker.com/compose/) [Docker 撰寫文件](https://docs.docker.com/engine/reference/commandline/compose/) ## Docker 註冊表 ![Docker 註冊表](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bzflp82qyg36y8fcf8k8.png) Docker 註冊表是人們儲存和分享 Docker 映像的地方,使其他人可以輕鬆使用和執行他們的軟體。它就像一個大型線上程式庫,可以輕鬆下載並在不同電腦上使用。 **Docker 註冊表基礎知識:** 1. **公共登記處:** - Docker 映像可以在 Docker Hub 等公共註冊表中儲存和共用。 - 例: ``` docker pull nginx ``` 2. **私人登記處:** - 有時,您可能希望將圖像保存在您自己的私人註冊表中。 - 例: - 登入私人註冊表: ``` bash docker login private-registry.io ``` - 從私有註冊表中的映像執行容器: ``` docker run private-registry.io/apps/internal-app ``` 3. **部署您自己的私有註冊表:** - 您可以為您的團隊或公司部署自己的私人註冊表。 - 例: - 在您的電腦上執行私有註冊表: ``` docker run -d -p 5000:5000 --name registry registry:2 ``` - 為私人註冊表標記您的圖像: ``` bash docker image tag my-image localhost:5000/my-image ``` - 將映像推送到您的私人註冊表: ``` bash docker push localhost:5000/my-image ``` - 從您的私人註冊表中提取映像: ``` bash docker pull localhost:5000/my-image ``` 4. **從遠端私有註冊表中提取:** - 您也可以使用 IP 位址或網域從遠端私有註冊表中提取映像。 - 例: ``` docker pull 192.168.56.100:5000/my-image ``` Docker 註冊表就像一個儲存空間,人們在其中保存和共享 Docker 映像。您可以將公用註冊表用於廣泛使用的映像,也可以根據您的特定需求設定自己的私人註冊表。它就像一個用於共享和儲存軟體藍圖(圖像)的特殊庫。 ## Docker 引擎 ![Docker 引擎](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hc6r4dvo9kg9xqgwsrbb.jpg) 想像一下,你有一個魔盒(Docker Engine),可以為你執行和管理各種程式(容器)。 Docker Engine 就像是這個魔盒的大腦。 1. **Docker 守護程式:** - 守護程式就像魔法盒的看門人。它始終在那裡,隨時準備接受指示並確保一切順利進行。 2. **REST API:** - 將 REST API 視為一組允許您與魔盒對話的規則。它就像你和守護程式用來溝通的語言。你告訴守護程式要做什麼,它會理解,因為你們說的是同一種語言。 3. **Docker CLI(命令列介面):** - Docker CLI 就像是用來命令守護程式的魔杖。您輸入指令,守護程式就會按照您的指示進行操作。這就像說「Abracadabra」就能讓事情發生。 **連線到遠端 Docker 引擎:** 連接到遠端 Docker 引擎可讓您控制另一台機器上的容器,且設定約束可確保容器僅使用指定的資源。 1. **Docker主機IP:** - 您可以使用 IP 位址和連接埠連接到不同電腦上的 Docker 引擎。 - 例: ``` docker -H=remote-docker-engine:2375 run nginx ``` - 這告訴您的本機 Docker CLI 與遠端 Docker 引擎進行通訊。 2. **有約束地執行容器:** - Docker 允許您設定容器的資源限制,例如 CPU 和記憶體限制。 - 例: ``` docker run --cpus=0.5 ubuntu docker run --memory=100m ubuntu ``` - 這些指令限制容器僅使用半個 CPU 核心和 100 MB 記憶體。 當然,讓我們簡化一下PID命名空間的概念: **命名空間PID:** PID 命名空間可讓您為容器中的進程(如程式或任務)建立單獨的區域,因此它們有自己的一組「票號」(進程 ID),不會與容器外的進程發生衝突。 **範例程式碼:** 1. **使用主機 PID 命名空間執行容器:** - 這表示容器與主機共用相同的「票號」。 ``` docker run --pid=host ubuntu ``` 2. **執行具有隔離 PID 命名空間的容器:** - 這表示容器有自己的一組獨立於主機的「票號」。 ``` docker run --pid=container ubuntu ``` 在第一個範例中,容器與進程交互,就好像它與主機位於同一空間中一樣。在第二個範例中,容器有自己的進程隔離空間。這就像在大型活動中擁有一個私人區域,您的團隊有自己的一套票號,讓您可以獨立於活動的其餘部分進行操作。 **容器化概念:** 1. **進程 ID 命名空間:** - 容器有自己獨立的流程 ID (PID) 空間,因此容器內的流程與容器外的流程是分開的。 - 例: ``` docker run --pid=host ubuntu ``` - 此指令使用主機的 PID 命名空間來執行容器,因此它共用相同的程序。 2. **網路命名空間:** - 容器也有自己獨立的網路命名空間,這意味著它們可以有自己的網路配置。 - 例: ``` docker run --net=host nginx ``` - 此指令使用主機的網路命名空間來執行容器。 3. **Unix分時命名空間:** - 此命名空間允許容器擁有自己的時間視圖,與主機和其他容器分開。 - 例: ``` docker run --uts=host ubuntu ``` - 此指令使用主機的 Unix 時間共用命名空間來執行容器。 4. **進程間掛載命名空間:** - Mount命名空間隔離檔案系統,讓容器擁有自己的檔案系統視圖。 - 例: ``` docker run --mount=type=bind,source=/host/folder,target=/container/folder ubuntu ``` - 此指令將主機中的資料夾安裝到容器中。 當然!我們來簡化一下cgroup的概念: **C組:** cgroup(控制組的縮寫)可協助在不同進程或容器之間管理和分配系統資源,例如 CPU 和記憶體。它們確保沒有任何一個進程或容器耗盡所有可用資源,從而保持一切平衡。 **範例程式碼:** 1. **使用 Cgroup 設定 CPU 限制:** - 這就像說聚會上的每位客人只能吃一定數量的食物。 ``` docker run --cpus=0.5 ubuntu ``` - 這限制容器僅使用一半的 CPU 核心。 2. **使用 Cgroup 設定記憶體限制:** - 這就像說每位客人只能在舞池上佔據一定的空間。 ``` docker run --memory=100m ubuntu ``` - 這限制容器僅使用 100 MB 記憶體。 [Docker 引擎概述](https://docs.docker.com/engine/) [使用 Docker Engine API 進行開發](https://docs.docker.com/engine/api/) [執行時指標](https://docs.docker.com/config/containers/runmetrics/#control-groups) ## Linux容器與Windows容器的概念: **Linux 容器(預設):** Linux 容器是一種打包和執行軟體及其所需一切的方法,它們最適合執行 Linux 的電腦。 **Windows 容器:** Windows 容器是一種打包和執行軟體的方式,就像 Linux 容器一樣,但它們設計用於執行 Windows 的電腦。 **Windows 容器基礎:** 1. **集裝箱類型:** - Windows 容器有兩種主要類型:Windows Server Core 和 Nano Server。 - **Windows Server Core:** 將其視為功能更齊全的容器,適合各種應用程式。 - **Nano Server:** 將其視為一個輕量級容器,專為特定的、簡約的用例而設計。 2. **基礎鏡像:** - 基礎映像就像是建立容器時開始使用的空白畫布。 - 例: ``` docker pull mcr.microsoft.com/windows/servercore:ltsc2019 ``` - 此指令擷取 Windows Server Core 基礎映像。 - 例: ``` docker pull mcr.microsoft.com/windows/nanoserver:ltsc2019 ``` - 此命令提取 Nano Server 基礎映像。 3. **支援的環境:** - Windows 容器可以在特定版本的 Windows 作業系統上運作。 - 例: - 您可以在 Windows Server 2016 上執行 Windows 容器。 - 例: - 您可以在 Windows 10 專業版和企業版上執行 Windows 容器,並使用 Hyper-V 隔離容器進行額外隔離。 ## 容器編排 ![容器編排](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lyleybojw3xmr2dgfep.png) 容器編排是一種管理和協調多個容器的方法,確保它們無縫協作來執行應用程式,就像一個超級智能的管理器確保所有機器人一起工作來建置完美的塔一樣。 **為什麼要編曲?** 1. **多項任務,一名經理:** - 想像一下您有許多機器人(容器)執行不同的工作。編排就像有一位超級聰明的經理(編排者),他告訴每個機器人該做什麼,並確保一切順利進行。 2. **一致性:** - 編排確保所有任務每次都以相同的方式完成。這就像為您的機器人提供了一套要遵循的指令,以確保其行為的一致性。 3. **效率:** - 編排有助於優化任務,確保資源(如時間和材料)有效利用。這就像經理確保所有機器人一起工作而不浪費能源。 4. **縮放比例:** - 當您需要完成更多工作時,編排可以輕鬆建立額外的機器人(容器)。這就像當有很多事情需要完成時神奇地召喚更多機器人來提供幫助。 5. **可靠性:** - 編排確保任務可靠地完成,即使機器人(容器)出現故障。這就像製定備份計劃來確保無論如何都能完成工作。 6. **協調:** - 編排協調任務,確保機器人無縫協作。這就像經理確保每個機器人都知道自己的角色並協作以實現總體目標。 **容器編排程式碼:** ``` # Create a Docker service with 100 replicas (instances) of a Node.js application docker service create --replicas 100 --name my-nodejs-app nodejs ``` 在這個例子中: - `docker service create`:該指令告訴 Docker 建立一個服務,該服務是一組正在執行的容器。 - `--replicas 100`:此標誌指定您需要 100 個服務實例(副本)。 - `--name my-nodejs-app`:此標誌為您的服務提供名稱,在本例中為「my-nodejs-app」。 - `nodejs`:這是 Node.js 應用程式的圖片或配方。這就像是烘焙紙杯蛋糕的藍圖。 因此,這段簡單的程式碼就像告訴您神奇的廚師助手 (Docker Swarm) 建立 Node.js 應用程式的 100 個副本,確保您有大量容器正在執行並準備好提供服務。 ## Docker 群 ![Docker Swarm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z5noonjo2ikyrp90kc38.png) Docker Swarm 是一個工具,可以幫助協調和管理一組電腦(節點)作為一個機器人團隊一起工作,使它們能夠以協調的方式部署和執行多個容器。這就像有一個首席機器人經理,確保所有單一機器人一起建造出偉大而令人驚嘆的東西。 **設定 Docker Swarm:** 1. **群組管理器:** - 想像你有一個首席機器人(Swarm Manager)來領導團隊。主機器人決定需要做什麼,並指導其他機器人(節點)如何協同工作。 ``` # Initiate Docker Swarm on the Swarm Manager docker swarm init ``` 2. **節點工作人員:** - 現在,您的工作機器人(節點工作人員)已準備好加入團隊。 Swarm Manager 共享一個特殊的程式碼(令牌)來邀請他們一起工作。 ``` # Join a Node Worker to the Docker Swarm docker swarm join --token <token> <Swarm Manager IP> ``` **Docker Swarm 服務:** 現在您已經有了一個協調的團隊,您想要建立一項服務,例如與您的機器人團隊一起建造塔: ``` # Create a Docker service (a group of containers) with 3 replicas (instances) docker service create --replicas 3 --network frontend --name my-web-server my-web-image ``` - `--replicas 3`:此標誌告訴 Docker 建立服務的三個實例(副本)。 - `--network frontend`:此標誌指定您的服務屬於名為「frontend」的網路。 - `--name my-web-server`:這會為您的服務命名,在本例中為「my-web-server」。 - `my-web-image`:這是您的網頁伺服器的圖片或藍圖。這就像建造塔樓的配方。 您建立了一個由隊長(Swarm Manager)和工作機器人(Node Workers)組成的機器人團隊。然後,您指示他們建立一個執行您的 Web 伺服器應用程式的服務(容器群組)。主機器人確保建立 Web 伺服器的三個副本並將其連接到「前端」網路。這就像有一個首席機器人經理在工作機器人的幫助下監督多個塔(貨櫃)的建造。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytitz2wt4jsufejox9yx.gif) **好的,這就是本文的內容。** 另外,如果您對此或其他任何問題有任何疑問,請隨時在下面的評論中或在 [Instagram](https://www.instagram.com/_abhixsh/) 、[Facebook](https://www.facebook.com/abhi.haththakage/) 或[Twitter](https://twitter.com/abhixsh)。 感謝您閱讀這篇文章,我們下一篇再見! ❤️ --- 原文出處:https://dev.to/abhixsh/docker-for-the-absolute-beginner-3h1p

🌌 31 個開源庫 + Good First Issues(開始你的旅程)⛰️

為優秀的開源庫做出貢獻是建立您的作品集並加入令人驚嘆的社群的最佳方式。 我編譯了 31 個開源程式庫和一些好的第一期,以幫助推動您的旅程。 不要忘記加星號並支持這些🌟 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/637xnt75fuwgfeaasdke.gif) --- #AI最愛🦾: ### 1. [CopilotKit](https://github.com/CopilotKit/CopilotKit) - 應用內 AI 聊天機器人與 AI 文字區域 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ox3mv8nmqzot6m4kvkdh.png) 開源平台,用於使用兩個 React 元件將關鍵 AI 功能整合到 React 應用程式中。 CopilotPortal:應用程式內人工智慧聊天機器人,可以「查看」當前應用程式狀態並採取行動。 CopilotTextarea:AI 驅動的 <textarea /'> 替換。具有自動完成、插入和生成功能。 ###[好第一期:](https://github.com/CopilotKit/CopilotKit/issues/62) ``` Gracefully fail if CopilotProvider is omitted The bug: Virtually every CopilotKit functionality depends on a CopilotContext provided by the CopilotProvider. e.g. CopilotTextarea autocompletions, chatbot, etc. However when a CopilotProvider does not wrap the component, functionality fails silently. To Reproduce 1. Omit <CopilotProvider>...</CopilotProvider> 2. trigger useMakeCopilotReadable, useMakeCopilotActionable, CopilotTextarea, CopilotSidebarUIProvider 3. See how functionality does not work, but no error is emitted Expected behavior An error is emitted, with clear description of the likely core issue and how to resolve it (namely, wrap the app in a CopilotProvider). Point to docs. ``` {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ###2.[PortKeyAI](https://github.com/Portkey-AI/gateway){% embed https://github.com/Portkey-AI/gateway no-readme %} ###3.[Pezzo.ai](https://github.com/pezzolabs/pezzo){% 嵌入 https://github.com/pezzolabs/pezzo no-readme %} ###4.[OpenVoice](https://github.com/myshell-ai/OpenVoice){% 嵌入 https://github.com/myshell-ai/OpenVoice no-readme %} ###5.[LLMCourse](https://github.com/mlabonne/llm-course){% 嵌入 https://github.com/mlabonne/llm-course no-readme %} --- &nbsp; #雲端和資料庫☁️ ### 6. [Winglang](https://github.com/winglang/wing) - 雲端導向的程式語言 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvfykepsj1tszs8260wj.png) Wing 是一種用於雲端應用程式的程式語言。 它結合了雲端基礎設施和應用程式的程式碼,使雲端服務開發變得更加容易。 Wing 獨特的執行模型和測試模擬器有助於高效建置和部署雲端應用程式。 ###[第一期好:](https://github.com/winglang/wing/issues/4998) ``` Support Array.sort() method Feature Spec: let arr: MutArray<num>=[2, 1, 3, 9, 6, 4]; arr.sort(); log("${arr}"); // it should print sorted array in ascending order, eg: [1, 2, 3, 4, 6, 9] Component: Wing SDK Community Notes: If you are interested to work on this issue, please leave a comment. If this issue is labeled needs-discussion, it means the spec has not been finalized yet. Please reach out on the #dev channel in the Wing Slack. ``` {% cta https://github.com/winglang/wing %} 星翼朗 ⭐️ {% endcta %} --- ### 7. [StackQL](https://github.com/stackql/stackql) - 以 SQL 為基礎的雲端資源管理 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdtf51ekap09idn80xnh.png) StackQL 提供了一個獨特的 SQL 為基礎的框架來管理和查詢跨不同提供者(例如 Google、AWS、Azure 等)的雲端資源和 API。 它允許使用類似 SQL 的命令來配置和操作雲端服務,從而簡化了雲端操作。 這使得 StackQL 成為雲端資源管理和互動的多功能工具,特別是對於熟悉 SQL 的人來說。 ###[好第一期:](https://github.com/stackql/stackql/issues/280) ``` Add unit testing to package writer Add unit testing for internal/stackql/writer . Description: add implementation for testing sql_writer.go modify sql_writer.go by adding function for dependency injection add implementation for testing generic.go modify generic.go by adding variable for patching GetDB function modify entryutil.go to adjust sql_writer.go ``` {% cta https://github.com/stackql/stackql/ %} Star StackSQL ⭐️ {% endcta %} --- ###8.[Appwrite](https://github.com/appwrite/appwrite){% 嵌入 https://github.com/appwrite/appwrite no-readme %} ###9.[Supabase](https://github.com/supabase/supabase){% 嵌入 https://github.com/supabase/supabase no-readme %} ###10.[SuperDuperDB](https://github.com/SuperDuperDB/superduperdb){% 嵌入 https://github.com/SuperDuperDB/superduperdb no-readme %} --- &nbsp; #開發實用程式🛠️ ### 11. [Firecamp](https://github.com/firecamp-dev/firecamp) - 多協定 API 協作工具 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/adt6n8uv5dseylmemng0.png) Firecamp 是一款多功能 API 開發工具,支援 Rest、GraphQL 和 WebSockets 等協定。 它簡化了 API 的設計、測試和記錄,並增強了 API 專案的團隊協作。 ###[好第一期:](https://github.com/firecamp-dev/firecamp/issues/137) ``` Help out with Manual Testing of Firecamp Responsibilities: Executing test cases and reporting results Logging bugs and issues in the Github issue tracker Providing feedback on usability and the testing process Suggesting improvements to tests and expanding test coverage. Benefits Benefits for your testing profile and career: Experience testing a real-world open source application Each release will include your name with bugs fixes. Exposure to different types of testing such as UI, API, integration, etc Opportunity to have your contributions and feedback incorporated into the product Collaborating with an open source community Having your testing work visible to potential employers Firecamp Swags (T-shirts and stickers) Community shoutout and promotion ``` {% cta https://github.com/firecamp-dev/firecamp %} 星際火營 ⭐️ {% endcta %} --- ###12.[Odigos](https://github.com/keyval-dev/odigos){% 嵌入 https://github.com/keyval-dev/odigos no-readme %} ###13.[Digger](https://github.com/diggerhq/digger){% 嵌入 https://github.com/diggerhq/digger no-readme %} ###14.[鏡像](https://github.com/metalbear-co/mirrord){% 嵌入 https://github.com/metalbear-co/mirrord no-readme %} --- &nbsp; #後端⚙️ ### 15. [Cerbos](https://github.com/cerbos/cerbos) - 可擴充、與語言無關的授權 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cljttnnxua54lyg4w65x.png) Cerbos 提供獨特、可擴展的解決方案,用於在應用程式中實施特定於上下文的使用者權限。 其靈活的、與語言無關的方法可以輕鬆整合和管理複雜的授權結構。 與眾不同的是,Cerbos 簡化了存取控制策略的開發,使其更能適應各種應用需求。 ###[第一期好:](https://github.com/cerbos/cerbos/issues/1920) ``` Produce output when the rule condition is not satisfied Currently the output block is only evaluated if the rule is actually activated (action, roles and conditions are satisfied). In certain situations, it's desirable to produce output when the rule is nearly activated (action and roles match but the condition is not satisfied). In order to maintain backward compatibility, reduce noise, and to keep policy execution as fast as possible (outputs incur a tiny overhead), the proposal is to let users add an optional when section to the output block to opt into this behaviour. - actions: ['view'] effect: EFFECT_ALLOW roles: ['user'] condition: match: expr: timestamp(R.expiry_date) > now() output: expr: > format("%d hours until expiry", (timestamp(R.expiry_date) - now()).getHours()) when: cond_fail: > format("expired on %s", R.expiry_date) When evaluating the above rule, if the action, roles and condition match, output will be the result of evaluating output.expr If the condition is not satisfied, output will be the result of evaluating output. when.cond_fail if it exists. Otherwise no output will be produced. ``` {% cta https://github.com/cerbos/cerbos %} 明星 Cerbos ⭐️ {% endcta %} --- ###16.[Novu](https://github.com/novuhq/novu){% 嵌入 https://github.com/novuhq/novu no-readme %} ###17.[Trigger.dev](https://github.com/triggerdotdev/trigger.dev){% 嵌入 https://github.com/triggerdotdev/trigger.dev no-readme %} ###18.[SuperTokens](https://github.com/supertokens/supertokens-core){% 嵌入 https://github.com/supertokens/supertokens-core no-readme %} ###19.[Wazuh](https://github.com/wazuh/wazuh){% 嵌入 https://github.com/wazuh/wazuh no-readme %} --- &nbsp; #UI/UX🦋: ### 20. [Flowbite](https://github.com/themesberg/flowbite) - 頂級 CSS 元件庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/98dwqyrhf1pbiqkpko8g.png) 最好、最受尊敬的 UI 元件庫之一。 基於實用優先的 CSS 框架。 易於使用,充滿重要的支援和模板。 {% cta https://github.com/themesberg/flowbite %} 明星 Flowbite ⭐️ {% endcta %} &nbsp; ###21.[MaterialUI](https://github.com/mui/material-ui) - 使用 Google 的 Material Design 實現的基礎 React 元件 {% 嵌入 https://github.com/mui/material-ui no-readme %} &nbsp; ###22。 [SwiperUI](https://github.com/nolimits4web/swiper) - 用於實現行動滑動 UI 的受人尊敬的庫 {% 嵌入 https://github.com/nolimits4web/swiper no-readme %} &nbsp; ###23.[ReactSpring](https://github.com/pmndrs/react-spring) - 在 React 中實現具有真實物理效果的動畫 {% 嵌入 https://github.com/pmndrs/react-spring no-readme %} --- &nbsp; #雜項🎨 ### 24. [SwirlSearch](https://github.com/swirlai/swirl-search) - 多源人工智慧資料搜尋器 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thplxod3d4vh1qq5hhpa.jpeg) Swirl 是一款由人工智慧驅動的搜尋工具,可同時查詢多個資料來源,包括資料庫和公用資料服務。 它使用人工智慧對結果進行排名並產生見解,從而可以跨不同的資料儲存庫進行全面搜尋。 Swirl 一次簡化了對各種來源的資料的搜尋和分析,使其成為資料驅動洞察的獨特工具。 ###[第一期好:](https://github.com/swirlai/swirl-search/issues/789) ``` Add a Connector: Yahoo search It would help to search anything with Swirl on Yahoo effectively. Locate and read a bit in their search API first. You might just need to make a new SearchProvider configration vs. a new Connector. Their docs should help guide you a bit in which way you might need to go. ``` {% cta https://github.com/swirlai/swirl-search/ %} Star SwirlSearch ⭐️ {% endcta %} --- ### 25. [Wasp](https://github.com/wasp-lang/wasp) - 使用 React 和 Node.js 開發全端 Web 應用程式 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/54jp6j6r8ils6we97i0f.png) 使用 React 和 Node.js 進行快速全端 Web 應用程式開發。 Wasp 提供了一種建立現代 Web 應用程式的簡化方法,將前端的 React 和後端的 Node.js 結合在一個緊密結合的框架中。 ###[好第一期:](https://github.com/wasp-lang/wasp/issues/874) ``` Add images (or link to the example app) of auth UI helpers Wasp provides At this point in docs (also in the tutorial if we're using it), it would be nice to add an image of UI helpers for Auth (login/signup form, Google/GitHub button, ...) so developers can immediately see what they are getting and how nice it looks. ``` {% cta https://github.com/wasp-lang/wasp %} 星黃蜂 ⭐️ {% endcta %} ###26.[Logstash](https://github.com/elastic/logstash) {% 嵌入 https://github.com/elastic/logstash 無自述文件 %} ###27.[Snapify](https://github.com/MarconLP/snapify) {% 嵌入 https://github.com/MarconLP/snapify 無自述文件 %} --- &nbsp; #為了好玩🎭 ###28.[Twitter 的演算法](https://github.com/twitter/the-algorithm){% embed https://github.com/twitter/the-algorithm no-readme %} ###29.[十億行挑戰](https://github.com/gunnarmorling/1brc){% embed https://github.com/gunnarmorling/1brc no-readme %} ###30.【秘密知識之書】(https://github.com/trimstray/the-book-of-secret-knowledge){% embed https://github.com/trimstray/the-book-of -秘密知識無自述文件%} ###31.[GenAI 初學者](https://github.com/microsoft/generative-ai-for-beginners){% 嵌入 https://github.com/microsoft/generative-ai-for-beginners no -自述文件%} --- 原文出處:https://dev.to/copilotkit/31-open-source-libraries-to-kickstart-your-journey-4hhd

关于 JS 的一些高级用法

在学习 JavaScript中,变量、函数、类、循环、异步这些都是基础知识。这些基础知识是我们使用 JavaScript 的基础。但是,在日常的业务开发中,我们需要一些更高级的技巧来更好地解决问题。 > 通过本文你将了解到 JS 的高级知识点以及实际应用技巧,如高级数据结构和算法、函数式编程、异步编程和面向对象编程。我们会利用代码实例来让大家更好地理解这些知识点。同时,我们也会提供一些实战案例的示范和使用技巧,让你更好地将这些技术应用到实际业务中。 ## 高级数据结构和算法 ### Map 和 Set 数据结构 在 JavaScript 中,Map 数据结构通常用于存储键值对,它可以使用任意类型作为键和值。Set 数据结构用于存储唯一值的集合。 ```js // 创建Map对象 const map = new Map(); // 设置键值对 map.set("name", "Tom"); map.set("age", 20); // 获取键值对 console.log(map.get("name")); // 'Tom' console.log(map.get("age")); // 20 // 创建Set对象 const set = new Set(); // 添加元素 set.add(10); set.add(20); set.add(30); // 删除元素 set.delete(20); // 判断元素是否存在 console.log(set.has(10)); // true console.log(set.has(20)); // false ``` ### 堆、栈和队列 堆和栈是常用的内存分配方式。栈是一种后进先出的数据结构,堆是一种动态分配的内存结构。队列是一种先进先出的数据结构,它通常用于缓存和并发编程中。 ```js // 使用数组模拟堆 const arr = [1, 2, 3, 4]; arr.push(5); // 入堆 console.log(arr.pop()); // 出堆 // 使用数组模拟栈 const stack = [1, 2, 3, 4]; stack.push(5); // 入栈 console.log(stack.pop()); // 出栈 // 使用数组模拟队列 const queue = [1, 2, 3, 4]; queue.push(5); // 入队 console.log(queue.shift()); // 出队 ``` ### 深度优先搜索和广度优先搜索 深度优先搜索(DFS)和广度优先搜索(BFS)是常用的遍历算法。DFS 通常用于解决深度问题,BFS 适用于宽度问题。 ```js // 深度优先遍历 function dfs(node) { if (node == null) return; console.log(node.value); dfs(node.left); dfs(node.right); } // 广度优先遍历 function bfs(node) { const queue = [node]; while (queue.length) { const curr = queue.shift(); console.log(curr.value); if (curr.left) queue.push(curr.left); if (curr.right) queue.push(curr.right); } } ``` ### 常用算法 常用的算法有排序、搜索、查找等。 ```js // 排序算法:快速排序使用分治思想,通过把数组分成较小的块来排序。 function quickSort(arr) { if (arr.length < 2) { return arr; } let pivot = arr[0]; let left = []; let right = []; for (let i = 1; i < arr.length; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...quickSort(left), pivot, ...quickSort(right)]; } // 查找算法: function binarySearch(arr, target) { let left = 0; let right = arr.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (arr[mid] === target) { return mid; } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; } ``` ## 函数式编程 ### 高阶函数和柯里化 高阶函数和柯里化是函数式编程中的常见概念,它们可以让我们创建更加抽象、灵活的函数。 ```js // 高阶函数 function higherOrderFunction(func) { return function (num) { return func(num); }; } function double(num) { return num * 2; } const doubleFunc = higherOrderFunction(double); console.log(doubleFunc(10)); // 20 // 柯里化 function curry(func) { return function curried(...args) { if (args.length >= func.length) { return func.apply(this, args); } else { return function (...args2) { return curried.apply(this, [...args, ...args2]); }; } }; } function sum(a, b, c) { return a + b + c; } const curriedSum = curry(sum); console.log(curriedSum(1)(2)(3)); // 6 ``` ### 闭包和作用域 闭包和作用域是 JavaScript 中比较常见的概念。闭包可以让我们维护函数内的状态,作用域则决定了变量的可见范围。 ```js // 闭包 function closure() { let i = 0; return function () { return ++i; }; } const func = closure(); console.log(func()); // 1 console.log(func()); // 2 // 作用域 let a = 10; function foo() { let a = 20; console.log(a); // 20 } foo(); console.log(a); // 10 ``` ### 函数式编程中的常见模式 函数式编程中有很多常见的模式,如 map、filter、reduce 等。 ```js // map const arr = [1, 2, 3]; const mapArr = arr.map((item) => item * 2); console.log(mapArr); // [2, 4, 6] // filter const filterArr = arr.filter((item) => item > 1); console.log(filterArr); // [2, 3] // reduce const reduceArr = arr.reduce((sum, curr) => sum + curr, 0); console.log(reduceArr); // 6 异步编程 Promise和async/await Promise和async/await是常见的异步编程方式,它们可以让我们更好地处理异步编程中的问题。 // Promise function promise() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('done'); }, 1000); }); } promise().then((result) => console.log(result)); // 'done' // async/await async function asyncFunc() { const result = await promise(); console.log(result); } asyncFunc(); // 'done' ``` ### 事件循环和 EventEmitter 事件循环和 EventEmitter 用于处理异步事件,它们可以让我们更好地处理事件流。 ```js // 事件循环 console.log("start"); setTimeout(() => { console.log("setTimeout"); }, 0); Promise.resolve().then(() => console.log("promise")); console.log("end"); // EventEmitter const { EventEmitter } = require("events"); const emitter = new EventEmitter(); emitter.on("doSomething", (arg1, arg2) => { console.log(`${arg1} ${arg2}`); }); emitter.emit("doSomething", "Hello", "World"); // 'Hello World' ``` ### Web Worker Web Worker 可以让我们将长时间运行的任务移出主线程,以避免阻塞 UI。 ```js // 主线程 const worker = new Worker("worker.js"); worker.onmessage = (event) => { console.log(event.data); }; worker.postMessage("start"); // worker.js self.onmessage = (event) => { const result = longCalculation(event.data); self.postMessage(result); }; ``` ## 面向对象编程 ### 类和继承 JavaScript 中的类和继承与其他面向对象编程语言类似。 ```js // 类 class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } } class Cat extends Animal { constructor(name, breed) { super(name); this.breed = breed; } speak() { console.log(`${this.name} meows.`); } get description() { return `${this.name} is a ${this.breed} cat.`; } set nickname(nick) { this.name = nick; } } const cat = new Cat("Fluffy", "Persian"); cat.speak(); // 'Fluffy meows.' console.log(cat.description); // 'Fluffy is a Persian cat.' cat.nickname = "Fuffy"; console.log(cat.name); // 'Fuffy' ``` ### Encapsulation、Inheritance、Polymorphism(封装、继承、多态) 封装、继承、多态是面向对象编程中的重要概念。 ```js // 封装 class Person { constructor(name) { this._name = name; } get name() { return this._name.toUpperCase(); } set name(newName) { this._name = newName; } } const person = new Person("John"); console.log(person.name); // 'JOHN' person.name = "Lisa"; console.log(person.name); // 'LISA' // 继承 class Shape { constructor(color) { this.color = color; } draw() { console.log("Drawing a shape..."); } } class Circle extends Shape { constructor(color, radius) { super(color); this.radius = radius; } draw() { console.log(`Drawing a ${this.color} circle with radius ${this.radius}.`); } } const circle = new Circle("red", 10); circle.draw(); // 'Drawing a red circle with radius 10.' // 多态 function drawShape(shape) { shape.draw(); } drawShape(new Shape("blue")); // 'Drawing a shape...' drawShape(new Circle("green", 20)); // 'Drawing a green circle with radius 20.' ``` ## 总结和实战 在本文中,我们介绍了一些 JavaScript 的高级知识点,如高级数据结构和算法、函数式编程、异步编程和面向对象编程。我们还提供了一些代码示例和实战案例,让大家更好地理解和掌握这些技术。 ### 通过 Promise.all 实现并发请求 ```js function fetchData(urls) { const promises = urls.map((url) => fetch(url)); return Promise.all(promises).then((responses) => Promise.all( responses.map((response) => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) ) ); } ``` ### 使用 async/await 实现异步调用 ```js async function getData(url) { const response = await fetch(url); if (!response.ok) throw new Error(response.statusText); const data = await response.json(); return data; } ``` ### 在面向对象编程中使用工厂模式 ```js class Product { constructor(name, price) { this.name = name; this.price = price; } } class ProductFactory { createProduct(name, price) { return new Product(name, price); } } const productFactory = new ProductFactory(); const product = productFactory.createProduct("Apple", 1); console.log(product.name); // 'Apple' console.log(product.price); // 1 ``` 本文结束,感谢阅读

✨ 每個開發者都需要了解的 7 個人工智慧庫(成為奇才)🧙‍♂️ 🪄

## 長篇大論;博士 如今,任何開發人員都可以利用人工智慧來建立強大的東西。 無需成為機器學習專家。 這裡有 7 個最好的庫,您可以使用它來增強您的開發並透過最先進的 AI 功能給用戶留下深刻的印象。 這些可以為你的專案帶來神奇的力量,所以不要忘記給他們加星號並支持他們🌟 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/she8nk1oksxmem791o09.gif) --- ## 1. [CopilotKit](https://github.com/RecursivelyAI/CopilotKit):將 AI 功能引入 React 應用程式。 (ChatBot 和 CopilotTexarea) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0zxu7wrchaod8eyvq46b.png) 常見的法學碩士產品用例被製作成簡單且可自訂的反應元件。 具有兩個元件: CopilotPortal:加入可以在您的應用程式內回答問題並採取行動的法學碩士! CopilotTextarea:任何具有 Github Copilot 功能的 <textarea/> 的直接替代品。 ``` import "@copilotkit/react-ui/styles.css"; import { CopilotProvider } from "@copilotkit/react-core"; import { CopilotSidebarUIProvider } from "@copilotkit/react-ui"; export default function App(): JSX.Element { return ( <CopilotProvider chatApiEndpoint="/api/copilotkit/chat"> <CopilotSidebarUIProvider> <YourContent /> </CopilotSidebarUIProvider> </CopilotProvider> ); } ``` {% cta https://github.com/RecursivelyAI/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- ## 2. Tavily GPT 研究員 - 取得法學碩士學位以搜尋網路和資料庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61mwfvsi4n9rnjet0j52.png) Tavilly 可讓您將 GPT 支援的研究和內容產生工具新增至您的 React 應用程式中,從而增強其資料處理和內容建立功能。 ``` # Create an assistant assistant = client.beta.assistants.create( instructions=assistant_prompt_instruction, model="gpt-4-1106-preview", tools=[{ "type": "function", "function": { "name": "tavily_search", "description": "Get information on recent events from the web.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "The search query to use. For example: 'Latest news on Nvidia stock performance'"}, }, "required": ["query"] } } }] ) ``` {% cta https://github.com/assafelovic/gpt-researcher %} 明星塔維利 ⭐️ {% endcta %} --- ## 3. Pezzo.ai - 可觀測性、成本和即時工程平台 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxvbgi5zkghkb0t64npw.jpeg) 用於管理 OpenAI 通話的集中平台。 優化您的提示和令牌使用。追蹤您的人工智慧使用情況。 免費且易於整合。 ``` const prompt = await pezzo.getPrompt("AnalyzeSentiment"); const response = await openai.chat.completions.create(prompt); ``` {% cta https://github.com/pezzolabs/pezzo %} 明星 Pezzo ⭐️ {% endcta %} --- ## 4. LangChain - 將人工智慧整合到行動鏈中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s87kvm5jt5wmsv702r1.png) 易於使用的 API 和函式庫,用於將 LLM 新增到應用程式中。 將不同的人工智慧元件和模型連接在一起。 輕鬆嵌入上下文和語義資料以實現強大的整合。 ``` from langchain.llms import OpenAI from langchain import PromptTemplate llm = OpenAI(model_name="text-davinci-003", openai_api_key="YourAPIKey") # Notice "food" below, that is a placeholder for another value later template = """ I really want to eat {food}. How much should I eat? Respond in one short sentence """ prompt = PromptTemplate( input_variables=["food"], template=template, ) final_prompt = prompt.format(food="Chicken") print(f"Final Prompt: {final_prompt}") print("-----------") print(f"LLM Output: {llm(final_prompt)}") ``` {% cta https://github.com/langchain-ai/langchain %} 星朗鏈 ⭐️ {% endcta %} --- ## 5. [Weaviate](https://github.com/weaviate/weaviate) - 用於人工智慧增強專案的向量資料庫 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brp7plpkk9sy44ubc14t.png) Weaviate 是一個針對大型資料集快速、高效搜尋而最佳化的向量資料庫。 它支援與 OpenAI 和 Hugging Face 等提供者的 AI 模型和服務集成,從而實現資料分類和自然語言處理等高級任務。 它是一種雲端原生解決方案,具有高度可擴展性,可以滿足不斷變化的資料需求。 ``` import weaviate import json client = weaviate.Client( embedded_options=weaviate.embedded.EmbeddedOptions(), ) uuid = client.data_object.create({ }) obj = client.data_object.get_by_id(uuid, class_name='MyClass') print(json.dumps(obj, indent=2)) ``` {% cta https://github.com/weaviate/weaviate %} 星織 ⭐️ {% endcta %} --- ## 6. [PrivateGPT](https://github.com/imartinez/privateGPT) - 與您的文件聊天,100% 私密 💡 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ap81ce5j9chc5c543jl6.jpg) PrivateGPT 允許在應用程式內進行安全的、GPT 驅動的文件交互,確保資料隱私並增強上下文感知處理能力。 PrivateGPT 透過本地處理和儲存文件和上下文來確保隱私,而無需將資料傳送到外部伺服器。 ``` from privategpt import PrivateGPT, DocumentIngestion, ChatCompletion client = PrivateGPT(api_key='your_api_key') def process_documents_and_chat(query, documents): ingestion_result = DocumentIngestion(client, documents) chat_result = ChatCompletion(client, query, context=ingestion_result.context) return chat_result documents = ['doc1.txt', 'doc2.txt'] query = "What is the summary of the documents?" result = process_documents_and_chat(query, documents) print(result) ``` {% cta https://github.com/weaviate/weaviate %} 星織 ⭐️ {% endcta %} --- ## 7. SwirlSearch - 人工智慧驅動的搜尋。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/extnr9oxhubs6m9x817a.png) LLM 支援的搜尋、摘要和輸出。 同時搜尋多個內容來源並產生整合輸出。 功能強大,可自訂各種資料來源的應用程式內整合。 {% cta https://github.com/swirlai/swirl-search %} 星旋搜尋 ⭐️ {% endcta %} --- 謝謝閱讀! 我希望這些可以幫助您使用人工智慧建立一些很棒的東西。 如果您喜歡並評論您想看到的任何其他庫或主題,請按讚。 --- 原文出處:https://dev.to/copilotkit/7-ai-libraries-every-dev-needs-to-know-to-be-a-wiz-4lim

大資料模型 📊 與電腦記憶體 💾

資料管道是任何資料密集型專案的支柱。 **隨著資料集的成長**超出記憶體大小(「核心外」),**有效處理它們變得具有挑戰性**。 Dask 可以輕鬆管理大型資料集(核心外),提供與 Numpy 和 Pandas 的良好相容性。 ![管道](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m6nswebbzlo96ml1ofeb.png) --- 本文重點介紹 **Dask(用於處理核心外資料)與 Taipy** 的無縫集成,Taipy** 是一個用於 **管道編排和場景管理** 的 Python 庫。 --- ## Taipy - 您的 Web 應用程式建構器 關於我們的一些資訊。 **Taipy** 是一個開源程式庫,旨在輕鬆開發前端 (GUI) 和 ML/資料管道。 不需要其他知識(沒有 CSS,什麼都不需要!)。 它旨在加快應用程式開發,從最初的原型到生產就緒的應用程式。 ![QueenB 星星](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bvt5qn1yadra3epnb07v.gif) https://github.com/Avaiga/taipy 我們已經快有 1000 顆星了,沒有你就無法做到這一點🙏 --- ## 1. 範例應用程式 透過範例最好地演示了 Dask 和 Taipy 的整合。在本文中,我們將考慮包含 4 個任務的資料工作流程: - **資料預處理與客戶評分** 使用 Dask 讀取和處理大型資料集。 - **特徵工程和分割** 根據購買行為對客戶進行評分。 - **細分分析** 根據這些分數和其他因素將客戶分為不同的類別。 - **高價值客戶的總統計** 分析每個客戶群以獲得見解 我們將更詳細地探討這 4 個任務的程式碼。 請注意,此程式碼是您的 Python 程式碼,並未使用 Taipy。 在後面的部分中,我們將展示如何使用 Taipy 對現有資料應用程式進行建模,並輕鬆獲得其工作流程編排的好處。 --- 該應用程式將包含以下 5 個檔案: ``` algos/ ├─ algo.py # Our existing code with 4 tasks data/ ├─ SMALL_amazon_customers_data.csv # A sample dataset app.ipynb # Jupyter Notebook for running our sample data application config.py # Taipy configuration which models our data workflow config.toml # (Optional) Taipy configuration in TOML made using Taipy Studio ``` --- ## 2. Taipy 簡介 - 綜合解決方案 [Taipy](https://docs.taipy.io/) **不只是另一個編排工具**。 Taipy 專為 ML 工程師、資料科學家和 Python 開發人員設計,帶來了幾個基本且簡單的功能。 以下是**一些關鍵要素**,使 Taipy 成為令人信服的選擇: 1. **管道執行註冊表** 此功能使開發人員和最終用戶能夠: - 將每個管道執行註冊為「*場景*」(任務和資料節點圖); - 精確追蹤每個管道執行的沿襲;和 - 輕鬆比較場景、監控 KPI 並為故障排除和微調參數提供寶貴的見解。 2. **管道版本控制** Taipy 強大的場景管理使您能夠輕鬆調整管道以適應不斷變化的專案需求。 3. **智能任務編排** Taipy 讓開發人員可以輕鬆地對任務和資料來源網路進行建模。 此功能透過以下方式提供對任務執行的內建控制: - 並行執行您的任務;和 - 任務“跳過”,即選擇要執行的任務並 要繞過哪個。 4. **任務編排的模組化方法** 模組化不僅僅是 Taipy 的一個流行詞;這是一個核心原則。 設定可以互換使用的任務和資料來源,從而產生更乾淨、更易於維護的程式碼庫。 --- ## 3. Dask 簡介 Dask 是一個流行的分散式運算 Python 套件。 Dask API 實作了熟悉的 Pandas、Numpy 和 Scikit-learn API - ,這使得許多已經熟悉這些 API 的資料科學家更愉快地學習和使用 Dask。 如果您是 Dask 新手,請查看 Dask 團隊撰寫的精彩 Dask [10 分鐘簡介](https://docs.dask.org/en/stable/10-minutes-to-dask.html)。 --- ## 4. 應用:顧客分析 (*algos/algo.py*) ![DAG 架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ru69b6jmhl73s9xxx2n.png) *我們的 4 項任務的圖表(在 Taipy 中可視化),我們將在下一節中對其進行建模。* 我們現有的程式碼(不含 Taipy)包含 4 個函數,您也可以在上圖中看到: - 任務 1:*預處理和評分* - 任務 2:*特徵化與細分* - 任務 3:*分段分析* - 任務 4:*high_value_cust_summary_statistics* 您可以瀏覽以下定義了 4 個函數的 *algos/algo.py* 腳本,然後繼續閱讀每個函數的簡要說明: ``` ### algos/algo.py import time import dask.dataframe as dd import pandas as pd def preprocess_and_score(path_to_original_data: str): print("__________________________________________________________") print("1. TASK 1: DATA PREPROCESSING AND CUSTOMER SCORING ...") start_time = time.perf_counter() # Start the timer # Step 1: Read data using Dask df = dd.read_csv(path_to_original_data) # Step 2: Simplify the customer scoring formula df["CUSTOMER_SCORE"] = ( 0.5 * df["TotalPurchaseAmount"] / 1000 + 0.3 * df["NumberOfPurchases"] / 10 + 0.2 * df["AverageReviewScore"] ) # Save all customers to a new CSV file scored_df = df[["CUSTOMER_SCORE", "TotalPurchaseAmount", "NumberOfPurchases", "TotalPurchaseTime"]] pd_df = scored_df.compute() end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return pd_df def featurization_and_segmentation(scored_df, payment_threshold, score_threshold): print("__________________________________________________________") print("2. TASK 2: FEATURE ENGINEERING AND SEGMENTATION ...") # payment_threshold, score_threshold = float(payment_threshold), float(score_threshold) start_time = time.perf_counter() # Start the timer df = scored_df # Feature: Indicator if customer's total purchase is above the payment threshold df["HighSpender"] = (df["TotalPurchaseAmount"] > payment_threshold).astype(int) # Feature: Average time between purchases df["AverageTimeBetweenPurchases"] = df["TotalPurchaseTime"] / df["NumberOfPurchases"] # Additional computationally intensive features df["Interaction1"] = df["TotalPurchaseAmount"] * df["NumberOfPurchases"] df["Interaction2"] = df["TotalPurchaseTime"] * df["CUSTOMER_SCORE"] df["PolynomialFeature"] = df["TotalPurchaseAmount"] ** 2 # Segment customers based on the score_threshold df["ValueSegment"] = ["High Value" if score > score_threshold else "Low Value" for score in df["CUSTOMER_SCORE"]] end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return df def segment_analysis(df: pd.DataFrame, metric): print("__________________________________________________________") print("3. TASK 3: SEGMENT ANALYSIS ...") start_time = time.perf_counter() # Start the timer # Detailed analysis for each segment: mean/median of various metrics segment_analysis = ( df.groupby("ValueSegment") .agg( { "CUSTOMER_SCORE": metric, "TotalPurchaseAmount": metric, "NumberOfPurchases": metric, "TotalPurchaseTime": metric, "HighSpender": "sum", # Total number of high spenders in each segment "AverageTimeBetweenPurchases": metric, } ) .reset_index() ) end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return segment_analysis def high_value_cust_summary_statistics(df: pd.DataFrame, segment_analysis: pd.DataFrame, summary_statistic_type: str): print("__________________________________________________________") print("4. TASK 4: ADDITIONAL ANALYSIS BASED ON SEGMENT ANALYSIS ...") start_time = time.perf_counter() # Start the timer # Filter out the High Value customers high_value_customers = df[df["ValueSegment"] == "High Value"] # Use summary_statistic_type to calculate different types of summary statistics if summary_statistic_type == "mean": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].mean() elif summary_statistic_type == "median": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].median() elif summary_statistic_type == "max": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].max() elif summary_statistic_type == "min": average_purchase_high_value = high_value_customers["TotalPurchaseAmount"].min() median_score_high_value = high_value_customers["CUSTOMER_SCORE"].median() # Fetch the summary statistic for 'TotalPurchaseAmount' for High Value customers from segment_analysis segment_statistic_high_value = segment_analysis.loc[ segment_analysis["ValueSegment"] == "High Value", "TotalPurchaseAmount" ].values[0] # Create a DataFrame to hold the results result_df = pd.DataFrame( { "SummaryStatisticType": [summary_statistic_type], "AveragePurchaseHighValue": [average_purchase_high_value], "MedianScoreHighValue": [median_score_high_value], "SegmentAnalysisHighValue": [segment_statistic_high_value], } ) end_time = time.perf_counter() # Stop the timer execution_time = (end_time - start_time) * 1000 # Calculate the time in milliseconds print(f"Time of Execution: {execution_time:.4f} ms") return result_df ``` --- ### 任務 1 - 資料預處理與客戶評分 Python 函數:*preprocess_and_score* 這是管道中的第一步,也許也是最關鍵的一步。 它使用 **Dask** 讀取大型資料集,專為大於記憶體的計算而設計。 然後,它根據“*TotalPurchaseAmount*”、“*NumberOfPurchases*”和“*AverageReviewScore*”等各種指標,在名為 *scored_df* 的 DataFrame 中計算“*Customer Score*”。 使用 Dask 讀取和處理資料集後,此任務將輸出一個 Pandas DataFrame,以供其餘 3 個任務進一步使用。 --- ### 任務 2 - 特徵工程與分割 Python 函數:*featureization_and_segmentation* 此任務採用評分的 DataFrame 並新增功能,例如高支出指標。 它還根據客戶的分數對客戶進行細分。 --- ### 任務 3 - 細分分析 Python 函數:*segment_analysis* 此任務採用分段的 DataFrame 並根據客戶細分執行分組分析以計算各種指標。 --- ### 任務 4 - 高價值客戶的總統計 Python 函數:*high_value_cust_summary_statistics* 此任務對高價值客戶群進行深入分析並傳回匯總統計資料。 --- ## 5. 在 Taipy 中建模工作流程 (*config.py*) ![工作室中的 DAG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5kyz7k3akkcbs48psodi.png) *Taipy DAG — Taipy「任務」為橘色,「資料節點」為藍色。* 在本節中,我們將建立對變數/參數進行建模的Taipy 配置(表示為[“資料節點”](https://docs.taipy.io/en/latest/manuals/core/concepts/data-node/ ))和 Taipy 中的函數(表示為 [“Tasks”](https://docs.taipy.io/en/latest/manuals/core/concepts/task/))。 --- 請注意,以下 *config.py* 腳本中的此配置類似於定義變數和函數 - 只不過我們定義的是「藍圖變數」(資料節點)和「藍圖函數」(任務)。 我們通知 Taipy 如何呼叫我們之前定義的函數、資料節點的預設值(我們可能會在執行時覆蓋)以及是否可以跳過任務: ``` ### config.py from taipy import Config from algos.algo import ( preprocess_and_score, featurization_and_segmentation, segment_analysis, high_value_cust_summary_statistics, ) # -------------------- Data Nodes -------------------- path_to_data_cfg = Config.configure_data_node(id="path_to_data", default_data="data/customers_data.csv") scored_df_cfg = Config.configure_data_node(id="scored_df") payment_threshold_cfg = Config.configure_data_node(id="payment_threshold", default_data=1000) score_threshold_cfg = Config.configure_data_node(id="score_threshold", default_data=1.5) segmented_customer_df_cfg = Config.configure_data_node(id="segmented_customer_df") metric_cfg = Config.configure_data_node(id="metric", default_data="mean") segment_result_cfg = Config.configure_data_node(id="segment_result") summary_statistic_type_cfg = Config.configure_data_node(id="summary_statistic_type", default_data="median") high_value_summary_df_cfg = Config.configure_data_node(id="high_value_summary_df") # -------------------- Tasks -------------------- preprocess_and_score_task_cfg = Config.configure_task( id="preprocess_and_score", function=preprocess_and_score, skippable=True, input=[path_to_data_cfg], output=[scored_df_cfg], ) featurization_and_segmentation_task_cfg = Config.configure_task( id="featurization_and_segmentation", function=featurization_and_segmentation, skippable=True, input=[scored_df_cfg, payment_threshold_cfg, score_threshold_cfg], output=[segmented_customer_df_cfg], ) segment_analysis_task_cfg = Config.configure_task( id="segment_analysis", function=segment_analysis, skippable=True, input=[segmented_customer_df_cfg, metric_cfg], output=[segment_result_cfg], ) high_value_cust_summary_statistics_task_cfg = Config.configure_task( id="high_value_cust_summary_statistics", function=high_value_cust_summary_statistics, skippable=True, input=[segment_result_cfg, segmented_customer_df_cfg, summary_statistic_type_cfg], output=[high_value_summary_df_cfg], ) scenario_cfg = Config.configure_scenario( id="scenario_1", task_configs=[ preprocess_and_score_task_cfg, featurization_and_segmentation_task_cfg, segment_analysis_task_cfg, high_value_cust_summary_statistics_task_cfg, ], ) ``` 號 您可以在[此處的文件](https://docs.taipy.io/en/latest/manuals/core/config/)中閱讀有關配置場景、任務和資料節點的更多資訊。 --- ### Taipy Studio [Taipy Studio](https://docs.taipy.io/en/latest/manuals/studio/config/) **是來自Taipy 的VS Code 擴充功能**,讓您**透過簡單的方式建置和視覺化您的管道拖放互動**。 Taipy Studio 提供了一個圖形編輯器,您可以在其中建立 Taipy 配置**存儲在 TOML 文件中**,您的 Taipy 應用程式可以加載並執行這些配置。 編輯器將場景表示為圖形,其中節點是資料節點和任務。 --- *作為本節中 config.py 腳本的替代方案,您可以使用 Taipy Studio 產生 config.toml 設定檔。 本文的倒數第二部分將提供有關如何使用 Taipy Studio 建立 config.toml 設定檔的指南。* --- ## 6. 場景建立與執行 執行 Taipy 場景涉及: - 載入配置; - 執行 Taipy Core 服務;和 - 建立並提交場景以供執行。 這是基本的程式碼模板: ``` import taipy as tp from config import scenario_cfg # Import the Scenario configuration tp.Core().run() # Start the Core service scenario_1 = tp.create_scenario(scenario_cfg) # Create a Scenario instance scenario_1.submit() # Submit the Scenario for execution # Total runtime: 74.49s ``` --- ### 跳過不必要的任務執行 Taipy 最實用的功能之一是,如果任務的輸出已經計算出來,它能夠跳過任務執行。 讓我們透過一些場景來探討這一點: --- #### 更改付款閾值 ``` # Changing Payment Threshold to 1600 scenario_1.payment_threshold.write(1600) scenario_1.submit() # Total runtime: 31.499s ``` *發生了什麼事*:Taipy 夠聰明,可以跳過任務 1,因為付款閾值只影響任務 2。 在這種情況下,透過使用 Taipy 執行管道,我們發現執行時間減少了 50% 以上。 --- #### 更改細分分析指標 ``` # Changing metric to median scenario_1.metric.write("median") scenario_1.submit() # Total runtime: 23.839s ``` *會發生什麼事*:在這種情況下,只有任務 3 和任務 4 受到影響。 Taipy 巧妙地跳過任務 1 和任務 2。 --- #### 更改總計統計類型 ``` # Changing summary_statistic_type to max scenario_1.summary_statistic_type.write("max") scenario_1.submit() # Total runtime: 5.084s ``` *發生了什麼事*:這裡,只有任務 4 受到影響,Taipy 僅執行此任務,跳過其餘任務。 Taipy 的智慧任務跳過功能不僅能節省時間,還能節省時間。它是一個資源優化器,在處理大型資料集時變得非常有用。 --- ## 7. Taipy Studio 您可以使用 Taipy Studio 建置 Taipy *config.toml* 設定檔來取代定義 *config.py* 腳本。 ![Studio 內的 DAG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct0bcisreqmg56mk4fgm.png) 首先,使用擴展市場安裝 [Taipy Studio ](https://marketplace.visualstudio.com/items?itemName=Taipy.taipy-studio)擴充。 --- ### 建立配置 - **建立設定檔**:在 VS Code 中,導覽至 Taipy Studio,然後透過點擊參數視窗上的 + 按鈕啟動新的 TOML 設定檔。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jqe1fq87jaauf56b7hg.png) - 然後右鍵單擊它並選擇 **Taipy:顯示視圖**。 ![配置顯示視圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v7rkyipli0oq13iw8mxc.png) - **新增實體**到您的 Taipy 配置: 在 Taipy Studio 的右側,您應該會看到一個包含 3 個圖示的列表,可用於設定管道。 ![配置圖示](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyxvv15nu9xr87n5y7q1.png) 1. 第一項是新增資料節點。您可以將任何 Python 物件連結到 Taipy 的資料節點。 2. 第二項用於新增任務。任務可以連結到預先定義的 Python 函數。 3. 第三項是新增場景。 Taipy 讓您在一個配置中擁有多個場景。 --- #### - 資料節點 **輸入資料節點**:建立一個名為“*path_to_data*”的資料節點,然後導航到“詳細資料”選項卡,新增屬性“*default_data*”,並將“*SMALL_amazon_customers_data.csv*”貼上為您的資料的路徑資料集。 --- **中間資料節點**:我們需要再增加四個資料節點:「*scored_df*」、「*segmented_customer_df*」、「*segment_result*」、「*high_value_summary_df*」。透過 Taipy 的智慧設計,您無需為這些中間資料節點進行任何配置;系統會巧妙地處理它們。 --- **具有預設值的中間資料節點**:我們最終定義了另外四個中間資料節點,並將「*default_data*」屬性設為以下內容: - payment_threshold: “1000:int” ![資料節點檢視](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odkrz0pq2dhqpm0gnta2.png) - 分數閾值:“1.5:浮動” - 測量:“平均值” -summary_statistic_type:“中位數” --- #### - 任務 點擊新增任務按鈕,您可以配置新任務。 新增四個任務,然後**將每個任務連結到「詳細資料」標籤下的對應函數**。 Taipy Studio 將掃描您的專案資料夾並提供可供選擇的分類函數列表,並按 Python 檔案排序。 --- **任務 1** (*preprocess_and_score*):在 Taipy studio 中,您可以按一下「任務」圖示以新增任務。 您可以將輸入指定為“*path_to_data*”,將輸出指定為“*scored_df*”。 然後,在「詳細資料」標籤下,您可以將此任務連結到 *algos.algo.preprocess_and_score* 函數。 ![任務流程及評分](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnc57wbxafjh2s3m6fat.png) --- **任務 2** (*featurization_and_segmentation*):與任務 1 類似,您需要指定輸入 (“*scored_df*”、“* payment_threshold*”、“*score_threshold*”) 和輸出 (“*segmented_customer_df*”) ” )。將此任務連結到 *algos.algo.featurization_and_segmentation* 函數。 ![任務特徵化](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbtm200u9meq1x1rcy2w.png) --- **任務 3** (*segment_analysis*):輸入為“*segmented_customer_df*”和“*metric*”,輸出為“*segment_result*”。 連結到 *algos.algo.segment_analysis* 函數。 ![任務片段分析](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnnl1w1q0blebzbyawvt.png) --- **任務 4** (high_value_cust_summary_statistics):輸入包含「*segment_result*」、「*segmented_customer_df*」和「*summary_statistic_type*」。輸出為“*high_value_summary_df*”。連結到 *algos.algo.high_value_cust_summary_statistics* 函數。 ![任務統計](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tynu6e718z1dwf8id05m.png) --- ## 結論 Taipy 提供了一種**智慧方式來建立和管理資料管道**。 特別是可跳過的功能使其成為優化運算資源和時間的強大工具,在涉及大型資料集的場景中特別有用。 Dask 提供了資料操作的原始能力,而 Taipy 增加了一層智能,使您的管道不僅強大而且智能。 --- 其他資源 如需完整程式碼和 TOML 配置,您可以存取此 [GitHub 儲存庫](https://github.com/Avaiga/demo-dask-customer-analysis/tree/develop)。若要深入了解 Taipy,請參閱[官方文件](https://docs.taipy.io/en/latest/)。 一旦您了解 Taipy 場景管理,您就可以更有效率地為最終用戶建立資料驅動的應用程式。只需專注於您的演算法,Taipy 就會處理剩下的事情。 --- ![很多](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ua3x4t3yttba6g25jjqo.gif) 希望您喜歡這篇文章! --- 原文出處:https://dev.to/taipy/big-data-models-vs-computer-memory-4po6

使用 Markdown 與 React 在 Github 文件中顯示淺色模式、深色模式

警告:所表達的觀點可能不適合所有受眾! 😂 ## 簡介 在本文結束時,您將了解並能夠根據使用者偏好 - **深色**或**淺色**模式展示您的 Markdown 影像。 1. 我將介紹如何在 GitHub README.md 中加入兩個圖像 - 根據所選的“主題”,您的圖像將正確回應。 2. 我將引導您在 Markdown 中合併影像的過程,並示範如何使用 React 使它們回應。 😎 ___ ## 你使用淺色還是深色? 我不了解你的情況,但無論平台如何,如果他們可以選擇在淺色和深色模式之間切換,那就沒有競爭了。 淺色主題正在切換為深色,事實上,當然在我寫這篇文章的時候! ![深色主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0yfjc2yv5pfyacu74go.png) 話雖如此,在軟體開發的快速發展中,創造無縫的使用者體驗至關重要。 這種體驗的一部分涉及適應使用者偏好,例如淺色和深色模式。 我還記得幾年前,Github 宣布了用戶可以切換到「深色模式」的選項,這是一件非常大的事情。 ![GitHub 深色主題](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qi10urco3o6fdojm6ipf.png) 【Github揭曉黑暗主題的重要時刻】(https://t.co/HEotvXVJ7R) 🤩 2020 年 12 月 8 日🎆 近年來,使用者介面中深色和淺色模式選項的出現已成為一種流行趨勢。 我絕對不是唯一一個喜歡使用深色主題選項的人,根據 Android 用戶的說法,[91.8% 的用戶更喜歡深色模式](https://www.androidauthority.com/dark-mode-poll-results-1090716/) 所以我們可以猜測這個數字在所有作業系統中都相當高。 這當然可能會引起激烈的爭論,所以我會盡力將自己的觀點降到最低。 ![輕模式迷因](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3yiepj8a46rwhu69fgw.png) ## 改善使用者體驗 主要目標是透過在應用程式中提供選項來改善用戶體驗。 有多種方法可以建立每個圖像的多個版本,在本教程中我們不會深入討論細節。 只要確保您的圖像在兩個主題中脫穎而出並具有透明背景,您就會獲得成功。 **_讓我們開始派對吧!_** ## GitHub 自述文件中的響應式圖像 您有一個專案並想讓您的 GitHub 專案 README.md 真正流行嗎? 無論使用者使用什麼淺色主題,我們都需要一種方法來指定圖像應在 Markdown 中顯示哪種主題(淺色或深色)。 當您想要根據使用者選擇的配色方案優化圖片的顯示時,這特別有用,並且它涉及將 **HTML `<picture>`** 元素與 `prefers-color-scheme` 媒體功能結合使用如下所示。 繼續將圖片檔案直接拖曳到 GitHub 中並放在“srcset=”後面。 ``` <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/boxyhq/.github/assets/66887028/df1c9904-df2f-4515-b403-58b14a0e9093"> <source media="(prefers-color-scheme: light)" srcset="https://github.com/boxyhq/.github/assets/66887028/e093a466-72ea-41c6-a292-4c39a150facd"> <img alt="BoxyHQ Banner" src="https://github.com/boxyhq/jackson/assets/66887028/b40520b7-dbce-400b-88d3-400d1c215ea1"> </picture> ``` 瞧! ![SAML Jackson 暗模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q51g41fjfqnposn50una.png) ![SAML Jackson 燈光模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d0xzs88txjylnixilaqu.png) 太好了,你有 5 秒嗎? https://github.com/boxyhq/jackson --- ## 使用 React 在 Markdown 中回應影像 假設今天我將像平常一樣用 Markdown 編寫博客,並將其發佈到我的網站上。 我使用的圖像需要根據使用者偏好做出回應,但在 Markdown 中不可能偵聽本地儲存和設定狀態中的「主題」變更。 ![本機儲存](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrjz4to8x17h63dtybxn.png) 值得慶幸的是,如果我們將 React 匯入到 Markdown 檔案中,但先建立一個元件,就有一種方法可以解決這個困境。 ## 反應文件 ``` src/components/LightDarkToggle.js import React, { useEffect, useState } from 'react'; function ToggleImages() { // Define a state variable to track the user's login status const [currentTheme, setcurrentTheme] = useState(localStorage.getItem('theme')); // Add an event listener for the 'storage' event inside a useEffect useEffect(() => { const handleStorageChange = (event) => { console.log('Storage event detected:', event); // Check the changed key and update the state accordingly console.log("event", event.key) if (event.key === 'theme') { setcurrentTheme(event.newValue); } }; window.addEventListener('storage', handleStorageChange); // Clean up the event listener when the component unmounts return () => { window.removeEventListener('storage', handleStorageChange); }; }, []); // The empty dependency array ensures that this effect runs once when the component mounts return ( <div className="image-container"> {currentTheme == 'light'? ( <img id="light-mode-image" src="/img/blog/boxyhq-banner-light-bg.png" alt="Light Mode Image" ></img> ):( <img id="dark-mode-image" src="/img/blog/boxyhq-banner-dark-bg.png" alt="Dark Mode Image" ></img> )} </div> ); } export default ToggleImages; ``` 我在程式碼中加入了註釋和一些控制台日誌,以幫助了解正在發生的事情,但讓我們快速分解它。 - React useState 鉤子管理 `currentTheme` 的狀態,它代表使用者選擇的儲存在本機儲存中的主題。 - useEffect 掛鉤用於為「儲存」事件新增事件偵聽器。當儲存事件發生時(表示本機儲存發生變化),元件會檢查變更的鍵是否為“theme”,並相應地更新“currentTheme”狀態。 - 此元件根據使用者選擇的主題呈現不同的影像,如果主題是“淺色”,則顯示淺色模式影像;如果主題是其他主題,則顯示深色模式影像。 酷,讓我們繼續吧! ## 降價文件 讓我們為新部落格建立一個 .md 檔案。 ``` --- slug: light-and-dark-mode-responsive-images title: 'Light and Dark Mode Responsive Images' tags_disabled: [ developer, react, javascript, open-source, ] image: /img/blog/light-dark.png author: Nathan Tarbert author_title: Community Engineer @BoxyHQ author_url: https://github.com/NathanTarbert author_image_url: https://boxyhq.com/img/team/nathan.jpg --- import ToggleImages from '../src/components/LightDarkToggle.js'; ## 🤩 Let's start this blog off with a bang! Our business logo is now responsive with each user's preference, whether it's **light** or **dark** mode! <div> <ToggleImages /> </div> More blog words... ``` 此時,我們只需匯入 React 元件並將其呈現在 Markdown 檔案中。 由於這是一個 Next.js 應用程式,讓我們啟動伺服器“npm run dev”並查看結果。 ![貓鼓滾](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lyjzjqgcwaubyj5ve1o3.gif) ![網站深色模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qraltb34mrl9y8j9jppq.png) 並切換到淺色主題 ![網站燈光模式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u33jgzha5fbfy6tlb4hs.png) 讓我們打開控制台來查看我們的事件 ![console.log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpxz5gbhwt308vatsnkp.png) 你有它! 這些是在 Markdown 中展示響應式映像的幾種方法,其中一個範例使用 React 來幫助我們在本地儲存中設定狀態。 我希望您喜歡這篇文章,如果您喜歡開發,請在 [X (Twitter)](https://twitter.com/nathan_tarbert) 上關注我,我們下次再見! --- 原文出處:https://dev.to/nathan_tarbert/the-zebras-guide-to-showcase-your-images-in-light-dark-17f5

使用 Python 和 Pytest 自動化 API 測試

您是否想過如何使用 python 測試您的 API?在本文中,我們將學習如何使用 Python 和 pytest 框架來測試我們的 API。 對於本教程,您需要安裝 python,您可以在[此處](https://www.python.org/downloads/)下載它 --- ### 簡介: - [什麼是 Python 和 Pytest 框架](#what-is-python-and-pytest-framework) - [我們專案的設定](#configuration-of-our-project) - [使用python建立虛擬環境](#creation-of-virtual-environment-with-python) - [測試的依賴關係設定](#setup-of-dependency-for-the-tests) - [建立我們的第一個測試](#creating-our-first-test) - [將被測試的 API 的定義](#definition-of-the-api-that-will-be-tested) - [建立我們的測試](#creating-our-test) - [重建我們的測試](#refactoring-our-tests) - [產生 html 報告結果](#generate-html-report-result) - [結論](#conclusion) --- ## 什麼是 Python 和 Pytest 框架 「Python」是一種高級通用程式語言,以其簡單性和可讀性而聞名。它由 Guido van Rossum 建立,於 1991 年首次發布。 Python 的設計易於學習,並且具有乾淨簡潔的語法,這使其成為初學者和經驗豐富的程式設計師的流行選擇。 「pytest」框架可以輕鬆編寫小型、可讀的測試,並且可以擴展以支援應用程式和程式庫的複雜功能測試。 --- ## 我們專案的配置 ### 用python建立虛擬環境 在開始建立之前,我們先來了解一下什麼是Python上的虛擬環境。 Python 中的虛擬環境是一個獨立的目錄或資料夾,可讓您為專案建立和管理隔離的 Python 環境。透過環境,您可以輕鬆管理依賴項,避免與不同版本的 python 發生衝突。 虛擬環境(除其他外)是: - 用於包含支援專案(庫或應用程式)所需的特定 Python 解釋器以及軟體庫和二進位檔案。預設情況下,它們與其他虛擬環境中的軟體以及作業系統中安裝的 Python 解釋器和庫隔離。 - 包含在專案目錄中的目錄中,通常名為“venv”或“.venv”,或在許多虛擬環境的容器目錄下,例如“~/.virtualenvs”。 - 未簽入原始碼控制系統(例如 Git)。 - 被認為是一次性的 - 應該很容易刪除並從頭開始重新建立它。您沒有在環境中放置任何專案程式碼 - 不被視為可移動或可複製 - 您只需在目標位置重新建立相同的環境。 您可以在[此處](https://docs.python.org/3/library/venv.html#venv-def)閱讀有關 python 環境的更多資訊。 #### 視窗 首先,為您的專案建立一個資料夾,然後打開 cmd 並使用命令 cd 導航到該資料夾: ``` cd tests_with_python ``` 如果您不知道資料夾在哪裡,可以執行命令“ls”,您將看到資料夾列表,並且可以瀏覽它們。在我們的專案資料夾中,執行以下命令: ``` python -m venv name_of_environment ``` 您的環境名稱可以是任何人,只需記住python 區分大小寫,請查看[PEP 8 風格指南](https://peps.python.org/pep-0008/) 以了解有關Python 約定的更多資訊. 要啟動我們的環境,我們使用以下命令: ``` name_of_environment\Scripts\Activate ``` 如果一切正確,您的環境將被激活,並且在 cmd 上您將看到如下所示: ``` (name_of_environment) C:\User\tests ``` 要停用您的環境,只需執行: ``` deactivate ``` #### Linux 或 MacOS 為您的專案建立一個資料夾,然後打開 cmd 並使用命令 cd 導航到該資料夾: ``` cd tests_with_python ``` 要啟動我們的環境,我們使用以下命令: ``` source name_of_environment/bin/activate ``` 如果一切正確,您的環境將被激活,並且在 cmd 上您將看到如下所示: ``` (name_of_environment) your_user_name tests % ``` 要停用您的環境,只需執行: ``` deactivate ``` ### 設定測試的依賴關係 當我們要測試 API 時,我們需要安裝依賴項來幫助我們進行測試,首先我們將安裝「requests」函式庫來幫助我們發出請求: PS:在執行此命令之前請確保您的環境已激活 ``` pip install requests ``` 為了進行測試,我們將安裝「pytests」框架: ``` pip install pytest ``` --- ## 建立我們的第一個測試 ### 將要測試的 API 的定義 在本教程中,我們將使用返回小行星列表的 Nasa API:[Asteroids - NeoWs](https://api.nasa.gov/#donkiGST),我們將測試檢索基於小行星列表的端點在他們最接近地球的日期。 關於API: - 基本網址:`https://api.nasa.gov/neo/rest/v1/feed` - 查詢參數: |參數|類型|預設|描述| | --------|---------|--------|--------------------| |start_date|YYYY-MM-DD|無|小行星搜尋的開始日期| |end_date|YYYY-MM-DD|start_date後7天|小行星搜尋的結束日期| |api_key|字串|DEMO_KEY|用於擴展用途的 api.nasa.gov 密鑰| 在本教程中,我們將重點放在三種類型的測試: - 合約:如果 API 能夠驗證傳送的查詢參數 - 狀態:狀態程式碼是否正確 - 身份驗證:即使這個API不需要令牌,我們也可以用它來做測試 我們的場景: |方法|測試|預期結果 | | --------|--------|--------------------| |獲取 |搜尋成功 | - 傳回狀態程式碼 200<br/> 正文回應包含小行星清單| |獲取 |無需任何查詢參數即可搜尋 | - 返回狀態碼403<br/>| |獲取 |僅搜尋開始日期| - 傳回狀態程式碼 200 <br/> 主體回應包含小行星清單| |獲取 |僅搜尋結束日期| - 傳回狀態程式碼 200 <br/> 主體回應包含小行星清單| |獲取 |在有效日期範圍內搜尋| - 傳回狀態碼 200<br/> - 正文回應包含所有非空白欄位| |獲取 |當開始日期大於結束日期時進行搜尋| - 傳回狀態程式碼 400 <br/>| |獲取 |使用無效的 API 令牌進行搜尋| - 傳回狀態程式碼 403 <br/> 主體回應包含小行星清單| ### 建立我們的測試 首先,我們將建立一個名為「tests.py」的文件,我們將在該文件中編寫測試。為了幫助我們使用良好的實踐並編寫良好的自動化測試,讓我們使用 [TDD(測試驅動開發)](https://www.browserstack.com/guide/what-is-test-driven-development?psafe_param=1?keyword=&campaignid=&adgroupid=&adid=8784011037660164696&utm_source=google&utm_medium=cpc&utm_platform=paidads&utm_content=602353912717&utm_campapaidads&utm_content=602353912717&utm_campa.utm_term=+&gad_source=1&gclid=CjwKCAiAxreqBhAxEiwAfGfndN8P705lwnkvEFnCz_lueR2hnhmZXgboBQEtKTaCIRbhcb1SXOxBYhoC-WoQAD_BwwE)技術。 該技術包括: - 紅色 - 進行失敗的測試 - 綠色 - 使此測試通過 - 重構 - 重構所做的事情,刪除重複的內容 為了編寫一套好的測試,我們將使用 3A 技術: - 安排:準備上下文。 - 行動:執行我們想要示範的行動。 - 斷言:表明我們預期的結果確實發生了。 從紅色開始,使用 3A 技術,我們將編寫第一個測試「成功搜尋小行星」: ``` import pytest def test_search_asteroids_with_sucess(): # Arrange: api_key = "DEMO_KEY" #Act: response = make_request(api_key) #Assertion: assert response.status_code == 200 # Validation of status code data = response.json() # Assertion of body response content: assert len(data) > 0 assert data["element_count"] > 0 ``` - 安排:我們建立一個變數來插入 api_key,在此步驟中,您可以插入執行測試所需的任何資料。通常,在這一步驟我們會建立模擬資料。 - Act:在這一步驟中我們呼叫了負責發出請求的方法 - 斷言:我們驗證回應 `方法或類別的名稱應以 test 開頭` 若要執行我們的測試,請在命令提示字元中執行: ``` pytest test.py ``` 我們將收到一個錯誤,因為我們沒有建立執行請求的方法: ``` test.py F [100%] ====================================================================== FAILURES ====================================================================== _________________________________________________________ test_search_asteroids_with_sucess __________________________________________________________ def test_search_asteroids_with_sucess(): > response = make_request() E NameError: name 'make_request' is not defined test.py:5: NameError ============================================================== short test summary info =============================================================== FAILED test.py::test_search_asteroids_with_sucess - NameError: name 'make_request' is not defined ================================================================= 1 failed in 0.01s ================================================================== ``` 現在,讓我們建立方法來執行請求: ``` import requests def make_request(api_key): base_url = "https://api.nasa.gov/neo/rest/v1/feed/" response = requests.get(f'{base_url}?api_key={api_key}') return response ``` 現在,再次執行我們的測試: ``` ================================================================ test session starts ================================================================= platform darwin -- Python 3.11.5, pytest-7.4.3, pluggy-1.3.0 rootdir: /Users/Documents/tests_python collected 1 item test.py . [100%] ================================================================= 1 passed in 20.22s ================================================================= ``` --- ## 重構我們的測試 現在我們已經了解如何使用 pytest 建立測試以及如何建立請求,我們可以編寫其他測試並開始重構測試。我們要做的第一個重構是從測試文件中刪除請求方法。我們將建立一個名為「make_requests.py」的新文件,其中將包含我們的請求,並將我們所做的請求移至此文件: ``` import requests def make_request(api_key): base_url = "https://api.nasa.gov/neo/rest/v1/feed/" response = requests.get(f'{base_url}?api_key={api_key}') return response ``` 現在,我們需要考慮在其他測試中重複使用此方法,因為我們需要為不同的測試傳遞不同的參數。我們可以透過很多方法來做到這一點,在本教程中,我們將參數的名稱從“api_key”更改為“query_parameters”。我們這樣做是為了讓我們的方法更加靈活,我們可以一次傳遞參數進行測試: ``` import requests def make_request(query_parameters): base_url = "https://api.nasa.gov/neo/rest/v1/feed/" response = requests.get(f'{base_url}?{query_parameters}') return response ``` 之後,我們需要更改我們的測試文件。我們將導入我們建立的這個方法: ``` from make_requests import make_request ``` 為了以更好的方式組織我們的測試,並遵循 pytest 文件的建議,我們將測試移至類別「TestClass」: 再次執行我們的測試: ``` ============================= test session starts ============================== collecting ... collected 7 items test.py::TestClass::test_search_asteroids_with_sucess test.py::TestClass::test_search_asteroids_with_query_parameters_empty test.py::TestClass::test_search_asteroids_with_start_date test.py::TestClass::test_search_asteroids_with_end_date test.py::TestClass::test_search_asteroids_in_valid_range test.py::TestClass::test_search_asteroids_in_invalid_range test.py::TestClass::test_search_asteroids_in_invalid_token ============================== 7 passed in 5.85s =============================== PASSED [ 14%]PASSED [ 28%]PASSED [ 42%]PASSED [ 57%]PASSED [ 71%]PASSED [ 85%]PASSED [100%] Process finished with exit code 0 ``` ### 產生 html 報告結果 為了更好地視覺化您的測試結果,我們可以使用 pytest-html-reporter 庫產生報告 html,為此,我們首先需要安裝該套件: ``` pip install pytest-html ``` 若要產生報告,請在執行測試時新增: ``` pytest test.py --html-report=./report/report.html ``` 將產生一個包含測試結果的 .html 文件,如下所示: ![report_example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eol3v7iex84t7prmyb9c.png) ## 結論 本文是一篇教程,介紹如何開始使用 python 和 pytest 框架為 API 編寫自動化測試以及如何產生一個報告 html。 您可以在[此處](https://github.com/aliciamarianne1507/tests_python)存取本教學中使用的專案。 我希望這些內容對您有用。 如果您有任何疑問,請隨時與我聯繫! 親親,下週見💅🏼 --- 原文出處:https://dev.to/m4rri4nne/automating-your-api-tests-using-python-and-pytest-23cc

如何從頭開始為基本網站設定 Webpack 4 或 5

原文出處:https://dev.to/antonmelnyk/how-to-configure-webpack-from-scratch-for-a-basic-website-46a5 #簡介 你好,讀者! 如您所知,設定 [Webpack](https://webpack.js.org/) 可能是一項令人沮喪的任務。儘管有很好的文件,但由於一些原因,這個捆綁器並不是一匹舒服的馬。 Webpack 團隊正在非常努力且相對快速地開發它,這是一件好事。然而,對於新開發人員來說,一次性學習所有內容是難以承受的。教程已經過時,一些插件損壞,發現的範例可能會令人困惑。有時,您可能會陷入一些瑣碎的事情,並透過 Google 進行大量搜尋,以在 GitHub issues 中找到一些最終有幫助的簡訊。 缺乏關於 Webpack 及其工作原理的介紹性文章,人們直接奔向 *create-react-app* 或 *vue-cli* 等工具,但有時需要編寫一些簡單的純 JavaScript 和 SASS,無需框架或任何奇特的東西。 本指南將逐步介紹 ES6、SASS 和圖片/字體的 Webpack 配置,無需任何框架。對於大多數簡單的網站開始使用 Webpack 或將其用作進一步學習的平台應該足夠了。儘管本指南需要一些有關 Web 開發和 JavaScript 的先驗知識,但它可能對某些人有用。至少當我開始使用 Webpack 時,我會很高興遇到這樣的事情! #我們的目標 我們將使用 Webpack 將 JavaScript、樣式、圖像和字體檔案捆綁到一個 *dist* 資料夾中。 ![](https://thepracticaldev.s3.amazonaws.com/i/9n9g3yr1v6lhjfg8roqc.png) Webpack 將產生 1 個捆綁的 JavaScript 檔案和 1 個捆綁的 CSS 檔案。您可以像這樣簡單地將它們加入到 HTML 文件中(當然,如果需要,您應該更改 dist 資料夾的路徑): ``` <link rel="stylesheet" href="dist/bundle.css"> <script src="dist/bundle.js"></script> ``` 你就可以走了:tropical_drink: 您可以查看本指南中完成的範例::link:[link](https://github.com/heyanton/simple_webpack_boilerplate)。 注意:我最近更新了依賴項。本指南適用於最新的 Webpack 5,但設定仍適用於 Webpack 4,以防您需要! #開始 ####1。安裝Webpack 我們使用 [npm](https://www.npmjs.com/): `$ npm init` 命令在專案資料夾中建立一個 *package.json* 文件,我們將在其中放置 JavaScript 依賴項。然後我們可以使用 $ npm i --save-dev webpack webpack-cli 來安裝 Webpack 本身。 ####2。建立入口點文件 Webpack 從單一 JavaScript 檔案開始運作,該檔案稱為入口點。在 *javascript* 資料夾中建立 *index.js*。您可以在這裡編寫一些簡單的程式碼,例如 `console.log('Hi')` 以確保其正常運作。 ![](https://thepracticaldev.s3.amazonaws.com/i/tlt38mn2u385i06y4vzn.png) ####3。建立 webpack.config.js ....在專案資料夾中。這裡是所有:sparkles:魔法發生的地方。 ``` // Webpack uses this to work with directories const path = require('path'); // This is the main configuration object. // Here, you write different options and tell Webpack what to do module.exports = { // Path to your entry point. From this file Webpack will begin its work entry: './src/javascript/index.js', // Path and filename of your result bundle. // Webpack will bundle all JavaScript into this file output: { path: path.resolve(__dirname, 'dist'), publicPath: '', filename: 'bundle.js' }, // Default mode for Webpack is production. // Depending on mode Webpack will apply different things // on the final bundle. For now, we don't need production's JavaScript // minifying and other things, so let's set mode to development mode: 'development' }; ``` ####4。在 *package.json* 中新增 npm 腳本來執行 Webpack 要執行 Webpack,我們必須使用 npm 腳本和簡單的命令「webpack」以及我們的設定檔作為 *config* 選項。我們的 *package.json* 現在應該如下所示: ``` { "scripts": { "build": "webpack --config webpack.config.js" }, "devDependencies": { "webpack": "^4.29.6", "webpack-cli": "^3.2.3" } } ``` ####5。執行Webpack 透過基本設置,您可以執行“$ npm run build”命令。 Webpack 將尋找我們的入口文件,解析其中的所有[*import*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 模組相依性並將其捆綁到*dist* 資料夾中的單一 *.js* 檔案。在控制台中,您應該看到如下內容: ![](https://thepracticaldev.s3.amazonaws.com/i/8j9rn72esbfbvn27duz6.png) 如果您將 `<script src="dist/bundle.js"></script>` 加入到 HTML 檔案中,您應該在瀏覽器控制台中看到「Hi」! #:顯微鏡:裝載機 偉大的!我們捆綁了標準 JavaScript。但是,如果我們想使用 ES6(及更高版本)的所有酷炫功能並保持瀏覽器相容性怎麼辦?我們應該如何告訴 Webpack 將我們的 ES6 程式碼轉換(*transpile*)相容於瀏覽器的程式碼? 這就是 Webpack **loaders** 發揮作用的地方。 Loader 是 Webpack 的主要功能之一。他們對我們的程式碼進行某些轉換。 讓我們在 *webpack.config.js* 檔案中新增選項 *module.rules*。在此選項中,我們將介紹 Webpack 如何準確地轉換不同類型的檔案。 ``` entry: /* ... */, output: /* ... */, module: { rules: [ ] } ``` 對於 JavaScript 文件,我們將使用: ###1。 [babel-loader](https://github.com/babel/babel-loader) [Babel](https://babeljs.io/) 是目前最好的 JavaScript 轉譯器。我們將告訴 Webpack 在捆綁之前使用它將現代 JavaScript 程式碼轉換為與瀏覽器相容的 JavaScript 程式碼。 Babel-loader 正是這樣做的。讓我們安裝它: `$ npm i --save-dev babel-loader @babel/core @babel/preset-env` 現在我們要新增有關 JavaScript 檔案的規則: ``` rules: [ { test: /\.js$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] ``` * `test` 是我們要轉換的檔案副檔名的[正規表示式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)。在我們的例子中,它是 JavaScript 檔案。 * `exclude` 是一個正規表示式,它告訴 Webpack 在轉換模組時應該忽略哪個路徑。這意味著如果我們將來導入它們,我們將不會轉換從 npm 匯入的供應商庫。 * `use` 是主要規則的選項。這裡我們設定 loader,它將應用於與「test」正規表示式相對應的檔案(在本例中為 JavaScript 檔案) * “選項”可能因載入程式而異。在這種情況下,我們為 Babel 設定預設預設,以考慮應該轉換哪些 ES6 功能,哪些不應該轉換。它本身是一個單獨的主題,如果您感興趣,可以深入研究它,但目前保持這樣是安全的。 現在您可以將 ES6 程式碼安全地放置在 JavaScript 模組中! ###2。 [sass-loader](https://github.com/webpack-contrib/sass-loader) 是時候處理樣式了。通常,我們不想寫純 CSS。我們經常使用 [SASS](https://sass-lang.com/) 預處理器。我們將 SASS 轉換為 CSS,然後應用自動前綴和縮小。這是 CSS 的一種「預設」方法。讓我們告訴 Webpack 要這樣做。 假設我們在 *javascripts/index.js* 入口點導入主 SASS 檔案 *sass/styles.scss*。 ``` import '../sass/styles.scss'; ``` 但目前,Webpack 不知道如何處理 *.scss* 檔案或除 *.js* 之外的任何檔案。我們需要新增適當的載入器,以便 Webpack 可以解析這些檔案: `$ npm i --save-dev sass sass-loader postcss-loader css-loader` 我們可以為 SASS 檔案新增規則並告訴 Webpack 如何處理它們: ``` rules: [ { test: /\.js$/, /* ... */ }, { // Apply rule for .sass, .scss or .css files test: /\.(sa|sc|c)ss$/, // Set loaders to transform files. // Loaders are applying from right to left(!) // The first loader will be applied after others use: [ { // This loader resolves url() and @imports inside CSS loader: "css-loader", }, { // Then we apply postCSS fixes like autoprefixer and minifying loader: "postcss-loader" }, { // First we transform SASS to standard CSS loader: "sass-loader" options: { implementation: require("sass") } } ] } ] ``` 請注意此處有關 Webpack 的重要事項。可連結多個裝載機;它們將從“use”陣列中的最後一個到第一個逐個應用。 現在,當 Webpack 在程式碼中遇到“import 'file.scss';”時,它知道該怎麼做! ####PostCSS 我們應該如何告訴 *postcss-loader* 它必須應用哪些轉換?我們建立一個單獨的設定檔 `postcss.config.js` 並使用我們的樣式所需的 postcss 外掛程式。您可能會發現對最基本和有用的插件進行縮小和自動加入前綴,以確保 CSS 為您的真實網站做好準備。 首先,安裝這些 postcss 外掛:`$ npm i --save-dev autoprefixer cssnano`。 其次,將它們加入到 *postcss.config.js* 檔案中,如下所示: ``` module.exports = { plugins: [ require('autoprefixer'), require('cssnano'), // More postCSS modules here if needed ] } ``` 您可以更深入地研究 [PostCSS](https://github.com/postcss/postcss),找到更多適合您的工作流程或專案需求的插件。 在完成所有 CSS 設定之後,只剩下一件事了。 Webpack 將解析您的 *.scss* 導入,對其進行轉換,然後...下一步是什麼?它不會神奇地建立一個與您的樣式捆綁在一起的單一 *.css* 檔案;我們必須告訴 Webpack 要這樣做。但這個任務超出了裝載機的能力範圍。為此,我們必須使用 Webpack 的 **插件**。 #:electric_plug: 插件 他們的目的是做裝載機做不到的任何事。如果我們需要將所有轉換後的 CSS 提取到一個單獨的「捆綁」檔案中,我們必須使用插件。我們的案例有一個特殊的插件:*MiniCssExtractPlugin*: `$ npm i --save-dev mini-css-extract-plugin` 我們可以在 *webpack.config.js* 檔案的開頭單獨導入外掛: ``` const MiniCssExtractPlugin = require("mini-css-extract-plugin"); ``` 在我們設定載入器的“module.rules”陣列之後加入新的“plugins”程式碼,我們用選項啟動我們的插件: ``` module: { rules: [ /* ... */ ] }, plugins: [ new MiniCssExtractPlugin({ filename: "bundle.css" }) ] ``` 現在我們可以將此插件連結到我們的 CSS 載入器中: ``` { test: /\.(sa|sc|c)ss$/, use: [ { // After all CSS loaders, we use a plugin to do its work. // It gets all transformed CSS and extracts it into separate // single bundled file loader: MiniCssExtractPlugin.loader }, { loader: "css-loader", }, /* ... Other loaders ... */ ] } ``` 完畢!如果您按照步驟操作,則可以執行「$ npm run build」命令並在 *dist* 資料夾中找到 *bundle.css* 檔案。現在的一般設定應如下所示: ![](https://thepracticaldev.s3.amazonaws.com/i/ye1wwk5r7rvxcu9n0ylm.png) Webpack 有大量用於不同目的的插件。您可以根據需要在[官方文件](https://webpack.js.org/plugins/)中探索它們。 # 更多載入器:圖像和字體 此時,您應該了解 Webpack 工作原理的基礎知識。但我們還沒完成。大多數網站都需要一些資源:我們透過 CSS 設定的圖像和字體。由於 *css-loader*,Webpack 可以解析 `background-image: url(...)` 行,但它不知道如果將 URL 設為 *.png* 或 *jpg* 檔案該怎麼辦。 我們需要一個新的載入器來處理 CSS 內的檔案或能夠直接在 JavaScript 中匯入它們。這是: ### [檔案載入器](https://github.com/webpack-contrib/file-loader) 使用 `$ npm i --save-dev file-loader` 安裝它,並在我們的 *webpack.config.js* 新增規則: ``` rules: [ { test: /\.js$/, /* ... */ }, { test: /\.(sa|sc|c)ss$/, /* ... */ }, { // Now we apply rule for images test: /\.(png|jpe?g|gif|svg)$/, use: [ { // Using file-loader for these files loader: "file-loader", // In options we can set different things like format // and directory to save options: { outputPath: 'images' } } ] } ] ``` 現在,如果你在 CSS 中使用一些像這樣的圖像: ``` body { background-image: url('../images/cat.jpg'); } ``` Webpack 將成功解決它。您將在 *dist/images* 資料夾中找到帶有雜湊名稱的圖像。在 *bundle.css* 會發現類似這樣的內容: ``` body { background-image: url(images/e1d5874c81ec7d690e1de0cadb0d3b8b.jpg); } ``` 正如你所看到的,Webpack 非常聰明——它正確解析了你的 url 相對於 *dist* 資料夾的路徑! 您也可以為字體加入規則並以與圖像類似的方式解析它們;將 outputPath 變更為 *fonts* 資料夾以保持一致性: ``` rules: [ { test: /\.js$/, /* ... */ }, { test: /\.(sa|sc|c)ss$/, /* ... */ }, { test: /\.(png|jpe?g|gif|svg)$/, /* ... */ }, { // Apply rule for fonts files test: /\.(woff|woff2|ttf|otf|eot)$/, use: [ { // Using file-loader too loader: "file-loader", options: { outputPath: 'fonts' } } ] } ] ``` #總結 就是這樣!經典網站的簡單 Webpack 設定。我們介紹了 **入口點**、**載入器** 和 **插件** 的概念以及 Webpack 如何轉換和捆綁檔案。 當然,這是一個非常簡單的配置,旨在了解 Webpack 的一般概念。如果您需要的話,還有很多事情可以加入:來源映射、熱重載、設定 JavaScript 框架以及 Webpack 可以做的所有其他事情,但我覺得這些事情超出了本指南的範圍。 如果您遇到困難或想了解更多訊息,我鼓勵您查看 Webpack [官方文件](https://webpack.js.org/concepts)。快樂捆綁!

寫出 Clean Code 的一些技巧&原則

## 介紹 編寫乾淨的程式碼是每個軟體開發人員的基本技能。乾淨的程式碼不僅使您的程式碼庫更易於維護和理解,而且還能促進團隊成員之間的協作。在這篇綜合文章中,我們將探討什麼是乾淨的程式碼、為什麼它很重要,並為您提供一組最佳實踐和原則來幫助您編寫乾淨且可維護的程式碼。 - 原文出處:https://dev.to/favourmark05/writing-clean-code-best-practices-and-principles-3amh --- ## 什麼是乾淨程式碼? 乾淨的程式碼是易於閱讀、易於理解且易於修改的程式碼。它是沒有不必要的複雜性、冗餘和混亂的程式碼。乾淨的程式碼遵循一組約定和最佳實踐,使其更加一致,使多個開發人員更容易無縫地處理同一個專案。 ## 為什麼乾淨的程式碼很重要? 1. **可讀性**:乾淨的程式碼易於閱讀,這意味著任何人 - 包括未來的你 - 都可以快速理解它。這減少了掌握程式碼功能所需的時間,從而加快了開發和除錯速度。 2. **可維護性**:程式碼的讀取次數多於編寫次數。當您編寫乾淨的程式碼時,隨著時間的推移,維護和擴展應用程式將變得更加容易。這在軟體開發生命週期中至關重要,因為專案經常發展和成長。 3. **協作**:簡潔的程式碼鼓勵協作。當您的程式碼乾淨且組織良好時,其他團隊成員就可以有效地處理它。這使得劃分任務和同時處理程式碼庫的不同部分變得更容易。 4. **減少錯誤**:乾淨的程式碼可以減少引入錯誤的可能性。難以理解的程式碼在修改或增強過程中更容易出錯。 5. **效率**:乾淨的程式碼就是高效率的程式碼。它通常執行速度更快並且使用更少的資源,因為它避免了不必要的操作和複雜性。 現在我們了解了為什麼乾淨的程式碼很重要,讓我們深入研究一些最佳實踐和原則來幫助您編寫乾淨的程式碼。 ## 編寫簡潔程式碼的最佳實踐和原則 1. **有意義的變數和函數名稱** 對變數、函數、類別和其他辨識碼使用描述性名稱。精心選擇的名稱可以傳達實體的目的,使程式碼更容易理解。避免使用單字母變數名或神秘的縮寫。 ``` # Bad variable name x = 5 # Good variable name total_score = 5 ``` 2. **保持函數和方法簡短** 函數和方法應該簡潔並專注於單一任務。單一職責原則(SRP)指出,一個函數應該要做一件事,並且把它做好。較短的函數更容易理解、測試和維護。如果函數變得太長或太複雜,請考慮將其分解為更小、更易於管理的函數。 ``` // Long and complex function function processUserData(user) { // Many lines of code... } // Refactored into smaller functions function validateUserInput(userInput) { // Validation logic... } function saveUserToDatabase(user) { // Database operation... } ``` 3. **評論和文件** 謹慎使用評論,當你使用評論時,要讓它們變得有意義。程式碼應該盡可能不言自明。文件(例如內嵌註解和自述文件)可協助其他開發人員了解程式碼的目的和用法。記錄複雜的演算法、重要的決策和公共 API。 ``` # Bad comment x = x + 1 # Increment x # Good comment # Calculate the total score by incrementing x total_score = x + 1 ``` 4. **一致的格式和縮排** 堅持一致的編碼風格和縮排。這使得程式碼庫看起來乾淨且有組織。大多數程式語言都有社群接受的編碼標準(例如,Python 的 PEP 8、JavaScript 的 eslint),您應該遵循。一致性也適用於命名約定、間距和程式碼結構。 ``` // Inconsistent formatting if(condition){ doSomething(); } else { doSomethingElse(); } // Consistent formatting if (condition) { doSomething(); } else { doSomethingElse(); } ``` 5. **DRY(不要重複)原則** 避免重複程式碼。重複的程式碼更難維護並增加不一致的風險。將通用功能提取到函數、方法或類別中以提高程式碼的可重複使用性。當您需要進行更改時,只需在一個地方進行即可。 假設您正在開發一個 JavaScript 應用程式來計算購物車中商品的總價。最初,您有兩個單獨的函數來計算每種商品類型的價格:一個用於計算一本書的價格,另一個用於計算筆記型電腦的價格。這是初始程式碼: ``` function calculateBookPrice(quantity, price) { return quantity * price; } function calculateLaptopPrice(quantity, price) { return quantity * price; } ``` 雖然這些函數有效,但它們違反了 DRY 原則,因為計算總價的邏輯對於不同的商品類型是重複的。如果您有更多的專案類型需要計算,您最終將重複此邏輯。為了遵循DRY原則,提高程式碼的可維護性,可以對程式碼進行如下重構: ``` function calculateItemPrice(quantity, price) { return quantity * price; } const bookQuantity = 3; const bookPrice = 25; const laptopQuantity = 2; const laptopPrice = 800; const bookTotalPrice = calculateItemPrice(bookQuantity, bookPrice); const laptopTotalPrice = calculateItemPrice(laptopQuantity, laptopPrice); ``` 在此重構的程式碼中,我們有一個calculateItemPrice函數,它根據作為參數提供的數量和價格計算任何商品類型的總價。這遵循了 DRY 原則,因為計算邏輯不再重複。 現在,您可以通過使用適當的數量和價格值呼叫calculateItemPrice來輕鬆計算書籍、筆記本電腦或任何其他商品類型的總價。這種方法提高了程式碼的可重用性、可讀性和可維護性,同時降低了重複程式碼引起的錯誤風險。 6. **使用有意義的空白** 使用空格和換行符正確設置程式碼格式。這增強了可讀性。使用空格來分隔程式碼的邏輯部分。格式良好的程式碼更容易瀏覽,減少讀者的認知負擔。 ``` // Poor use of whitespace const sum=function(a,b){return a+b;} // Improved use of whitespace const sum = function (a, b) { return a + b; } ``` 7. **錯誤處理** 優雅地處理錯誤。在程式碼中使用適當的 try-catch 塊或錯誤處理機制。這可以防止意外崩潰並為除錯提供有價值的訊息。不要抑制錯誤或在沒有正確響應的情況下簡單地記錄錯誤。 ``` // Inadequate error handling try { result = divide(x, y); } catch (error) { console.error("An error occurred"); } // Proper error handling try { result = divide(x, y); } catch (error) { if (error instanceof ZeroDivisionError) { console.error("Division by zero error:", error.message); } else if (error instanceof ValueError) { console.error("Invalid input:", error.message); } else { console.error("An unexpected error occurred:", error.message); } } ``` 8. **測試** 編寫單元測試來驗證程式碼的正確性。測試驅動開發 (TDD) 可以迫使您預先考慮邊緣情況和預期行為,從而幫助您編寫更清晰的程式碼。經過良好測試的程式碼更加可靠並且更容易重構。 ``` // Example using JavaScript and the Jest testing framework test('addition works correctly', () => { expect(add(2, 3)).toBe(5); expect(add(-1, 1)).toBe(0); expect(add(0, 0)).toBe(0); }); ``` 9. **重構** 定期重構你的程式碼。隨著需求的變化以及您對問題域的理解的加深,請相應地調整您的程式碼。隨著專案的發展,重構有助於保持乾淨的程式碼。必要時不要害怕重新存取和改進現有程式碼。 假設您有一個函數,可以計算購物車中具有固定折扣百分比的商品的總價: ``` function calculateTotalPrice(cartItems) { let totalPrice = 0; for (const item of cartItems) { totalPrice += item.price; } return totalPrice - (totalPrice * 0.1); // Apply a 10% discount } ``` 最初,此函數計算總價並應用 10% 的固定折扣。然而,隨著專案的發展,您意識到您需要支持可變折扣。為了重構程式碼使其更加靈活,可以引入折扣參數: ``` function calculateTotalPrice(cartItems, discountPercentage) { if (discountPercentage < 0 || discountPercentage > 100) { throw new Error("Discount percentage must be between 0 and 100."); } let totalPrice = 0; for (const item of cartItems) { totalPrice += item.price; } const discountAmount = (totalPrice * discountPercentage) / 100; return totalPrice - discountAmount; } ``` 在這段重構的程式碼中: * 我們在calculateTotalPrice函數中新增了discountPercentage參數,讓您在呼叫函數時指定折扣百分比。 * 我們對discountPercentage 參數進行驗證,以確保其落在有效範圍內(0 到100%)。如果不在範圍內,我們會拋出錯誤。 * 折扣計算現在基於提供的discountPercentage,使功能更加靈活,能夠適應不斷變化的需求。 通過這種方式重構程式碼,你提高了它的靈活性和可維護性。您可以輕鬆地調整該函數來處理不同的折扣場景,而無需重寫整個邏輯。這證明了隨著專案的發展和需求的變化定期進行程式碼重構的重要性。 10. **版本控制** 使用 Git 等版本控制系統來跟踪程式碼更改。這使您可以與團隊成員有效協作,在必要時恢復到以前的版本,並維護專案開發的清晰歷史記錄。 Git 提供了程式碼審查、分支和合併工具,促進協作和程式碼整潔。 ##結論 編寫乾淨的程式碼不僅僅是一套規則,更是一種心態和紀律。它是關於建立易於閱讀、維護和擴展的軟體。通過遵循這些最佳實踐和原則,您可以成為一名更熟練的開發人員,生成高質量的程式碼。投入時間仔細檢查其他工程師的程式碼庫,特別是在開源專案中,可能是一種啟發性的體驗。通過這種探索,您將獲得對不同編碼風格和策略的寶貴見解。這種接觸使您能夠提煉出編寫原始、可持續程式碼庫的精髓。請記住,乾淨的程式碼是一個持續的旅程,通過練習,它會成為第二天性,從而實現更高效、更愉快的軟體開發。

寫單元測試的五個技巧分享

測試程式碼是軟體開發最重要的方面之一,因為它可以確保產品的品質、可擴展性和可靠性。 但是,如果沒有任何指導,編寫有效的測試可能會很困難。測試程式碼可能比實際功能的程式碼更複雜、更難維護! 原文出處:https://dev.to/one-beyond/the-5-principles-of-unit-testing-1p5f ## 1.精簡和準確的測試 測試程式碼必須**簡單且易於使用**。任何查看測試的人都應該立即知道測試的內容及其目的是什麼。開發測試應該以很少的精力和時間投入帶來巨大的價值。 > _您是否需要超過 30 秒的時間來閱讀和理解您的測試?重寫它!_ 測試“覆蓋率”是重點嗎?不! **僅測試必要的部份就好**。為了敏捷性和簡單性,最好放棄一些測試,**只測試主要業務邏輯和主要邊緣情況就好**。 ## 2.測試行為,而不是元件實作 不要檢查程式碼中的每一行和內部變數。測試時,您應該**專注於結果**。即使方法內的程式碼被重構,結果也應該永遠保持不變! 這樣,如果程式碼庫發生更改,您**不需要重寫測試**。 ``` // Wrong ❌ - Test behaviour describe('Evaluation Service', () => { describe('Register Students', () => { it('Should add new students to the evaluation service', () => { const studentJosh = { id: 1, name: 'Josh McLovin', average: 6.98, } evaluationService.addStudent(studentJosh) expect(evaluationService._students[0].name).toBe('Josh') expect(evaluationService._students[0].average).toBe(6.98) }) }) }) ``` ``` // Right ✅ - Test behaviour describe('Evaluation Service', () => { describe('Register Students', () => { it('Should add new students to the evaluation service', () => { const studentJosh = { id: 1, name: 'Josh McLovin', average: 6.98, } evaluationService.addStudent(studentJosh) expect(evaluationService.getStudentAverage('Josh')).toBe(6.98) }) }) }) ``` ## 3. 關於命名和結構:AAA 模式 您是否曾經遇到過名為“_它應該[...]正確_”的失敗測試,但花了幾分鐘才找到問題所在? 良好命名和結構可以讓您快速且準確地找到任何失敗的測試,最終節省您的寶貴時間。因此,讓我們深入探討兩個關鍵原則,以便在下次測試時牢記: ### 3.1 經過深思熟慮的測試命名: 在命名您的測試時,請嘗試合併以下資訊:   - **正在測試什麼**?   - **什麼情況**?   - **預期結果**是什麼? ``` // Right ✅ - Test naming // 1. What is being tested: describe('Evaluation Service', () => { describe('Evaluate Students', () => { // 2 & 3. Context and expected result it('If the student grade is below the minimum grade, student should be suspended', () => { const students = [ { name: 'Mark', grade: 4.25 }, { name: 'Colin', grade: 6.7 }, { name: 'Ben', grade: 5.3 }, ] const result = evaluationService.evaluateStudents({ students, minGrade: 5 }) expect(result['Mark']).toBe('suspended') }) }) }) ``` ### 3.2 測試程式碼結構的AAA模式: 如果您想維護一個可讀且易於理解的測試套件,請按如下方式建立測試: - **安排**:設定模擬所需情況所需的所有程式碼。這可以包括初始化變數、模擬響應、實例化被測單元等。 - **行為**:執行正在測試的內容,通常在一行程式碼中。 - **斷言**:檢查得到的結果是否為預期的結果。與上面的一樣,這應該只需要一行。 ``` // Right - AAA Testing Pattern describe('Evaluation Service', () => { describe('Average Calculation', () => { it('Should calculate the average grade of all the students', () => { // Arrange: create an object with the student names and their grades const students = [ { name: 'Mark', grade: 4 }, { name: 'Colin', grade: 10 }, { name: 'Ben', grade: 7 }, { name: 'Tim', grade: 3 }, ] // Act: execute the getAverage method const avg = evaluationService.getAverage(students) // Assert: check if the result is the expected one -> (4+10+7+3)/4 = 6 expect(avg).toEqual(6) }) }) }) ``` ##4。確定性和隔離測試 如果一個失敗的測試使您的整個套件變紅,那麼您可能沒有以正確的方式處理它! 測試應該**獨立和隔離**,一次針對並處理一個特定邏輯,從而完成更快、更穩定的測試套件。 **如果您不**獨立編寫測試會發生什麼? - 您將無法找出錯誤和問題的**確切原因和位置**。 - 重構測試時,您必須**更新和同步多個測試**。 - 您將無法以任何順序執行測試,這可能會導致**破壞或跳過某些斷言或期望**。 ## 5。基於屬性的測試和真實資料 厭倦了在測試中編寫大量可能的輸入?基於屬性的測試可以為您做到這一點!……那是什麼? 基於屬性的測試建立了數百種可能的組合,強調了測試並**增加了發現以前未被注意到的錯誤的機會**。這種方法甚至可以傳回可能導致意外結果的輸入。 [JSVerify](https://github.com/jsverify/jsverify) 或 [Fast-Check](https://github.com/dubzzz/fast-check) 等函式庫提供了促進基於屬性的測試的基本工具。 但是,如果您不想深入進行如此廣泛的測試,那麼盡可能利用**真實資料**至關重要。像“abc”或“1234”這樣的輸入可能會在實際失敗時錯誤地通過測試。 ``` // Wrong ❌ - False Positive - Test that passes even though it shouldn't class EvaluationService { _students = []; addStudent(student) { // Add the student if the name has no numbers if(!student.name.matches(/^([^0-9]*)$/)){ this._students.push(student); } } } describe('Evaluation Service', () => { describe('Register Students', () => { it('Should add students to the Evaluation service', () => { const mockStudent = { id: 2, name: 'username', average: 7 } // Won't fail because the name is a string without number -> We are not checking what happens if the user // inputs a name with a number. evaluationService.addStudent(mockStudent) expect(evaluationService.getStudentAverage('username')).toBe(7) }) }) }) // In the example above, we are making a test so the test passes, instead of looking for edge cases with realistic data ``` ##額外提示! 如果您在測試元件中的任何邏輯時遇到困難,這可能反映出您也許應該將元件邏輯分解為更小、更容易且可測試的程式碼片段! 總而言之,遵循此最佳實踐可能會帶來一種新的、可讀且可維護的方法來測試您喜愛的生產程式碼。 > 經過測試的應用程式才是可靠的應用程式!

AI 咒語工程簡介:什麼是 AI 咒語工程?如何利用各種 AI 得到理想產出?

人工智能遍地開花,隨處可見一些新的人工智能工具。在 [ChatGPT](https://openai.com/blog/chatgpt)、[AutoGPT](https://autogpt.net/)、[Midjourney](https://www.midjourney.com/home/) 之間, [Dall-E](https://openai.com/dall-e-2) 和 [GitHub Copilot](https://github.com/features/copilot),您可以建置、編碼、獲取答案,以及創造美麗的藝術品……至少我們中的一些人可以。 為什麼有些人在使用生成式 AI 時比其他人獲得更好的結果?為什麼有些人製作的藝術品配得上盧浮宮,而另一些人卻得到了接近狗屁的東西? 這一切都取決於您使用的輸入。此輸入稱為“提示”。提示是您提出的問題,或者您用來建立內容的詞語。那些“製作”提示或對輸入有策略的人將其稱為“提示工程”。 原文出處:https://dev.to/github/prompt-engineering-for-ai-what-is-prompt-engineering-and-how-to-get-good-results-from-ai-engines-5ch6 ## 什麼是提示工程? 提示工程是指專門設計提示,以便從 AI 獲得更好的結果。 [OpenAI](https://openai.com/)、[Google](https://bard.google.com/) 等人工智能係統的建立者甚至聘請“快速工程師”來幫助訓練他們的模型。一些“創作者”甚至在 Etsy 等平台上出售他們的 Midjourney 提示。 簡而言之,人工智能系統就像資料:輸入垃圾,輸出垃圾。如果輸入錯誤,可能會得到錯誤的結果。提示工程在很大程度上受上下文影響。 ## 人工智能的背景 當涉及到我們得到的結果時,上下文是最大的問題之一。例如,如果我用谷歌搜尋“donut”(或“doughnut”:doughnut:),我可以獲得一整套結果;從甜甜圈食譜到甜甜圈圖片,或者在哪裡可以買到這種美味的甜點。這是因為我沒有給搜尋引擎任何其他上下文。當然,Google 會使用我之前的搜尋歷史記錄和我的位置等訊息來幫助確定結果,但僅此而已。 ![GitHub 甜甜圈](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rp4pnnp89sr7u1nkqnli.jpg) _搜尋引擎中的術語“甜甜圈”可以指任何形狀、Slack 插件、應用程式,或者 GitHub Universe 2022 上提供的這些美味的 GitHub 甜甜圈_ 例如,如果我想找到一個關於在 Blender 中建立甜甜圈 3D 模型的教程,那麼如果我只輸入“甜甜圈”,搜尋結果可能不會顯示。我需要更具體。諸如“甜甜圈 Blender3D 教程”之類的東西會為我提供更準確的結果。 人工智能也是如此。您需要為 AI 提供足夠的上下文,以便根據您的需要獲得更好的結果。 ## 聊天應用的提示工程 許多人向我們展示了來自 ChatGPT 的一些瘋狂結果。雖然它們並不總是準確的,但 ChatGPT 確實擅長一件事:散文。寫出優美、結構合理、流暢的句子真是令人難以置信。結果很容易閱讀,聽起來真的很棒。但獲得準確的回應完全是另一回事。例如,人們厭倦了使用 ChatGPT 撰寫歷史文章,雖然文章可能讀起來不錯,但在歷史上可能並不准確。比如你讓ChatGPT“寫一篇關於中國的淪陷的2000字的文章”,它就會給你寫一篇關於中國的淪陷的2000字的文章。但它不一定是事實正確的。 ![ChatGPT 生物響應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jc4mphrlgsmfw3yobfo.png) _雖然某些內容可能讀起來不錯,但實際上可能不正確。提示:我沒有博士學位_ :wink: 這是因為 ChatGPT 從各種來源獲取訊息並將它們融合在一起。這些來源本身可能並不准確。 ChatGPT 也不知道您指的是中國的_哪個_秋天。因此它很容易錯誤地交叉引用日期。通過以對話方式向 ChatGPT 提供訊息,然後要求它寫一篇 2000 字的文章,您將獲得更好的結果。 那到底是什麼意思?有些人認為 ChatGPT 是一種單向的、對話式的、單一輸入的獲取訊息的方法。但事實並非如此。它被稱為“聊天”是有原因的。進行對話,完善您的問題,為您的回答提供背景訊息。 例如,如果我想要一段關於“NDC 會議”的旅行報告,我不會以“給我寫一段 NDC 旅行報告”來開始我的 ChatGPT。相反,我會首先弄清楚 ChatGPT 對 NDC 了解多少,並在此過程中提供上下文。您提供的輸入在很大程度上決定了輸出。這就是為什麼有些人能夠獲得非常好的結果,而其他人則不能。 ![ChatGPT 響應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eks1e166pqqu0jh9hpll.png) _沒有任何上下文,ChatGPT 不知道我指的是什麼 NDC_ 另一個例子:如果你要去參加工作面試,你想要一些提示,讓 ChatGPT“給我一些準備工作面試的提示”,會給你一些很好的回應,但它遠非具體。相反,諸如“我要去一家 AI 初創公司面試軟體開發人員的職位。你能給我一些準備面試的提示嗎?”將為您提供更加量身定制的個性化結果。這就好比你讓台上的專家給觀眾中的 1000 個人一個答案,他們可能會提供一些通用的東西,這樣每個人都有一個外賣訊息。但如果你一對一地問同一個人,他們可能會問你一些後續問題以了解你的情況,因此會提供更個性化、更具體的答案。 ## 藝術應用的提示工程 您可能已經看到一些人使用穩定的傳播應用程式創作的一些精美藝術品。然後是看起來“錯誤”的藝術品。其中很多都歸結為上下文。例如,如果我使用 [Night Café](https://nightcafe.studio/)(我最喜歡的生成器之一),然後輸入“dog”這個詞,這就是我得到的: ![圖片說明](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61ibs1fd5j3ht37e4epn.jpg) _使用 Night Café 生成的圖像,以及提示符“dog”_ 有一些隨機的“狗”字寫成一個標誌,前景中有一隻看起來很奇怪的狗,而且它的顏色非常奇怪。現在,如果我想像成年德國牧羊犬在陽光明媚的日子裡在公園裡的照片般的圖像,那可能不是我要得到的。人工智能沒有那種背景。它無法讀懂我的想法(還沒有!)。當你想創作藝術品時,你需要描述你在腦海中想像的圖像。您提供的細節越多,輸出就越好。這就是它變得棘手的地方。許多穩定的擴散應用程式都有有限的字符數。因此,您需要對如何製作提示具有意義和戰略性。 與 ChatGPT 類似,您需要不斷地重新製作提示並完善它們。然而,基於聊天的 AI 的優勢在於,您可以繼續對話並不斷向 AI 提供更多訊息和不同的問題,以獲得良好的回應。雖然一些藝術生成器允許您“重新混合”您的輸出,但它仍然依賴於新的提示。因此,您一直在等待輸出,查看未加入的內容,然後發送經過調整的新提示。一些用戶在 Midjourney 上花費數小時,接收輸出並重新製作他們的提示以產生一些驚人的作品。這都是練習的問題。這就是為什麼一些創作者在 Etsy 上出售他們的提示! ![AI生成戰鬥兔](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0411o5c2fgbz7oikzwz.jpg) _我的朋友 Jean 使用 Midjourney 製作的藝術品_ 有一件事是肯定的,如果你想製作一些高質量的藝術品,不要指望花幾秒鐘寫一個提示,點擊“建立”按鈕然後看到莫內。沒有!相反,您需要投入時間(和金錢)來創作數百件藝術品,在每次迭代中重新處理您的提示以製作您的傑作。 ## 提示工程程式碼 我不會花大量時間談論如何為 [GitHub Copilot](https://github.com/features/copilot) 之類的東西製作好的提示。我的同事 [Rizel](https://twitter.com/blackgirlbytes/) 寫了一篇精彩的博文,深入探討了 GitHub Copilot 的提示工程: https://dev.to/github/a-beginners-guide-to-prompt-engineering-with-github-copilot-3ibp 我要說的是——類似於 ChatGPT——GitHub Copilot 依賴於上下文。存儲庫中還編寫了哪些其他程式碼?文件的擴展名(以及語言)是什麼? GitHub Copilot 還為您打造了什麼?您在程式碼中加入了哪些註釋?所有這些都將幫助 GitHub Copilot 為您合成更準確的程式碼。 可以這樣想:如果您寫了一條評論,說明您想要建立一個使用後端資料並解決特定問題的複雜函數,那麼您可能不會僅僅通過一條評論就得到很好的回應。就像你的程式碼——至少它應該是——被分解成許多函數,(希望)有很多有用的評論,當你分解東西時,GitHub Copilot 工作得更好。 而不是要求 GitHub Copilot: `//反轉句子`(使用 JavaScript) 想想如何從邏輯上分解問題。例如,如果有人給我一張紙,上面寫著一句話,讓我把它倒過來,我會怎麼做?像這樣寫評論會更有益。如果您這樣做,GitHub Copilot 將擁有更多的上下文並更好地理解您想要什麼。 與 ChatGPT 之類的東西相比,GitHub Copilot 的另一個區別是 GitHub Copilot 考慮了你擁有的_所有_上下文。我上面提到的所有事情: - 什麼是文件擴展名 - 專案中還有哪些其他文件 - 你是怎麼寫其他評論的 - 其他程式碼是如何建置的 - 您輸入的評論是什麼 - 您輸入的程式碼是什麼 ChatGPT 和其他聊天應用程式會更加重視您對聊天所做的最後評論; 也就是,您加入到對話中的最後一條訊息。但是,GitHub Copilot 始終會考慮上下文以生成更好的程式碼結果。 ## 更好的提示工程 歸根結底,從任何類型的生成式 AI 中獲得好的結果都在你身上——你提供輸入的人。正如我一開始所說:垃圾進,垃圾出。因此,在製作提示時請考慮以下重要提示: - 提供良好的**上下文**;提供有關您要實現的目標的示例和訊息 - 請明確點**;如果它是針對特定觀眾的,那麼就說 - **分解**問題 - **清楚**你如何提問。如果返回的內容聽起來不對,請澄清 - 根據您的提示**改寫**和**精煉** 最後,始終,始終驗證您從 AI 收到的訊息。對於藝術品生成器而言,這不太重要,但如果您查看程式碼和訊息,它就很重要。檢查您收到的程式碼是否按照您的預期工作。驗證提供給您的書面訊息的準確性。 請記住,無論發生什麼,您仍然是飛行員。您仍然是負責人,您對使用和共享哪些藝術作品、哪些程式碼片段以及哪些訊息有最終決定權。