猜妳喜歡
Number.isNaN 回傳 false,輸入卻不是數字:表單驗證怎樣寫
Number.isNaN("abc") 為何是 false?用數量欄位示例,先檢查空白與十進位格式,再轉成數字,保留合法的 0 並拒絕超出範圍的值。
2026-11-01 0 0
JS填0卻變預設值:分清雙豎線與空值合併
設定值明明是0,畫面卻套用了預設的10;開關明明是false,結果又變成開啟。先找程式裡是否用了value || 預設值。雙豎線會把0、false和空字串等falsy值一起當成切換到右側的條件。若只有null與undefined才代表缺值,應改用??。 0有值,雙豎線仍會換掉它 這個例子假設0是允許的設定,10才是缺值時的預設。可直接在瀏覽器Console或Node.js執行。 const configuredCount = 0 console.log(configuredCount || 10) // 10 console.log(configuredCount ?? 10) // 0 ||不是只檢查變數有沒有資料。它先判斷左側是否為truthy;若不是,就回傳右側的值。??的條件較窄,只在左側為null或undefined時採用右側;0會原樣保留。 關閉與空白,也可能是刻意的選擇 const enabled = false const customLabel = '' console.log(enabled || true) // true console.log(enabled ?? true) // false console.log(customLabel || '未填') // 未填 console.log(customLabel ?? '未填') // 空字串 若false表示關閉某項功能,使用|| true會把它改成true。若空字串表示「不要顯示標籤」,||也會補回文字。先決定這些值在你的功能裡是否合法,再選運算子;不是所有預設值都應一律改成??。 例如名稱欄位若不允許空白,保留空字串就不符合需求。那是欄位驗證與資料整理的責任,不能只靠??替你判斷。 真正缺值時,才採用預設 const settings = { count: 0, label: null } console.log(settings.count ?? 10) // 0 console.log(settings.label ?? '未填') // 未填 console.log(settings.delay ?? 5) // 5 這裡settings已經是物件;沒有delay屬性,讀取結果為undefined,因此採用5。label明確設為null,也會採用預設。這不表示??能讓不存在的變數或任何屬性存取都安全,它處理的是左側已求值後的結果。 保留值,不等於值已經合法 ??不會把字串「0」轉成數字,也不會把NaN變成預設值。若資料來自表單或API,仍要依資料格式確認型別、範圍與缺值規則,再把可用資料帶進畫面。...
2026-10-21 57 0
文尼先生
2026-10-21
JS明明return成功卻拿到別的值:檢查finally裡的return
函式的try裡已經寫了return 'ready',呼叫端卻收到「done」?先往下看finally。在一般JavaScript函式中,try準備回傳後仍會執行finally;若finally又執行return,它會覆蓋原本的回傳結果。 兩個return,最後收到哪一個? 把下面整段貼到瀏覽器Console,或存成檔案用Node.js執行。這個例子沒有網路請求,只有函式的回傳順序。 function readStatus() { try { return 'ready' } finally { return 'done' } } console.log(readStatus()) // done 這不是try沒有執行,也不是呼叫端讀到舊值。程式準備把ready交回去時,先進入finally;那裡的第二個return改了最後結果。所以只在try的回傳行設中斷點,可能看不出問題。 不帶值的return也會覆蓋 有時候finally裡沒有寫替代值,只是加了一個看似無害的提早離開。它仍然會影響結果。 function readStatusWithoutValue() { try { return 'ready' } finally { return } } console.log(readStatusWithoutValue()) // undefined 呼叫端得到undefined,並不代表try沒有找到資料;也可能是finally中的空return把原值蓋掉了。不要用「finally沒有回傳新資料」判斷它不影響結果。 原本丟出的錯誤,也可能被藏起來 這個問題不只發生在成功分支。try已經丟出錯誤,但finally回傳一個普通值時,外層也可能收不到原本的錯誤。 function loadStatus() { try { throw new Error('讀取失敗') } finally { return 'fallback' } } try { console.log(loadStatus()) // fallback } catch (error) { console.log('外層收到:', error.message) } 這段會輸出fallback,不會進入外層catch。若你看到程式明明throw了,頁面卻像成功一樣繼續走,就要檢查finally是否有return。需要轉成預設結果時,應把錯誤處理規則寫在明確的catch或業務分支,別讓清理區順手吞掉例外。 把finally留給收尾,讓結果沿原路出去 finally適合恢復狀態或釋放已取得的資源。下面以記錄「收尾完成」代替實際資源操作;收尾區不回傳值,也不另外丟出錯誤。 function readStatusSafely() { try { return 'ready'...
2026-10-17 79 0
文尼先生
2026-10-17
JS數量填12px也過關?別把parseInt當輸入驗證
商品數量欄位收到「12px」,程式卻照樣當成12送出?如果你用parseInt(value, 10)判斷輸入是否合法,就可能遇到這個問題。它能讀取開頭的整數部分,並不要求整個字串都是數字。 先看被截掉的是哪一段 在瀏覽器Console貼上這幾行,留意字串尾巴以及小數點之後的內容。 console.log(parseInt('12px', 10)) // 12 console.log(parseInt('12.8', 10)) // 12 console.log(parseInt('1e3', 10)) // 1 console.log(parseInt('px12', 10)) // NaN 前三個例子都能從開頭讀到數字,遇到不能當作十進位數字的字元便停止。最後一個連開頭都讀不到數字,才得到NaN。所以「結果不是NaN」只代表讀到某個整數部分,不能證明輸入符合數量欄位的要求。 先定義數量欄位接受什麼 假設購物表單接受1至99件、只允許十進位數字,不接受正負號、小數、指數或單位;前後空白可忽略,前導零也可接受。這是下面案例的規則,99不是所有網站都適用的上限。 function readQuantity(raw) { if (typeof raw !== 'string') return null const text = raw.trim() if (!/^[0-9]+$/.test(text)) return null const quantity = Number(text) if (!Number.isSafeInteger(quantity)) return null if (quantity < 1 || quantity > 99) return null return quantity } 先確認整個去除前後空白後的字串都由數字組成,再轉成Number,最後核對安全整數與範圍。空字串過不了第一道格式檢查,「12px」也不能只靠開頭的12過關。null表示拒絕輸入,別把它再轉成0送出。 這個函式要接收輸入框的字串value。如果你改用已轉成數字的資料,或要接受小數與正負號,就應重新定義規則,不能直接照搬這個格式檢查。 用幾個容易漏掉的值試一次 先貼上完整函式,再貼下面的呼叫。函式名稱相同時不要在同一測試檔重複宣告多份。 for (const raw of [ '12', ' 12 ', '012', '12px', '12.8', '1e3', '',...
2026-10-13 76 0
文尼先生
2026-10-13
JS一個區塊載入失敗,全頁都空白?用Promise.allSettled保留成功結果
網站一次載入最新文章、分類與推薦內容,推薦區塊出錯後,連已經取得的文章都沒有顯示?如果你把所有請求放進Promise.all(),並只在整組成功後更新畫面,任何一個Promise拒絕,都會讓整組進入錯誤分支。 幾個區塊可以各自顯示時,可用Promise.allSettled()收集每項結果,成功的照常顯示,失敗的留在自己的區塊提示。它不會把失敗變成功,也不會自動重試或取消請求。 先看Promise.all為何沒有交出整組結果 下面用虛構的Promise重現,不需要連到真實API。把程式貼進自己的空白測試頁開發者工具主控台;成功項目代表取得資料,失敗項目代表推薦服務出錯。 const tasks = [ Promise.resolve('文章已載入'), Promise.reject(new Error('推薦服務失敗')), Promise.resolve('分類已載入') ]; Promise.all(tasks).then(values => { console.log('整組成功', values); }).catch(error => { console.log('整組失敗', error.message); }); 主控台會顯示「整組失敗 推薦服務失敗」,而不是執行上面的整組成功分支。這不代表另外兩項沒有完成;Promise.all()本身不會取消它們,只是這個寫法沒有在失敗分支使用它們的成功結果。 區塊彼此獨立,用allSettled逐項處理 改成下面的寫法,先檢查每項status,再讀取成功項的value或失敗項的reason。名稱與結果都按輸入順序配對,不能按哪個服務先回來猜區塊。 const names = ['文章', '推薦', '分類']; const settledTasks = [ Promise.resolve('文章已載入'), Promise.reject(new Error('推薦服務失敗')), Promise.resolve('分類已載入') ]; Promise.allSettled(settledTasks).then(results => { results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(names[index], result.value); } else { console.log(names[index], '暫時無法載入'); } }); }); 這次三行依序是「文章 文章已載入」、「推薦 暫時無法載入」與「分類 分類已載入」。推薦失敗的原因仍留在對應結果中;公開畫面使用簡短提示,維護紀錄再保存必要的錯誤類別,不要把含憑證或個人資料的完整錯誤直接印給訪客。 比較完成順序與結果順序 結果陣列依輸入順序排列,即使第一項比較慢也是如此。下面讓第二項先失敗,第一項晚一點成功,仍會先得到文章的結果,再得到推薦的結果。 const slowArticle = new Promise(resolve => { setTimeout(() =>...
2026-10-09 111 0
文尼先生
2026-10-09
