【问题标题】:Clean way to keep original variable and destructure at the same time同时保持原始变量和解构的干净方式
【发布时间】:2018-06-10 21:13:00
【问题描述】:

有没有更简洁的方法来做到这一点(任何至少是 ES 草案并具有 babel 插件的东西,即 ES6、ES7 等):

const { a, b } = result = doSomething();

我想将整体结果保留为一个单一的对象,但同时也要对其进行解构。它在技术上有效,但 result 是隐式声明的(带有隐式 var),而我真的很希望它也是一个 const。

我目前正在这样做:

const result = doSomething();
const { a, b } = result;

这同样有效,但有点冗长,因为我需要重复这个模式几十次。

理想情况下,我想要一些类似的东西:

const { a, b } = const result = doSomething();

但这显然是一个无效的语法。

【问题讨论】:

  • 第一个 sn-p 没有隐式声明 var。它是未声明的变量,在松散模式下会导致全局变量在严格模式下会失败。
  • 使用两个语句是干净且正确的方法。它也不过分冗长。我倒是担心你为什么说要重复几十遍?
  • 在这种特殊情况下,是因为有一系列基本的reducer函数。每个功能都是独立的,但遵循类似的模式。每个函数都应该更新原始输入,然后将其传回,以便链中的下一个减速器可以运行它。它们的结尾是这样的:return Object.assign(result, { a: a + 5 }) 我吐出了所有内容以及更新。

标签: javascript ecmascript-6 babeljs ecmascript-next ecmascript-2016


【解决方案1】:

一种可能的方式:

const result = doSomething(), 
    { a, b } = result;

不过,您仍然必须重复名称。 const 令牌不是很方便。 )

【讨论】:

  • 这有点干净,并且可能是我目前能够获得的最接近的标准。谢谢。
【解决方案2】:

想法 1

创建这个辅助函数:

function use(input, callback) {
    callback(input, input);
}

并像这样使用它:

use(doSomething(), (result, {a, b}) => {
    // Do something with result as a whole, or a and b as destructured properties.
});

例如:

use ({a: "Hello", b: "World", c: "!"}, (result, {a, b}) => {
  console.log(result);
  console.log(a);
  console.log(b);
});

// generates
// {a: "Hello", b: "World", c: "!"}
// Hello
// World

它们不是 const,但它们的作用域是好是坏!


想法 2

结合arrayobject解构。创建这个辅助函数:

const dup = input => [input, input];

然后像这样解构:

const [result, {a, b}] = dup(doSomething());

现在,您的resultab 都是consts。

