🔍 搜尋結果:console

🔍 搜尋結果:console

後端 JS 訓練一:第2課 ── 用 node 讀取檔案內容

## 課程目標 - 用 node 讀取檔案內容 ## 課程內容 來學習一下用 node 讀取檔案的方法 學會這方法,可以用 node 寫出簡易的檔案內容分析程式 --- 建立一個 `my-name.txt` 文字檔,在裡面輸入你的名字 然後建立 `read-my-name.js` 程式,裡面輸入 ``` var fs = require('fs'); fs.readFile('my-name.txt', function(err, data) { console.log("您好," + data); process.exit(0); }); ``` 然後去終端機輸入 ``` node read-my-name.js ``` 你會看到一段打招呼的訊息! --- 讓我們逐行說明一下 ``` var fs = require('fs'); ``` 這是載入「檔案系統模組」的意思,`fs` 是 `file system` 的縮寫 `readFile` 函式,第一個參數是檔案名稱,第二個參數是「一個函式定義」 看起來有點怪,其實是「非同步程式設計」的關係 之前在學網頁元素的事件處理時,就是用「非同步」程式設計處理 也就是我不確定「點擊」事件何時會發生,但我先「綁定」好事件發生時要做的任務 還有在網頁呼叫 AJAX 時也是,我不確定「主機回應」何時會拿到,但我先「綁定」好拿到之後要做的任務 這邊 `fs` 的意思就是:我不確定何時會「檔案讀取完畢」,但我先「綁定」好讀取完畢之後,要做的任務 看起來很怪、很醜,這種設計的意義不明,對嗎?我也覺得!後面會教你如何改寫成「同步程式設計」,先照做就好 -- `console.log` 是印出訊息到終端機(語法就跟在瀏覽器環境一樣!) `process.exit(0);` 是結束程式的意思。那個 `process` 是代表當前 node 程式的全域變數 --- 在瀏覽器裡面,無法去操作「檔案系統模組」,否則也太可怕,每個人上網時,都能用 js 亂改電腦上的檔案,資安大問題! 在瀏覽器裡面,也沒有 `process` 這個全域變數 用 node 跑完這段程式,你應該會發現,這段程式拿到網頁環境,是無法執行的! 在瀏覽器、node 跑 js 程式,有很多相似,也有很多相異之處,希望你慢慢抓到兩者的感覺! ## 課後作業 讓我們嘗試開發一個 CLI 版本的待辦管理工具 請建立一個檔案 `todos.json` 並放入以下內容 ``` [ { "title": "去操場跑步" }, { "title": "去市場採購" }, { "title": "找朋友吃飯" } ] ``` 然後建立一個 `read.js` 檔案 使用者輸入 `node read.js` 之後,終端機會去讀取 `todos.json` 並且顯示 ``` 您的待辦事項: #0 去操場跑步 #1 去市場採購 #2 找朋友吃飯 ``` 完成以上任務,你就完成這次的課程目標了! --- 交作業的方法: **方法一** 直接截圖視窗內容,上傳到留言區 **方法二** 開一個新資料夾,用 git 初始化這個資料夾 接下來的作業,都放在這個資料夾,然後上傳 github 然後把 github 專案連結,貼到留言區即可

分享最近面試心得

正想分享近期面試心得,站長就先PO文了,哈哈 由於我本身是美術科系畢業,之前也是做包裝和平面設計的工作,所以網頁設計和前端的職缺我都有投,目標是先讓自己進入業界環境。 剛開始蠻挫折的因為沒什麼前端junior職缺,且職缺要求五花八門技術又要有相關工作經驗或是相關科系,工作內容好像什麼都要會,也很多我看不懂的工具... 過一兩週後,開始有人找我,但大多都是網頁設計相關的工作,至於工作內容也很雜,且薪資也不在預期之中。 這幾天陸續面試,其實沒什麼自信且緊張,有些公司面試有筆試,拿到試題腦袋一片空白,平常用電腦打字,現在變成拿筆寫出code或回答問題,最後就把試題會的部分寫一寫,完全看不懂的就跳過,之後面試官來面試我,原本想說試題好多都不會,根本亂寫一通,可能會被洗臉,先做好心理準備XD 結果面試官一題一題有耐心的問我為什麼會這樣回答,不管我答對還是錯面試官都詳細的告訴我釐清觀念,其實考官有說題目都比較刁專有陷阱,因為知道我是初學者,所以也在他預料範圍內,很感謝他們耐心教導,對方也告訴我一些這方面的經驗或關鍵字,真感人! 總之,近期的面試都吸取很多知識和建議,也被面試官提點我的JS部分要在深入學習扎實一點,還有去看看什麼是clean code --- 分享一下面試題目(我比較有印象的) ``` if ([]) { console.log('Y') } else { console.log('N') } //結果為如何 ``` ``` console.log( (0.1 + 0.2) === 0.3 ) //結果為如何 ``` ``` console.log( NaN === NaN ) //結果為如何 ``` ``` console.log(parseInt('123ABC')) //結果為如何 ``` ``` const a = '3' const b = 1 console.log (a+b) //結果為如何 ``` ``` console.log ([...'...'].length) //結果為如何 ``` 還有一系列的`typeof`型別比較問題,但我無法**直覺信心**的直接寫出答案 因為平常都是寫code時順便用`console.log`來確認型別,這部分要再重新複習😥 還有一些`class`和`this`的試題,我完全看不懂也不會😵 先分享這樣,希望近期能收到好結果~~

新手適用:重構程式碼的幾個簡單方向

## 介紹 程式碼重構,在不改變外部功能的情況下改進現有程式碼。它是寫程式的核心部分之一,不容忽視,否則您將無法獲得更好的程式碼。程式碼重構可以增強程式碼的可讀性、可維護性和可擴展性。它還能提高性能並提高開發人員的工作效率。 今天,我們將研究一些可以幫助您重構程式碼的技術。 原文出處:https://dev.to/documatic/5-code-refactoring-techniques-to-improve-your-code-2lia ## 如何整合Refactoring 在尋找改進重構的技術之前,讓我們看看如何將程式碼重構整合到您的工作流程中。您可以使用以下建議: - 你應該專門分配時間來重構程式碼。 - 將較大的重構問題分解為較小的重構問題進行管理。 - 嘗試讓整個團隊參與重構過程。 - 使用可以幫助您查找常見重構錯誤的自動化工具。 現在,讓我們從用於重構的技術開始。 --- ## 提取方法 此方法將程式碼轉換為單獨的方法/函數。這樣做是為了改善程式碼的結構和可讀性。它將長而復雜的程式碼提取為更小且更易於管理的方法。 要使用這種技術,我們首先需要找到一個執行有點複雜的特定任務的程式碼區塊。找到後,我們提取程式碼並放入新方法中。另外,請確保為該方法提供一個有意義的名稱。 **例子:** 重構前 ``` function calculateInvoiceTotal(items) { let total = 0; for (let i = 0; i < items.length; i++) { const item = items[i]; if (!item.quantity || !item.price) { console.error('Invalid item', item); continue; } const itemTotal = item.quantity * item.price; total += itemTotal; } return total; } ``` 重構後: ``` function calculateInvoiceTotal(items) { let total = 0; for (let i = 0; i < items.length; i++) { const item = items[i]; const itemTotal = calculateItemTotal(item); total += itemTotal; } return total; } function calculateItemTotal(item) { if (!item.quantity || !item.price) { console.error('Invalid item', item); return 0; } return item.quantity * item.price; } ``` 您可以看到我們如何將在 `for` 迴圈內執行的複雜程式碼轉換為另一種方法,以實現簡單性和可讀性。 --- ## 使用常數 此程式碼重構是為了編寫更清晰、更易讀的程式碼。直接寫數字可能會導致其他人感到困惑,因為他們的目的沒有定義。將硬編碼值轉換為具有有意義名稱的變數可以幫助其他人理解它。此外,您可以為其加入註解以進一步說名。它還可以幫助除錯並降低未來出錯的風險。 **例子:** 前 ``` if (temperature > 32) { // Do something if temperature is above freezing } ``` 後 ``` const int FREEZING_POINT = 32; if (temperature > FREEZING_POINT) { // Do something if temperature is above freezing } ``` ## 合併重複程式碼 重複或相同的程式碼可能出現在來自不同地方的程式碼中。此程式碼不需要完全相同,但它可以執行類似的任務或從原始程式碼擴展一點。重複的程式碼會導致幾個問題,包括增加維護成本、難以更改程式碼庫以及引入錯誤的更高風險。 在重構程式碼時,您必須注意重複程式碼。找到此類程式碼時,一種處理方法是將此類程式碼轉換為單個可重用的函數/方法。 例子: 前 ``` function calculateTotal(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } function calculateAverage(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } const average = total / numbers.length; return average; } ``` 後 ``` function calculateSum(numbers) { let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } return total; } function calculateTotal(numbers) { return calculateSum(numbers); } function calculateAverage(numbers) { const total = calculateSum(numbers); const average = total / numbers.length; return average; } ``` 在之前的程式碼範例中,我們進行了求和並再次求和以求平均值。之後,我們將其替換為為兩者提供總和的函數。 --- ## 簡化方法 這與您正在尋找要優化的方法/功能時進行辨識非常相似。此技術可以幫助您減少程式碼行數。 此方法可以分解為更小的程式碼塊,您可以在要優化的函數中找到這些程式碼塊: - 刪除不必要的變數和表達式:可能有一些變數或表達式是您為了除錯而遺漏但忘記刪除的,例如 JavaScript 中的 console.log。 - 使用內建函數:有時使用庫或語言的內建函數會更好。因為可以用更少的程式碼實現相同的功能。 - 簡化條件語句:如果一個方法有複雜的條件語句,可以考慮通過組合條件或使用三元運算符來簡化它們。 ## 使用延遲載入 這是一種僅在需要時才載入物件的技術。這可以通過減少使用的內存量來提高應用程式的性能。這導致更快地載入應用程式。 這種技術在 Web 開發中非常流行。特別是對於像 React 這樣的 JavaScript 框架,您可以通過延遲載入來導入不同的元件。這也可以用於根據需要載入圖像。 **例子:** 前 ``` import React from 'react'; import MyComponent from './MyComponent'; const App = () => { return ( <div> <h1>My App</h1> <MyComponent /> </div> ); } export default App; ``` 後: ``` import React, { lazy, Suspense } from 'react'; const MyComponent = lazy(() => import('./MyComponent')); const App = () => { return ( <div> <h1>My App</h1> <Suspense fallback={<div>Loading...</div>}> <MyComponent /> </Suspense> </div> ); } export default App; ``` 在更新版本中,我們使用 `lazy` 函數異步導入 `MyComponent` 元件。這意味著該元件僅在實際需要時才載入,從而提高了我們應用程式的整體性能。我們還使用“Suspense”元件在載入元件時顯示後備 UI。 ## 結論 重構是任何想要提高程式碼質量、性能和可維護性的開發人員的基本實踐。通過花時間分析和優化您的程式碼,您可以消除冗餘、降低複雜性並建立更高效和可擴展的應用程式。 通過不斷審查和改進您的程式碼,您可以建立更健壯和更有彈性的應用程式。

# JS油猴系列-遮蔽廣告(D卡未登入)腳本、自定義新增名單

