参数解构

解构还有一个特别有用的场景,即在传递函数参数时。当 JS 的函数接收大量可选参数时,一个常用模式是创建一个 options 对象,其中包含了附加的参数,就像这样:

// properties on options represent additional parameters
function setCookie(name, value, options) {

    options = options || {};

    let secure = options.secure,
        path = options.path,
        domain = options.domain,
        expires = options.expires;

    // code to set the cookie
}

// third argument maps to options
setCookie("type", "js", {
    secure: true,
    expires: 60000
});

很多 JS 的库都包含了类似于此例的 setCookie() 函数。在此函数内,namevalue 参数是必需的,而 securepathdomainexpires 则不是。并且因为此处对于其余数据并没有顺序要求,将它们作为 options 对象的具名属性会更有效率,而无须列出一堆额外的具名参数。这种方法很有用,但无法仅通过查看函数定义就判断出函数所期望的输入,你必须阅读函数体的代码。

参数解构提供了更清楚地标明函数期望输入的替代方案。它使用对象或数组解构的模式替代了具名参数。要看到其实际效果,请查看下例中重写版本的 setCookie() 函数:

function setCookie(name, value, { secure, path, domain, expires }) {

    // code to set the cookie
}

setCookie("type", "js", {
    secure: true,
    expires: 60000
});

此函数的行为类似上例,但此时第三个参数使用了解构来抽取必要的数据。现在对于 setCookie() 函数的使用者来说,解构参数之外的参数明显是必需的;而可选项目存在于额外的参数组中,这同样是非常明确的;同时,若使用了第三个参数,其中应当包含什么值当然也是极其明确的。解构参数在没有传递值的情况下类似于常规参数,它们会被设为 undefined

参数解构拥有此前你在本章已经学过的其他解构方式的所有能力。你可以在其中使用默认参数、混合解构,或使用与属性不同的变量名。

解构的参数是必需的

参数解构有一个怪异点:默认情况下调用函数时未给参数解构传值会抛出错误。例如,用以下方式调用上例中的 setCookie() 函数就会出错:

// Error!
setCookie("type", "js");

调用时第三个参数缺失了,因此它不出预料地等于 undefined。这导致了一个错误, 因为参数解构实际上只是解构声明的简写。当 setCookie() 函数被调用时,JS 引擎实际上是这么做的:

function setCookie(name, value, options) {

    let { secure, path, domain, expires } = options;

    // code to set the cookie
}

既然在赋值右侧的值为 nullundefined 时,解构会抛出错误,那么未向 setCookie() 函数传递第三个参数就同样会出错。

若你让解构的参数作为必选参数,那么上述行为并不会令人困扰。但若你要求它是可选的, 可以给解构的参数提供默认值来处理这种行为,就像这样:

function setCookie(name, value, { secure, path, domain, expires } = {}) {

    // ...
}

此例为第三个参数提供了一个空对象作为其默认值。给解构的参数提供默认值,也就意味着若未向 setCookie() 函数传递第三个参数,则 securepathdomainexpires 的值全都会是 undefined,此时不会有错误被抛出。

参数解构的默认值

你可以为参数解构提供可解构的默认值,就像在解构赋值时所做的那样,只需在其中每个参数后面添加等号并指定默认值即可。例如:

function setCookie(name, value,
    {
        secure = false,
        path = "/",
        domain = "example.com",
        expires = new Date(Date.now() + 360000000)
    } = {}
) {

    // ...
}

此代码中参数解构给每个属性都提供了默认值,所以你可以避免检查指定属性是否已被传入 (以便在未传入时使用正确的值)。而整个解构的参数同样有一个默认值,即一个空对象, 令该参数成为可选参数。这么做使得函数声明看起来比平时要复杂一些,但却是为了确保每个参数都有可用的值而付出的微小代价。