同时保留原始变量和解构的清晰方式

30

有没有更简洁的方法来完成这件事(使用任何至少是ES草案且具有Babel插件的东西,即ES6、ES7等):

Is there a cleaner way to do this (with anything that is at least an ES draft and has a babel plugin, i.e., ES6, ES7, etc.):
const { a, b } = result = doSomething();

我希望将整体结果保持为一个单一对象,同时也对其进行解构。虽然这样做在技术上是可行的,但result被隐式声明(使用隐式的var),而我真的希望它也是一个const。

我目前正在这样做:

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

这种方法也可以工作,但稍微有些冗长,因为我需要重复这个模式数十次。

理想情况下,我希望得到类似以下的内容:

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

但那显然是无效语法。


1
第一个代码片段没有隐式声明 var。它是未声明的变量,在松散模式下会导致全局变量,并在严格模式下失败。 - Estus Flask
1
使用两个语句是干净和正确的方式。而且也不会过于冗长。我更关心的是,为什么你要说你必须重复这个动作数十次? - Bergi
在这种特殊情况下,是因为有一系列基本的 reducer 函数。每个函数都是独立的,但遵循类似的模式。每个函数都应该更新原始输入,然后将其传递回去,以便链中的下一个 reducer 可以运行它。它们以这样的方式结束:return Object.assign(result, { a: a + 5 }),其中我输出所有内容和更新。 - samanime
5个回答

25

一种可能的方式:

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

但是您仍需要复制名称。const标记不是很方便。


这更清晰一些,也可能是我在当前标准下能得到的最接近的结果。谢谢。 - samanime

10

想法 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,但它们有作用域,好或坏!


Idea 2

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

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

然后像这样进行分解:

const [result, {a, b}] = dup(doSomething());
现在,你的resultab都是const

5
在@raina77ow的答案中,他们抱怨const token不太方便; 但是,如果您使用冒号(并重复关键字)而不是逗号,这就是您的答案。
但是,在您的问题中,您已经提到了const result = doSomething(); const {a, b} = result;,我看不出它有什么不好,而且它也是可行的。

但是从这个例子中,您可以看到let something = x; let another = y;let[something, another]=[x,y];相同。
因此,一个非常优雅的解决方案实际上就是简单地使用:

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

需要使用额外的 ,,因为它是尾随逗号



除此之外,在解构语法中也可以进行重复操作(这就是我浏览此问题的原因)。
假如在 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());

现在您有 resultabc,即使a和b已经在result中,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提出的第二个想法,但它不够通用,无法帮助去重示例中的b,并且复制了const行。

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

  • 您的解构写成模板文字,输入对象显示为插值
    例如[,,] = input变成`[,,] = ${input}`
  • 等号实际上是可选的
  • 您在分解中从未重命名输出(within the destruction)
    例如[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];
});

enter image description here

例如2,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]解析器非常小心,使所有未使用的组都不进行捕获,所以如果你有兴趣,让它兼容Firefox也并不难。

那么代码在哪里呢?
你真是太热心了。
祝你好运。

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

如果你的浏览器无法运行该程序,但你很好奇(错误是本地测试与此工具的输出进行比较的结果),以下是其输出:

输入图像描述


2

可以使用同一个键来解构对象。将其封装在一个包装器中,这对我很有用。键可以是data_或任何你喜欢的键。

这适用于Javascript和Typescript。

const getMealData = () => ({ breakfast: 'fried egg ', lunch: 'sushi ' })

const {
  data: mealData,
  data: { breakfast },
} = { data: getMealData() }

console.log(mealData) // { breakfast: 'fried egg ', lunch: 'sushi ' }
console.log(breakfast) // "fried egg "


0
找了一个好的解决方案,最终自己解决了,想着把这个加到这个旧帖子的众多解决方案中...... 这将返回一个具有附加字段为原始返回的对象。 同样的事情也适用于将hooks解构成数组。

  const { a, b, func } = (x => ({ ...x, func: x }))(doSomething());


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接