## 前情提要 逛論壇或網站常常有惱人的蓋板廣告,要馬就是要你登入、註冊, 要馬就是搞一堆課程的販售廣告, 對重視使用者體驗的人來說,真的是一場噩夢! 為了解決這個問題,我模擬了AdBlock的其中一個功能,刪除頁面上的元素! 乾乾淨淨才是王道阿。 ## 效果 按下ctrl+Q啟動刪除元素模式: ![](https://i.imgur.com/411nh3Q.png) 被選取的會加上邊框色彩,讓人確認是否要刪除: ![](https://i.imgur.com/zPEKMhC.png) 點選確認後: ![](https://i.imgur.com/nnOY4Ji.png) 煩人的元素就此消失在這個永恆的宇宙裡。 ## 觀念筆記 ### 套件一:toastr ```javascript= //toast.js cdn匯入 const toastJS = ` <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>` $('body').append(toastJS); ``` ![](https://i.imgur.com/lNDch3V.png) [測試效果反應網站](https://codeseven.github.io/toastr/demo.html) ![](https://i.imgur.com/v95GUfO.png) [[十分鐘學習] toastr - 簡易提示訊息](https://ithelp.ithome.com.tw/articles/10197861) ### 套件二:SweetAlert2 ```javascript= //設定cdn使用Sweetalert套件 let Sweetalert2=`<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>` $('html').append(Sweetalert2) ``` ![](https://i.imgur.com/qUeTUlj.png) ![](https://i.imgur.com/FJH8EMX.png) 有提供很多API範例,還可以自己定義可愛的小動物XD [官方網站](https://sweetalert2.github.io/) ### 監聽鍵盤組合鍵 ``` document.addEventListener('keydown', function(e){ if(e.which==81 && e.ctrlKey==true){ //..... } ``` ### Object.assign 動態修改樣式 ![](https://i.imgur.com/AzZMRCc.png) [用JS動態修改CSS的三種寫法,花式JS玩弄樣式,嚇死你的同事!](https://codelove.tw/@JsLover0018/post/Ja64Kx) ## 油猴程式碼 ```javascript= // ==UserScript== // @name 刪除廣告元素 // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://*/* // @require https://code.jquery.com/jquery-3.6.3.min.js // @icon https://www.hexschool.com/ // @grant none // ==/UserScript== (function() { //--------------------------------------- // 宣告變數 let set let Check = true; let List=[] //設定選取的border色彩效果 let styleNeon = `@keyframes neon-color { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } }` $('style:last').append(styleNeon) //設定cdn使用Sweetalert套件 let Sweetalert2=`<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>` $('html').append(Sweetalert2) //使用者點選某個元素 執行刪除元素function function SetDeleteEl(e){ e.preventDefault(); set=e.target.class console.log(set) const border={ border:"4px dotted red" } const Neon={ animation: "neon-color 2.5s linear infinite" } const NoBorder={ border:"none" } const StopNeon={ animation: "" } Object.assign(e.target.style,border) Object.assign(e.target.style,Neon) //e.target.remove() if (Check==true){ Swal.fire({ title: '確定要刪除這個元素?', showDenyButton: true, showCancelButton: false, confirmButtonText: '確認', denyButtonText: `取消`, }).then((result) => { /* Read more about isConfirmed, isDenied below */ if (result.isConfirmed) { e.target.remove() List.push(e.target.className) localStorage.setItem('List',List); Check=false; Swal.fire('成功刪除此元素!', '', 'success') } else if (result.isDenied) { Check=false; Swal.fire('動作已取消', '', 'info') Object.assign(e.target.style,NoBorder) Object.assign(e.target.style,StopNeon) }; }); } //選取流程結束後 解開選取流程的事件綁定 $('body').unbind('click',SetDeleteEl) } //toast.js cdn匯入 const toastJS = ` <link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js"></script>` $('body').append(toastJS); // 當使用者按下Ctrl+Q 就執行主程序 document.addEventListener('keydown', function(e){ if(e.which==81 && e.ctrlKey==true){ RemoveListItem(); toastr.options = { // 參數設定[註1] "closeButton": false, // 顯示關閉按鈕 "debug": false, // 除錯 "newestOnTop": false, // 最新一筆顯示在最上面 "progressBar": false, // 顯示隱藏時間進度條 "positionClass": "toast-bottom-right", "preventDuplicates": false, // 隱藏重覆訊息 "onclick": null, // 當點選提示訊息時,則執行此函式 "showDuration": "300", // 顯示時間(單位: 毫秒) "hideDuration": "1000", // 隱藏時間(單位: 毫秒) "timeOut": "2000", // 當超過此設定時間時,則隱藏提示訊息(單位: 毫秒) "extendedTimeOut": "1000", // 當使用者觸碰到提示訊息時,離開後超過此設定時間則隱藏提示訊息(單位: 毫秒) "showEasing": "swing", // 顯示動畫時間曲線 "hideEasing": "linear", // 隱藏動畫時間曲線 "showMethod": "fadeIn", // 顯示動畫效果 "hideMethod": "fadeOut" // 隱藏動畫效果 } toastr.success( "已開啟刪除元素模式!" ); $('body').on('click',SetDeleteEl) Check=true }}) function RemoveListItem(){ if (localStorage.getItem('List')!=null){ item=localStorage.getItem('List').split(',') for (i=0;i<item.length;i++){ item[i]='.'+item[i] item[i]=item[i].replaceAll(' ','.') try{ document.querySelector(item[i]).remove()} catch{} } // item='.'+item // item=item.replaceAll(' ','.') // document.querySelector(item).remove() } } //-------------------------------------------------------- })(); ``` ## 心得 短短150行左右的程式碼,就寫得好辛苦!(汗 過去寫過很多次類似的東西,所以基本上沒有卡關的地方, 只是設計邏輯、嘗試的過程還是比想像的還要耗精神( ´•̥̥̥ω•̥̥̥ ) 但寫出來比過去快很多,也比過去好看超級多的。 給自己一個讚讚! 重新把localstorge複習一遍,Object.assign、套件使用等。 算是一個確確實實的實力鞏固過程。 ## 延伸 未來可以新增控制黑名單元素的UI。 可以新增、刪除,看有哪些元素被列入要刪除的黑名單。

JavaScript 語言中 13 個經常令新手困惑的設計

寫 JavaScript 偶爾會踩到一些小地雷,會讓新手思考很久很久,這邊整理一份清單! 原文出處:https://dev.to/codeofrelevancy/unexpected-moments-of-javascript-that-will-challenge-your-understanding-of-the-language-4834 --- 1️⃣ ``` 2 == [2] // true ``` 使用 `==` 做比較,可能會對變數型別做一些額外的轉換 在本例中,數字 2 被轉換為字串,陣列“[2]”也被轉換為字串。導致兩個值都是“2”。這就是為什麼比較結果為“真”。 通常建議使用 [嚴格相等運算符 `===`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality) 而不是 `==`避免意外結果.. 更多案例: ``` '123' == 123 // true 'foo' == NaN // false undefined == null // true NaN === NaN // false NaN == NaN // false 0 == null // false ``` --- 2️⃣ ``` [] == ![] // true ``` 將空陣列“[]”與通過取反建立的“布林值”進行比較(使用 [`!` 運算符](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT)) 一個非空陣列 `[]`。這種比較的結果是 `true`,乍一看似乎出乎意料。 在 JS 中,每個值在 `boolean` 上下文中可以是 `true` 或 `false`。空陣列是真值,這意味著它在“布林”上下文中被視為“真”。當我們對其應用 `!` 運算符時,它會轉換為 `false`。 另一方面,通過否定非空陣列建立的“boolean”值為“false”。當我們使用 `==` 運算符,JS 執行類型約束,這意味著它會在比較它們之前嘗試將值轉換為通用類型。因此,空陣列被轉換為 `false`,結果兩邊都是 `false`。最後,比較返回“true”。 探索更多: ``` ['a', 'b'] !== ['a', 'b'] // true ['a', 'b'] == ['a', 'b'] // false [1, 2] + [3, 4] // "1,23,4" ``` --- 3️⃣ ``` null == undefined // true ``` [double equals `==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality) 用於比較兩個值是否相等,同時忽略它們的資料類型。當使用雙等號運算符比較值“null”和“undefined”時,它們被認為是相等的,比較結果將為“true”。這是因為“null”和“undefined”都表示缺少值,並且在這種情況下彼此非常等價。 使用嚴格相等運算符: ``` null === undefined // false ``` --- 4️⃣ ``` typeof NaN // number typeof null // object ``` 在 JS 中,[typeof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) 是一個運算符,用於確定值或變數的類型。 [NaN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/NaN) 代表 **Not a Number** 是 JS 中的一個特殊值,代表一個 ` undefined` 或 `unrepresentable` 數值。 當您將 `typeof` 與 `NaN` 一起使用時,它將返回 `number`。這可能看起來很奇怪,但這是因為 `NaN` 在技術上是 JS 中的數字資料類型,儘管它代表的實際上不是數字。 當 typeof 應用於 null 時,它返回字串 object 。這是因為“null”被認為是一個表示空物件引用的特殊值。 `null` 本身不是一個物件,而是一個原始值。這被認為是 JS 語言設計中的一個怪癖。 探索更多: ``` typeof function(){} // "function" null instanceof Object // false ``` --- 5️⃣ ``` true == "1" // true false == "0" // true ``` JS 將字串“1”轉換為“布林”值“true”,將字串“0”轉換為“false”,因為任何非空字串都被視為_truthy_,而在另一端則被視為_falsy_。因此,比較變成了 true == true 是 true 和 false == false 是 true 。 探索更多: ``` 1 + true // 2 1 - true // 0 '' == false // true 0 == false // true true + false // 1 ``` --- 6️⃣ ``` "1" + 1 // "11" 2 + "2" // "22" "5" - 3 // 2 ``` 當您將 [+ 運算符](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Addition) 與“字串”和“數字”一起使用時,數字會被轉換到一個字串並連接到該字串。 如果可以將“字串”解析為“數字”,它將[減去](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Subtraction)“數字”來自“字串”。 所以, `"1" + 1` 變成字串 `"11"` `2 + "2"` 變成字串 `"22"` `"5" - 3` 變成數字 `2` 探索更多: ``` +"1" // 1 -"1" // -1 +true // 1 -true // -1 +false // 0 -false // -0 +null // 0 +undefined // NaN 1 / "2" // 0.5 "2" / 1 // 2 1 / 0 // Infinity -1 / 0 // -Infinity 3 * "abc" // NaN true > false // true undefined + 1 // NaN undefined - 1 // NaN undefined - undefined // NaN undefined + undefined // NaN null + 1 // 1 null - 1 // -1 null - null // 0 null + null // 0 Infinity + 1 // Infinity Infinity - 1 // Infinity Infinity - Infinity // NaN Infinity + Infinity // Infinity Infinity / Infinity // NaN ``` --- 7️⃣ ``` "b" + "a" + + "a" + "a" // "baNaNa" ``` 它連接字串 `b`、字串 `a`、由表達式 `+"a"` 和字串 `a` 產生的字串。 `+"a"` 將字串 `a` 強制轉換為計算結果為 `NaN`(不是數字)的數字,因為 `a` 不是有效數字。 當我們連接“b”、“a”、“NaN”(表示為空字串)和“a”時,我們得到字串“baNaNa”。 --- 8️⃣ ``` !{} // false {} == !{} // false {} == {} // false ``` 當我們將一個空物件“{}”與一個否定的空物件“!{}”進行比較時。驚嘆號 ! 是一個邏輯運算符,它否定物件的值,所以 !{} 返回 false 因為對像在 JS 中被認為是真實的。我們實際上是在將 `{}` 與 `false` 進行比較,結果是 `false` 值,因為它們在值或資料類型上不相等。 在最後一個表達式中,我們正在比較兩個空物件“{}”。儘管它們可能看起來相同,但它們是兩個獨立的物件,在內存中具有不同的引用,因此它們在值或資料類型上並不相等。最後,比較也會產生一個“假”值。 當您在用大括號`{}` 包裹的兩個物件之間使用**plus operator +** 時,它嘗試將物件連接為字串。 探索更多: ``` {} + [] === "" // false !!{} // true !![] // true [] + [] // "" [] + {} // "[object Object]" {} + [] // "[object Object]" {} + {} // "[object Object][object Object]" [] == false // true !!'' // false !!0 // false !!null // false !!undefined // false ``` --- 9️⃣ ``` 7 > 6 > 5 // false ``` 首先,“7 > 6”的計算結果為“真”,因為 7 大於 6。 接下來,評估“true > 5”。在 JS 中,`true` 強制轉換為數字 `1`,`false` 強制轉換為 `0`。所以 `1 > 5` 是 `false`,因為 `1` 不大於 `5`。 所以最後,`7 > 6 > 5` 等價於 `true > 5`,即 `false`。 探索更多: ``` 5 < 6 < 7 // true 0 > null // false ``` --- 1️⃣0️⃣ ``` Math.max() // -Infinity Math.min() // Infinity ``` `Math.max()` 和 `Math.min()` 是可用於分別查找一組數字中的最大值和最小值的函數。 當不帶任何參數呼叫時,`Math.max()` 返回 `-Infinity`,它表示 JS 中可能的最小 `number`,另一方面,`Math.min()` 返回 `Infinity`,它表示可能的最大 ` JS 中的數字`。 這種行為是有道理的,因為如果沒有提供數字,則 `Math.max()` 沒有要返回的最大數字,同樣,沒有為 `Math.min()` 返回的最小數字 --- 1️⃣1️⃣ ``` parseInt('08') // 8 parseInt('08', 10) // 8 parseInt('0x10') // 16 ``` `parseInt('08')` 將字串 `08` 轉換為整數 `8`。如果您要編寫“parseInt('08', 10)”,該函數仍將返回“8”。 這背後的原因是因為 parseInt 函數的第二個參數是指定要使用的編號系統的基數。比方說:`binary`、`octal`、`decimal`、`hexadecimal` 等。如果未指定基數,`parseInt` 將嘗試根據字串格式檢測基數。在上面的例子中,“08”被認為是一個八進制數,因為它以“0”開頭,所以它被轉換為十進制數“8”。 `parseInt('0x10')` 將 `hexadecimal` 字串 `0x10` 轉換為整數 `16`。基數也未指定,但前綴“0x”表示該數字應被視為“十六進制”數字,因此它被轉換為“16”作為十進制數。 探索更多: ``` parseFloat('3.14.15') // 3.14 parseFloat('0.0') // 0 ``` --- 1️⃣2️⃣ ``` (function(x) { delete x; return x; })(1); // 1 ``` 採用參數“x”的匿名函數。在函數內部,它試圖刪除 `x` 變數,但這是不可能的,因為 `x` 是函數參數,無法刪除。然後該函數返回 `x` 的值。 當使用參數“1”呼叫此函數時,函數內部的“x”值將設置為“1”。在這種情況下,刪除操作沒有效果,函數只是返回 `x` 的值,即 `1` --- 1️⃣3️⃣ ``` for (var i = 0; i < 3; ++i) { setTimeout(() => console.log(i), 1000); // returns 3 three times } for (let i = 0; i < 3; ++i) { setTimeout(() => console.log(i), 1000); // returns 0 1 2 } ``` 這是因為 `var` 在函數範圍內建立了一個綁定,所以在一秒超時後循環已經執行完了,因此你得到了 3 次 `3`。通過使用 `let`,您可以在塊作用域(循環)綁定變數,因此它返回您期望的值,因為 `i` 指的是該循環迭代中的值。 --- 簡單分享,希望對您有幫助!

