【问题标题】:ES6 destructuring preprocessingES6 解构预处理
【发布时间】:2017-11-12 09:27:30
【问题描述】:

问题

函数参数解构是 ES6 中一个了不起的特性。 假设我们想要一个名为ffunction 接受一个Object,它有一个a

function f({ a }) {
    return a;
}

对于未向函数提供参数的情况,我们有默认值以避免Type Error

function f({ a } = {}) {
    return a;
}

这将有助于以下情况

const a = f(); // undefined

虽然会失败

const a = f(null); // TypeError: Cannot match against 'undefined' or 'null'.

你可以看到 Babel 如何将函数转译为 ES5 here

可能的解决方案

可以通过参数验证和预处理来避免。在Python 中我可以使用装饰器,但是在 JS 中我们没有将它们标准化,所以使用它们还不是一个好主意。 不过,假设我们有一个装饰器checkInputObject,它使用给定的项目列表(或嵌套解构情况下的树)进行必要的检查并提供默认值。 我们可以在没有@ 符号的情况下按以下方式使用它

const f = checkInputObject(['a'])(({ a }) => a);

使用@ 表示法可能看起来像这样

@checkInputObject(['a'])
function f({ a }) {
    return a;
}

我也可以在函数本身中进行所有需要的操作,然后才使用解构,但在这种情况下,我失去了函数参数解构的所有优点(我根本不会使用它)

function f(param) {
    if (!param) {
        return;
    }
    const { a } = param;
    return a;
}

我什至可以实现一些常见的函数,比如checkInputObject,以便在函数内部使用它

const fChecker = checkInputObject(['a']);
function f(param) {
    const { a } = fChecker(param);
    return a;
}

不过,使用附加代码对我来说并不优雅。我希望不存在的实体被分解为undefined。 假设我们有

