- 教程
【免费下载链接】jstips
This is about useful JS tips!
本指南以 jstips 專案第 12 期技巧(_posts/zh_TW/javascript/2016-01-12-pseudomandatory-parameters-in-es6-functions.md)為主體,講解如何運用 ES6 的參數預設值(default parameter values)特性,在 JavaScript 中模擬其他程式語言「參數預設必填」的行為。讀完後,你將掌握一套不污染函式主體、能精準定位缺失參數的強制參數檢查方案,並理解預設值背後的求值機制與實務注意事項。
問題背景:JavaScript 的每個參數天生都是可選的
在 Java、C#、Python 等許多程式語言中,函式的參數預設是強制必填的,開發者必須明確標記,才能把某個參數宣告為可選。JavaScript 的設計則完全相反——每一個參數都是可選的:呼叫函式時少傳參數,引擎不會報錯,只會把缺失的參數填充為undefined。
function add(a, b) { return a + b; } add(1); // => NaN(b 是 undefined) add(); // => NaN(a 和 b 都是 undefined)這種寬鬆行為在大型專案中容易埋下隱患:漏傳參數往往不會在第一時間拋出異常,而是靜默地以undefined參與計算,最後在離呼叫點很遠的地方才暴露出難解的 bug。
核心技巧:用「會拋錯的預設值」強制參數必填
jstips 第 12 期提出的解法非常優雅:既然預設值只在參數缺失(等於undefined)時才被採用,那麼只要把預設值設計成一個立即拋出錯誤的函式呼叫,就能在參數缺失的瞬間主動引爆異常,而且完全不動函式主體的一行程式碼。
以下是原文的完整範例(原封不動地保留了箭頭函式語法):
const _err = function( message ){ throw new Error( message ); } const getSum = (a = _err('a is not defined'), b = _err('b is not defined')) => a + b getSum( 10 ) // 拋出錯誤,b 沒有被定義 getSum( undefined, 10 ) // 拋出錯誤,a 沒有被定義拆解這個模式,可以歸納為三個要素:
_err是一個立即拋出Error的工具函式:它接收一個訊息字串,並以throw new Error(message)中斷執行流程。它不修改任何狀態,是純粹的「拋錯閘門」。a、b的預設值被寫成_err('...')的呼叫結果:當呼叫方沒有為該參數提供值時,預設值表達式會被求值,_err被執行,異常立即被丟出。- 錯誤訊息指名道姓:
'a is not defined'、'b is not defined'讓開發者一眼就能看出是哪個參數漏了,定位成本幾乎為零。
行為驗證:
getSum(10):a收到10,但b缺失,觸發b的預設值_err('b is not defined'),拋出錯誤;getSum(undefined, 10):a被顯式傳入undefined——在預設值語義中,顯式傳undefined與完全不傳是等效的——因此a的預設值被觸發,拋出「a is not defined」。
原理深挖:預設值何時被求值?
要正確使用這個模式,必須吃透 ES6 參數預設值的兩條關鍵語義:
1. 只有undefined會觸發預設值,null不會
預設值(default value)只在參數值為undefined時生效——無論是「完全沒傳」還是「顯式傳了undefined」。傳入null或其他任何 falsy 值(0、''、false、NaN)都不會觸發預設值,而是原封不動地被接收。
const check = (x = _err('x is not defined')) => x; check(0); // => 0,不會拋錯 check(null); // => null,不會拋錯 check(''); // => '',不會拋錯 check(undefined); // 拋出 Error: x is not defined check(); // 拋出 Error: x is not defined換句話說,這個模式保護的是「未定義」而非「falsy」。如果你的業務語義要求0、''也必須被視為非法輸入,就需要在函式主體內另行判斷,或改用後文提到的arguments.length方案。
2. 預設值是惰性求值的(lazy evaluation)
預設值表達式只在需要時才被求值,而不是在函式定義時就計算好。這正是「把拋錯寫進預設值」能夠成立的根本原因——正常傳參的呼叫完全不會觸發_err,因此沒有任何性能浪費:
getSum(1, 2):a、b都有值,兩個_err('...')都不會被執行,函式直接回傳3;getSum(1):只有b的_err被執行,a的_err安然無恙。
同時,預設值可以引用位於其左側的先前參數(後文會展示這個能力的實際用途),但要注意不能反向引用右側尚未初始化的參數。
進階變體一:讓預設值驗證參數的「類型與內容」
「偽強制參數」的核心是「用預設值做檢查」。既然預設值是惰性求值的,它就不只限於拋錯,還能承擔更複雜的校驗職責。例如把_err擴充成一個通用的「必填參數守門員」,甚至加入型別檢查:
const required = (name) => { throw new Error(`Missing required parameter: ${name}`); }; // 基本必填檢查 const connect = (host = required('host'), port = required('port')) => { return `connecting to ${host}:${port}`; }; // 結合「先前參數」做交叉驗證(預設值可引用左側參數) const withTimeout = ( timeout = required('timeout'), retries = required('retries'), total = timeout * (retries + 1) // 引用左側已初始化的參數 ) => total; connect('127.0.0.1'); // 拋出 Error: Missing required parameter: port withTimeout(1000, 3); // => 4000 withTimeout(1000); // 拋出 Error: Missing required parameter: retries這種寫法的優勢是檢查邏輯與業務邏輯完全分離:函式主體裡乾乾淨淨,沒有一行if (a === undefined) throw ...的樣板程式碼,所有校驗都收斂在參數簽名層。
進階變體二:與解構參數的預設值搭配
jstips 第 43 期(_posts/zh_TW/javascript/2016-02-12-use-destructuring-in-function-parameters.md)展示了另一個與本技巧正交的用法:在解構參數中設定屬性層級的預設值。當函式接受一個「設定物件」時,可以在參數簽名中直接解構並補上預設值:
var sayHello = function({ name = "Anony", surname = "Moose" } = {}) { console.log(`Hello ${name} ${surname}! How are you?`); }; sayHello(); // -> Hello Anony Moose! How are you?其中的= {}保證了「呼叫方完全沒傳參數(undefined)或傳了錯誤型別」時,解構有一個空物件兜底,不會對undefined執行解構而拋出TypeError。若要在此基礎上強制「某個屬性必填」,可以把它跟本技巧的_err結合:
const _err = (msg) => { throw new Error(msg); }; const createUser = ({ id = _err('id is required'), name = "Guest" } = {}) => { return { id, name }; }; createUser(); // 拋出 Error: id is required createUser({ name: "Tom" }); // 拋出 Error: id is required createUser({ id: 1 }); // => { id: 1, name: "Guest" }值得注意的是解構語法目前的相容性問題:原文第 43 期提到解構賦值當時在 Node.js 與部分瀏覽器尚未普及,需要以--harmony-destructuring旗標試用;時至今日(現代 Node.js 與主流瀏覽器),解構與參數預設值均已成為標準特性,可以放心使用。
對照方案一:||設定預設值的侷限
在 ES6 出現之前,JS 社群慣用的預設值寫法是邏輯或運算子(||),jstips 第 27 期(_posts/zh_TW/javascript/2016-01-27-short-circiut-evaluation-in-js.md)與第 60 期(_posts/zh_TW/javascript/2016-10-28-three-useful-hacks.md)都有範例:
function theSameOldFoo(name){ name = name || 'Bar'; console.log("My best friend's name is " + name); } var a; a = a || 'default value'; console.log(a); // default value這種寫法固然簡潔,卻有一個根本缺陷:||會把所有 falsy 值(0、''、false、NaN)都替換成預設值,而不是只處理undefined。例如theSameOldFoo(0)也會得到'Bar',這在處理合法的 falsy 輸入時會產生靜默的資料竄改。相比之下,本技巧所用的「ES6 參數預設值」只針對undefined生效,語義精確得多——兩者的差異正是理解「偽強制參數」價值的最佳切入點。
對照方案二:以arguments.length做嚴格判定
如果你需要區分「完全沒傳」與「傳了undefined」這兩個語義(ES6 預設值把兩者視為等效),可以退回arguments.length做嚴格檢查:
function strictSum(a, b) { if (arguments.length < 2) { throw new Error('a and b are required'); } return a + b; } strictSum(undefined, 10); // 不會拋錯(arguments.length === 2) strictSum(10); // 拋出 Error: a and b are required需要留意的是,jstips 第 31 期(_posts/zh_TW/javascript/2016-01-31-avoid-modifying-or-passing-arguments-into-other-functions—it-kills-optimization.md)提醒過:對arguments物件進行修改或把它傳入其他函式,會破壞 V8 等引擎對函式的優化(arguments被別名化後引擎無法再使用快速路徑)。因此,若追求極致效能,檢查arguments.length時應只讀不寫、不外傳;而本篇的「偽強制參數」模式完全不碰arguments,天然避開了這個優化陷阱。兩者都是合法工具,可按語義需求取捨。
其他實務提醒
- 與箭頭函式的親和力:本技巧的範例使用了箭頭函式(jstips 第 14 期 fat-arrow-functions 有完整介紹)。箭頭函式與普通
function在「參數預設值」上的行為完全一致,因此這個模式在兩者身上皆可套用;但請記得箭頭函式沒有自己的arguments,需要arguments.length方案時請改用普通函式或 rest 參數。 - 錯誤訊息的品質:務必讓
_err的訊息包含參數名稱,例如'a is not defined'。相較於引擎預設的NaN、undefined等曖昧結果,指名道姓的錯誤可以讓開發者與錯誤追蹤系統(Sentry 等)立刻鎖定肇事參數。 - 與「可選參數」的相容:jstips 第 54 期(
_posts/zh_TW/javascript/2016-05-06-use-optional-arguments.md)示範了用arguments或 rest 參數處理「中間夾雜可選參數」的函式簽名。若函式介面是「必填在前、可選在後」的傳統形態,本技巧可以精準守住必填段;若可選參數夾在中間,則更適合 rest 參數的解法——兩者應視簽名形態搭配使用。
總結
ES6 的參數預設值不只是一種「補預設值」的語法糖,它憑藉「僅對undefined生效」與「惰性求值」兩條語義,讓開發者得以把必填檢查、型別校驗甚至跨參數驗證全部收斂到函式簽名層。jstips 第 12 期用三行程式碼演示了這一思想的精髓:一個拋錯函式_err、兩個以_err(...)為預設值的參數,就讓 JavaScript 函式獲得了其他語言「參數預設必填」的強制力——既不打亂函式主體,又能即時、精準地揪出漏傳的參數。
- 教程
【免费下载链接】jstips
This is about useful JS tips!
相关推荐
Ethereum 智能合約 ABI 深入解析:Function Selector 與參數編碼實戰指南
Ethereum 智能合約 ABI 深入解析:Function Selector 與參數編碼實戰指南 在 Ethereum 生態系統中,ABI(Applicat
文档网络安全教程learnyounode「BABY STEPS」實戰指南:用 process.argv 讀取命令列參數並計算總和
learnyounode「BABY STEPS」實戰指南:用 process.argv 讀取命令列參數並計算總和 本指南以 learnyounode 的第一個練
教程CLIPython MCP SDK 伺服器自動完成(Completions)完整實戰指南:提示詞引數與資源範本參數的智慧建議
Python MCP SDK 伺服器自動完成(Completions)完整實戰指南:提示詞引數與資源範本參數的智慧建議 導讀 自動完成(Completions)
人工智能MCP 服务MCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考