15 個 JavaScript 日常操作的實用、簡潔寫法

這裡有多種簡短而強大的 JavaScript 技巧,可以最大限度地提高生產力 ⚡️ 並最大限度地減少痛苦 🩸。 讓我們深入研究看看🤘 原文出處:https://dev.to/ironcladdev/15-killer-js-techniques-youve-probably-never-heard-of-1lgp ### 唯一陣列 過濾掉陣列中的重複值。 ``` const arr = ["a", "b", "c", "d", "d", "c", "e"] const uniqueArray = Array.from(new Set(arr)); console.log(uniqueArray); // ['a', 'b', 'c', 'd', 'e'] ``` ### 唯一的物件陣列 `Set` 物件不允許您過濾掉重複的物件,因為每個物件都是不同的。 `JSON.stringify` 在這裡幫我們解決了這個問題。 ``` const arr = [{ key: 'value' }, { key2: 'value2' }, { key: 'value' }, { key3: 'value3' }]; const uniqueObjects = Array.from( new Set( arr.map(JSON.stringify) ) ).map(JSON.parse) console.log(uniqueObjects); ``` 在 [此評論](https://dev.to/jonrandy/comment/24ojn) 中查看更有效但稍長的方法。 ### 陣列迭代器索引 通過 `.map` 和 `.forEach` javascript 迭代函數,您可以獲得每個項目的索引。 ``` const arr = ['a', 'b', 'c']; const letterPositions = arr.map( (char, index) => `${char} is at index ${index}` ) ``` ### 按字元數拆分字串 我們可以使用 `.match` 正則表達式函數將字串拆分為 `n` 個字符。 ``` const str = "asdfghjklmnopq"; const splitPairs = str.match(/.{1,2}/g); console.log(splitPairs); // ['as', 'df', 'gh', 'jk', 'lm', 'no', 'pq'] ``` ### 用不同的字元拆分字串 另一個使用 .match 的正則表達式 hack 允許您將像“aabbc”這樣的字串拆分為陣列“[”aa”,“bb”,“c”]`。 ``` const str = "abbcccdeefghhiijklll"; const splitChars = str.match(/(.)\1*/g); console.log(splitChars); // ['a', 'bb', 'ccc', 'd', 'ee', 'f', 'g', 'hh', 'ii', 'j', 'k', 'lll'] ``` ### 遍歷物件 `Object.entries` 允許我們將 JSON 物件轉換為鍵值對陣列,從而使我們能夠使用循環或陣列迭代器對其進行迭代。 ``` const obj = { "key1": "value1", "key2": "value2", "key3": "value3" }; const iteratedObject = Object.entries(obj) .map(([key, value]) => `${key} = ${value}`); console.log(iteratedObject); // ['key1 = value1', 'key2 = value2', 'key3 = value3'] ``` ### 鍵值陣列到物件 您可以使用“Object.fromEntries”將“Object.entryified”鍵值對陣列轉換回物件 ``` const entryified = [ ["key1", "value1"], ["key2", "value2"], ["key3", "value3"] ]; const originalObject = Object.fromEntries(entryified); console.log(originalObject); // { key1: 'value1', ... } ``` ### 發生次數計數 您可能想計算一個專案在陣列中出現的次數。我們可以使用帶有迭代器的 .filter 函數來完成此操作。 ``` const occurrences = ["a", "b", "c", "c", "d", "a", "a", "e", "f", "e", "f", "g", "f", "f", "f"]; // creating a unique array to avoid counting the same char more than once const unique = Array.from(new Set(occurrences)); const occurrenceCount = Object.fromEntries( unique.map(char => { const occurrenceCount = occurrences.filter(c => c === char).length; return [char, occurrenceCount] }) ) console.log(occurrenceCount); // { a: 3, b: 1, c: 2, ... } ``` 在 [此評論](https://dev.to/jonrandy/comment/24ojn) 中查看可靠的單行程式碼以執行此操作! ### 替換回調 `.replace` 函數並不限制您只能用固定字串替換。您可以將回調傳遞給它並使用匹配的子字串。 ``` const string = "a dog went to dig and dug a doggone large hole"; const replacedString = string.replace(/d.g/g, str => str + "gy") console.log(replacedString); // a doggy went to diggy and duggy a doggygone large hole ``` ### 條件串接 你們中的許多人都熟悉在 JS 中遇到未定義的錯誤,條件連結可以防止很多這種情況的發生。 > **可選連結** (`?.`) 運算符存取物件的屬性或呼叫函數。如果使用此運算符存取的對像或呼叫的函數未定義或為空,則表達式短路併計算為未定義,而不是拋出錯誤。 ``` const obj = { "a": "aaaaaaa", "b": null }; console.log(obj.b.d); // throws an error console.log(obj.b?.d); // returns undefined ``` ### 限定一個數字 通常,您可能需要將數字限制在特定範圍內。每次需要時都用三元運算符來做是一件很痛苦的事情。函數要乾淨得多。 ``` const constrain = (num, min, max) => { if(num < min) return min; else if(num > max) return max; else return num; } constrain(5, 1, 3) // 3 constrain(2, 1, 5) // 2 constrain(0, -100, 100) // 0 ``` 一個更好的方法是像這樣使用 `Math.min` 和 `Math.max`: ``` const constrain = (num, min, max) => Math.min(Math.max(num, min), max) ``` ### 索引陣列的前後 `.at` 函數允許您使用正數和負數從頭到尾對陣列進行索引。 ``` const arr = [1, 2, 3, 4, 5]; arr.at(0) // 1 arr.at(1) // 2 arr.at(-1) // 5 arr.at(-2) // 4 ``` ### 按字母順序排序 按字母順序對字串陣列進行排序 ``` const words = ["javascript", "typescript", "python", "ruby", "swift", "go", "clojure"]; const sorted = words.sort((a, b) => a.localeCompare(b)); console.log(sorted); // ['clojure', 'go', 'javascript', 'python', 'ruby', 'swift', 'typescript'] ``` 💡 **提示**:您可以通過將 `a.localeCompare(b)` 切換為 `b.localeCompare(a)` 來切換升序和降序 ### 按 Truthy/Falsy 值排序 您可以按真值/假值對陣列進行排序,將具有真值的值放在最前面,然後是假值。 ``` const users = [ { "name": "john", "subscribed": false }, { "name": "jane", "subscribed": true }, { "name": "jean", "subscribed": false }, { "name": "george", "subscribed": true }, { "name": "jelly", "subscribed": true }, { "name": "john", "subscribed": false } ]; const subscribedUsersFirst = users.sort((a, b) => Number(b.subscribed) - Number(a.subscribed)) ``` `Number(false)` 等於 0,`Number(true)` 等於 1。這就是我們如何通過排序函數傳遞它。 ### 四捨五入到 `n` 位 您可以使用 .toFixed 將小數四捨五入為 n 位。請注意,`.toFixed` 將數字轉換為字串,因此我們必須將其重新解析為數字。 ``` console.log(Math.PI); // 3.141592653589793 console.log(Number(Math.PI.toFixed(2))) ``` --- 感謝閱讀✨!

在 React 使用 useWorker 來跑多執行緒,大幅改善 UX 效能

使用 useWorker 在單獨的執行緒中,處理昂貴且阻塞 UI 的任務。 眾所周知,Javascript 是一種單線程語言。所以,做任何昂貴的任務,它都會阻塞 UI 互動。用戶需要等到它完成,才能執行剩餘的其他操作,這會帶來糟糕的用戶體驗。 為了克服和執行這些任務,Javascript 有一個名為 [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) 的解決方案,它允許在 web 中執行消耗效能的任務時,瀏覽器不會阻塞用戶界面,使用戶體驗非常流暢。 本篇文章簡單介紹這個 hook。 原文出處:https://dev.to/nilanth/multi-threaded-react-app-using-useworker-gf8 --- ## Web Workers Web Worker 是一個在後台執行而不影響用戶界面的腳本,因為它在一個單獨的線程而不是主線程中執行。所以它不會對用戶交互造成任何阻塞。 Web Worker 主要用於在 Web 瀏覽器中執行昂貴的任務,例如對大量資料進行排序、CSV 導出、圖像處理等。 ![工作線程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2ikpn8va49460arsuum.png) 參考上圖,我們可以看到一個昂貴的任務是由一個工作線程並行執行的,而不會阻塞主線程。當我們在主線程中執行相同的操作時,會導致 UI 阻塞。 ## React 中的 Concurrent mode 呢? React [並發模式](https://beta.reactjs.org/reference/react/startTransition) 不會並行執行任務。它將非緊急任務轉移,接著立即執行緊急任務。它使用相同的主線程來處理。 正如我們在下圖中看到的,緊急任務是使用上下文切換來處理的。例如,如果一個表正在呈現一個大型資料集,並且用戶試圖搜尋某些內容,React 會將任務切換為用戶搜尋,並首先處理它,如下所示。 ![任務切換](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7nc8978zlpi8gmrcxlnd.png) 當為同一任務使用 worker 時,表格渲染在一個單獨的線程中並行執行。檢查下圖。 ![反應工作者](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4tnoy22tqchmq8cuavdp.png) ## useWorker [useWorker](https://github.com/alewin/useWorker) 是一個通過 React Hooks 在簡單配置中使用 Web Worker API 的函式庫。它支持在不阻塞 UI 的情況下執行昂貴任務,支援 promise 而不是事件監聽器,還有一些值得注意的功能: 1. 結束超時 worker 的選項 2. 遠程依賴 3. Transferable 4. Worker status > useWorker 是一個 3KB 的庫 ## 為什麼不用 JavaScript 內置 web worker? 在使用 javascript web worker 時,我們需要加入一些複雜的配置,來設置多行程式碼的 worker。使用 useWorker,我們可以簡化 worker 的設置。讓我們在下面的程式碼中,看看兩者之間的區別。 ## 在 React App 中使用 JS Web Worker 時 ``` // webworker.js self.addEventListener("message", function(event) { var numbers = [...event.data] postMessage(numbers.sort()) }); ``` ``` //index.js var webworker = new Worker("./webworker.js"); webworker.postMessage([3,2,1]); webworker.addEventListener("message", function(event) { console.log("Message from worker:", event.data); // [1,2,3] }); ``` ## 在 React App 中使用 useWorker 時 ``` // index.js const sortNumbers = numbers => ([...numbers].sort()) const [sortWorker] = useWorker(sortNumbers); const result = await sortWorker([1,2,3]) ``` 正如我之前所說,與普通的 javascript worker 相比,`useWorker()` 簡化了配置。 讓我們與 React App 整合並執行高 CPU 密集型任務,來看看 useWorker() 的實際執行情況。 ## Quick Start 要將 useWorker() 加入到 React 專案,請使用以下命令 `yarn add @koale/useworker` 安裝套件後,導入 useWorker()。 `import { useWorker, WORKER_STATUS } from "@koale/useworker";` 我們從函式庫中導入 useWorker 和 WORKER_STATUS。 **useWorker()** 鉤子返回 workerFn 和 controller。 1. `workerFn` 是允許使用 web worker 執行函數的函數。 2.controller 由 status 和 kill 參數組成。 status 參數返回 worker 的狀態和用於終止當前執行的 worker 的 kill 函數。 讓我們用一個例子來看看 **useWorker()**。 使用 **useWorker()** 和主線程對大型陣列進行排序 首先建立一個SortingArray元件,加入如下程式碼 ``` //Sorting.js import React from "react"; import { useWorker, WORKER_STATUS } from "@koale/useworker"; import { useToasts } from "react-toast-notifications"; import bubleSort from "./algorithms/bublesort"; const numbers = [...Array(50000)].map(() => Math.floor(Math.random() * 1000000) ); function SortingArray() { const { addToast } = useToasts(); const [sortStatus, setSortStatus] = React.useState(false); const [sortWorker, { status: sortWorkerStatus }] = useWorker(bubleSort); console.log("WORKER:", sortWorkerStatus); const onSortClick = () => { setSortStatus(true); const result = bubleSort(numbers); setSortStatus(false); addToast("Finished: Sort", { appearance: "success" }); console.log("Buble Sort", result); }; const onWorkerSortClick = () => { sortWorker(numbers).then((result) => { console.log("Buble Sort useWorker()", result); addToast("Finished: Sort using useWorker.", { appearance: "success" }); }); }; return ( <div> <section className="App-section"> <button type="button" disabled={sortStatus} className="App-button" onClick={() => onSortClick()} > {sortStatus ? `Loading...` : `Buble Sort`} </button> <button type="button" disabled={sortWorkerStatus === WORKER_STATUS.RUNNING} className="App-button" onClick={() => onWorkerSortClick()} > {sortWorkerStatus === WORKER_STATUS.RUNNING ? `Loading...` : `Buble Sort useWorker()`} </button> </section> <section className="App-section"> <span style={{ color: "white" }}> Open DevTools console to see the results. </span> </section> </div> ); } export default SortingArray; ``` 這裡我們配置了 useWorker 並傳遞了 bubleSort 函數來使用 worker 執行昂貴的排序。 接下來,將以下程式碼加入到 App.js 元件,並導入 SortingArray 元件。 ``` //App.js import React from "react"; import { ToastProvider } from "react-toast-notifications"; import SortingArray from "./pages/SortingArray"; import logo from "./react.png"; import "./style.css"; let turn = 0; function infiniteLoop() { const lgoo = document.querySelector(".App-logo"); turn += 8; lgoo.style.transform = `rotate(${turn % 360}deg)`; } export default function App() { React.useEffect(() => { const loopInterval = setInterval(infiniteLoop, 100); return () => clearInterval(loopInterval); }, []); return ( <ToastProvider> <div className="App"> <h1 className="App-Title">useWorker</h1> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <ul> <li>Sorting Demo</li> </ul> </header> <hr /> </div> <div> <SortingArray /> </div> </ToastProvider> ); } ``` 我們已將 React 徽標加入到 **App.js** 元件,該組件每 **100ms** 旋轉一次,以直觀地表示執行昂貴任務時的阻塞和非阻塞 UI。 執行上面的程式碼時,我們可以看到兩個按鈕 Normal Sort 和 Sort using **useWorker()**。 接下來,單擊 Normal Sort 按鈕在主線程中對陣列進行排序。我們可以看到 React 徽標停止旋轉幾秒鐘。由於排序任務阻塞了UI渲染,所以排序完成後logo又開始旋轉了。這是因為兩個任務都在主線程中處理。檢查以下gif ![正常排序](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k74iuo6qd36qbvpr9iaz.gif) 讓我們使用 [chrome 性能記錄](https://developer.chrome.com/docs/devtools/performance/) 檢查其性能分析。 ![性能正常](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8eclh8a6o7g9xzta03ic.png) 我們可以看到 Event: click 任務用了 **3.86 秒** 來完成這個過程,它阻塞了主線程 3.86 秒。 接下來,讓我們嘗試使用 **useWorker()** 選項進行排序。點擊它的時候我們可以看到 react logo 還在不間斷的旋轉。由於 useWorker 在不阻塞 UI 的情況下在後台執行排序。這使得用戶體驗非常流暢。檢查以下gif ![工人排序](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xe5ci0p3hy5nca4321cg.gif) 您還可以使用 `sortWorkerStatus` 在控制台中看到 worker 狀態為 **RUNNING**、**SUCCESS**。 讓我們看看這種方法的性能分析結果 ![主線程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxsl696fld6u79dqpwra.png) ![工作線程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcnwe2lczguyxgfqftrj.png) 正如我們所看到的,第一張圖片表示主線程中沒有長時間執行的進程。在第二張圖片中,我們可以看到排序任務由一個單獨的工作線程處理。所以主線程沒有阻塞任務。 您可以在以下沙箱中試用整個範例。 https://codesandbox.io/embed/useworker-sorting-example-041qhc?fontsize=14&hidenavigation=1&theme=dark ## 何時使用worker 1.圖像處理 2. 排序或處理大資料集。 3. 大資料CSV或Excel導出。 4. 畫布繪圖 5. 任何 CPU 密集型任務。 ## useWorker 的限制 1. web worker 無權存取 window 物件和 document。 2. 當 worker 正在執行時,我們不能再次呼叫它,直到它完成或被中止。為了解決這個問題,我們可以建立兩個或多個 useWorker() 鉤子實例。 3. Web Worker 無法返回函數,因為響應是序列化的。 4. Web Workers 受限於最終用戶機器的可用 CPU 內核和內存。 ## 結論 Web Worker 允許在 React 應用程式中使用多線程來執行昂貴的任務而不會阻塞 UI。而 useWorker 允許在 React 應用程式中以簡化的掛鉤方法使用 Web Worker API。 Worker 不應該被過度使用,我們應該只在必要時使用它,否則會增加管理 Worker 的複雜性。

