Чистый способ сохранить исходную переменную и уничтожение одновременно

Есть ли более чистый способ сделать это (с чем-нибудь, что, по крайней мере, является черновиком 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();

Но это, очевидно, неверный синтаксис.

5 ответов

Решение

Один из возможных способов:

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

Вы все еще должны дублировать имя, хотя. const маркер не совсем удобный.)

Идея 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

скомбинировать array а также object деконструкция. Создайте эту вспомогательную функцию:

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

А потом деконструировать так:

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

Теперь ваш result, a, а также b являются все 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());

Однако массивы являются объектами, поэтому вы можете просто использовать индексы в качестве ключей и дублировать ссылку на индекс:

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 для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 для этого), если вы используете какую-то проверку типов
  • и как упоминалось до того, как вы получите немного худшие ошибки времени компиляции для вашего синтаксиса. Я просто говорю вам, что что-то было не так, а не что.
    Но, честно говоря, я все еще говорю вам, где вы ошиблись, если у вас несколько операторов отдыха, я не думаю, что 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];
});

  • это действительно того стоит, только если вы имеете дело с массивами или все равно переименовываете все выходы, потому что в противном случае вам придется указывать имя дважды. Это будет исправлено вместе с точкой 1, если:{ :[ а также [2 были адаптированы на языке, вам не нужно повторно указывать вне в вашем 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];
});

И вывод, если ваш браузер не может запустить его, но вам любопытно (ошибки - это проверка вывода ошибок для нативной и этой штуки)

Разрешена деструктуризация объекта с использованием ключа дважды. Сделайте это оберткой, и она хорошо работает для меня. Ключ может бытьdata,_, или как вам нравится.

Это работает в Javascript и Typescript.

Пошел искать хорошее решение и в итоге разобрался с ним, поэтому подумал, что добавлю его к МНОЖЕСТВУ решений в этой старой теме...

Это возвращает объект с дополнительным полем, являющимся необработанным возвратом. То же самое работает для деструктуризации хуков в массив.

Другие вопросы по тегам