☰
ES6 參數預設值實戰:在 JavaScript 函式中打造「偽強制參數」(Pseudomandatory Parameters)
2026/10/9 10:11:39 网站建设 项目流程
  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

本指南以 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 沒有被定義

拆解這個模式,可以歸納為三個要素:

  1. _err是一個立即拋出Error的工具函式:它接收一個訊息字串,並以throw new Error(message)中斷執行流程。它不修改任何狀態,是純粹的「拋錯閘門」。
  2. a、b的預設值被寫成_err('...')的呼叫結果:當呼叫方沒有為該參數提供值時,預設值表達式會被求值,_err被執行,異常立即被丟出。
  3. 錯誤訊息指名道姓:'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!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

相关推荐

上一篇:Certbot 官方变更日志深度解读:从 0.1.1 到 5.7.0 的版本演进、核心功能与技术脉络
下一篇:Swift 实现最小编辑距离:基于 Levenshtein 距离的动态规划字符串相似度算法

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询