JavaScript 系列六:第5課 ── 熟悉匿名函式

## 課程目標 能夠在陣列更新元素 繼續熟悉 anonymous function 的觀念 ## 課程內容 這課先介紹一種資料型態:布林(boolean) 這種資料型態,幾乎所有程式語言都有,是一種寫程式必備的基本資料型態 ``` var x = true; var y = false; if (x) { alert('hello x'); } if (y) { alert('hello y'); } ``` 布林值只有 `true` 跟 `false` 兩種,在 if/else 條件流程控制中,會影響程式流程 這種型態,也是建造 data model 時一定會用到的型態 --- 在 JavaScript 更新陣列元素的方法,一樣有非常多種 這邊介紹一種根據索引更新的方法 ``` var fruits = ['apple', 'banana', 'orange']; fruits[2] = 'lemon'; console.log(fruits) ``` 老話一句,就是實務上,你就根據情況,隨便找一個能用的方式,來操作陣列就對了 ## 課後作業 接續上一課作業,這次來實作「已完成」按鈕 data model 的結構請更新成這樣 ``` var todos = [ { title: "倒垃圾", category: "normal", isCompleted: false }, { title: "繳電話費", category: "important", isCompleted: false }, { title: "採買本週食材", category: "urgent", isCompleted: false }, ]; ``` 請更新 `render` 函式,讓 UI 看起來像這樣 ``` - 倒垃圾 [標示為已完成][刪除] - 繳電話費 [標示為已完成][刪除] - 採買本週食材 [標示為已完成][刪除] ``` 如果倒完垃圾了,就可以點擊按鈕,來備註已完成,這時才顯示 (已完成) 文字 ``` - 倒垃圾(已完成)[標示為未完成][刪除] - 繳電話費 [標示為已完成][刪除] - 採買本週食材 [標示為已完成][刪除] ``` 然後在 render 過程中,動態產生 button 的時候,將 `.onclick` 屬性設定為一個 arrow function 這個 arrow function 不能直接更新 DOM,而是先去更新 data model,接著 render,用這種方式間接更新 DOM ``` toggleBtn.onclick = () => { // 請寫出此 arrow function 內容(更新 todos 陣列) render(); }; ``` 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列六:第3課 ── 認識匿名函式

## 課程目標 能夠從陣列刪除元素 認識 for in 迴圈寫法 認識匿名函式(anonymous function) ## 課程內容 在 JavaScript 中,刪除陣列元素的方法,有超多種 這邊介紹一種根據索引刪除的方法 ``` var fruits = ["apple", "banana", "orange"]; var index = 1; var num = 1; fruits.splice(index, num) console.log(fruits) ``` `.splice()` 函式第一個傳索引,第二個傳要刪的數量,通常就傳 1 就好了(一次刪一個即可) 實務上,你就根據情況,隨便找一個能用的方式,來操作陣列就對了 --- 之前我們介紹過 for of 的寫法,很簡單好用 ``` var fruits = ["apple", "banana", "orange"]; for (const fruit of fruits) { console.log(fruit) } ``` 這邊多介紹一個 for in 的寫法,需要索引時可以用 ``` var fruits = ["apple", "banana", "orange"]; for (const index in fruits) { const fruit = fruits[index]; console.log(index); console.log(fruit); } ``` --- 最後來介紹匿名函式(anonymous function) 聽起來很玄,但其實就只是沒有名字的函式而已 以下是有名字的函式 ``` function hello1() { alert("hello1"); } var hello2 = () => { alert("hello2"); } ``` 以下是沒有名字的函式 ``` <button> hello </button> ``` ``` var button = document.querySelector('button'); button.onclick = () => { alert(123) }; ``` 動態宣告一個函式,然後直接指派、使用,就是匿名函式 在 JavaScript 中,很多時候,有些小任務,需要宣告新函式來用,但又懶得去設計命名那些的 這時候就可以用匿名函式來節省時間 雖然看起來有點不習慣、有點奇怪 但在實務上,非常多地方,其實都會用到匿名函式,算是 JavaScript 非常好用的一個功能、特性 ## 課後作業 接續上一課作業,這次來實作「刪除事項」 請更新 `render` 函式,讓 UI 看起來像這樣 ``` <ul> <li> <span>倒垃圾</span> <button>刪除</button> </li> <li> <span>繳電話費</span> <button>刪除</button> </li> <li> <span>採買本週食材</span> <button>刪除</button> </li> </ul> ``` 然後在過程中,動態產生 button 的時候,將 `.onclick` 屬性設定為一個 arrow function 這個 arrow function 不能直接更新 DOM,而是先去更新 data model,接著 render,用這種方式間接更新 DOM ``` deleteBtn.onclick = () => { // 請寫出此 arrow function 內容(更新 todos 陣列) render(); }; ``` --- 提示:由於 javascript 中 hoisting 的特性,for loop 拿到的索引,在裡面的 arrow function 中使用,很容易抓錯 關於索引一直拿不到的問題,請參閱這邊我跟 birdie 同學的討論 https://codelove.tw/@birdie2019/post/2anbka --- 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列五:第6課 ── 學會 AJAX 與各種 HTTP 請求方法

## 課程目標 認識 AJAX 與不同的 HTTP 請求方法 ## 課程內容 HTTP 協定中,HTTP Request 有多種不同的方法 前面幾課的寫法,都是 HTTP GET 類型,這一課來接著談談更多不同的請求方法 繼續使用模擬電商網站的範例 API ### 使用 HTTP POST 新增一筆用戶資料 ``` fetch('https://fakestoreapi.com/users', { method: "POST", body: JSON.stringify({ email: '[email protected]', username: 'johnd', password: 'm38rmF$', name: { firstname: 'John', lastname: 'Doe' }, address: { city: 'kilcoole', street: '7835 new road', number: 3, zipcode: '12926-3874', geolocation: { lat: '-37.3159', long: '81.1496' } }, phone: '1-570-236-7033' }) }) .then(res => res.json()) .then(json => console.log(json)) ``` 在這個範例中,`fetch()` 函式的第二個參數是一個物件,把 method 屬性設定好,然後 body 代表 HTTP body 的內容 必須是字串,所以用 `JSON.stringify()` 把物件轉換成 JSON 字串 整段看不太懂沒關係,需要了解 HTTP 協定的細節才比較看得懂,現在就先照做即可 要注意我們是用模擬電商 API,一切都是模擬的 最後主機會回應一個新的用戶 ID,看起來是新增成功了,但實際上並沒有東西新增到資料庫喔~ ### 使用 HTTP PUT 更新一筆用戶資料 ``` fetch('https://fakestoreapi.com/users/7', { method: "PUT", body: JSON.stringify({ email: '[email protected]', username: 'johnd', password: 'm38rmF$', name: { firstname: 'John', lastname: 'Doe' }, address: { city: 'kilcoole', street: '7835 new road', number: 3, zipcode: '12926-3874', geolocation: { lat: '-37.3159', long: '81.1496' } }, phone: '1-570-236-7033' }) }) .then(res => res.json()) .then(json => console.log(json)) ``` 在這個範例中,去更新用戶 ID 為 7 的使用者資料 方法設定為 PUT,body 一樣放整個 JSON 字串 ### 使用 HTTP DELETE 刪除一筆用戶資料 ``` fetch('https://fakestoreapi.com/users/6', { method: "DELETE" }) .then(res => res.json()) .then(json => console.log(json)) ``` 在這個範例中,去刪除用戶 ID 為 6 的使用者資料 方法設定為 DELETE,不需要提供 body --- 實務上,API 設計時,有人偏好這種 GET POST PUT DELETE 都用到的寫法 有人則偏好只使用 GET 與 POST 撈資料一律都用 GET,除此之外,會更新到資料庫內容的動作,通通都用 POST 這屬於主觀偏好,沒有對錯問題,團隊討論後有共識即可 --- 上面的範例,用戶參數都是在網址最後加上 `/{ID}` 這種格式帶入 實務上,GET 參數也可能用 `?id={ID}` 這種格式 而在 POST 或其他類型的請求中,用戶參數也可能直接加在 `body: JSON.stringify({` 裡面的屬性之中 各種做法,都可以,一樣屬於主觀偏好,沒有對錯問題,團隊討論後有共識即可 ## 課後作業 接續上一課的作業,加上刪除按鈕 請翻閱 API 文件說明 https://fakestoreapi.com/docs 找出「刪除商品」的 API 把每個商品的 html 改成 ``` <li> <span>xxx</span> <button>Details</button> <button>Delete</button> </li> ``` 點擊 Delete 按鈕,就發送 API 出去 - 主機回應成功的話,就把整個 `<li>` 元素刪掉 - 主機回應失敗的話,就跳 alert 提醒用戶稍後再試 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列五:第5課 ── 學會 AJAX 錯誤處理

## 課程目標 認識 AJAX 錯誤處理 ## 課程內容 在前一課,我們說過以下的話 > 有些任務現在還不會立刻執行,但我先把要執行的任務交待清楚,時間點到的時候,就執行 > 以 UI 動作來說,時間點就是 `onclick` 之時、`onchange` 之時 > 以 AJAX 動作來說,時間點就是 `拿到主機回應` 之時 實際上,AJAX 的時間點,除了 `拿到主機回應` 之時,還有一個,就是 `發現主機回應失敗` 之時 把原本的範例 ``` fetch('https://fakestoreapi.com/users/1') .then(res=>res.json()) .then(json=>console.log(json)) ``` 故意打錯字試試看,然後加上錯誤處理機制 ``` fetch('https://fakestoreapi.com/users1') .then(res=>res.json()) .then(json=>console.log(json)) .catch(error => { alert(error); }) ``` 就跟 `.then()` 函式把任務傳進去類似,`.catch()` 一樣是把任務傳進去,只是改傳「AJAX 失敗的時候要執行的任務」 --- 這邊只是舉例,才故意打錯字 實務上,AJAX 失敗可能是 - 主機故障、或者過度忙碌無法回應 - 呼叫 API 時,登入驗證資訊過期 - API 設計成有額度限制,用戶額度耗完了,被主機拒絕 - 用戶自己的網路斷線了(網頁打開時正常,但發送 AJAX 時已斷線) 等等很多可能,要看 API 主機是如何設計的 --- 實務上,`.catch()` 內要提醒使用者,剛才的動作失敗,請他重新嘗試 我個人通常就用 alert 跳一個訊息「抱歉,系統出現錯誤,請稍後重新嘗試。若持續出錯,請聯絡客服信箱」就結束了 但在公司的大型專案,需要更好 UX 的話,請與設計師討論後決定如何優雅地處理錯誤情境 ## 課後作業 接續前一課的作業,請加上錯誤處理機制 用 alert 跳出 `抱歉,請稍後重新嘗試。` 就好了 接著,請把電腦的 wifi 或有線網路,斷線 然後點擊「Load Products」按鈕,應該會看到錯誤提示訊息 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列五:第4課 ── 學會 AJAX 基本原理