function f({a: [, c]) {
    return c;
}

f() 的情况下获得undefined 会很好。

问题

您知道任何优雅且方便的方法可以使 [嵌套] 解构抵抗不存在的嵌套键吗?

我担心的是:似乎这个功能在公共方法中使用是不安全的,我需要在使用它之前自己进行验证。这就是为什么在私有方法中使用之前它似乎没用。

【问题讨论】:

  • 使用try ... catch?
  • 当输入与预期格式不匹配时,您究竟希望checkInputObject 做什么,从而引发异常?这正是您的代码已经完成的工作。
  • 如果你想编写一个在使用无效参数 (if (!param) return;) 调用时什么都不做的函数,你应该明确地写出来。反正很少需要这样的功能。
  • @Bergi 在我写过的问题的“可能的解决方案”部分,我希望undefined 用于未解构的值,就像未提供的普通参数一样。例如,当您调用 (a => a)() 时,它将返回 undefined 并且不会失败,因此我希望对未提供的项目进行类似的解构。
  • @Charlie 但是如果你打电话给(a => a.prop)(),它会失败。是的,你总是可以添加语法来接受更多的值而不会失败,但你为什么要这样做呢?我看不出写一个“总是默默地忽略失败”功能的意义。

标签: javascript ecmascript-6 destructuring


【解决方案1】:

您可以使用 try...catch 并在通用装饰器函数中使用它:

function safe(f) {
   return function (...args) {
       try {
           return f(...args);
       } catch (e) {}; // return undefined
   };
}

function f({ a } = {}) {
    return a;
}

f = safe(f);
console.log(f(null)); // -> undefined
console.log(f( { a:3 } )); // -> 3

【讨论】:

  • 换句话说,您建议使用我在问题中提到的装饰器/包装器,对吧?
  • 这是解决此类问题的最佳通用解决方案,但应谨慎使用。 @Charlie 此解决方案适用于所有类型的嵌套/非嵌套解构,与您在问题中提出的不同。
  • 它确实是一个装饰器,但它不像你的那样接受最后的参数,而是函数。这是一个通用的装饰器。确保你的函数能很好地处理错误,否则外部的try ... catch 会起作用,并掩盖其他可能发生的错误。
【解决方案2】:

在 ES6 中处理这个问题的正确方法是尊重语言和形状 API 处理参数的方式以更好地适应它,而不是相反。

fn(undefined) 中解构参数背后的语义是参数将被替换为默认值,在 fn(null) 的情况下意味着 nully 参数不会被替换为默认值。

如果数据来自外部并且应该被调节/预处理/验证,则应该明确处理,而不是通过解构:

function fn({ foo, bar}) { ... }

fn(processData(data));

function fn(data) {
  const { foo, bar } = processData(data);
  ...
}

fn(data);

应该调用processData 的位置完全由开发人员自行决定。

由于提议的 ECMAScript 装饰器只是具有特定签名的辅助函数,因此相同的辅助函数可以用于 @ 语法和通常的函数调用,具体取决于项目。

function processDataDecorator(target, key) {
  const origFn = target[key];
  return target[key] = (...args) => origFn.apply(target, processData(...args));
}

class Foo {
  constructor() {
    this.method = processDataDecorator(this, 'method');
  }

  method(data) {...}
}

class Bar {
  @processDataDecorator
  method(data) {...}
}

自 ES5 以来该语言为默认属性值提供的方式是 Object.assigndataundefinednull 还是其他原语都没有关系,结果始终是一个对象:

function processData(data) {
  return Object.assign({ foo: ..., bar: ...}, data);
}

【讨论】:

  • 感谢您的详细解答!对我来说最有意义的部分是The proper way to handle this in ES6 is to respect the way params are being processed by the language and shape API to fit it better, not in the opposite way.。示例很有用,但我已经在我的问题中一般性地解释了这些方法。
  • 不客气。是的,如果您可以控制传递给函数的数据并且不需要清理,那么就像遵循解构约定一样简单。
【解决方案3】:

我相信我可以通过向您展示一种不同的方式来帮助您解决这个问题。

您面临的问题与解构无关,因为您将在任何向其发送预期的不同类型的函数中遇到它。

解决此类问题,例如。解构,您可以使用类型检查解决方案,例如Flow,它静态检查类型,因此只要您向函数发送错误的内容,它就会在构建时失败。

javascript的本质是它是一种动态类型语言,因此您可能希望该函数能够处理发送给它的任何参数,在这种情况下,您可以使用动态类型检查,解决方案很多。

函数参数更像是你如何使用函数的“协议”,而不是访问它的参数(毕竟你可以使用arguments来访问它们),因此通过发送不同的东西你违反了这个协议,这无论如何都不是一个好习惯,最好有 1 个函数来做 1 件事,并且接受尽可能少的复杂参数。

【讨论】:

  • 你的意思是 JS 不需要为这个问题提供一些优雅的解决方案,对吧?我已经考虑过这一点,但我认为我可能是错的,并且会出现替代解决方案。
  • 类型检查器是解决方案,但目前不是 JavaScript 原生的。 javascript 是动态的,因此它应该处理不同的类型,但是为了能够使用 if/switch 语句,我建议使用纯函数来做一些小事情,例如直接接受 a 中的参数,那么这个问题就会消失
【解决方案4】:

你不是刚刚回答了你自己提出的问题吗?

我认为默认参数是更优雅的方式。

function f({a: [,c]}={a:[undefined,undefined]}) {
    return c;
}

f();

【讨论】:

  • 尝试调用f(null),它不会设置默认参数值
  • a 永远不会是 [undefined, undefined],除非没有任何东西发送到函数。
  • @digitake 对,为了使用默认参数的深度分配,您应该使用({ a: [, c] = [3, 4] } = { a: [1, 2] })。虽然它会在null 提供时失败
猜你喜欢
  • 2016-11-20
  • 1970-01-01
  • 2016-07-19
  • 2019-06-10
  • 1970-01-01
  • 2018-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多