🔍 搜尋結果:`

🔍 搜尋結果:`

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

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

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

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

9 個極為強大的 JavaScript Hack

我喜歡優化。 但如果網站無法在 Internet Explorer 11 瀏覽器中執行,用戶不會關心我的優化程式碼。 我使用**[Endtest](https://endtest.io)**建立自動化測試並在跨瀏覽器雲端上執行它們。 **[Netflix](https://jobs.lever.co/netflix/db335e29-c731-42ff-ad3a-eeecbe95b36f)**使用相同的平台來測試他們的網路應用程式。 它甚至被列為某些**[工作](https://www.linkedin.com/jobs/view/1486749071/)**的必備技能。 **[Endtest](https://endtest.io)**確實有一些非常好的功能,例如: • 跨瀏覽器網格,在 Windows 和 macOS 電腦上執行 • 用於自動化測試的無程式碼編輯器 • 支援網頁應用程式 • 支援本機和混合Android 和iOS 應用程式 • 用於測試執行的無限視訊錄製 • 螢幕截圖比較 • 地理位置 • If 語句 • 循環 • 在測試中上傳文件 • Endtest API,可輕鬆與您的 CI/CD 系統集成 • 進階斷言 • 在真實行動裝置上進行行動測試 • 使用 Endtest Mailbox 進行電子郵件測試 您應該查看**[文件](https://endtest.io/guides/docs/how-to-create-web-tests/)**。 以下是 9 個極為強大的 JavaScript 技巧。 **1.全部替換** ---------- 我們知道 string.replace() 函數僅取代第一次出現的位置。 您可以透過在正規表示式末尾新增 /g 來取代所有出現的情況。 ``` var example = "potato potato"; console.log(example.replace(/pot/, "tom")); // "tomato potato" console.log(example.replace(/pot/g, "tom")); // "tomato tomato" ``` **2. 提取唯一的值** -------------- 我們可以使用 Set 物件和 Spread 運算子建立一個僅具有唯一值的新陣列。 ``` var entries = [1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 4, 2, 1] var unique_entries = [...new Set(entries)]; console.log(unique_entries); // [1, 2, 3, 4, 5, 6, 7, 8] ``` **3. 將數字轉換為字串** --------------- 我們只需使用帶有一組空引號的串聯運算子即可。 ``` var converted_number = 5 + ""; console.log(converted_number); // 5 console.log(typeof converted_number); // string ``` **4. 將字串轉換為數字** --------------- 我們需要的只是 + 運算子。 請小心這一點,因為它僅適用於“字串數字”。 ``` the_string = "123"; console.log(+the_string); // 123 the_string = "hello"; console.log(+the_string); // NaN ``` **5. 隨機排列陣列中的元素** ----------------- **我每天都在洗牌** ``` var my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(my_list.sort(function() { return Math.random() - 0.5 })); // [4, 8, 2, 9, 1, 3, 6, 5, 7] ``` **6. 展平多維陣列** ------------- 只需使用 Spread 運算子即可。 ``` var entries = [1, [2, 5], [6, 7], 9]; var flat_entries = [].concat(...entries); // [1, 2, 5, 6, 7, 9] ``` **7. 短路條件** ----------- 讓我們來看這個例子: ``` if (available) { addToCart(); } ``` 並通過簡單地將變數與函數一起使用來縮短它: ``` available && addToCart() ``` **8. 動態屬性名稱** ------------- 我一直認為我必須先聲明一個物件,然後才能指派動態屬性。 ``` const dynamic = 'flavour'; var item = { name: 'Coke', [dynamic]: 'Cherry' } console.log(item); // { name: "Coke", flavour: "Cherry" } ``` **9. 使用 length 調整陣列大小/清空陣列** ---------------------------- 我們基本上覆蓋了陣列的長度。 如果我們想要調整陣列的大小: ``` var entries = [1, 2, 3, 4, 5, 6, 7]; console.log(entries.length); // 7 entries.length = 4; console.log(entries.length); // 4 console.log(entries); // [1, 2, 3, 4] ``` 如果我們想清空陣列: ``` var entries = [1, 2, 3, 4, 5, 6, 7]; console.log(entries.length); // 7 entries.length = 0; console.log(entries.length); // 0 console.log(entries); // [] ``` 我認為您正在尋找 JavaScript hack 真的很酷,但是您確定您的 Web 應用程式可以在所有瀏覽器和裝置上正常運作嗎? 您可以使用**[Endtest](https://endtest.io)**快速建立自動化測試並在跨瀏覽器雲端上執行它們。 您甚至無需編寫程式碼即可使用它。 說真的,只需閱讀**[文件](https://endtest.io/guides/docs/how-to-create-web-tests/)**即可。 --- 原文出處:https://dev.to/razgandeanu/9-extremely-powerful-javascript-hacks-4g3p

TypeScript - 使用 typeof 從資料中取得類型

確保為變數設定的值符合您的預期的一個好方法是為它們設定特定類型。 如果您已經在物件或陣列中擁有資料。以下方法非常適合建立您的類型! 假設您有以下資料: ``` const data = { value: 123, text: 'text' }; ``` 然後您可以使用以下命令建立基於該類型的類型: ``` type Data = typeof data; // type Data = { // value: number; // text: string; // } ``` 您可以對嵌套物件執行相同的操作: ``` const data = { value: 123, text: 'text', subData: { value: false } }; type Data = typeof data; // type Data = { // value: number; // text: string; // subData: { // value: boolean; // }; // } ``` 從 TypeScript 3.4 開始,如果你有一個字串陣列,你可以執行以下操作(注意`as const` ): ``` const data = ['text 1', 'text 2'] as const; type Data = typeof data[number]; // type Data = "text 1" | "text 2" ``` 也可以從有物件的陣列中取得類型: ``` const locales = [ { locale: 'se', language: 'Swedish', }, { locale: 'en', language: 'English', } ] as const; type Locale = typeof locales[number]['locale']; // type Locale = "se" | "en" ``` 也可以從鍵取得類型: ``` const currencySymbols = { GBP: '£', USD: '$', EUR: '€', } type CurrencySymbol = keyof typeof currencySymbols; // type CurrencySymbol = "GBP" | "USD" | "EUR" ``` 關於`as const`與使用`<const>`的通知。它們的工作原理相同,但`<const>`在 .tsx 檔案中會失敗(React)。 ``` const data = ['text 1', 'text 2'] as const; // is the same as const data = <const>['text 1', 'text 2']; ``` --- 原文出處:https://dev.to/andreasbergqvist/typescript-get-types-from-data-using-typeof-4b9c

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

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

如何寫偽程式碼

您曾經遇到過非常複雜的程式設計問題嗎?您可能可以寫出邏輯,但不太確定應該使用的語法?編寫偽程式碼是一個很好的起點。 這是什麼 ---- 偽程式碼是一種“語言”,您可以在其中編寫所有編碼邏輯,而無需編寫一行特定於語言的程式碼。在演算法研究中,尤其是機器學習演算法中,你經常會看到這種情況。這並不意味著您不能將其用於 Web 開發。 為什麼你會使用它 -------- 有些專案非常龐大,如果您不花時間編寫一些偽程式碼,您最終可能會迷失在大量已實現的程式碼中。當您編寫一些偽程式碼時,它讓您有機會真正思考潛在的問題。您可以查看純邏輯和程式流程,而不必擔心程式碼如何運作。 在開始輸入真實程式碼之前編寫偽程式碼也將幫助您更快地完成專案。將其視為藍圖。您知道一切需要去哪裡以及一切如何協同工作。因此,當您進入實際的建造階段時,您無需考慮太多,因為您已經考慮過需要做什麼。 最好的部分是偽程式碼不依賴任何程式語言。您剛剛寫出的邏輯可以被任何人採用並翻譯成他們選擇的語言。它使您可以自由地重複使用和改進您正在建立的應用程式的體系結構。 偽程式碼更微妙的用途之一是與其他人共享它。有時,您會有一個可以在多個專案中使用的特定邏輯,但它們都採用不同的程式語言。當您擁有可用的偽程式碼時,您可以將其提供給其他程式設計師,他們可以用他們需要的任何語言編寫該邏輯。 另一個很棒的功能是您可以以任何您喜歡的格式編寫偽程式碼。您可以使用學術格式。它的結構和細節令人難以置信,但往往涉及大量數學知識。或者,您可以寫出您希望程式碼執行的操作的簡單概述。 怎麼寫 --- 這是我在一篇學術論文中寫的一些偽程式碼的範例: ![偽程式碼-ex.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1556193559488/mwN-ITK1f.png) 我將是第一個承認這對 Web 開發來說可能有點大材小用的人。如果您發現自己使用 LaTex 編寫偽程式碼,則可能會使其變得比您需要的更複雜。很可能在 Word 甚至記事本中快速寫下一些內容就足夠了。 以下是我為我的一個 Web 開發專案編寫的一些簡單偽程式碼的範例: ``` IF userlogin = true API call to get user data Assign data to variables Re-route user to dashboard ELSEIF userlogin failed more than 3 times Don't allow more attempts Send user notification email Re-route user to home page ELSE Log bad login attempt Show error message Clear login form ``` 您不必對偽程式碼掌握太多技術,但通常您可以包含的細節越多,編寫實際程式碼就越容易。想像一下你正在為你的程式寫一個大綱。它讓您有機會真正思考您想要完成的任務,並且可以準確地看到所有程式碼是如何組合在一起的。 使用偽程式碼需要關注的主要內容是: - 程式的邏輯流程 - 程序中複雜部分的詳細訊息 - 一致的格式 除了一些硬核思考之外,編寫偽程式碼其實並不需要太多。當您編寫它時,您將開始看到可以加入更多細節的地方或可以刪除一些細節的地方。請記住,這主要是供您使用,因此請以對您有意義的方式編寫。 就我個人而言,我喜歡偽程式碼。當我開始輸入真正的程式碼時,它可以幫助我保持思路清晰。當您規劃好所有邏輯後,您就有更多時間來試驗效能和最佳化。最重要的是,當您深入程式碼編寫時,您不必費力思考(是的,有計劃的懶惰)。 你怎麼認為?您認為偽程式碼值得花時間還是您寧願直接跳入程式碼? --- 嘿!你應該在 Twitter 上關注我:https://twitter.com/FlippedCoding --- 原文出處:https://dev.to/flippedcoding/how-to-write-pseudo-code-2jfe

如何將 .py 轉換為 .exe?一步步指導。

--- 標題: 如何將 .py 轉換為 .exe?一步步指導。 發表:真實 封面圖片:https://thepracticaldev.s3.amazonaws.com/i/3c27e6ni9a7preeeqo0v.jpg 描述:如何將 .py 轉換為 .exe 的說明 標籤: python, 初學者, oop, 學習 --- 自動 PY 到 EXE =========== 我們要使用的唯一工具是**Auto PY to EXE** ! **Auto PY to EXE**是一款令人驚嘆的應用程式,用於從專案中產生 .exe 文件,無論它是一個 .py 檔案還是任意數量的檔案。 該應用程式有一個漂亮的 GUI,如下所示: ![所有文字](https://warehouse-camo.cmh1.psfhosted.org/4fc81b16448c94eda8a266110b56284ca9883185/68747470733a2f2f692e696d6775722e636f6d2f6464304c43326e2e706e67) 如何開始 ---- 步驟一、安裝 ------ 使用 PyPI 安裝: 要安裝該應用程式,請在**cmd**中執行以下行: `pip install auto-py-to-exe` 若要開啟應用程式,請在**cmd**中執行此行: `auto-py-to-exe` 注意:如果您以這種方式安裝時遇到任何問題,或者您想從GitHub 安裝它,請前往\[主頁\] (https://pypi.org/project/auto-py-to-exe) 或觀看此說明影片「Auto PY to EXE」的開發者\[他自己\] (https://github.com/brentvollebregt)。 {% youtube OZSZHmWSOeM %} ### 欲了解更多附加訊息,請使用此 #### \[「使用 auto-py-to-exe 時的問題」\] (https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe) 步驟 2. 轉換 -------- 您需要選擇幾個主要選項: 1. 選擇你的 .py 文件 2. 選擇“一個目錄”或“一個檔案”選項 3. 選擇其他文件 1. 選擇你的 .py 文件 -------------- 如果您有多個文件,請選擇一個來啟動程式。 2.1. 「一個目錄」選項 ------------- ![所有文字](https://thepracticaldev.s3.amazonaws.com/i/9mrf0i0cm0shdgoizjdz.JPG) 很簡單。當選擇“One Directory”選項時,“Auto PY to EXE”會將所有相依性放在**一個資料夾**中。您可以在“進階”選單中選擇輸出目錄。如果您有圖標和背景等媒體文件,如果您將媒體文件/資料夾放在輸出目錄中,那麼在 .exe 中使用它們應該不會有任何問題。 像這樣的東西: ![所有文字](https://thepracticaldev.s3.amazonaws.com/i/1a8b99f7f5gggq7fe7cv.JPG) 2.2. 「一個檔案」選項 ------------- ![所有文字](https://thepracticaldev.s3.amazonaws.com/i/cuq5tm4xsjnngoi6uqmc.JPG) 當選擇“One File”選項時,“Auto PY to EXE”將建立一個包含所有依賴項但**不包含媒體檔案的.exe 檔案**。如果您的程式只有**預設的 Windows GUI** ,沒有圖示、背景、媒體文件,或者您可以將媒體資料夾放在 .exe 文件附近,請隨意跳過以下說明。對於那些想要將媒體檔案打包到 .exe 檔案本身的人,請閱讀第 3 段。 3. 選擇其他文件 --------- 「Auto PY to EXE」中有一個名為「Additional Files」的選單,可讓您新增您選擇的檔案。但有一個問題。 「Auto PY to EXE」使用**pyinstaller**將資料解壓縮到臨時資料夾中,並將該目錄路徑儲存在 \_MEIPASS 環境變數中。您的專案將找不到必要的文件,因為路徑已更改,也不會看到新路徑。換句話說,如果選擇「一個檔案」選項,則在「其他檔案」功能表中選擇的檔案**將不會新增**到 .exe 檔案中。要解決此問題,您應該使用Auto PY to EXE 開發人員提供的程式碼\[此處\](https://nitratine.net/blog/post/issues-when-using-auto-py-to-exe/#debugging ) ``` def resource_path(relative_path): ``` ``` """ Get absolute path to resource, works for dev and for PyInstaller """ ``` ``` try: ``` ``` # PyInstaller creates a temp folder and stores path in _MEIPASS ``` ``` base_path = sys._MEIPASS ``` ``` except Exception: ``` ``` base_path = os.path.abspath(".") ``` ``` return os.path.join(base_path, relative_path) ``` 要在您的專案中使用此程式碼,請替換您*現在*擁有的媒體檔案的連結 例如: ``` setWindowIcon(QIcon('media\icons\logo.png')) ``` *和* ``` setWindowIcon(QIcon(resource_path('logo.png')) ``` 現在連結將被正確引用,並且所選檔案已成功打包到 .exe 檔案中。 用於比較: 之前可能有連結 ``` "C:\Users\User\PycharmProjects\media\icons\logo.png" ``` 之後可能有連結 ``` "C:\Users\User\AppData\Local\Temp\\_MEI34121\logo.png" ``` 按下**“轉換 .PY 至 .EXE”** ![所有文字](https://warehouse-camo.cmh1.psfhosted.org/d2f89e7dfcbbd3635e0f098b43dbc9df7c74b7a4/68747470733a2f2f692e696d6775722e636f6d2f663354456e5a492e706e67) 等待 ![所有文字](https://warehouse-camo.cmh1.psfhosted.org/a71bb45213b2285ac68eedc2994709c478deb12d/68747470733a2f2f692e696d6775722e636f6d2f4d6a644f4e63432e706e67) 步驟 3. 執行您的程式! ------------- 現在一切都完成了! 執行。測試一下。看看發生了什麼事。 確保一切正常。 #### 您建立了一個目錄 您需要的每個檔案都應該位於**單一目錄**中。 #### 你製作了一個文件 這樣你應該有**一個 .exe 檔**。如果您有需要並且正確完成,您的 .exe 檔案將包含其中的所有媒體。**您不需要任何帶有 .exe 檔案的媒體檔案/資料夾**即可正確顯示它們。 --- ### 聚苯乙烯 如果您對應加入哪些重要資訊有任何回饋或建議,請隨時告訴我! 本指南並未描述以各種可能的方式完成的每個可能的選項。 我希望您發現這些資訊有用! 祝您的專案順利! --- 原文出處:https://dev.to/eshleron/how-to-convert-py-to-exe-step-by-step-guide-3cfi

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

我建立了一個 AI PowerPoint 產生器 - 方法如下:(Next.js、OpenAI、CopilotKit)

長話短說 ==== 在本文中,您將學習如何使用 Nextjs、CopilotKit 和 OpenAI 建立人工智慧驅動的 PowerPoint 應用程式。我們將涵蓋: - 利用 ChatGPT 建立您的 PowerPoint 簡報📊 - 與您的 PowerPoint 簡報聊天💬 - 將音訊和圖像新增至您的 PowerPoint 簡報🔉 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zkbn3i2vw59na8yn2gm0.gif) --- CopilotKit:建構深度整合的應用內人工智慧聊天機器人💬 ------------------------------- CopilotKit 是[開源人工智慧副駕駛平台。](https://github.com/CopilotKit/CopilotKit)我們可以輕鬆地將強大的人工智慧聊天機器人整合到您的 React 應用程式中。完全可定制和深度集成。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pixiay2v8raimvui28l6.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} ###### \*在布胡布上 現在回到文章。 --- **先決條件** -------- 要開始學習本教程,您需要在電腦上安裝以下軟體: - 文字編輯器(例如 Visual Studio Code) - Node.js - 套件管理器 使用 NextJS 建立 PowerPoint 應用程式前端 ------------------------------ **步驟 1:**使用下列指令 Git 複製 PowerPoint 應用程式樣板。 ``` git clone https://github.com/TheGreatBonnie/aipoweredpowerpointpresentation ``` **步驟 2:**在文字編輯器上開啟 PowerPoint 應用程式樣板,並使用下列指令安裝所有專案相依性。 ``` npm install ``` 步驟3: • 前往根目錄\*\*\*\*並建立一個名為**`.env.local`的檔案。**在該文件中,加入下面保存您的 ChatGPT API 金鑰的環境變數。 ``` OPENAI_API_KEY="Your ChatGPT API Key” ``` **步驟4:**在命令列執行命令*npm run dev* 。導航至 http://localhost:3000/,您應該會看到 PowerPoint 應用程式前端。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kom1urw8ggeevz2dspk8.png) 建立 PowerPoint 投影片功能 ------------------- 步驟 1:前往**`/[root]/src/app/components`** ,並建立一個名為`present.tsx`的檔案。然後在文件頂部導入以下相依性。 ``` "use client"; import { useCopilotContext } from "@copilotkit/react-core"; import { CopilotTask } from "@copilotkit/react-core"; import { useMakeCopilotActionable, useMakeCopilotReadable, } from "@copilotkit/react-core"; import { useEffect, useState } from "react"; import "./../presentation/styles.css"; import Markdown from "react-markdown"; ``` 步驟 2:定義一個名為 Slide 的 TypeScript 接口,其中包含 PowerPoint 簡報投影片的標題和內容屬性。 ``` // Define slide interface interface Slide { title: string; content: string; } ``` 步驟 3:建立一個名為`Presentation`函數,並使用usestate 初始化名為`allSlides`和`currentSlideIndex`狀態變數`usestate.`變數`allSlides`將保存幻燈片陣列,而`currentSlideIndex`將保存目前幻燈片索引。 ``` export function Presentation (){ const [allSlides, setAllSlides] = useState<Slide[]>([]); const [currentSlideIndex, setCurrentSlideIndex] = useState<number>(0); } ``` 步驟 4:在`Presentation`函數中,使用`useMakeCopilotReadable`掛鉤新增投影片的`allSlides`陣列作為應用程式內聊天機器人的上下文。 ``` useMakeCopilotReadable("Powerpoint presentation slides: " + JSON.stringify(allSlides)); ``` 步驟 5:使用`useMakeCopilotActionable`掛鉤設定一個名為`createNewPowerPointSlide`操作,其中包含描述和更新`allSlides`和`currentSlideIndex`狀態變數的實作函數,如下所示。 ``` useMakeCopilotActionable( { name: "createNewPowerPointSlide", description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.", argumentAnnotations: [ { name: "slideTitle", type: "string", description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.", required: true, }, { name: "content", type: "string", description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.", required: true }, ], implementation: async (newSlideTitle, newSlideContent) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent}; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); }, }, [], ); ``` 步驟6:定義一個名為`displayCurrentSlide`的函數,用於在前端顯示目前投影片索引。 ``` // Display current slide const displayCurrentSlide = () => { const slide = allSlides[currentSlideIndex]; return slide ? ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center" style={{ textShadow: "1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000", }} > <Markdown className="markdown">{slide.title}</Markdown> <Markdown className="markdown">{slide.content}</Markdown> </div> ) : ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center"> No Slide To Display </div> ); }; ``` 步驟 7: 定義一個名為 addSlide 的函數,該函數包含一個名為 CopilotTask 的類別。 CopilotTask 類別定義新增投影片的功能。 ``` // Add new slide function const addSlide = new CopilotTask({ instructions: "create a new slide", actions: [ { name: "newSlide", description: "Make a new slide related to the current topic.", argumentAnnotations: [ { name: "title", type: "string", description: "The title to display in the presentation slide.", required: true, }, { name: "content", type: "string", description: "The title to display in the presentation slide.", required: true, }, ], implementation: async (newSlideTitle,newSlideContent,) => { const newSlide: Slide = {title: newSlideTitle,content: newSlideContent,}; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); }, }, ], }); const context = useCopilotContext(); const [randomSlideTaskRunning, setRandomSlideTaskRunning] = useState(false); ``` 步驟 8:定義兩個函數 goBack 和 goForward。 goBack 函數定義導覽到上一張投影片的功能,而 goForward 函數定義導覽到下一張投影片的功能。 ``` // Button click handlers for navigation const goBack = () => setCurrentSlideIndex((prev) => Math.max(0, prev - 1)); const goForward = () => setCurrentSlideIndex((prev) => Math.min(allSlides.length - 1, prev + 1)); ``` 步驟9:傳回一個呼叫displayCurrentSlide函數的元件,並包含呼叫addSlide、goBack和goForward函數的按鈕。 ``` return ( <div> {displayCurrentSlide()} <button disabled={randomSlideTaskRunning} className={`absolute bottom-12 left-0 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded ${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`} onClick={async () => { try { setRandomSlideTaskRunning(true); await addSlide.run(context); } finally { setRandomSlideTaskRunning(false); } }} > {randomSlideTaskRunning ? "Loading slide..." : "Add Slide +"} </button> <button className={`absolute bottom-0 left-0 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded ${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`} onClick={goBack}>Prev</button> <button className={`absolute bottom-0 left-20 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded ${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`} onClick={goForward}>Next</button> </div> ); ``` 步驟10:在Presentation資料夾中,將以下程式碼加入page.tsx檔案。 ``` "use client"; import { CopilotKit } from "@copilotkit/react-core"; import { CopilotSidebar } from "@copilotkit/react-ui"; import {Presentation} from "../components/present"; import "./styles.css"; let globalAudio: any = undefined; let globalAudioEnabled = false; const Demo = () => { return ( <CopilotKit url="/api/copilotkit/openai"> <CopilotSidebar defaultOpen={true} labels={{ title: "Presentation Copilot", initial: "Hi you! 👋 I can give you a presentation on any topic.", }} clickOutsideToClose={false} onSubmitMessage={async (message) => { if (!globalAudioEnabled) { globalAudio.play(); globalAudio.pause(); } globalAudioEnabled = true; }} > <Presentation/> </CopilotSidebar> </CopilotKit> ); }; export default Demo; ``` 步驟11:導覽至http://localhost:3000/,點擊「開始」按鈕,您將被重定向到與聊天機器人整合的演示頁面,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a74ilgzf5ep6snbli7uh.png) 步驟12:給右側的聊天機器人一個提示,例如「在TypeScript上建立PowerPoint簡報」聊天機器人將開始產生回應,完成後,它將在頁面左側顯示產生的PowerPoint投影片,如下圖所示 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lxhkvltf33uwyvrt4a8a.png) 步驟 13:關閉聊天機器人窗口,然後按一下新增投影片 + 按鈕將新投影片新增至 PowerPoint 簡報中,如下所示。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxl59gxkt02pmyadxzuk.png) 第 14 步:按一下「上一張」按鈕,您將導覽至上一張投影片。如果您按一下「下一步」按鈕,您將導覽至下一張投影片。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/expdwslfo2o49x7y6a6p.png) 建立 PowerPoint 投影片 自動語音功能 ------------------------ 步驟1:在`present.tsx`檔案中,宣告一個名為`globalAudio`的變數,如下所示。 ``` let globalAudio: any = undefined; ``` 步驟2:在`Presentation`元件中,宣告一個`useEffect`鉤子,用一個新的**`Audio`**物件初始化**`globalAudio`** ,如下所示。 ``` useEffect(() => { if (!globalAudio) { globalAudio = new Audio(); } }, []); ``` 步驟 3:更新 useMakeCopilotActionable 掛鉤,透過 API 將 PowerPoint 幻燈片文字轉換為語音,如下所示。 ``` useMakeCopilotActionable( { name: "createNewPowerPointSlide", description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.", argumentAnnotations: [ { name: "slideTitle", type: "string", description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.", required: true, }, { name: "content", type: "string", description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.", required: true }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, }, [], ); ``` 步驟 4:更新 addSlide 函數,透過 API 將新的 PowerPoint 投影片文字轉換為語音,如下所示。 ``` // Add new slide function const addSlide = new CopilotTask({ instructions: "create a new slide", actions: [ { name: "newSlide", description: "Make a new slide related to the current topic.", argumentAnnotations: [ { name: "title", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "content", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, } ] }); ``` 步驟 5: 再次向聊天機器人發出「在 TypeScript 上建立 PowerPoint 簡報」提示,您應該會聽到簡報投影片的聲音。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iu6s1c4q5foto1xe919a.png) 建立圖像生成功能 -------- 步驟1:在present.tsx檔案中,新增一個名為backgroundImage的新屬性來鍵入介面Slide,如下所示。 ``` // Define slide interface interface Slide { title: string; content: string; backgroundImage: string; } ``` 步驟 2:更新 useMakeCopilotActionable 掛鉤以產生 PowerPoint 簡報投影片的圖片。 ``` useMakeCopilotActionable( { name: "createPowerPointSlides", description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.", argumentAnnotations: [ { name: "slideTitle", type: "string", description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.", required: true, }, { name: "content", type: "string", description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.", required: true }, { name: "backgroundImage", type: "string", description: "What to display in the background of the slide (i.e. 'dog' or 'house').", required: true, }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, newSlideBackgroundImage, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent, backgroundImage: newSlideBackgroundImage }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, }, [], ); ``` 步驟 2:更新 addSlide 函數以產生新的 PowerPoint 簡報投影片的圖片。 **步驟3:**更新`slide.tsx`檔案中的`Slide`元件以透過[`unsplash.com`](http://unsplash.com/)產生映像 ``` // Add new slide function const addSlide = new CopilotTask({ instructions: "create a new slide", functions: [ { name: "newSlide", description: "Make a new slide related to the current topic.", argumentAnnotations: [ { name: "title", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "content", type: "string", description:"The title to display in the presentation slide.", required: true, }, { name: "backgroundImage", type: "string", description: "What to display in the background of the slide (i.e. 'dog' or 'house').", required: true, }, { name: "speech", type: "string", description: "An informative speech about the current slide.", required: true, }, ], implementation: async (newSlideTitle, newSlideContent, newSlideBackgroundImage, speech) => { const newSlide: Slide = { title: newSlideTitle, content: newSlideContent, backgroundImage: newSlideBackgroundImage }; const updatedSlides = [...allSlides, newSlide]; setAllSlides(updatedSlides); setCurrentSlideIndex(updatedSlides.length - 1); const encodedText = encodeURIComponent(speech); const url = `/api/tts?text=${encodedText}`; globalAudio.src = url; await globalAudio.play(); await new Promise<void>((resolve) => { globalAudio.onended = function () { resolve(); }; }); await new Promise((resolve) => setTimeout(resolve, 500)); }, } ] }); ``` 步驟 3:更新 displayCurrentSlide 函數以將背景圖像新增至 PowerPoint 簡報投影片中。 ``` // Display current slide const displayCurrentSlide = () => { const slide = allSlides[currentSlideIndex]; return slide ? ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center" style={{ backgroundImage: 'url("https://source.unsplash.com/featured/?' + encodeURIComponent(allSlides[currentSlideIndex].backgroundImage) + '")', textShadow: "1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000", }} > <Markdown className="markdown">{slide.title}</Markdown> <Markdown className="markdown">{slide.content}</Markdown> </div> ) : ( <div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center"> No Slide To Display </div> ); }; ``` **步驟 4:**前往 Web 應用程式,您應該會看到背景圖像已新增至 PowerPoint 投影片中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1zgcgn1h6c5hz8dh7m9a.png) 結論 -- 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 筆記: - 告訴聊天機器人要寫什麼 - 建立包含文字、圖片和自動語音的幻燈片。 這是全部實作程式碼: https://github.com/CopilotKit/CopilotKit/blob/mme/tools/CopilotKit/examples/next-openai/src/app/helloworld/page.tsx 你可以拿程式碼寫文章,一定要截圖。 確保按照邏輯實現的順序寫文章: - 首先,外部 React 元件( `page.tsx`中的`HelloWorld`元件) ``` - cover `CopilotKit` and `CopliotSidebar` ``` - 然後覆蓋`Presentation`反應元件而不加入 Copilot 功能 ``` - include the `Slide` react component ``` - 最後,在建立基本的 React 應用程式後,加入描述 Copilot 特定功能的部分( `useMakeCopilotReadable`和`useMakeCopilotActionable` ) - 最後,介紹生成音訊+影像的函數。保持描述非常簡短,只需說“我們透過瀏覽器audio-gen API產生音訊”和“我們透過[`unsplash.com`](http://unsplash.com/)產生圖像” 執行演示: - 請參閱文件/貢獻指南,以了解如何執行 CopilotKit 儲存庫中包含的範例應用程式:https://docs.copilotkit.ai/contribute/quickstart-contribute - 執行 - 前往`/helloworld`頁面 --- 結論 -- 總而言之,您可以使用 CopilotKit 建立應用內 AI 聊天機器人,該機器人可以查看當前應用程式狀態並在應用程式內執行操作。 AI 聊天機器人可以與您的應用程式前端、後端和第三方服務對話。 對於完整的源程式碼: https://github.com/TheGreatBonnie/aipoweredpresentation 感謝@theGreatBonnie 在本文中所做的出色工作。 別忘了... ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pixiay2v8raimvui28l6.gif) {% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %} --- 原文出處:https://dev.to/copilotkit/how-to-build-ai-powered-powerpoint-app-nextjs-openai-copilotkit-ji2

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

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

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

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

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

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

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

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

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

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

再見電子。你好金牛座!

利用 Rust 支援的後端框架與 React 前端相結合,提供出色的無瀏覽器體驗 ---------------------------------------- ![最初發佈於 Medium](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjac58ufbb26esj569wa.png) 許多開發人員都可以告訴您他們對 JavaScript GUI 框架又愛又恨的關係,該框架幫助徹底改變了許多桌面應用程式的製作方式。事實上,我們今天使用的許多應用程式,無論是與開發、社交媒體、通信……凡是有關的應用程式,都是使用 Electron 框架建置的。 如果您好奇,請從其[網站](https://www.electronjs.org/apps)查看使用 Electron 的應用程式的精選清單。 --- 關鍵是,Electron 為具有 Web 開發經驗的開發人員提供了為多個平台建立獨立桌面應用程式的機會…所有這些都無需學習任何新的程式語言!它提供了使用我們許多人一直使用的相同 JavaScript、CSS 和 HTML 的能力,這當然是非常棒的! 簡單總結一下 Electron 是如何做到這一點的: > Electron 是一個使用 JavaScript、HTML 和 CSS 建立桌面應用程式的框架。透過將 Chromium 和 Node.js 嵌入到其二進位檔案中,Electron 允許您維護一個 JavaScript 程式碼庫並建立可在 Windows、macOS 和 Linux 上執行的跨平台應用程式 - 無需本地開發經驗。 --- ![用電子建置](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o0sy6qchjuwm028t080p.png) --- 使用 Electron 建立桌面應用程式的最大缺點之一,正如你們中的一些人可能在 Stack Overflow 和其他論壇中看到的那樣…生成的二進位檔案往往非常大!如此之大,即使只是一個中等大小的程式碼庫也可能產生約 60MB 的最終二進位。 在親身經歷了這種挫敗感之後,我開始想知道是否有一個神奇的解決方案可以解決這個問題……事實證明, **Rust 恰好提供了一個!** --- ![巨大差距](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jkpvcpzi3wz10ewunbg2.png) --- > Tauri 是一個工具包,可協助開發人員使用幾乎任何現有的前端框架為主要桌面平台開發應用程式。核心是用 Rust 建構的,CLI 利用 Node.js,使 Tauri 成為一種真正的多語言方法來建立和維護出色的應用程式。 ### 潛入 令我興奮的是,他們的命令列鷹架工具建立了使用熟悉的前端框架啟動和執行所需的所有 Rust 檔案。不僅如此,一旦我準備好開始將自己的功能加入到後端以供 UI 使用,Tauri 就可以讓一切工作變得相當無縫! --- ![快速開始](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rnu0x8rtnvn5eq92272j.png) --- 我決定嘗試讓 Tauri 為我計劃參與的一個社區專案建立一個新的桌面儀表板應用程式,劇透一下……**它並沒有讓人失望!** 正如我之前提到的,入門相當簡單: - 確保您的系統已安裝 Rust - 如果在 Windows 或 Linux 上,請確保安裝相關相依性 - 執行一個簡單的命令來設定您的專案 之後,問題是將前端原始檔全部放在一起,同時當我需要一些有關 UI 和後端之間的進程間通訊的指導時,偶爾會參考 Tauri 的文件。 --- {% 嵌入 https://gist.github.com/dedSyn4ps3/5703367578339fc8c3440ea8a09fa449 %} --- 使用 Tauri 建立儀表板的美妙之處在於,我不再需要為了能夠正確利用進程間通訊而建立單獨的`preload.js`檔案。 所需要的只是在我想要從 UI 呼叫的函數上方的`main.rs`中進行正確的註釋,以及在我的 React `jsx`檔案中加入一個簡單的導入行: `import { invoke } from '@tauri-apps/api/tauri'` 最終,Tauri 建置的應用程式的強大之處在於其後端使用 Rust。這使得像我這樣的開發人員能夠將他們的最終產品建置成本地執行的二進位文件,其大小只是許多 Electron 建置的應用程式的一小部分。 ### 結論 至少對我來說,很明顯,Tauri 肯定有潛力繼續發展,直至推翻 Electron 作為主導的「前端」GUI 框架。儘管由於是基於 Rust 建置的,可能會存在一些令人生畏的因素,但了解其內部結構所需的少量時間是非常值得的! --- ![最後結果](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grj3o7g3hhbndota5fgn.png) --- 我鼓勵任何想要開始前端 GUI 開發之旅的人檢查 Tauri 框架,並抵制立即將 Electron 作為直接解決方案的衝動……我向你保證,你會感到驚訝! 為了完整起見,您可以在我的 Gitlab 上找到我的社群專案的完整程式碼庫。如果您正在為自己的專案尋找任何想法,請檢查一下! {% 嵌入 https://gitlab.com/dedSyn4ps3/enviroplus-desktop %} {% 嵌入 https://dev.to/dedsyn4ps3 %} {% cta https://github.com/dedsyn4ps3 %} 💻 也查看我的 Github!{% endcta %} --- 原文出處:https://dev.to/dedsyn4ps3/goodbye-electron-hello-tauri-26d5

6 個月內成為後端開發人員的技能(路線圖)

讓我給你一個簡單的🚦路線圖,讓你知道你現在在哪裡以及下一步應該去哪裡。 ![Shahan 在 6 個月內成為後端開發人員的技能](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3uek3ahjesssiakqs0xe.jpeg) 🔑關鍵概念 ----- 每個網站都有兩個部分。一個**前端**,一個**後端**。 前端是您在瀏覽器中看到它並與之互動的部分。**所有視覺方面**。 後端是為前端提供動力的部分。它*在幕後*,**主要是儲存資料和資料庫**並提供給前端。 🌐工作 --- 因此,網路開發工作分為`three categories` 。 - 👨‍💻前端開發 - 🛟後端開發 - 🚢以及全端開發(涉及前端和後端開發) 👷‍♂️後端開發人員到底是做什麼的? ------------------ 後端開發人員負責建立驅動用戶所使用的應用程式*功能*的**基礎系統**。 這包括設計架構、實施和維護這些關鍵系統等各種任務。 他們的職責通常涉及與: - 💾 資料庫,例如MySQL, - ✂️ 框架,例如 Laravel 或 Ruby on Rails,以及 - 🧨 API(應用程式介面)。 他們的專業知識確保後端基礎設施無縫執行,從而實現使用者介面與底層資料和流程之間的順暢互動。 如果您不知道的話,這是後端開發人員的`core`職責: |任務|描述| |---|---| |了解績效需求與目標 |了解網站的效能要求和目標 | | API的開發與管理|為網站建立和管理 API | |開發資料儲存與處理系統|建置系統以安全地儲存和處理支付處理等流程所需的資料| |編寫、測試和維護程式碼 |編寫程式碼、測試和開發編碼問題的解決方案,包括維護任務 | |設計網站架構|使用敏捷 Scrum 和框架等既定方法設計網站架構 | |組織系統邏輯|有效建構系統邏輯| |提供系統問題的解決方案|提供解決方案來解決與系統相關的問題和挑戰 | --- 🥷 六個月內成為後端開發人員的技能 ----------------- 成為熟練的後端開發人員需要在相對較短的時間內掌握各種技能和技術。 下面,我概述了一個**全面的路線圖**,以幫助您在六個月內實現這一目標: **第 1 個月:🦴後端開發基礎** - **第一週:**了解後端開發人員的角色和職責。熟悉資料庫、框架和 API。 - **第 2-3 週:**參加資料庫綜合課程。了解 SQL 和 MySQL 等關聯式資料庫。練習建立和管理資料庫。 - **第 4 週:**了解伺服器端程式語言。從 PHP 或 Python 開始。學習基本語法、控制結構和資料類型。 **第 2 個月:🍖高階後端概念** - **第 1-2 週:**加深對資料庫的理解。探索 MongoDB 等 NoSQL 資料庫。了解*資料建模和優化*。 - **第 3-4 週:**掌握伺服器端程式語言。深入研究 PHP 或 Python。了解函數、類別和物件導向程式設計。 - **最後 2 天:**使用框架進行實踐專案。選擇流行的框架,例如*適用於 PHP 的 Laravel 或適用於 Python 的 Django* 。建立簡單的應用程式以理解模型-視圖-控制器 (MVC) 架構。 **第 3 個月:🌦️API 開發和集成** - **第 1-2 週:**了解 API 及其在後端開發中的重要性。探索 RESTful API 設計原則。 - **第 3 週:**開始建立您自己的 API。使用`Postman`等工具來測試和偵錯 API 端點。了解身份驗證和安全性。 - **第 4 週:**關注*API 整合*。了解如何在 Web 和行動應用程式中使用 API。練習將第三方 API 整合到您的專案中。 **第 4 個月:🛸故障排除與最佳化** - **第 1-2 週:**掌握故障排除技術。了解如何有效診斷和除錯後端應用程式。 - **第 3 週:**進入效能優化階段。了解快取、負載平衡和資料庫索引。 - **第 4 週:**學習監控和分析工具。使用 New *Relic 或 Datadog*等工具來監控應用程式效能。了解如何產生和**分析**績效指標。 **第 5 個月:🌥️雲端基礎設施和部署** - **第 1 週:**了解雲端運算概念。了解 AWS 或 Azure 等流行的雲端供應商。 - **第 2-4 週:**學習後端開發雲端服務課程。了解無伺服器運算、容器化和微服務。 - **休息日:**練習將應用程式部署到雲端。了解 CI/CD 管道和自動化部署策略。 **第 6 個月:🏗️高級主題和專案** - **第 1-2 週:**在上個月,學習更多高階後端主題。選擇訊息佇列、事件驅動架構和即時通訊等主題。 - **第 3 週:**開展*頂點計畫*。選擇一個具有挑戰性的專案,整合各種後端技術和概念。 - **第 4 週:**最後,反思您的學習歷程。審查您的專案並確定需要改進的領域。透過練習**程式設計挑戰**來準備工作面試。 ⛏️後端開發:工具與軟體 ------------ 以下是後端開發中常用的工具和軟體的細分: **1. 📇資料庫框架:** - **MySQL:**用於儲存和檢索資料的關聯式資料庫管理系統。它廣泛用於 Web 應用程式,並提供可擴展性和可靠性。 - **MongoDB:**一種 NoSQL 資料庫程序,使用具有模式彈性的類似 JSON 的文件。它適用於資料模型快速變化或非結構化資料的應用程式。 **2. 🛝網路伺服器:** - **Apache HTTP Server 2:**一種開源 Web 伺服器軟體,可透過網際網路提供 Web 內容。它以其穩定性、安全性和靈活性而聞名,使其成為託管網站和 Web 應用程式的熱門選擇。 **3. 🔐安全協定:** - **SSL/TLS 憑證:**這些是透過電腦網路提供安全通訊的加密協定。 SSL(安全通訊端層)及其後繼者 TLS(傳輸層安全性)對伺服器和用戶端之間的資料進行加密,確保線上連線的機密性和完整性。 **4. 💫版本控制系統:** - 接下來,請熟悉版本控制系統。這些系統有助於追蹤專案歷史並實現與其他人的協作工作。 - 🔌 吉特: - Git 是使用最廣泛的版本控制系統,超過 70% 的軟體開發團隊都在使用。這是一個必須知道的工具,建議分配大約兩週的時間來學習 Git。 後端開發人員通常利用這些工具和軟體來建立強大、可擴展且安全的 Web 應用程式,用於處理資料儲存、伺服器託管和線上通訊加密。 如果您尋求有關後端開發的更多知識,請考慮查看此[深入的後端開發路線圖](https://roadmap.sh/backend)。 --- 👏結論 --- 透過遵循此時間表並持續努力學習,您可以在 6 個月內獲得寶貴的後端開發技能,並為找到後端開發工作做好充分準備。 如果您想在六個月內成為前端開發人員,您也可以閱讀這篇文章。 👇 [6 個月內成為前端開發人員的路線圖](https://dev.to/codewithshahan/must-have-frontend-development-skills-roadmap-2024-28jc) 最後,如果你想知道[前端開發的未來](https://dev.to/codewithshahan/the-future-of-frontend-development-1amd),你也可以看看這篇文章。 請關注更多有價值的內容,如果您覺得有幫助,您可能也會喜歡我的[YouTube 頻道](https://www.youtube.com/programmingwithshahan)。 感謝您花時間閱讀這篇文章。 🤳我的社交: [X](https://twitter.com/shahancd) --- 原文出處:https://dev.to/codewithshahan/skills-to-become-a-backend-developer-in-6-months-roadmap-4li3

如何建立一個好的 README.md 文件

什麼是 readme 文件? -------- 自述文件是用戶在查看儲存庫時首先看到的內容。它讓用戶了解專案的內容、使用的語言、條款和條件、您的專案可以做什麼、顯示正在執行的應用程式的螢幕截圖等。 為什麼它如此重要? --------- 該用戶可能是招募人員、您未來的老闆或客戶。因此,需要注意的是,專案的自述文件應該回答專案的內容、原因和方式。 因此,重要的是要包含最重要的訊息,對專案和所使用的技術堆疊進行清晰的描述,並提供可能不適合 README 文件的連結和進一步說明,以避免不必要地搜尋所有其他文件。可能會導致用戶失去興趣並轉向下一個潛在員工。 編寫有關該專案的良好文件是多麼重要,我怎麼強調都不為過。使用者不僅在尋找有關專案本身的訊息,而且還看到您的文件技能和對細節的關注,這可以讓您更接近找到工作。 如果您讀過我的其他文章,您可能已經注意到,除了程式設計之外,學習其他技能對我的職業生涯有多麼重要,這些技能最終幫助我找到了工作。良好的文件就是其中之一。 自述文件中應包含哪些內容? ------------- 以下是一些可以幫助您的指導性問題: - 該專案是關於什麼的? - 為什麼開發它,你的動機是什麼? - 它解決什麼問題? - 你學到了什麼? - 是什麼讓您的專案脫穎而出? 我將向您展示如何將這些問題轉換為文字。 ### 可能的結構 **描述** 專案的目的和描述,以便閱讀您的作品集的人可以在閱讀專案資訊的前幾秒鐘內了解該專案。 **技術堆疊** 技術堆疊,包括您的專案使用的程式語言、程式庫和框架(例如:Python、React 等)。如果您有使用外部公共 API 的前端應用程式,請提及這一點。 **設計** 與專案相關的使用者介面範例。如果專案有使用者介面,您可以插入使用者介面的 GIF、影片或圖像。 如果它是在終端上執行的應用程式,您可以建立一個 GIF 來展示如何使用它。這有助於了解如何與應用程式互動以及人們在執行專案時會看到什麼。 **特徵** 如果您的專案有很多功能,您應該加入**“功能”**部分並在此處列出它們。 **如何執行專案** 有關如何設定、執行和使用該專案的說明。如果有人想從頭開始該專案,這很好,他們應該在專案的自述文件中找到他們需要了解的所有內容,而不需要您的任何幫助。 如果很簡單,您可以將其包含在自述文件中。如果說明較長,您也可以參考專案中的另一個文件。 您還應該使用 Netlify 來託管您的專案,以便使用者可以打開已部署的應用程式並立即使用它來查看它是如何運作的。 (請記住,並非每個查看您 GitHub 個人資料的招募人員都充分了解如何在本地建立專案。) 如何設計自述文件的樣式? ------------ `README.md`中的`.md`副檔名代表 Markdown,這是一種輕量級標記語言,具有簡單的文字格式化語法。它是一種非常簡單的語言,用於為 GitHub 建立美觀且美觀的自述文件。 因此,您可以使用典型的 Markdown 語言,例如 ``` # Heading 1 ## Heading 2 ### Heading 3 Emphasis, aka italics, with *asterisks* or _underscores_. Strong emphasis, aka bold, with **asterisks** or __underscores__. Combined emphasis with **asterisks and _underscores_**. 1. First ordered list item 2. Another item ⋅⋅* Unordered sub-list. 1. Actual numbers don't matter, just that it's a number ⋅⋅1. Ordered sub-list 4. And another item. [I'm an inline-style link](https://www.google.com) [I'm an inline-style link with title](https://www.google.com "Google's Homepage") ![descriptive alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1") ``` --- 原文出處:https://dev.to/yuridevat/how-to-create-a-good-readmemd-file-4pa2

為什麼我從 Visual Studio Code 切換到 Sublime Text

最近,我改用[Sublime Text](https://www.sublimetext.com/)作為我的主要程式碼編輯器。一年多來,我一直使用[Visual Studio Code](https://code.visualstudio.com/)來編寫程式碼。這兩個編輯器非常相似,但也有足夠的差異,我想分享一下是什麼讓我全職使用 Sublime。 *注意:這篇文章並不是要為了一項技術而抨擊另一項技術。我嘗試根據我的個人經驗進行誠實的比較,但選擇程式碼編輯器是一個主觀過程,因此每個人都會對自己的最愛有不同的看法。* 是什麼讓我跳槽 ------- ### 偉大的符號分析 當您在 Sublime Text 中開啟一個專案時,它會自動啟動一個稱為「符號分析」的過程,這是在程式碼中尋找關鍵字的一個奇特術語。符號分析的好處在於,我可以輸入 Cmd + Shift + R 來調出符號搜尋選單,並在整個程式碼中快速找到類別名稱和方法。我主要使用 PHP,因此如果我已經知道我正在處理的類別名稱是`PostController` ,我可以在符號搜尋中搜尋它並立即在編輯器中開啟我的 PHP 類別檔案。 VS Code 也支援符號搜尋,但是,它只支援幾種開箱即用的語言。有一個與 VS Code 一起使用的第三方 PHP 符號分析器,但是,我發現它在處理大型程式碼庫時遇到困難,而 Sublime 則沒有問題。 ### 超快 Sublime Text 是可用於編寫程式碼的最快的文字編輯器。它幾乎立即打開並執行非常快速的搜尋。 Microsoft 在保持 VS Code 效能方面做得很好,但是 VS Code 是基於[Electron](https://electronjs.org/)的。 Electron 是一個用於捆綁 Chromium 實例和用 JavaScript/Node.js 編寫的程式碼的框架。它使編輯器具有很強的可擴展性,但使用 Chromium 的整個實例作為文字編輯器會使應用程式啟動緩慢並使用更多記憶體。 Sublime Text 是一個用 C++ 編寫的本機應用程式,因此其佔用空間要少得多。 ### 更好的 Vim 綁定 我非常喜歡在編寫程式碼時使用 Vim 鍵綁定。儘管我喜歡 Vim 鍵盤快捷鍵,但我仍然喜歡使用標準文字編輯器來利用側邊欄文件清單和文件標籤等現代功能。我發現 Sublime 的 Vim 支援比 VS Code 更準確,這有助於我更快地編寫程式碼。 Sublime 支援開箱即用的 Vim 綁定,但如果您使用[Vintageous](https://github.com/guillermooo/Vintageous)插件,您可以獲得更多功能。 我在 Visual Studio Code 中懷念的事情 ---------------------------- ### 功能豐富的側邊欄 VS Code 有一個非常好的側邊欄,可以更靈活地建立和移動檔案。 Sublime 有一個更好的側邊欄插件,還有其他鍵盤快捷鍵插件(例如[AdvancedNewFile)](https://github.com/skuroda/Sublime-AdvancedNewFile)可以使轉換更容易,但有時我會懷念 VS Code 側邊欄的開箱即用功能。 ### 內建偵錯工具 VS Code 有一個內建的偵錯器,適用於多種程式語言。它使得使用 PHP 的 xdebug 變得非常簡單。儘管 Sublime 有除錯插件,但它們並不像 VS Code 提供的開箱即用的那樣可靠。在這種情況下,如果我正在除錯一些棘手的東西,我仍然會打開 VS Code。 結論 -- 最後,文字編輯器完全取決於個人喜好和工作要求。對於我的用例來說,Sublime 是一次非常愉快的體驗,它幫助我更快地編寫程式碼。如果您想了解有關 Sublime Text 的更多訊息,Jeffrey Way 在 Laracasts 上開設了相關[課程](https://laracasts.com/series/sublime-text-mastery),Wes Bos 也寫了一本相關[書籍](http://wesbos.com/sublime-text-book/)。 請在評論中告訴我您最喜歡的編輯器是什麼! --- 原文出處:https://dev.to/restoreddev/why-i-switched-from-visual-studio-code-to-sublime-text-28k0

為 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