【问题标题】:How to simulate let expressions in JavaScript?如何在 JavaScript 中模拟 let 表达式?
【发布时间】:2018-12-26 16:55:26
【问题描述】:

考虑take的以下实现:

const take = (n, [x, ...xs]) =>
    n === 0 || x === undefined ?
    [] : [x, ...take(n - 1, xs)];

console.log(take(7, [1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
console.log(take(3, [1, 2, 3, 4, 5])); // [1, 2, 3]
console.log(take(1, [undefined, 1]));  // []

如您所见,它不适用于带有undefined 的数组,因为x === undefined 不是测试数组是否为空的最佳方法。下面的代码解决了这个问题:

const take = (n, xs) =>
    n === 0 || xs.length === 0 ?
    [] : [xs[0], ...take(n - 1, xs.slice(1))];

console.log(take(7, [1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
console.log(take(3, [1, 2, 3, 4, 5])); // [1, 2, 3]
console.log(take(1, [undefined, 1]));  // [undefined]

但是,编写 xs[0]xs.slice(1) 并不优雅。此外,如果您需要多次使用它们也是有问题的。您要么必须复制代码并做不必要的额外工作,要么必须创建块作用域、定义常量并使用 return 关键字。

最好的解决方案是使用let expression。不幸的是,JavaScript 没有它们。那么,如何在 JavaScript 中模拟 let 表达式呢?

【问题讨论】:

  • undefined 是一个真实值,但不是稀疏数组的一部分。一个真正的稀疏数组看起来像[, 1]
  • @NinaScholz 啊,我明白了。我的印象是[, 1][undefined, 1] 是等价的,但显然并非如此。谢谢你告诉我。
  • 使用slice 的数组take 函数肯定很奇怪,是的:-)
  • 为什么不简单地const take = (n, xs) => xs.slice(0, n)
  • @AramilRey 因为那是我为这个问题编造的一个例子。

标签: javascript ecmascript-6 functional-programming let


【解决方案1】:

不要使用 IIFE,而是使用具有正确名称的普通函数来使事情更明确:

const _let = f =>
  f();

const collateBy = f => xs =>
  xs.reduce((m, x) =>
    _let((r = f(x), ys = m.get(r) || []) =>
      m.set(r, (ys.push(x), ys))), new Map());

const includes = t => s =>
  s.includes(t);

xs = ["Dev", "Jeff", "Kalib", "Amy", "Gemma"];

const collation = collateBy(includes("e")) (xs);

console.log(collation.get(true));
console.log(collation.get(false));

【讨论】:

    【解决方案2】:

    在 Lisp 中,let expression 只是左左 lambda 的语法糖(即immediately-invoked function expression)。例如,考虑:

    (let ([x 1]
          [y 2])
      (+ x y))
    
    ; This is syntactic sugar for:
    
    ((lambda (x y)
        (+ x y))
      1 2)
    

    在 ES6 中,我们可以使用 arrow functionsdefault parameters 创建一个类似于 let 表达式的 IIFE,如下所示:

    const z = ((x = 1, y = 2) => x + y)();
    
    console.log(z);

    使用这个hack,我们可以定义take如下:

    const take = (n, xxs) =>
        n === 0 || xxs.length === 0 ?
        [] : (([x, ...xs] = xxs) => [x, ...take(n - 1, xs)])();
    
    console.log(take(7, [1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
    console.log(take(3, [1, 2, 3, 4, 5])); // [1, 2, 3]
    console.log(take(1, [undefined, 1]));  // [undefined]

    希望对您有所帮助。

    【讨论】:

    • 使用默认参数值绝对是我认为的 hack :-)
    • 也可以将值作为参数传递,而不是使用默认参数,不是吗?
    • @FelixKling 确实如此。但是,当您使用默认参数时,它看起来更像是一个 let 表达式。
    • hack 只是语言使用不当的委婉说法。相反,您应该通过引入 memoization 来减少 JS 中对 let 表达式的需求。
    • @Bergi 这很hacky,但我喜欢它,因为它比(() => {const x = 1; const y = 2; return x + y;})()干净得多
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 2014-01-01
    • 2019-05-09
    • 1970-01-01
    相关资源
    最近更新 更多