## 課程目標 認識基本的 AJAX 原理 ## 課程內容 這一課來認識大名鼎鼎的 AJAX 觀念 AJAX 全名 Asynchronous JavaScript and XML 簡單來說,就是「非同步從主機取得資料來更新網頁內容」的技術 舊式的網頁,都是瀏覽器向主機發送 HTTP 請求 -> 主機回應一大坨 html 內容 -> 瀏覽器顯示漂亮網頁給用戶看 因為是一次拿到一大坨 html 內容,我們說「網頁上全部內容都是同步取得」 現代的網頁,也是有很多頁面是這樣直接取得,但有更多功能,是依靠非同步取得資料之後來更新的 - 滑動到網頁下方,動態載入了更多貼文 - 對內容按讚,按讚成功網頁出現了小變化 - 聊天室與別人聊天,網頁也是一段一段文字更新 這些都是使用 AJAX 技術的例子 也就是先載入基本網頁內容,再接著根據需求,於不同時間點發送 HTTP 請求取得部份內容,所以叫做非同步 實務上,我們會說「這邊要發一個 AJAX 跟主機要資料」 --- 讓我們拿一個「模擬線上購物網站 API」來當作例子 https://fakestoreapi.com/ 發一個 AJAX 取得 ID 為 1 的用戶資料 ``` fetch('https://fakestoreapi.com/users/1') .then(res=>res.json()) .then(json=>console.log(json)) ``` 請在 jsfiddle 試試,看看結果 會看到一個包含信箱、ID、姓名、電話等等欄位的用戶個資,以物件的形式呈現 這邊使用了內建的 fetch 函式,參數放入要呼叫的 API 網址 接著使用 `.then()` 函式,由於是直接寫在後面,這相當於把 `fetch()` 回傳的東西,直接當成物件再接著呼叫 `.then()` 函式,然後再把結果當成物件再呼叫 `.then()` 一次 也就是跟這段一模一樣 ``` var result1 = fetch('https://fakestoreapi.com/users/1'); var result2 = result1.then(res=>res.json()); var result3 = result2.then(json=>console.log(json)); alert(result1) alert(result2) alert(result3) ``` 請在 jsfiddle 試試,會發現 console 顯示的個資一樣,這邊用三個 alert 觀察過程中的東西 會發現顯示三次 `[object Promise]`,這個 Promise 是一個進階觀念,這邊不細談,簡單講就是處理非同步請求的一種資料格式 `.then()` 參數傳進一個箭頭函式,這是省略大括號 `{}` 的箭頭函式寫法,其實就只是會自動回傳結果的函式寫法而已 但參數放了個函式,看起來有點怪,為何要這樣寫? --- 記得我們之前寫過的動態綁定 onclick 事件嗎? ``` <button id="my-btn">Click me</button> ``` ``` // 第一種寫法 function myFunction() { alert('你點擊了按鈕!'); } var btn = document.getElementById('my-btn'); btn.onclick = myFunction; ``` 網頁元素的事件處理,也是一種「非同步」程式設計 也就是我不確定「點擊」事件何時會發生,但我先「綁定」好事件發生時要做的任務,綁完就讓網頁正常呈現就好 上面的程式碼,可以改寫成這樣 ``` // 第二種寫法 var btn = document.getElementById('my-btn'); btn.onclick = () => { alert('你點擊了按鈕!'); } ``` 如果使用 jQuery,那還可以這樣改寫 ``` // 第三種寫法 $('#my-btn').click(() => { alert('你點擊了按鈕!'); }) ``` 第一種寫法,看起來像是:我先定義好函式,接著把函式名稱當作變數,綁定到 onclick 屬性 第二種寫法,看起來像是:onclick 這邊現場寫一個箭頭函式,把要執行的任務,當場交待清楚 第三種寫法,看起來像是:jQuery 提供的 `.click()` 函式,會負責把事件綁好,參數傳任務進去就對了 以上三種寫法,效果是完全一模一樣的! 所以你早就接觸過「非同步」程式設計了 也就是「有些任務現在還不會立刻執行,但我先把要執行的任務交待清楚,時間點到的時候,就執行」 以 UI 動作來說,時間點就是 `onclick` 之時、`onchange` 之時 以 AJAX 動作來說,時間點就是 `拿到主機回應` 之時 像這種不是馬上執行的動作,在 JavaScript 領域,我們習慣用「寫一段函式定義當作參數傳進去」來表達! --- 回頭看一下我們的範例 ``` fetch('https://fakestoreapi.com/users/1') .then(res=>res.json()) .then(json=>console.log(json)) ``` 因為 fetch 第一個回傳的結果,代表的是一個 `HTTP 回應物件`,這個回應物件的 HTTP body 是實際的 JSON 內容,可以用 `.json()` 函式取得內容 所以第二個 `.then()` 的參數,才是我們真正想做的事情 看不懂沒關係,我們多看幾個例子吧 取得全部用戶個資的 AJAX。觀察 console 結果,會看到一個陣列,內含大量個資物件 ``` fetch('https://fakestoreapi.com/users') .then(res=>res.json()) .then(json=>console.log(json)) ``` 取得五筆用戶個資,也是拿到陣列 ``` fetch('https://fakestoreapi.com/users?limit=5') .then(res=>res.json()) .then(json=>console.log(json)) ``` 以上內容,全部通通看不懂沒關係,畢竟,需要多了解一些 HTTP 協定與術語,比較好理解 你就先照做就好:要發 AJAX,就用 `fetch()` 函式,接著第一個 `then()` 要執行 `.json()` 函式,然後第二個函式才是你真正要執行的任務! ## 課後作業 請使用 https://jsfiddle.net/ 請使用「模擬線上購物網站 API」 https://fakestoreapi.com/ 假設正在開發一個讀取全部商品資料的頁面 用以下 html 為基礎 ``` <button>Load Products</button> <hr> <ul></ul> ``` 點擊按鈕,發送 AJAX 到 https://fakestoreapi.com/products 請求全部商品資料 拿到資料之後,將每筆資料用以下格式呈現,塞進 `<ul>` 元素裡面 ``` <li> <span>xxx</span> <button>Details</button> </li> ``` xxx 是商品名稱。點擊 Details 按鈕,連續跳出三個 alert,分別顯示 `id` `category` `description` --- 請注意,在 for 迴圈裡面綁定 onclick 事件的時候,for 迴圈的參數請加上 `const` 舉例來說,請這樣寫 ``` for (const product of json) { ``` 請「不要」這樣寫 ``` for (product of json) { ``` 否則,在迴圈裡面的 onclick 事件,執行起來會有 bug 原因跟上一課提到的 Hoisting 現象有關 我認為這是 JavaScript 的設計失敗,所以詳細原因我不想說明 這是屬於上個世代 JS 工程師的痛苦回憶,這一代的 JS 工程師不需要經歷 現在就用 ES6 語法,宣告變數一律記得加上 `const` 或 `let` 就對了 --- 做出以上功能,你就完成這次的課程目標了!

7 個好用的 React Hooks:可以在很多專案直接使用

