在@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
的引用。
const result = doSomething(); const {a, b} = result; const {c} = b;
const [result, {a, b}, {b:{c}}] = [,,,].fill(doSomething());
实际上你可以只是
const [result, {a, b, b:{c}}] = [,,].fill(doSomething());
现在您有 result
、a
、b
和 c
,即使a和b已经在result中,c也在b中。
如果您实际上不需要result
,那么这将特别方便,看起来只需要对根对象使用fill()
:
const {a, b, b:{c}} = doSomething();
对于数组来说,这似乎不起作用,因为语法中的位置是键
const [result, [a, b, ]] = [,,].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](https://istack.dev59.com/l8GKw.webp)
例如2,ES6表示_
是罪魁祸首。我不仅正确地说出了是1
的问题,而且告诉你在解构中它发生了什么:
_ = [1];
console.warn('ES6');
out(() => {
const [[a]] = _;
return [a];
});
console.warn('hashbrown');
out(() => {
const [a] = μ`[[]] ${_}`;
return [a];
});
![在此输入图片描述](https://istack.dev59.com/PgjiL.webp)
- 如果需要跳过大数组或保留许多内部变量,这将非常方便
例如:
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];
});
![输入图像描述](https://istack.dev59.com/UpDuz.webp)
- 只有在处理数组或者需要重命名所有输出时才值得使用,否则你需要两次指定名称。如果
:{
:[
和[2
被采用到语言中来,这个问题将会与第1点一起解决,你就不需要在const [
之外重新指定了。
- 按照我的写法,它可能只在Chrome浏览器上运行正常,因为Firefox仍然没有命名捕获组。我写的[regex]解析器非常小心,使所有未使用的组都不进行捕获,所以如果你有兴趣,让它兼容Firefox也并不难。
那么代码在哪里呢?
你真是太热心了。
祝你好运。
window.μ = (() => {
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*[,}]
).*)
$`,
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*[,\]]
).*)
$`,
rest : (obj, keys) => (Array.from(keys))
};
let destructure = ({next, input, used}) => {
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";',
);
};
debugging = null;
let read, quoted, key, save, open, inside, close, done, rest, type, keys, parents, stack, obj, skip;
try {
let output = [];
while (
[obj, ...parents] = input,
[keys, ...stack] = used,
type = (!keys) ? start : (typeof keys.next == 'function') ? array : object,
phrase += (read || ''),
read = '',
debugging && debugging(),
{read, quoted, next, key, save, open, inside, close, done, rest, skip} = next.match(type.parse).groups,
done == null
) {
if (open) {
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;
}
if (skip) {
for (skip = parseInt(skip); skip-- > 0; keys.next());
continue;
}
if (rest) {
obj = type.rest(obj, keys);
input = [null, ...parents];
}
else if (key) {
if (quoted) {
key = JSON.parse(key);
}
keys[key] = true;
obj = obj[key];
}
else
obj = keys.next().value;
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;
};
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 _;
_ = {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 {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];
});
_ = [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];
});
如果你的浏览器无法运行该程序,但你很好奇(错误是本地测试与此工具的输出进行比较的结果),以下是其输出:
![输入图像描述](https://istack.dev59.com/7snT8.webp)
var
。它是未声明的变量,在松散模式下会导致全局变量,并在严格模式下失败。 - Estus Flaskreturn Object.assign(result, { a: a + 5 })
,其中我输出所有内容和更新。 - samanime