清洁方式同时保持原始变量和破坏

sam*_*ime 8 javascript ecmascript-6 babeljs ecmascript-7 ecmascript-next

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

const { a, b } = result = doSomething();
Run Code Online (Sandbox Code Playgroud)

我希望将整体结果保持为一个单一对象,同时也将其结构化.它在技术上有效,但result隐式声明(带隐式var),而我真的希望它也是一个const.

我现在正在这样做:

const result = doSomething();
const { a, b } = result;
Run Code Online (Sandbox Code Playgroud)

这再次起作用,但它略显冗长,因为我需要重复这种模式数十次.

理想情况下,我想要的是:

const { a, b } = const result = doSomething();
Run Code Online (Sandbox Code Playgroud)

但这显然是无效的语法.

rai*_*7ow 10

一种可能的方式:

const result = doSomething(), 
    { a, b } = result;
Run Code Online (Sandbox Code Playgroud)

但是,您仍然需要复制名称.const令牌不是很方便.)


Ara*_*edi 6

想法1

创建这个辅助函数:

function use(input, callback) {
    callback(input, input);
}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

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

例如:

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
Run Code Online (Sandbox Code Playgroud)

他们不是const,但他们的范围,无论好坏!


想法2

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

const dup = input => [input, input];
Run Code Online (Sandbox Code Playgroud)

然后像这样解构:

const [result, {a, b}] = dup(doSomething());
Run Code Online (Sandbox Code Playgroud)

现在,你result,ab都是const秒.


Has*_*own 5

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

\n\n

但从中你可以看到的一件事是,它let something = x; let another = y;与 相同let [something, another] = [x, y];
\n因此,一个真正优雅的解决方案实际上很简单

\n\n
const [result, {a, b}] = [,,].fill(doSomething());\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n\n
\n\n

除此之外(使其成为自己的答案,而不仅仅是值得评论的) ,这种复制也可以解构语法中完成(这就是我遇到这个问题的原因)。
\n假设其b内部result有一个c; 你想要解构它,但也要保留对 的引用b

\n\n
//The above might lead you to believe you need to do this:\nconst result = doSomething(); const {a, b} = result; const {c} = b;\n//or this\nconst [result, {a, b}, {b:{c}}] = [,,,].fill(doSomething());\n
Run Code Online (Sandbox Code Playgroud)\n\n

但你实际上可以

\n\n
const [result, {a, b, b:{c}}] = [,,].fill(doSomething());\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在你有result, a, b, & c,即使 a & b 在结果中,而 c 在 b 中。
\n如果您实际上不需要result,这尤其方便,看起来fill()只需要根对象:
\nconst {a, b, b:{c}} = doSomething();

\n\n

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

\n\n
const [result, [a, b, /*oops, I\'m referencing index 2 now*/]] = [,,].fill(doArrayThing());\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,数组是对象,因此您可以仅使用索引作为键并欺骗索引引用:

\n\n
const [result, {0:a, 1:b, 1:{c}}] = [,,].fill(doArrayThing());\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n\n
\n\n

但我对此并不满足,而且我真的很喜欢@Arash 的想法#2,但它不够通用,无法帮助消除b上面示例中的重复内容,而且它会欺骗这些const行。

\n\n

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

\n\n
    \n
  • 您的解构是用模板文字编写的,输入对象显示为插值
    \neg[,,] = input变为`[,,] = ${input}`
  • \n
  • equals实际上是可选的
  • \n
  • 你永远不会重命名破坏中的输出
    \neg[a, b, ...c] = input变成`[, , ...] ${input}`
  • \n
  • 该模板的输出\xce\xbc(您可以将其命名为任何名称)是您指定的元素的数组,以便
    \negconst {a:A, b:B} = input;成为const [A,B] = \xce\xbc`{a, b} ${input}`;
    \nNB 重命名如何出现在输出中。即使输入是一个对象,输出也始终是一个平面数组。
  • \n
  • 您可以使用数字而不是重复的逗号来跳过迭代器中的元素
    \neg const [a, , , d] = input;isconst [a,d] = \xce\xbc`[ , 2, ]`;
  • \n
  • 最后,这是整个要点;当进入一个对象时,在它前面加上一个冒号会将其保存到输出中
  • \n
\n\n

例如

\n\n
const [result, {a, b, b:{c}}] = [,,].fill(doSomething());\n
Run Code Online (Sandbox Code Playgroud)\n\n

变成

\n\n
const [result, a, b] = \xce\xbc`:{a, b::{c}} ${doSomething()}`;\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

因此,除了上述优点之外,还有以下优点:

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

例如,ES6 甚至不关心这个:

\n\n
_ = {a:7, get b() {throw \'hi\'}};\nconsole.warn(\'ES6\');\nout(() => {\n    const {a, b} = _;\n    return [a, b];\n});\nconsole.warn(\'hashbrown\');\nout(() => {\n    const {a,b} = \xce\xbc`{a,...} ${_}`;\n    return [a, b];\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

在此输入图像描述

\n\n

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

\n\n
_ = [1];\nconsole.warn(\'ES6\');\nout(() => {\n    const [[a]] = _;\n    return [a];\n});\nconsole.warn(\'hashbrown\');\nout(() => {\n    const [a] = \xce\xbc`[[]] ${_}`;\n    return [a];\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

在此输入图像描述

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

例如

\n\n
const [[a,,,,,,,,,j], [[aa, ab], [ba]]] = [,,].fill(_);\nconst [a, aa, ab, ba, j] = \xce\xbc`[:[ , ], [ ], 7, ] ${_}`;\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

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

\n\n
    \n
  • 即使是最后一个专业人士,缺少所有名称的破坏语法也可能很难阅读。实际上,我们需要语言中的这种语法,因此名称位于其中,而不是const [发生在其外部。
  • \n
  • 编译器不知道如何处理这个问题,语法错误是运行时的(而 ES6 会提前告诉你),IDE 可能无法分辨出什么内容(而且我拒绝编写正确完成的模板.d.ts)如果您正在使用某种类型检查
  • \n
  • 正如之前提到的,您的语法会出现稍微更糟糕的编译时错误。我只是告诉你有些事情不对劲,而不是具体是什么。
    \n但是,公平地说,我还是告诉你哪里出了问题,如果你有多个rest操作符,我不认为ES6有多大帮助
  • \n
\n\n

例如

\n\n
_ = [1, 2, 3, 4];\nconsole.warn(\'ES6\');\nout(() => {\n    eval(`const [a, ...betwixt, b] = _`);\n    return [a, betwixt, b];\n});\nconsole.warn(\'hashbrown\');\nout(() => {\n    const [a, betwixt, b] = \xce\xbc`[, ..., ] ${_}`;\n    return [a, betwixt, b];\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n

在此输入图像描述

\n\n
    \n
  • 只有当您正在处理数组,或者您要重命名所有输出时,这才真正值得,因为否则您将需要指定两次名称。:{ :[如果和被采用到语言中,这将与第 1 点一起修复[2,您不需要在您的外部重新指定const [
  • \n
  • 我是如何编写它的,老实说它可能只能在 Chrome 中运行,因为firefox 仍然没有命名捕获组。我努力编写 [regex] 解析器以使所有未使用的组不被捕获,因此如果您愿意,使其与 FF 兼容并不难
  • \n
\n\n
\n\n

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

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

演示的测试:

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

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

\n\n

在此输入图像描述

\n