Hooks 是 React 最強大的功能之一。 它們使我們能夠輕鬆地在應用程式的元件中重用功能。掛鉤的最大優點在於它們的可重用性——您可以跨元件和專案重用掛鉤。 以下是我在每個 React 專案中重複使用的七個最重要的鉤子。今天就試一試,看看它們在建置您自己的 React 應用程式時是否有幫助吧。 原文出處:https://dev.to/webdevhero-com/7-react-hooks-for-every-project-1jdo --- 在我們開始之前,要先澄清一下:並非每個自定義 React 鉤子都需要由您編寫。事實上,我將提到的所有鉤子都來自一個庫“@mantine/hooks”。 Mantine 是一個很棒的第三方庫,其中包含這些鉤子等等。他們將為您的 React 應用程式加入您能想到的幾乎所有重要功能。 您可以在 [mantine.dev](https://mantine.dev) 查看“@mantine/hooks”的文件。 ## `useIntersection` 鉤子 當用戶在您的應用程式中向下滾動頁面時,您可能想知道某個元素何時對他們可見。 例如,您可能希望僅在用戶看到特定元素時才啟動動畫。或者,您可能希望在他們向下滾動頁面一定程度後,顯示或隱藏元素。 ![use intersection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zilwvvdjppw9uz82iixo.gif) 要獲取有關元素是否可見的訊息,我們可以使用 **Intersection Observer API。**這是瀏覽器中內建的 JavaScript API。 我們可以使用純 JavaScript 單獨使用 API,但要知道有關特定元素是否在其滾動容器內,有個好方法是使用 useIntersection 掛鉤。 ``` import { useRef } from 'react'; import { useIntersection } from '@mantine/hooks'; function Demo() { const containerRef = useRef(); const { ref, entry } = useIntersection({ root: containerRef.current, threshold: 1, }); return ( <main ref={containerRef} style={{ overflowY: 'scroll', height: 300 }}> <div ref={ref}> <span> {entry?.isIntersecting ? 'Fully visible' : 'Obscured'} </span> </div> </main> ); } ``` 要使用它,我們需要做的就是在我們的元件中呼叫鉤子,並提供一個根元素。 Root 是滾動容器,可以使用 useRef 掛鉤將其作為 ref 提供。 `useIntersection` 回傳一個我們傳遞給目標元素的 ref,我們想要觀察其在滾動容器中的交集。 一旦我們有了對元素的引用,我們就可以追蹤元素是否相交。在上面的範例中,我們可以根據 entry.isIntersecting 的值查看元素何時被遮擋或何時完全可見。 您可以傳遞其他參數,這些參數允許您配置與目標可見百分比相關的**閾值**。 ## `useScrollLock` 鉤子 另一個與滾動相關的鉤子是 useScrollLock 鉤子。這個鉤子非常簡單:它使您能夠鎖定 body 元素上的任何滾動。 我發現當您想在當前頁面上顯示疊加層或跳出視窗,並且不想讓用戶在後台頁面上上下滾動時,它會很有幫助。這使您可以將注意力集中在視窗上,或允許在其自己的滾動容器內滾動。 ``` import { useScrollLock } from '@mantine/hooks'; import { Button, Group } from '@mantine/core'; import { IconLock, IconLockOpen } from '@tabler/icons'; function Demo() { const [scrollLocked, setScrollLocked] = useScrollLock(); return ( <Group position="center"> <Button onClick={() => setScrollLocked((c) => !c)} variant="outline" leftIcon={scrollLocked ? <IconLock size={16} /> : <IconLockOpen size={16} />} > {scrollLocked ? 'Unlock scroll' : 'Lock scroll'} </Button> </Group> ); } ``` `useScrollLock` 將用戶的滾動鎖定在頁面上的當前位置。該函數回傳一個陣列,它可以被解構,如上面的程式碼所示。 第二個值是一個允許我們鎖定滾動的函數。另一方面,第一個解構值是一個布林值,它告訴我們滾動條是否已被鎖定。 這個值很有用,例如,如果你想在滾動鎖定時顯示某些內容或告訴用戶它已被鎖定。您可以在下面的示例中看到,當滾動條被鎖定或解鎖時,我們會在我們的按鈕中進行指示。 ![use scroll lock](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wywccv7uexfrhfgayqj9.gif) ## `useClipboard` 鉤子 在許多情況下,您希望提供一個按鈕,允許用戶將內容複製到他們的剪貼板,這是存儲複製文本的地方。 一個很好的例子是,如果您的網站上有一個程式碼片段,並且您希望用戶輕鬆複製它。為此,我們可以使用另一個 Web API——**剪貼板 API**。 `@mantine/hooks` 為我們提供了一個方便的 `useClipboard` 鉤子,它回傳幾個屬性:`copied`,它是一個布林值,告訴我們是否已使用鉤子將值複製到剪貼板,以及` copy` 函數,我們可以將我們喜歡的任何字串值傳遞給它以進行複制。 在我們的範例中,我們想複製一個程式碼片段,供我們的用戶粘貼到他們喜歡的地方,如下面的影片所示: ![使用剪貼板](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6n0xd02vdg65bbfmb2xh.gif) ## useClipboard demo 當他們點擊我們指定的複制按鈕時,我們呼叫我們的 `copy` 函數,將程式碼片段傳遞給它,然後顯示一個小複選標記或向他們表明文本已被複製的東西。 巧妙的是 `useClipboard` 掛鉤帶有 **超時值**。在給定的超時時間(以毫秒為單位)之後,複製的狀態將被重置,向用戶顯示他們可以再次復製文本。 ## `useDebouncedValue` 鉤子 如果您的應用程式中有搜尋輸入,下一個鉤子“useDebouncedValue”是必不可少的。 每當用戶使用輸入執行搜尋時,搜尋操作通常涉及對 API 的 HTTP 請求。 您將遇到的一個典型問題是每次擊鍵都會執行查詢(請求),尤其是如果您希望用戶在鍵入時接收搜尋結果。即使對於一個簡單的搜尋查詢,也不需要在用戶完成輸入他們想要的內容之前執行這麼多請求。 這是 useDebounceValue 掛鉤的一個很好的用例,它對傳遞給它的文本應用「防抖」功能。 ``` import { useState } from 'react'; import { useDebouncedValue } from '@mantine/hooks'; import { getResults } from 'api'; function Demo() { const [value, setValue] = useState(''); const [results, setResults] = useState([]) const [debounced] = useDebouncedValue(value, 200); // wait time of 200 ms useEffect(() => { if (debounced) { handleGetResults() } async function handleGetResults() { const results = await getResults(debounced) setResults(results) } }, [debounced]) return ( <> <input label="Enter search query" value={value} style={{ flex: 1 }} onChange={(event) => setValue(event.currentTarget.value)} /> <ul>{results.map(result => <li>{result}</li>}</ul> </> ); } ``` 您使用 useState 將輸入的文本儲存在一個狀態中,並將狀態變數傳遞給 useDebouncedValue 。 作為該掛鉤的第二個參數,您可以提供一個等待時間,即值被「反抖」的時間段。反抖使我們能夠執行更少的查詢。 您可以在下面的影片中看到結果,用戶在其中鍵入內容,並且僅在 200 毫秒後,我們才能看到去抖值。 ![使用去抖值](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfb5kqb6bxbkqfvog1hw.gif) ## `useMediaQuery` 鉤子 我一直使用的另一個非常有用的鉤子是 useMediaQuery 鉤子。 Media Queries 在純 CSS 中使用,`useMediaQuery` 鉤子允許我們訂閱我們傳遞給鉤子的任何媒體查詢。 例如,在我們的元件中,假設我們想要顯示一些文本或根據特定螢幕寬度(例如 900 像素)更改元件的樣式。我們像在 CSS 中一樣提供媒體查詢,並且 useMediaQuery 回傳給我們一個 true 或 false 的 matches 值。 ``` import { useMediaQuery } from '@mantine/hooks'; function Demo() { const matches = useMediaQuery('(min-width: 900px)'); return ( <div style={{ color: matches ? 'teal' : 'red' }}> {matches ? 'I am teal' : 'I am red'} </div> ); } ``` 它用 JavaScript 告訴我們媒體查詢的結果,這在我們想要使用 `style` 屬性在 JSX 中直接更改樣式時特別有用,例如。 ![使用媒體查詢](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7al1apywiq08ntgs4d8l.gif) 簡而言之,對於少數無法使用 CSS 處理媒體查詢的情況,這是一個必不可少的鉤子。 ## `useClickOutside` 鉤子 下一個掛鉤 - `useClickOutside` - 可能看起來很奇怪,但當您真正需要它時,您會發現它的重要性。 當你開發一個下拉菜單,或者在頁面內容前面彈出,並且之後需要關閉的東西時,這個鉤子是必不可少的。通過單擊按鈕打開這些類型的元件之一非常容易。關閉這些元件有點困難。 為了遵循良好的 UX 實踐,我們希望任何阻礙用戶視圖的東西都可以通過單擊元素外部輕鬆關閉。這正是 useClickOutside 掛鉤讓我們做的。 當我們呼叫 useClickOutside 時,它會返回一個 ref,我們必須將其傳遞給我們想要檢測點擊的外部元素。通常該元素將由一個布林狀態片段控制,例如我們在下面的示例中的狀態(即值“opened”)。 ``` import { useState } from 'react'; import { useClickOutside } from '@mantine/hooks'; function Demo() { const [opened, setOpened] = useState(false); const ref = useClickOutside(() => setOpened(false)); return ( <> <button onClick={() => setOpened(true)}>Open dropdown</button> {opened && ( <div ref={ref} shadow="sm"> <span>Click outside to close</span> </div> )} </> ); } ``` `useClickOutside` 接受一個回調函數,該函數控制當您實際單擊該元素外部時發生的情況。 在大多數情況下,我們想做一些非常簡單的事情,就是關閉它。為此,您可能需要一個狀態設置器(如 `setOpened`)並向其傳遞一個 false 值,然後隱藏您覆蓋的內容。 ![use click outside](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jidp380brennyatol7w.gif) ## `useForm` 鉤子 在這個列表中,我最喜歡和最有用的鉤子是 `useForm` 鉤子。 這個鉤子專門來自 Mantine,涉及從庫中安裝一個特定的包:`@mantine/form`。它會為您提供在 React 中建立表單所需的一切,包括驗證輸入、顯示錯誤訊息以及在提交表單之前確保輸入值正確的能力。 `useForm` 接受一些初始值,這些初始值對應於您在表單中的任何輸入。 ``` import { TextInput, Button } from '@mantine/core'; import { useForm } from '@mantine/form'; function Demo() { const form = useForm({ initialValues: { email: '' }, validate: { email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'), }, }); return ( <div> <form onSubmit={form.onSubmit((values) => console.log(values))}> <TextInput withAsterisk label="Email" placeholder="[email protected]" {...form.getInputProps('email')} /> <Button type="submit">Submit</Button> </form> </div> ); } ``` `useForm` 的最大好處是它的助手,例如 `validate` 函數,它接收輸入到每個輸入的值,然後允許您建立驗證規則。 例如,如果您有一個電子郵件輸入,您可能有一個正則表達式來確定它實際上是否是一個有效的電子郵件(如您在上面的程式碼中所見)。如果沒有,那麼您可以顯示一條錯誤訊息並阻止提交表單。 ![使用表格](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n13l1gqejzx6y227p0j2.gif) 您如何獲取已輸入到表單中的值? Mantine 提供了一個非常方便的助手,叫做“getInputProps”,你只需提供你正在使用的輸入的名稱(比如電子郵件),它就會自動設置一個 onChange 來追蹤你在表單中輸入的值. 此外,為了處理表單提交,並在其值未通過驗證規則時阻止送出,它有一個特殊的 `onSubmit` 函數,您可以將其包裹在常規的 onSubmit 函數中。除了應用驗證規則之外,它還會負責在表單事件上呼叫 `preventDefault()`,這樣您就不必手動執行此操作。 我只用了這個鉤子的基本功能,但我強烈建議您在下一個專案中使用它。傳統上,表單很難正常工作,尤其是需要驗證和可見錯誤訊息的表單。 `useForm` 讓它變得異常簡單! --- 以上,簡單分享,希望對您有幫助。

JavaScript 系列二:第5課 ── 認識 onchange 事件

## 課程目標 學會 onchange 事件的用法 ## 課程內容 目前為止,應該已經熟悉 `onclick` 事件的用法 接下來,學一個很常用、很好用的新事件 `onchange` ``` <select onchange="show1()"> <option value="apple">蘋果</option> <option value="banana">香蕉</option> </select> <label> <input type="radio" name="gender" value="male" onchange="show2()"> 男生 </label> <label> <input type="radio" name="gender" value="female" onchange="show2()"> 女生 </label> ``` ``` function show1() { alert(event.target.options[event.target.selectedIndex].value) } function show2() { alert(event.target.value) } ``` 到 jsfiddle 試試看,就會清楚實際效果囉! --- 不同的 html 元素,對於觸發 `onchange` 的定義有點不同,取得「變化後的新值」的方式也有點不同 上面簡單示範兩種。實務上,如果一直觸發不了,或是抓不到值的話,就多上網查查範例 然後一直嘗試 alert 或者 console.log 看看到底抓到什麼變數、到底變數內容是什麼 多實驗、多練習,習慣之後就會上手了 ## 課後作業 接續前一課的作業,現在要讓清單看起來更「花俏一點」 你希望選擇任務急迫性之後,輸入文字的框框,能跟著有樣式變化 --- 目前的 select 選單 選單內有「一般」、「重要」、「緊急」三種選項 請在選單加上 onchange 事件,讓選項改變的時候,輸入文字的框框,能跟著有樣式變化 請讓文字輸入框框,在選單為「重要」時,顯示為「橘色」;在選單為「緊急」時,顯示為「紅色」 這個顏色變化,可以是邊框、背景顏色、字體顏色、影子顏色,都可以,你自由決定 目的只是讓用戶有更多「視覺回饋」,讓用戶可以預期,新事項在清單內會變色而已 請在 `<input>` 或者在它父元素,加上不同的 class,用不同 class 顯示的不同顏色,來做到這點 做出這個功能,你就完成這次的課程目標了!

JavaScript 系列二:第1課 ── 認識 DOM 樹、新增元素

## 課程目標 認識 DOM 樹基本觀念 能夠在 DOM 樹進行「新增」 學會基本的除錯技巧 ## 課程內容 請打開這個網站,用這個網站來練習&寫作業: https://jsfiddle.net 在學 JavaScript 的時候,你會聽到很多人在講「DOM 樹」這個名詞 什麼是「DOM 樹」? 其實只是,你在網頁上看到的東西,瀏覽器會在背後的 JavaScript 環境,維持一個對應的「DOM 樹」 比如說,當你寫這樣的 html(這邊先省略內容,只留結構) ``` <html> <head> <script></script> </head> <body> <div> <h1></h1> <p></p> </div> </body> </html ``` 瀏覽器會在背後創造一個這樣的資料結構 ``` document └── html ├── head │   └── script └── body └── div ├── h1 └── p ``` 很像是樹吧?這就是「DOM 樹」 記得之前用過的 `document.getElementById` 嗎? 這就是 DOM 最上面的 document 物件 所謂的「DOM 樹」其實就是一個「巢狀物件」。 「巢狀物件」的意思是,物件的屬性,又是另一個物件,然後這物件的屬性,又可以是另一個物件 用 JavaScript 來表示的話,類似這樣(只是概念喔,真實的物件不只是這樣,而是有一堆內建函式與屬性可以用) ``` var document = { name: 'document', children: [ { name: 'html', children: [ { name: 'head', children: [ { name: 'script', children: [] } ] }, { name: 'body', children: [ { name: 'div', children: [ { name: 'h1', children: [] }, { name: 'p', children: [] } ] } ] } ] } ] } ``` 在 DOM 中,被包起來的元素,稱為子元素;把別人包起來的元素,稱為父元素。就跟學 html 時的稱呼一樣 瀏覽器會確保用戶介面(User Interface,簡稱 UI)上看到的,跟環境中實際運行的 DOM 長得一模一樣 也就是說,你修改 UI 的內容,DOM 物件就會自動更新;你用 JavaScript 修改 DOM 物件的內容,UI 就會自動更新 --- 來學學怎麼用 JavaScript 操作 DOM 樹吧! 如果原本 html 長這樣 ``` <div id="app"></div> ``` 只要加入這段程式碼 ``` var heading = document.createElement('h1'); heading.textContent = '這是標題'; var para = document.createElement('p'); para.textContent = '這是段落文字'; var app = document.getElementById('app'); app.append(heading); app.append(para); ``` 利用 document 的內建函數 `createElement` 來動態創造新的元素 修改元素的屬性 `.textContent` 來設定文字內容 接著用 `.append()` 函數來讓元素吃下別的元素,也就是「把另一個元素收為子元素」,就跟收養子女一樣! 最後 html 就會變成這樣 ``` <div id="app"> <h1>這是標題</h1> <p>這是段落文字</p> </div> ``` 馬上貼到 jsfiddle 試試看,實驗一下、玩玩看! --- 在寫 html 的時候,出現巢狀結構是很常見的 操作 DOM 時,當然也可以自由創造巢狀結構 ``` var elem1 = document.createElement('div'); elem1.textContent = 'level 1'; var elem2 = document.createElement('div'); elem2.textContent = 'level 2'; var elem3 = document.createElement('div'); elem3.textContent = 'level 3'; var app = document.getElementById('app'); app.append(elem1); elem1.append(elem2); elem2.append(elem3); ``` 到 jsfiddle 試試看,就會清楚實際效果囉! --- 在開發的過程中,基本上會不斷打錯字、寫錯用法。程式跑不出結果,很正常 這種時候,有兩個常用的除錯方法,我們叫「debug 的方法」 第一個方法是 `alert`,你就把不確定的內容,給 alert 出來看看就對了,到處寫 alert 看一下變數內容,確定內容跟預期一樣,再接著繼續寫 第二個方法是 `console.log()`,把參數傳進去,接著打開瀏覽器的「開發者工具」,就會看到結果,通常會比 alert 內容更詳細、更方便除錯 除此之外,JavaScript 相關的各種錯誤訊息,都會在「開發者工具」出現。請去研究你所使用的瀏覽器,如何打開這個工具 ``` var x = 123; var y = 'hello'; var z = document.createElement('div'); console.log(x) console.log(y) console.log(z) ``` 請打開瀏覽器的開發者工具,實際看一下上述範例的結果。 ## 課後作業 假設你是一個健忘的人,你決定寫一個「待辦事項管理」應用程式,來幫助自己 這一課,先實作「新增事項」的功能 --- 在上面,做一個文字輸入框,旁邊有一個「新增」按鈕 在下面,做一個展示待辦事項的清單,請使用這樣的結構,id 跟 css 之類的你可以自由決定 ``` <ul> <li> <span>倒垃圾</span> </li> <li> <span>繳電話費</span> </li> <li> <span>採買本週食材</span> </li> </ul> ``` 點擊「新增」按鈕,會將新事項插入到清單最底部 請稍微替這個清單工具加一點 css 屬性,弄得漂亮一點,這樣的工具才讓人想用 --- 除此之外,請練習一下 `console.log` 的用法,不然之後遇到錯誤,幾乎沒辦法除錯 請把作業內容中,出現的變數,隨便挑三個,用 `console.log` 印到開發者工具中,然後用瀏覽器看一下內容 --- 做出以上功能,你就完成這次的課程目標了!

