保持原始变量和同时解构的干净方法

IT技术 javascript ecmascript-6 babeljs ecmascript-next ecmascript-2016
2021-03-16 12:01:49

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

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

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

我目前正在这样做:

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

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

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

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

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

3个回答

一种可能的方式:

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

但是,您仍然必须重复该名称。const令牌不是很合适。)

这更简洁一些,并且可能是我能用当前标准获得的最接近的。谢谢。
2021-05-17 12:01:49

想法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。

在@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());

你需要额外的,,因为它是尾随



除此之外(使其成为自己的答案,而不仅仅是值得评论的),这种复制也可以解构语法中完成(这就是我遇到这个问题的原因)。
b里面result有一个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());

现在你有result, a, b, & c,即使 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());

但是,数组是 objects,因此您可以仅使用索引作为键并复制索引引用:

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台词。

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

  • 您的解构是用模板文字编写的,输入对象显示为插值,
    例如[,,] = input变成`[,,] = ${input}`
  • 等号实际上是可选的
  • 你永远不会重命名销毁中的输出
    例如[a, b, ...c] = input变成`[, , ...] ${input}`
  • 该模板的输出μ(您可以随意命名)是您按顺序指定的元素的数组,
    例如const {a:A, b:B} = input;成为const [A,B] = μ`{a, b} ${input}`;
    NB 如何在输出中重命名。即使输入是一个对象,输出也始终是一个平面数组。
  • 您可以使用许多跳过一个迭代的元素,而不是重复逗号
    例如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 仍然没有命名捕获组我努力编写 [regex] 解析器以使所有未使用的组都无法捕获,所以如果您热衷,让它与 FF 兼容并不难

那么代码在哪里呢?
你很热心
祝你好运。

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];
});

如果您的浏览器无法运行它但您很好奇,则输出(错误正在测试本机与此东西的错误输出)

在此处输入图片说明