【讨论】:

    【解决方案3】:

    在@raina77ow 的回答中,他们感叹 consttoken isn't quite right-handy;但是,如果您使用冒号(并重复关键字)而不是逗号,那就是您的答案。
    但是你已经在你的问题中提到了const result = doSomething(); const {a, b} = result;,我看不出它有多糟糕,而且它有效。

    但从中可以看出,let something = x; let another = y;let [something, another] = [x, y]; 相同。
    因此,一个真正优雅的解决方案实际上很简单

    const [result, {a, b}] = [,,].fill(doSomething());
    

    你需要额外的,,因为它是trailing



    除了这个 (使其成为自己的答案,而不仅仅是值得评论),这种复制也可以在解构语法内部完成(这就是为什么我遇到了这个问题)。
    bresult本身有一个c;你想解构它,但也要保留对b的引用

    //The above might lead you to believe you need to do this:
    const result = doSomething(); const {a, b} = result; const {c} = b;
    //or this
    const [result, {a, b}, {b:{c}}] = [,,,].fill(doSomething());
    

    但实际上你可以这样做

    const [result, {a, b, b:{c}}] = [,,].fill(doSomething());
    

    现在你有resultabc,即使结果是a和b,而c是b。
    如果您实际上不需要result,这将特别方便,看起来fill() 只需要根对象:
    const {a, b, b:{c}} = doSomething();

    这似乎不适用于数组,因为语法 中的 位置 是键

    const [result, [a, b, /*oops, I'm referencing index 2 now*/]] = [,,].fill(doArrayThing());
    

    然而,数组是对象,所以你可以只使用索引作为键并复制索引引用:

    const [result, {0:a, 1:b, 1:{c}}] = [,,].fill(doArrayThing());
    

    这也意味着您可以解构类似数组,而通常它会抱怨对象不可迭代,并且您可以通过使用更高的键而不是必须编写空逗号的数组语法来跳过索引。
    也许最好的是,{0:a, 1:b, ...c} 仍然像 [a, b, ...c] 那样工作,因为数组的 Object.keys() 会提取其索引(但生成的 c 不会有 .length)。



    但我对此并不满意,我真的很喜欢 @Arash 的想法 #2,但它还不够通用,无法帮助对上面示例中的 b 进行重复数据删除,而且它欺骗const 行。

    所以...我自己写了:| (goodluck 的 ctrl+F)
    您使用相同的正常语法,但有一些例外:

    • 您的解构是用模板文字编写的,输入对象显示为插值
      例如 [,,] = input 变为 `[,,] = ${input}`
    • equals 实际上是可选的
    • 您永远不会在销毁中重命名输出
      例如 [a, b, ...c] = input 变为 `[, , ...] ${input}`
    • 此模板的输出针对μ(您可以随便命名)是您按顺序指定的元素的数组
      例如 const {a:A, b:B} = input; 变为 const [A,B] = μ`{a, b} ${input}`;
      注意重命名如何出现在输出中。即使输入是一个对象,输出也始终是一个平面数组。
    • 您可以使用数字而不是重复的逗号跳过迭代器中的元素
      例如const [a, , , d] = input;const [a,d] = μ`[ , 2, ]`;
    • 最后,这是重点;当进入一个对象时,在它前面加上一个冒号将它保存到输出中

    例如

    const [result, {a, b, b:{c}}] = [,,].fill(doSomething());
    

    变成

    const [result, a, b] = μ`:{a, b::{c}} ${doSomething()}`;
    

    所以,除了上述之外,优点:

    • 我根本没有运行 eval,而是实际解析并将逻辑应用到您的输入,
      因此,我可以在 运行时为您提供方式更好的错误消息>.

    例如 ES6 甚至不关心这个:

    _ = {a:7, get b() {throw 'hi'}};
    console.warn('ES6');
    out(() => {
        const {a, b} = _;
        return [a, b];
    });
    console.warn('hashbrown');
    out(() => {
        const {a,b} = μ`{a,...} ${_}`;
        return [a, b];
    });
    

    Eg2 这里 ES6 说 _ 是罪魁祸首。我不仅正确地说这是 1 的错,而且我告诉你它发生在哪里:

    _ = [1];
    console.warn('ES6');
    out(() => {
        const [[a]] = _;
        return [a];
    });
    console.warn('hashbrown');
    out(() => {
        const [a] = μ`[[]] ${_}`;
        return [a];
    });
    

    • 如果您需要跳过大型数组或保留大量内部变量,则非常方便

    例如

    const [[a,,,,,,,,,j], [[aa, ab], [ba]]] = [,,].fill(_);
    const [a, aa, ab, ba, j] = μ`[:[ , ], [ ], 7, ] ${_}`;
    

    好的,有什么问题吗?缺点:

    • 即使是最后一位专业人士,缺少所有名称的破坏语法也难以阅读。我们真的需要这种语法在语言中,所以名称在它里面而不是const [出现在它外面。
    • 编译器不知道如何处理这个问题,语法错误是运行时的(而你会在前面用 ES6 原生地告诉你),IDE 可能无法分辨吐出来的内容(而且我拒绝正确编写为它完成了模板化.d.ts)如果你正在使用某种类型检查
    • 并在您的语法出现稍差的编译时间 错误之前提到过。我只是告诉你,有些事情不对,不是什么。
      但是,公平地说,我还是告诉你哪里出错了,如果你有多个 rest 运算符,我认为 ES6 没有多大帮助

    例如

    _ = [1, 2, 3, 4];
    console.warn('ES6');
    out(() => {
        eval(`const [a, ...betwixt, b] = _`);
        return [a, betwixt, b];
    });
    console.warn('hashbrown');
    out(() => {
        const [a, betwixt, b] = μ`[, ..., ] ${_}`;
        return [a, betwixt, b];
    });
    

    • 只有在处理数组或重命名所有输出时才真正值得这样做,否则您需要指定两次名称。如果 :{ :[[2 被采用到语言中,这将与第 1 点一起修复,您无需在 const [ 外部重新指定
    • 老实说,我写它的方式可能只在 Chrome 中运行,因为 firefox still doesn't have named capture groups。我努力编写 [regex] 解析器以使所有未使用的组不被捕获,所以如果你热衷于,让它与 FF 兼容并不难

    So where's the code?
    You're keen.
    Goodluck.

    window.μ = (() => {
        //build regexes without worrying about
        // - double-backslashing
        // - adding whitespace for readability
        // - adding in comments
        let clean = (piece) => (piece
            .replace(/(?<=^|\n)(?<line>(?:[^\/\\]|\/[^*\/]|\\.)*)\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$<line>')
            .replace(/(?<=^|\n)(?<line>(?:[^\/\\]|\/[^\/]|\\.)*)\/\/[^\n]*/g, '$<line>')
            .replace(/\n\s*/g, '')
        );
        let regex = ({raw}, ...interpolations) => (
            new RegExp(interpolations.reduce(
                (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
                clean(raw[0])
            ))
        );
    
        let start = {
            parse : regex`^\s*(?:
                //the end of the string
                //I permit the equal sign or just declaring the input after the destructure definition without one
                (?<done>=?\s*)
                |
                //save self to output?
                (?<read>(?<save>:\s*|))
                //opening either object or array
                (?<next>(?<open>[{[]).*)
            )$`
        };
        let object = {
            parse : regex`^\s*
                (?<read>
                    //closing the object
                    (?<close>\})|
    
                    //starting from open or comma you can...
                    (?:[,{]\s*)(?:
                        //have a rest operator
                        (?<rest>\.\.\.)
                        |
                        //have a property key
                        (?<key>
                            //a non-negative integer
                            \b\d+\b
                            |
                            //any unencapsulated string of the following
                            \b[A-Za-z$_][\w$]*\b
                            |
                            //a quoted string
                            (?<quoted>"|')(?:
                                //that contains any non-escape, non-quote character
                                (?!\k<quoted>|\\).
                                |
                                //or any escape sequence
                                (?:\\.)
                            //finished by the quote
                            )*\k<quoted>
                        )
                        //after a property key, we can go inside
                        \s*(?<inside>:|)
                    )
                )
                (?<next>(?:
                    //after closing we expect either
                    // - the parent's comma/close,
                    // - or the end of the string
                    (?<=\})\s*(?:[,}\]=]|$)
                    |
                    //after the rest operator we expect the close
                    (?<=\.)\s*\}
                    |
                    //after diving into a key we expect that object to open
                    (?<=:)\s*[{[:]
                    |
                    //otherwise we saw only a key, we now expect a comma or close
                    (?<=[^:\.}])\s*[,}]
                ).*)
            $`,
            //for object, pull all keys we havent used
            rest : (obj, keys) => (
                Object.keys(obj)
                    .filter((key) => (!keys[key]))
                    .reduce((output, key) => {
                        output[key] = obj[key];
                        return output;
                    }, {})
            )
        };
        let array = {
            parse : regex`^\s*
                (?<read>
                    //closing the array
                    (?<close>\])
                    |
                    //starting from open or comma you can...
                    (?:[,[]\s*)(?:
                        //have a rest operator
                        (?<rest>\.\.\.)
                        |
                        //skip some items using a positive integer
                        (?<skip>\b[1-9]\d*\b)
                        |
                        //or just consume an item
                        (?=[^.\d])
                    )
                )
                (?<next>(?:
                    //after closing we expect either
                    // - the parent's comma/close,
                    // - or the end of the string
                    (?<=\])\s*(?:[,}\]=]|$)
                    |
                    //after the rest operator we expect the close
                    (?<=\.)\s*\]
                    |
                    //after a skip we expect a comma
                    (?<=\d)\s*,
                    |
                    //going into an object
                    (?<=[,[])\s*(?<inside>[:{[])
                    |
                    //if we just opened we expect to consume or consume one and close
                    (?<=\[)\s*[,\]]
                    |
                    //otherwise we're just consuming an item, we expect a comma or close
                    (?<=[,[])\s*[,\]]
                ).*)
            $`,
            //for 'array', juice the iterator
            rest : (obj, keys) => (Array.from(keys))
        };
    
        let destructure = ({next, input, used}) => {
    //for exception handling
    let phrase = '';
    let debugging = () => {
        let tmp = type;
        switch (tmp) {
        case object: tmp = 'object'; break;
        case array : tmp = 'array'; break;
        case start : tmp = 'start'; break;
        }
        console.warn(
            `${tmp}\t%c${phrase}%c\u2771%c${next}`,
            'font-family:"Lucida Console";',
            'font-family:"Lucida Console";background:yellow;color:black;',
            'font-family:"Lucida Console";',
    //input, used
        );
    };
    debugging = null;
            //this algorithm used to be recursive and beautiful, I swear,
            //but I unwrapped it into the following monsterous (but efficient) loop.
            //
            //Lots of array destructuring and it was really easy to follow the different parse paths,
            //now it's using much more efficient `[].pop()`ing.
            //
            //One thing that did get much nicer with this change was the error handling.
            //having the catch() rethrow and add snippets to the string as it bubbled back out was...gross, really
            let read, quoted, key, save, open, inside, close, done, rest, type, keys, parents, stack, obj, skip;
    try {
            let output = [];
            while (
                //this is the input object and any in the stack prior
                [obj, ...parents] = input,
                //this is the map of used keys used for the rest operator
                [keys, ...stack] = used,
                //assess the type from how we are storing the used 'keys'
                type = (!keys) ? start : (typeof keys.next == 'function') ? array : object,
    phrase += (read || ''),
    read = '',
    debugging && debugging(),
                //parse the phrase, deliberately dont check if it doesnt match; this way it will throw
                {read, quoted, next, key, save, open, inside, close, done, rest, skip} = next.match(type.parse).groups,
                done == null
            ) {
                if (open) {
                    //THIS IS THE EXTRA FUNCTIONALITY
                    if (save)
                        output.push(obj);
                    switch (open) {
                    case '{':
                        used = [{}, ...stack];
                        break;
                    case '[':
                        used = [obj[Symbol.iterator](), ...stack];
                        input = [null, ...parents];
                        break;
                    default:
                        throw open;
                    }
                    continue;
                }
    
                if (close) {
                    used = stack;
                    input = parents;
                    continue;
                }
                //THIS IS THE EXTRA FUNCTIONALITY
                if (skip) {
                    for (skip = parseInt(skip); skip-- > 0; keys.next());
                    continue;
                }
    
                //rest operator
                if (rest) {
                    obj = type.rest(obj, keys);
                    //anticipate an immediate close
                    input = [null, ...parents];
                }
                //fetch the named item
                else if (key) {
                    if (quoted) {
                        key = JSON.parse(key);
                    }
                    keys[key] = true;
                    obj = obj[key];
                }
                //fetch the next item
                else
                    obj = keys.next().value;
    
                //dive into the named object or append it to the output
                if (inside) {
                    input = [obj, ...input];
                    used = [null, ...used];
                }
                else
                    output.push(obj);
            }
            return output;
    }
    catch (e) {
        console.error('%c\u26A0 %cError destructuring', 'color:yellow;', '', ...input);
        console.error(
            `%c\u26A0 %c${phrase}%c${read || '\u2771'}%c${next || ''}`,
            'color:yellow;',
            'font-family:"Lucida Console";',
            'font-family:"Lucida Console";background:red;color:white;',
            'font-family:"Lucida Console";'
        );
        throw e;
    }
    return null;
        };
        //just to rearrange the inputs from template literal tags to what destructure() expects.
        //I used to have the function exposed directly but once I started supporting
        //iterators and spread I had multiple stacks to maintain and it got messy.
        //Now that it's wrapped it runs iteratively instead of recursively.
        return ({raw:[next]}, ...input) => (destructure({next, input, used:[]}));
    })();
    

    演示的测试:

    let out = (func) => {
        try {
            console.log(...func().map((arg) => (JSON.stringify(arg))));
        }
        catch (e) {
            console.error(e);
        }
    };
    let _;
    
    //THE FOLLOWING WORK (AND ARE MEANT TO)
    _ = {a:{aa:7}, b:8};
    out(() => {
        const [input,{a,a:{aa},b}] = [,,].fill(_);
        return [input, a, b, aa];
    });
    out(() => {
        const [input,a,aa,b] = μ`:{a::{aa},b}=${_}`;
        return [input, a, b, aa];
    });
    
    _ = [[65, -4], 100, [3, 5]];
    out(() => {
        //const [[aa, ab], , c] = input; const [ca, cb] = c;
        const {0:{0:aa, 1:ab}, 2:c, 2:{0:ca, 1:cb}} = _;
        return [aa, ab, c, ca, cb];
    });
    out(() => {
        const [aa,ab,c,ca,cb] = μ`{0:{0,1}, 2::{0,1}}=${_}`;
        return [aa, ab, c, ca, cb];
    });
    
    _ = {a:{aa:7, ab:[7.5, 7.6, 7.7], 'a c"\'':7.8}, b:8};
    out(() => {
        const [input,{a,a:{aa,ab,ab:{0:aba, ...abb},"a c\"'":ac},b,def='hi'}] = [,,].fill(_);
        return [input, a, aa, ab, aba, abb, ac, b, def];
    });
    out(() => {
        const [input,a,aa,ab,aba,abb,ac,b,def='hi'] = μ`:{a::{aa,ab::{0, ...},"a c\"'"},b}=${_}`;
        return [input, a, aa, ab, aba, abb, ac, b, def];
    });
    
    _ = [{aa:7, ab:[7.5, {abba:7.6}, 7.7], 'a c"\'':7.8}, 8];
    out(() => {
        const [input,[{aa,ab,ab:[aba,{abba},...abc],"a c\"'":ac}],[a,b,def='hi']] = [,,,].fill(_);
        return [input, a, aa, ab, aba, abba, abc, ac, b, def];
    });
    out(() => {
        const [input,a,aa,ab,aba,abba,abc,ac,b,def='hi'] = μ`:[:{aa,ab::[,{abba},...],"a c\"'"},]=${_}`;
        return [input, a, aa, ab, aba, abba, abc, ac, b, def];
    });
    
    _ = [[-1,-2],[-3,-4],4,5,6,7,8,9,0,10];
    out(() => {
        const [[a,,,,,,,,,j], [[aa, ab], [ba]]] = [,,].fill(_);
        return [a, aa, ab, ba, j];
    });
    out(() => {
        const [a, aa, ab, ba, j] = μ`[:[ , ], [ ], 7, ] ${_}`;
        return [a, aa, ab, ba, j];
    });
    
    
    //THE FOLLOWING FAIL (AND ARE MEANT TO)
    
    _ = [1];
    console.warn('ES6');
    out(() => {
        const [[a]] = _;
        return [a];
    });
    console.warn('hashbrown');
    out(() => {
        const [a] = μ`[[]] ${_}`;
        return [a];
    });
    
    
    _ = [1, 2, 3, 4];
    console.warn('ES6');
    out(() => {
        eval(`const [a, ...betwixt, b] = _`);
        return [a, betwixt, b];
    });
    console.warn('hashbrown');
    out(() => {
        const [a, betwixt, b] = μ`[, ..., ] ${_}`;
        return [a, betwixt, b];
    });
    
    
    _ = {a:7, get b() {throw 'hi'}};
    console.warn('ES6');
    out(() => {
        const {a, b} = _;
        return [a, b];
    });
    console.warn('hashbrown');
    out(() => {
        const {a,b} = μ`{a,...} ${_}`;
        return [a, b];
    });
    

    如果你的浏览器无法运行但你很好奇的输出(错误是测试本机与这个东西的错误输出)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多