20 個冷門、但很實用的 git 指令:值得你稍微認識一下

如果您曾經瀏覽過 [git 手冊](https://git-scm.com/docs)(或執行 `man git`),那麼您會發現 git 指令比我們每天在用的多很多。很多指令非常強大,可以讓你的生活更輕鬆(有些比較小眾,但知道一下還是不錯)。 > 這篇文章整理了我最喜歡的 20 個冷門 git 功能,您可以使用來改善您的開發流程、給您的同事留下深刻印象、幫助您回答 git 面試問題,最重要的是 - 可以玩得很開心! 原文出處:https://dev.to/lissy93/20-git-commands-you-probably-didnt-know-about-4j4o --- ## Git Web > 執行 [`git instaweb`](https://git-scm.com/docs/git-instaweb) 可以立即瀏覽 gitweb 中的工作存儲庫 Git 有一個內建的[基於網路可視化工具](https://git-scm.com/docs/gitweb) 可以瀏覽本地存儲庫,它允許您通過瀏覽器中的 GUI 查看和管理您的存儲庫。它包含許多有用的功能,包括: - 瀏覽和單步執行修訂並檢查差異、文件內容和元資料 - 可視化查看提交日誌、分支、目錄、文件歷史和附加資料 - 生成提交和存儲庫活動日誌的 RSS 或 Atom 提要 - 搜尋提交、文件、更改和差異 要打開它,只需從您的存儲庫中執行 `git instaweb`。您的瀏覽器應該會彈出並讀取 http://localhost:1234 。如果您沒有安裝 Lighttpd,您可以使用“-d”標誌指定一個備用 Web 伺服器。其他選項可以通過標誌配置(例如 `-p` 用於端口,`-b` 用於打開瀏覽器等),或在 git 配置中的 `[instaweb]` 塊下配置。 還有 `git gui` 命令,它可以打開一個基於 GUI 的 git 應用程式 ![](https://i.ibb.co/0DrmcWG/Screenshot-from-2022-12-17-20-26-30.png) --- ## Git Notes > 使用 [`git notes`](https://git-scm.com/docs/git-notes) 向提交加入額外訊息 有時您需要將其他資料附加到 git 提交(除了更改、訊息、日期時間和作者訊息之外)。 註釋存儲在 .git/refs/notes 中,由於它與提交對像資料是分開的,因此您可以隨時修改與提交關聯的註釋,而無需更改 SHA-1 哈希。 您可以使用 `git log`、使用大多數 git GUI 應用程式或使用 `git notes show` 命令查看筆記。一些 git 主機還在提交視圖中顯示註釋(儘管 [GH 不再顯示註釋](https://github.blog/2010-08-25-git-notes-display/))。 --- ## Git Bisect > 使用 [`git bisect`](https://git-scm.com/docs/git-bisect) 你可以使用二進制搜尋找到引入錯誤的提交 這是最強大又好用的 git 命令之一 - bisect 在除錯時絕對是救命稻草。開始對分後,它會為您檢查提交,然後您告訴它提交是“好”(沒有錯誤)還是“壞”(引入錯誤),這可以讓您縮小最早提交的錯誤。 請執行 `git bisect start`,然後使用 `git bisect good <commit-hash>` 向其傳遞一個已知的良好提交,並使用 `git bisect bad <optional-hash>` 傳遞一個已知的錯誤提交(預設為當前)。然後它會檢查好提交和壞提交之間的提交,然後你用 `git bisect good` 或 `git bisect bad` 指定錯誤存在與否。然後它會重複這個過程,在好與壞的中心檢查一個提交,一直到你找到引入錯誤的確切提交。隨時使用 `git bisect reset` 取消。 bisect 命令還有更多功能,包括回放、查看提交、跳過,因此下次除錯時值得查看文檔。 --- ## Git Grep > 使用 [`git grep`](https://git-scm.com/docs/git-grep) 在您的存儲庫中搜尋程式碼、文件、提交或任何其他內容 有沒有發現自己需要在 git 專案中的任何地方搜尋字串?使用 git grep,您可以輕鬆地在整個專案中和跨分支搜尋任何字串或 RegEx(例如更強大的 <kbd>Ctrl</kbd> + <kbd>F</kbd>!)。 `git grep <regexp> <ref>` 它包括大量 [選項](https://git-scm.com/docs/git-grep#_options) 來縮小搜尋範圍,或指定結果格式。例如,使用 `-l` 僅返回文件名,`-c` 指定每個文件返回的匹配數,`-e` 排除匹配條件的結果,`--and` 指定多個條件,` -n` 以行號搜尋。 由於 git grep 與正則表達式兼容,因此您可以使用搜尋的字串獲得更多進階訊息。 您還可以使用它來指定文件擴展名,例如 `git grep 'console.log' *.js`,它將顯示 JavaScript 文件中的所有 console.logs 第二個參數是一個 ref,可以是分支名稱、提交、提交範圍或其他任何內容。例如。 `git grep "foo" HEAD~1` 將搜尋之前的提交。 --- ## Git Archive > 使用 [`git archive`](https://git-scm.com/docs/git-archive) 將整個 repo 合併到一個文件中 共享或備份存儲庫時,通常首選將其存儲為單個文件。使用 git archive 將包括所有 repo 歷史記錄,因此可以輕鬆將其提取回其原始形式。該命令還包括許多附加選項,因此您可以準確自定義存檔中包含和不包含的文件。 ``` git archive --format=tar --output=./my-archive HEAD ``` --- ## Git Submodules > 使用 [`git submodule`](https://git-scm.com/docs/git-submodule) 將任何其他存儲庫拉入您的存儲庫 在 git 中,[submodules](https://git-scm.com/docs/gitsubmodules) 讓您可以將一個存儲庫掛載到另一個存儲庫中,通常用於核心依賴項或將組件拆分到單獨的存儲庫中。有關詳細訊息,請參閱[這篇文章](https://notes.aliciasykes.com/17996/quick-tip-git-submodules)。 執行以下命令會將模塊拉到指定位置,並建立一個 .gitmodules 文件,以便在複製 repo 時始終下載它。複製 repo 時使用 `--recursive` 標誌來包含子模塊。 ``` git submodule add https://github.com/<user>/<repo> <path/to/save/at> ``` 還有 [`git subtree`](https://www.atlassian.com/git/tutorials/git-subtree),它做類似的事情,但不需要元資料文件。 --- ## Git Bug Report > 使用 [`git bugreport`](https://git-scm.com/docs/git-bugreport) 編寫錯誤票,包括 git 和系統訊息 此命令將捕獲系統訊息,然後打開一個標準錯誤模板(重現步驟、實際 + 預期輸出等)。完成的文件應該是一個非常完整的錯誤報告,包含所有必要的訊息。 如果您是開源包的維護者並要求用戶(開發人員)提出錯誤報告,這將非常方便,因為它確保包含所有必要的資料。 如果您向核心 git 系統提交錯誤報告,您還可以執行 [`git diagnostic`](https://git-scm.com/docs/git-diagnose) 命令,然後提出您的問題 [這裡](https://github.com/git/git)。 --- ## Git Fsck > 使用 [`git fsck`](https://git-scm.com/docs/git-fsck) 檢查所有物件,或恢復無法存取的物件 雖然不常需要,但有時您可能必須驗證 git 存儲的物件。這就是 fsck(或文件系統檢查)發揮作用的地方,它測試對像資料庫並驗證所有物件的 SHA-1 ID 及其建立的連接。 它還可以與 `--unreachable` 標誌一起使用,以查找不再可以從任何命名引用存取的物件(因為與其他命令不同,它包括 `.git/objects` 中的所有內容)。 --- ## Git Stripspace > 使用 [`git stripspace`](https://git-scm.com/docs/git-stripspace) 格式化給定文件中的空格 最佳做法是避免在行尾尾隨空格,避免有多個連續的空行,避免輸入的開頭和結尾出現空行,並以新行結束每個文件。有很多特定於語言的工具可以自動為您執行此操作(例如 prettier),但 Git 也內置了此功能。 它用於元資料(提交訊息、標籤、分支描述等),但如果您將文件通過管道傳輸給它,然後將響應通過管道傳輸回文件,它也可以工作。例如。 `cat ./path-to-file.txt | git stripspace` 或 `git stripspace < dirty-file.txt > clean-file.txt` 您還可以使用它來刪除註釋(使用 `--strip-comments`),甚至註釋掉行(使用 `--comment-lines`)。 --- ## Git Diff > 使用 [`git diff`](https://git-scm.com/docs/git-diff) 你可以比較 2 組程式碼之間的差異 您可能知道您可以執行 `git diff` 來顯示自上次提交以來的所有更改,或者使用 `git diff <commit-sha>` 來比較 2 次提交或 1 次提交到 HEAD。但是您可以使用 diff 命令做更多的事情。 您還可以使用它來比較任意兩個任意文件,使用 `diff file-1.txt file-2.txt`(不再存取 [diffchecker.com](https://www.diffchecker.com/compare/)! ) 或者使用 `git diff branch1..branch2` 相互比較 2 個分支或引用 請注意,雙點 (`..`) 與空格相同,表示 diff 輸入應該是分支的尖端,但您也可以使用三點 (`...`) 來轉換第一個參數進入兩個差異輸入之間共享的共同祖先提交的引用 - 非常有用!如果只想跨分支比較單個文件,只需將文件名作為第三個參數傳遞。 您可能希望查看在給定日期範圍內所做的所有更改,為此使用 `git diff HEAD@{7.day.ago} HEAD@{0}`(上週),同樣可以將其與文件名、分支名稱、特定提交或任何其他參考。 還有 [`git range-diff`](https://www.git-scm.com/docs/git-range-diff) 命令,它提供了一個用於比較提交範圍的簡單界面。 git diff 工具還有更多功能(以及使用您自己的差異檢查器的選項),因此我建議查看 [文檔](https://git-scm.com/docs/git-diff#_description) . --- ## Git Hooks > 使用 [`hooks`](https://git-scm.com/docs/githooks) 在給定的 get 操作發生時執行命令或執行腳本 Hooks 可以讓你自動化幾乎所有的事情。例如:確保滿足標準(提交訊息、分支名稱、補丁大小)、程式碼質量(測試、lint)、將附加訊息附加到提交(用戶、設備、票證 ID)、呼叫 webhook 來記錄事件或執行管道等 對於大多數 git 事件,如 commit, rebase, merge, push, update, applypatch 等,都有前後 [hooks available](https://git-scm.com/docs/githooks)。 鉤子存儲在 `.git/hooks` 中(除非您使用 `git config core.hooksPath` 在其他地方配置它們),並且可以使用 [`git hook`](https://git-scm.com/docs) 進行測試/git-hook) 命令。由於它們只是 shell 文件,因此可用於執行任何命令。 掛鉤不會推送到遠程存儲庫,因此要在您的團隊中共享和管理它們,您需要使用 [掛鉤管理器](https://github.com/aitemr/awesome-git-hooks#tools) ,例如 [lefthook](https://github.com/evilmartians/lefthook) 或 [husky](https://github.com/typicode/husky)。還有幾個[3rd-party tools](https://githooks.com/#projects),這使得管理鉤子更容易,我推薦[overcommit](https://github.com/sds/overcommit)。 請記住,掛鉤總是可以跳過(使用 `--no-verify` 標誌),所以永遠不要純粹依賴掛鉤,尤其是對於任何與安全相關的事情。 --- ## Git Blame > 使用 [`git blame`](https://git-scm.com/docs/git-blame) 顯示特定修訂版和行的作者訊息 一個經典的,快速找出誰寫了特定程式碼行(也就是你的哪個同事應該為這個錯誤負責!)。但它也有助於確定在哪個時間點發生了某些更改並檢查該提交和關聯的元資料。 例如,要查看 index.rs 第 400 到 420 行的作者和提交訊息,您可以執行: ``` git blame -L 400,420 index.rs ``` --- ## Git LFS > 使用 [`git lfs`](https://git-lfs.github.com/) 存儲大文件,以免拖慢您的存儲庫 您的專案通常會包含較大的文件(例如資料庫、二進制資產、檔案或媒體文件),這會減慢 git 工作流程並使使用限制達到最大。這就是 [大型文件存儲](https://git-lfs.github.com/) 的用武之地 - 它使您能夠將這些大型資產存儲在其他地方,同時使它們可以通過 git 進行跟踪並保持相同的存取控制/權限。 LFS 的工作原理是將這些較大的文件替換為在 git 中跟踪的文本指針。 要使用它,只需執行 `git lfs track <file glob>`,這將更新您的 `.gitattributes` 文件。您可以通過擴展名(例如“*.psd”)、目錄或單獨指定文件。執行 git lfs ls-files 以查看跟踪的 LFS 文件列表。 --- ## Git GC > 使用 [`git gc`](https://git-scm.com/docs/git-gc) 優化您的存儲庫 隨著時間的推移,git repos 會積累各種類型的垃圾,這些垃圾會佔用磁盤空間並減慢操作速度。這就是內置垃圾收集器的用武之地。執行 `git gc` 將刪除孤立的和不可存取的提交(使用 [`git prune`](https://git-scm.com/docs/git-prune)),壓縮文件修訂和存儲的 git 物件,以及一些其他一般的內務管理任務,如打包引用、修剪引用日誌、尊重元資料或陳舊的工作樹和更新索引。 加入 `--aggressive` 標誌將 [積極優化](https://git-scm.com/docs/git-gc#_aggressive) 存儲庫,丟棄任何現有的增量並重新計算它們,這需要更長的時間執行但如果你有一個大型存儲庫可能需要。 --- ## Git Show > 使用 [`git show`](https://git-scm.com/docs/git-show) 輕鬆檢查任何 git 物件 以易於閱讀的形式輸出物件(blob、樹、標籤或提交)。要使用,只需執行 `git show <object>`。您可能還想附加 `--pretty` 標誌,以獲得更清晰的輸出,但還有許多其他選項可用於自定義輸出(使用 `--format`),因此此命令對於準確顯示非常強大你需要什麼。 這非常有用的一個實例是在另一個分支中預覽文件,而無需切換分支。只需執行 `git show branch:file` --- ## Git Describe > 使用 [`git describe`](https://git-scm.com/docs/git-describe) 查找可從提交中存取的最新標記,並為其指定一個人類可讀的名稱 執行 `git describe`,您將看到一個人類可讀的字串,該字串由最後一個標籤名稱與當前提交組合而成,以生成一個字串。您還可以將特定標籤傳遞給它, 請注意,您必須已建立標籤才能使其正常工作,除非您附加了 `--all` 標誌。默認情況下,Git describe 也只會使用帶註釋的標籤,因此您必須指定 `--tags` 標誌以使其也使用輕量級標籤。 --- ## Git Tag > 使用 [`git tag`](https://git-scm.com/docs/git-tag) 在你的 repo 歷史中標記一個特定點 能夠[標記](https://git-scm.com/book/en/v2/Git-Basics-Tagging) 存儲庫歷史記錄中最常用於表示發布版本的特定重要點通常很有用。建立標籤就像 `git tag <tagname>` 一樣簡單,或者您可以使用 `git tag -a v4.2.0 <commit sha>` 標記歷史提交。與提交一樣,您可以使用“-m”在標籤旁邊包含一條訊息。 不要忘記使用 `git push origin <tagname>` 將您的標籤推送到遠程。 要列出所有標籤,只需執行 `git tag`,並可選擇使用 `-l` 進行通配符搜尋。 然後,您將能夠使用 `git checkout <tagname>` 檢出特定標籤 --- ## Git Reflog > 使用 [`git reflog`](https://git-scm.com/docs/git-reflog) 列出對您的存儲庫所做的所有更新 Git 使用稱為參考日誌或“reflogs”的機制跟踪分支尖端的更新。跟踪各種事件,包括:克隆、拉取、推送、提交、檢出和合併。能夠找到事件引用通常很有用,因為許多命令都接受引用作為參數。只需執行 `git reflog` 即可查看 `HEAD` 上的最近事件。 reflog 真正有用的一件事是恢復丟失的提交。 Git 永遠不會真正丟失任何東西,即使是在重寫歷史時(比如變基或提交修改)。 Reflog 允許您返回提交,即使它們沒有被任何分支或標記引用。 默認情況下,reflog 使用 `HEAD`(您當前的分支),但您可以在任何 ref 上執行 reflog。例如 `git reflog show <branch name>`,或者使用 `git reflog stash` 查看隱藏的更改。或者使用 `git reflog show --all` 顯示所有引用 --- ## Git Log > 使用 [`git log`](https://git-scm.com/docs/git-log) 查看提交列表 您可能已經熟悉執行 `git log` 來查看當前分支上最近提交的列表。但是您可以使用 git log 做更多的事情。 使用 `git log --graph --decorate --oneline` 將顯示一個漂亮整潔的提交圖以及 ref 指針。 ![示例 git 日誌輸出](https://i.ibb.co/c1WByg8/Screenshot-from-2022-12-17-20-43-56.png) 您還經常需要能夠根據各種參數過濾日誌,其中最有用的是: - `git log --search="<anything>"` - 搜尋特定程式碼更改的日誌 - `git log --author="<pattern>"` - 只顯示特定作者的日誌 - `git log --grep="<pattern>"` - 使用搜尋詞或正則表達式過濾日誌 - `git log <since>..<until>` - 顯示兩個引用之間的所有提交 - `git log -- <file>` - 顯示僅對特定文件進行的所有提交 或者,只需執行 `git shortlog` 以獲得匯總的提交列表。 --- ## Git Cherry Pick > 使用 [`git cherry-pick`](https://git-scm.com/docs/git-cherry-pick) 通過引用選擇指定的提交並將它們附加到工作 HEAD 有時你需要從其他地方拉一個特定的提交到你當前的分支。這對於應用熱修復、撤消更改、恢復丟失的提交以及在某些團隊協作設置中非常有用。請注意,通常傳統的合併是更好的做法,因為挑選提交會導致日誌中出現重複提交。 用法很簡單,只需執行 `git cherry-pick <commit-hash>`。這會將指定的提交拉入當前分支。 --- ## Git Switch > 使用 [`git switch`](https://git-scm.com/docs/git-switch) 在分支之間移動是我們經常做的事情,`switch` 命令就像是`git checkout` 的簡化版本,它可以用來建立和在分支之間導航,但不像 checkout 在分支之間移動時不會復制修改的文件. 類似於 `checkout -b`,使用 switch 命令你可以附加 `-c` 標誌來建立一個新分支,然後直接跳入其中,例如`git switch -c <新分支>`。執行 `git switch -` 將放棄您所做的任何實驗性更改,並返回到您之前的分支。 --- ## Git Standup > 使用 [`git standup`](https://github.com/kamranahmedse/git-standup) 回憶你在最後一個工作日做了什麼,基於 git 提交 我把它放在最後,因為它不包含在大多數 git 客戶端中,但是您可以使用系統包管理器[輕鬆安裝](https://github.com/kamranahmedse/git-standup#install) ,使用 1 行 curl 腳本,或從源程式碼建置。 如果您的老闆要求您每天站立一次,以更新昨天的工作,但您永遠記不起自己到底做了什麼——這個適合您!它將顯示一個格式良好的列表,列出在給定時間範圍內完成的所有事情。用法很簡單,只需執行 `git standup`,或使用 [這些選項](https://github.com/kamranahmedse/git-standup#options) 指定應顯示哪些資料(作者、時間範圍、分支機構等)。 --- ## 結論 希望對您有幫助!

都要 2023 年了還在用 console.log 嗎?來看看 10 個 console 進階用法!

寫 JavaScript 時,仍在使用 console.log 來除錯抓蟲? 是時候**升級你的技能**並學習 JavaScript console 物件的全部功能了。 從 console.table 到 console.time,這些進階方法和技巧將改善您 **debug 訊息的品質和可讀性**,讓您更輕鬆地故障排除和修復問題。 在 2023 年成為 JavaScript 除錯高手吧! - 原文出處:https://dev.to/naubit/why-using-just-consolelog-in-2023-is-a-big-no-no-2429 ## 😞 問題 僅使用 console.log 的最大問題之一是,它會使程式碼混亂且難以閱讀。此外,它本身的資訊量不是很大。它只是輸出你傳遞給它的任何內容的值,沒有任何上下文或其他資訊。 所以,這裡有十個你應該知道的 JavaScript console 物件方法和技巧,值得了解一下! ## 1️⃣ console.table 此方法以可讀且易於理解的格式,輸出資料表格。console.table 不是只顯示陣列或物件,而是以表格格式顯示,使它更易於瀏覽和理解。 ``` // Output an array of objects as a table const users = [ { id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Doe' } ]; console.table(users); ``` 這將以表格格式輸出 users 陣列,每個物件的屬性作為列,物件作為行。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hek6gxo4x73b6mvmlawr.png) ## 2️⃣console.group console.group 和 console.groupEnd。這些方法可以在控制台中,創建嵌套的可摺疊資料組。**這對於組織和構建 debug 訊息非常有用**,讓您可以輕鬆查看程式碼不同級別發生的情況。 ``` console.group('User Details'); console.log('Name: John Doe'); console.log('Age: 32'); console.groupEnd(); ``` 這將在 console 中創建一個嵌套的、可摺疊的資料組,標題為“使用者詳細資訊”。組內的日誌消息將縮進並分組在一起。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ybn1uwvyltjzxcaaytx.png) ## 3️⃣console.time console.time 和 console.timeEnd 可以測量執行程式碼所需的時間。這對於識別程式中的性能瓶頸、效能優化非常有用。 ``` console.time('Fetching data'); fetch('https://reqres.in/api/users') .then(response => response.json()) .then(data => { console.timeEnd('Fetching data'); // Process the data }); ``` 這將測量「從特定 URL 獲取資料並解析 JSON 回應」所需的時間。耗用時間會在控制台中輸出。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3hdvnz2of3an1a21j3c.png) ## 4️⃣console.assert 此方法允許您在程式碼中寫 assertions,這些是應該始終為 true 的語句。如果 assertions 失敗,console.assert 將在控制台中輸出錯誤消息。**這對於除錯抓蟲、確保程式碼正常運作非常有用。 ``` function add(a, b) { return a + b; } // Test the add function const result = add(2, 3); console.assert(result === 5, 'Expected 2 + 3 = 5'); ``` 如果 add 函數在給定輸入 2 和 3 時未返回預期結果 5,就在控制台中輸出錯誤訊息。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y25c157xwhasxnaiakd5.png) ## 5️⃣設置日誌樣式 使用 `console` 物件輸出樣式和顏色。 `console` 物件**允許您輸出不同顏色和樣式的文字**,使您的除錯輸出更具可讀性和更容易理解。 您可以在 console.log 語句中使用 %c 佔位符來指定輸出文字的 CSS 樣式。 ``` console.log('%cHello world!', 'color: red; font-weight: bold;'); ``` 這將輸出文字“Hello world!”,並使用指定的 CSS 樣式,以紅色和粗體顯示。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1p82u116icnlodu046nr.png) ## 6️⃣console.trace 使用 `console.trace` 方法輸出堆疊追踪(stack trace)。這對於理解程式碼中的執行流程、識別**特定日誌消息的來源非常有用。** ``` function foo() { console.trace(); } function bar() { foo(); } bar(); ``` 這將在控制台中輸出堆疊跟踪,顯示導致 `console.trace` 呼叫的函數呼叫序列。看起來像這樣: ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx16nee1mp0avh04msri.png) ## 7️⃣console.dir 使用 `console.dir` 方法以分層格式輸出物件的屬性。這對於**探索物件的結構**、查看其屬性和方法非常有用。 ``` const obj = { id: 1, name: 'John Doe', address: { street: '123 Main St', city: 'New York', zip: 10001 } }; console.dir(obj); ``` 這將以分層格式輸出 `obj` 物件的屬性,允許您查看物件的結構及其所有屬性和值。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihgkg5e4wxznpaqzb8jr.png) ## 8️⃣console.count 使用 `console.count` 方法計算特定日誌消息的輸出次數。這對於**追蹤特定程式碼路徑的執行次數**以及識別程式碼中的熱點很有用。 ``` function foo(x) { console.count(x); } foo('hello'); foo('world'); foo('hello'); ``` 這將在控制台中輸出字串“hello”,然後是數字 1。然後它將在控制台中輸出字串“world”,然後是數字 1。最後,它會再次輸出字符串“hello”,然後出書數字 2(因為它被呼叫了兩次)。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cx7n8aargexpq1pvqj6n.png) ## 9️⃣console.clear 使用 `console.clear` 方法清除控制台輸出。這能保持您的除錯輸出**有條理和整潔**,讓您更容易專注於您感興趣的訊息。 ``` console.log('Hello world!'); console.clear(); console.log('This log message will appear after the console is cleared.'); ``` 這將輸出字串 “Hello world!”,接著一個空行(因為控制台已清除)。然後它會輸出字串 “This log message will appear after the console is cleared.” ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihzapi1gcin6gtqa600v.png) ## 1️⃣0️⃣console.profile 使用 `console.profile` 和 `console.profileEnd` 方法來衡量程式碼的性能。這對於**識別性能瓶頸和優化代碼**、提高速度和效率非常有用。 ``` console.profile('MyProfile'); // Run some code that you want to measure the performance of for (let i = 0; i < 100000; i++) { // Do something } console.profileEnd('MyProfile'); ``` 這將開始分析 `console.profile` 和 `console.profileEnd` 呼叫之間的程式碼,並在執行 `console.profileEnd` 時在控制台中輸出結果。輸出將包括有關執行所花費時間的詳細訊息、以及其他與性能相關的各種訊息。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjw25tagjwdupmqxy1d5.png) ## 💭 最後的一些想法 在 2023 年,不要僅僅滿足於 `console.log` - JavaScript 控制台物件中有許多**更強大、更有價值的工具和技術。** 從 `console.table` 到 `console.time`,這些方法和技巧將幫助您提高除錯輸出的品質和可讀性,並使其**更容易排除和修復代碼中的問題**。 所以,為什麼不在 2023 年提高您的除錯技能並嘗試這些技術呢? 以上簡單分享,希望對您有幫助!