ECMAScript 6及更高版本中的高尔夫技巧


88

这与其他“ <...>中打高尔夫球的技巧”相似,但专门针对ECMAScript 6及更高版本中JavaScript的新功能。

JavaScript的本身是一个非常冗长的语言,function(){}.forEach(),将字符串数组,类数组对象数组,等等等等都超涨大,而不是健康的高尔夫。

另一方面,ES6 +具有一些超级方便的功能并减少了占地面积。x=>y[...x]等等只是一些示例。

请发布一些不错的技巧,以帮助减少代码中的少量额外字节。

注意:ES5技巧已在JavaScript打高尔夫球技巧中提供;请参见 该线程的答案应集中于仅在ES6和其他将来的ES版本中可用的技巧。

但是,此线程也适用于当前使用ES5功能打高尔夫球的用户。答案也可能包含一些技巧,以帮助他们理解ES6功能并将其映射到他们的ES5编码风格。

Answers:


42

点差运算符 ...

传播运算符将数组值转换为逗号分隔的列表。

用例1:

直接在函数需要列表的地方使用数组

list=[1,2,3]
x=Math.min(...list)
list=[10,20], a.push(...list) // similar to concat()

用例2:

从可迭代(通常是字符串)创建数组文字

[...'buzzfizz'] // -> same as .split('')

用例3:

为函数声明可变数量的参数

F=(...x) => x.map(v => v+1)
// example: F(1,2,3) == [2,3,4]

参见mozilla文档


3
现在,我在这里有票。显然有人在此技巧中指出了一个非常糟糕的错误,因为太害羞而无法发表评论并解释什么……
edc65 '16

看起来不错。也许是缺少分号?;)(顺便说一句,您也可以将其用作rest参数,例如Ruby中的
splats

您可以添加它在函数签名中也有一个用例:)
Felix Dombek

Misclick并不意味着不赞成
Stan Strum

@StanStrum它发生了。我将对此帖子进行小幅更新,以便您最终可以更改投票(或您已经投票了)
edc65

21

我加入以来在这里学到的技巧

我的主要编程语言是JS,主要是ES6。自从一周前加入该网站以来,我已经从其他会员那里学到了许多有用的技巧。我将其中一些结合起来。所有积分都归社区所有。

箭头功能和循环

我们都知道箭头功能可以节省很多钱

function A(){do something} // from this
A=a=>do something // to this

但是你要记住一些事情

  • 尝试使用,ie 合并多个语句(a=b,a.map(d))-在这里,返回的值是最后一个表达式a.map(d)
  • 如果您的do something陈述不止一个,那么您需要添加{}方括号。
  • 如果有{}括号,则需要添加一个显式的return语句。

当涉及到循环时,上述情况适用于很多情况。所以像这样:

u=n=>{for(s=[,1,1],r=[i=1,l=2];c=l<n;i+=!c?s[r[l++]=i]=1:1)for(j of r)c-=j<i/2&s[i-j];return n>1?r:[1]}

由于返回,我在这里浪费了至少9个字符。这可以优化。

  • 尝试避免for循环。使用.map.every.some代替。请注意,如果要更改要映射到的同一阵列,它将失败。
  • 将循环包装在闭合箭头函数中,将主箭头函数转换为单个语句。

因此,以上内容变为:

u=n=>(s=>{for(r=[i=1,l=2];c=l<n;i+=!c?s[r[l++]=i]=1:1)for(j of r)c-=j<i/2&s[i-j]})([,1,1])|n>1?r:[1]

删除的字符: {}return

添加的字符: (){}>|

请注意,我如何调用Closure方法,该方法正确地填充了变量n,然后由于Closure方法未返回任何内容(即undefinedreturn),所以按位执行该操作或将其返回,并n在外部箭头函数的单个语句中返回数组u

逗号和分号

永远避免他们,

如果要在循环中声明变量,或者如上一节所述,使用,分隔的语句具有单个语句箭头函数,则可以使用一些漂亮的技巧来避免这些,;删除最后的几个字节。

考虑以下代码:

r=v=>Math.random()*100|0;n=r();m=r();D=v=>A(n-x)+A(m-y);d=0;do{g();l=d;d=D();....

在这里,我正在调用许多方法来初始化许多变量。每次初始化都使用,;。可以重写为:

r=v=>Math.random()*100|0;n=r(m=r(d=0));D=v=>A(n-x)+A(m-y);do{d=D(l=d,g());....

请注意,我如何使用该方法不会干扰传递给它的变量的事实,并使用该事实剃3个字节。

杂项

.search 代替 .indexOf

两者都给出相同的结果,但是search更短。尽管搜索需要正则表达式,所以请明智地使用它。

模板字符串

当您必须根据特定条件连接一个或多个琴弦部件时,这些功能非常方便。

以下面的示例在JS中输出一个Quine

(f=x=>alert("(f="+f+")()"))()

(f=x=>alert(`(f=${f})()`))()

在模板字符串(这是两个反引号(`)内的字符串)中, ${ }均被视为代码,并进行了评估以将结果答案插入字符串中。

我稍后再发布一些技巧。打高尔夫球快乐!


1
.search较短,请尽可能使用它!但是.indexOf并不相同。.search想要一个regexp,而不是字符串。试试'abc'.search('.')
edc65

@ edc65更新!
Optimizer

您可以使用实例方法修改原始数组。第二个是当前索引,第三个是要迭代的数组。
伊西亚·梅多斯

8
“一周前加入了该网站”-21.4万次报道
GamrCorps,2015年

2
和一样.map,递归是另一种有时可以帮助您将for循环变成表达式的技术。
尼尔

20

使用属性速记

属性速记允许您将变量设置为数组的值:

a=r[0];b=r[1] // ES5
[a,b]=r       // ES6 - 6 bytes saved

也可以像这样使用:

a=r[0],b=r[2] // ES5
[a,,b]=r      // ES6 - 5 bytes saved

您甚至可以使用它来反转变量:

c=a,a=b,b=c // ES5 - uses extra variable
[b,a]=[a,b] // ES6 - not shorter, but more flexible

您也可以使用它来缩短slice()功能。

z = [1, 2, 3, 4, 5];

a=z.slice(1) // a = [2,3,4,5]; ES5
[,...a]=z    // a = [2,3,4,5]; ES6

基本转换

ES6提供了一种将Base-2(二进制)和Base-8(八进制)格式转换为十进制的简短方法:

0b111110111 // == 503
0o767       // == 503

+可用于将二进制,八进制或十六进制字符串转换为十进制数字。您可以使用0b0o0x分别为二进制,八进制,十六进制和:

parseInt(v,2) // ES5
+('0b'+v)     // ES6 - 4 bytes saved; use '0o' for octal and '0x' for hex
'0b'+v-0      // Shorter, but may not work in all cases
              // You can adapt this your case for better results

如果您使用此> 7次,则使用时间会更短 parseInt和重命名的:

(p=parseInt)(v,2)

现在p可用于parseInt,从长远来看可以节省许多字节。


基本的转换技巧很好,但是转换数更有可能是变量形式而不是文字形式,在这种情况下,转换时间会更长。
Optimizer

1
'0x'+v-0甚至更短,但在某些情况下可能无法正常工作。
ETHproductions 2015年

1
顺便说一下,0767(ES5)比0o767(ES6)表示法短。
卡米洛·马丁

@CamiloMartin 0767是非标准扩展名,在严格模式下明确禁止使用。
Oriol

1
@Oriol严格模式是一个坏模因。它无助于提高性能,并没有真正迫使您编写出色的代码,也永远不会成为默认值。0前缀的八进制文字不会随处可见,并且与ecmascript一样有效0o
卡米洛·马丁

19

将字符串模板与函数一起使用

当您有一个带有一个字符串作为参数的函数时。()如果没有任何表达式,则可以省略:

join`` // Works
join`foobar` // Works
join`${5}` // Doesn't work 

9
警告,这实际上传递了一个数组。fun`string` 是一样的fun(["string"]),没有fun("string")。这对于强制转换为字符串的函数(例如)很好alert,但对于其他函数,可能会导致问题。有关更多信息,请参见MDN文章
Cyoce,2016年

5
快速参考:fun`foo${1}bar${2}baz等同于致电fun(["foo","bar","baz"],1,2)
Cyoce '16

14

数组推导(Firefox 30-57)

注意:数组解析从未被标准化,并且在Firefox 58中已经过时了。使用后果自负。


最初,ECMAScript 7规范包含了许多基于数组的新功能。尽管其中大多数都没有成为最终版本,但Firefox支持可能是这些功能中最大的功能:新奇的语法可以替换.filter.map带有for(a of b)语法。这是一个例子:

b.filter(a=>/\s/.test(a)).map(a=>a.length)
[for(a of b)if(/\s/.test(a))a.length]

如您所见,除了第二行不包含笨重的关键字和箭头功能之外,这两行没有什么不同。但这仅占订单.filter().map(); 如果您选择了该.map().filter()怎么办?这实际上取决于情况:

b.map(a=>a[0]).filter(a=>a<'['&&a>'@')
[for(a of b)if(a<'['&&a>'@')a[0]]

b.map(a=>c.indexOf(a)).filter(a=>a>-1)
[for(a of b)if((d=c.indexOf(a))>-1)d]

b.map(a=>a.toString(2)).filter(a=>/01/.test(a))
[for(a of b)if(/01/.test(c=a.toString(2)))c]

或者,如果你想要的东西要么 .map 还是 .filter?好吧,通常结果不太好:

b.map(a=>a.toString(2))
[for(a of b)a.toString(2)]

b.filter(a=>a%3&&a%5)
[for(a of b)if(a%3&&a%5)a]

因此,我的建议是在通常使用.map 和的 任何地方使用数组推导.filter,而不仅仅是一个或另一个。

字符串理解

ES7理解的一个优点是,与诸如.map和的数组特定功能不同.filter,它们可以用于任何可迭代对象,而不仅仅是数组。这在处理字符串时特别有用。例如,如果要通过以下方式运行c字符串中的每个字符c.charCodeAt()

x=>[...x].map(c=>c.charCodeAt())
x=>[for(c of x)c.charCodeAt()]

那是两个字节,以相当小的规模保存。如果要过滤字符串中的某些字符怎么办?例如,这个仅保留大写字母:

x=>[...x].filter(c=>c<'['&&c>'@')
x=>[for(c of x)if(c<'['&&c>'@')c]

嗯,这并不短。但是,如果我们将两者结合起来:

x=>[...x].filter(c=>c<'['&&c>'@').map(c=>c.charCodeAt())
x=>[for(c of x)if(c<'['&&c>'@')c.charCodeAt()]

哇,节省了整整10个字节!

字符串理解的另一个优点是,硬编码的字符串节省了一个额外的字节,因为您可以在以下位置省略空格of

x=>[...'[](){}<>'].map(c=>x.split(c).length-1)
x=>[for(c of'[](){}<>')x.split(c).length-1]

x=>[...'[](){}<>'].filter(c=>x.split(c).length>3)
x=>[for(c of'[](){}<>')if(x.split(c).length>3)c]

索引编制

数组理解使在字符串/数组中获取当前索引更加困难,但是可以这样做:

a.map((x,i)=>x+i).filter ((x,i)=>~i%2)
[for(x of(i=0,a))if(++i%2)x+i-1]

要注意的主要事情是确保索引每次都递增,而不仅仅是在满足条件时递增。

发电机理解

生成器理解与数组理解的语法基本相同。只需将括号替换为括号即可:

x=>(for(c of x)if(c<'['&&c>'@')c.charCodeAt())

这将创建一个生成器,其功能与数组几乎相同,但这是另一个答案的故事。

摘要

基本上,尽管理解力通常比 .map().filter(),但这全都取决于具体情况。最好同时尝试两种方法,看看哪种方法更好。

PS随时建议另一个与理解有关的技巧或改善此答案的方法!


这是一个可以节省更多字符的范围的技巧:(x,y)=>[...Array(y-x)].map(a=>x++)
Mwr247

2
您可以剪掉另外11个字节,以使范围从0到x:x=>[...Array(x).keys()]
Mwr247

最后一个用于理解的地方:(n=>[for(x of Array(n).keys())if(/1/.test(x))x]节省7个字节)
Mwr247

@ Mwr247实际上,我现在可以看到,理解范围通常不如其他出色的ES6功能那么短。我将在字符串部分添加一个部分,让您处理范围。
ETHproductions

值得注意的是,不推荐使用Array Comprehensions并将其从所有最新版本的javascript中删除。请参阅有关该主题的MDN文档
Keefer Rourke

13

ES6中的函数表达式使用箭头表示法,与ES5版本相比,它有助于节省很多字节:

f=function(x,y){return x+y}
f=(x,y)=>x+y

如果函数只有一个参数,则可以省略括号以保存两个字节:

f=x=>x+1

如果您的函数根本没有参数,则将其声明为好像有一个参数来保存一个字节:

f=()=>"something"
f=x=>"something"

注意:箭头功能与并不完全相同function () {}。规则this不同(更好的是IMO)。查看文件


2
但是当你是高尔夫-ING,你通常不关心this
优化

1
通常不是,但这只是警告,您可能永远不知道它何时出现。对于lambda,在生产中不需要函数本地此绑定也更为常见。
伊西亚·梅多斯

另外,如果您想接受所有参数,则可以使用“ rest”参数功能,例如,f=(...x)=>x 具有that f(1,2,3) => [1,2,3]
科纳·奥布莱恩

1
这是此站点的提示:如果您使用的函数采用以下形式(x,y)=>...,则可以通过替换为x=>y=>...
Curce

12

使用eval与多条语句和箭头功能return

我偶然发现的更荒谬的把戏之一...

想象一个简单的箭头函数,它需要多个语句和一个return

a=>{for(o="",i=0;i<a;i++)o+=i;return o}

一个接受单个参数的简单函数a,该参数遍历in中的所有整数[0, a),并将它们附加到o返回的输出字符串的末尾。例如,使用4as作为参数调用将产生yield 0123

请注意,此箭头功能必须用大括号括起来{},并return o在末尾加一个。

第一次尝试重39字节

不错,但是通过使用eval,我们可以改善这一点。

a=>eval('for(o="",i=0;i<a;i++)o+=i;o')

此函数通过将代码包装在中,eval然后将eval评估中的最后一条语句简化为来删除花括号和return语句o。这将导致evalreturn o,进而导致函数return o,因为它现在是单个语句。

改进后的尝试重达38个字节,比原来节省了一个字节。

但是,等等,还有更多!Eval语句返回其最后一条语句求值的结果。在这种情况下,o+=i计算结果为o,因此我们不需要;o(谢谢,edc65!)

a=>eval('for(o="",i=0;i<a;i++)o+=i')

最终尝试仅重36个字节 -比原始版本节省3个字节!


该技术可以扩展到箭头函数需要返回值并具有多个语句(无法通过其他方式组合)的任何一般情况

b=>{statement1;statement2;return v}

变成

b=>eval('statement1;statement2;v')

保存一个字节。

如果statement2评估为v,这可以是

b=>eval('statement1;statement2')

共节省3个字节。


1
我认为,仅编写匿名函数可能会更短
Downgoat

@vihan是的,可以将这两个函数都设为匿名,以分别节省2个字节。虽然节省了一个字节。
jrich 2015年

1
但更好的是:eval返回最后一个被求值的表达式,因此您不需要;o-尝试一下:a=>eval('for(o="",i=0;i<a;i++)o+=i')
edc65

4
但是模板字符串!
Conor O'Brien 2015年

1
@CᴏɴᴏʀO'Bʀɪᴇɴ使用示例函数作为上下文,在这里解释模板字符串如何工作吗?
WallyWest '16

10

优先使用模板字符串新行而不是“ \ n”

即使代码中只有一个换行符,这也将开始得到回报。一种用例可能是:

(16字节)

array.join("\n")

(15个字节)

array.join(`
`)

更新:由于标记了模板字符串,您甚至可以省去大括号(感谢edc65!):

(13个字节)

array.join`
`

5
但更好的是,您可以避免使用括号。阅读文档(developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...)看出为什么
edc65

嗯对 谢谢,我已经添加了。
Chiru 2015年

9

填充数组-静态值和动态范围

我最初将这些内容留在了理解之下,但由于该帖子主要侧重于理解,因此我认为将其放置在自己的位置会很好。

ES6使我们能够使用静态值填充数组,而无需使用循环:

// ES5
function(x){for(i=0,a=[];i<x;i++)a[i]=0;return a}

// ES6
x=>Array(x).fill(0)

两者均返回长度为x的数组,并填充值为0。

但是,如果要用动态值(例如0 ... x的范围)填充数组,则结果会更长一些(尽管仍然比旧方法短):

// ES5
function(x){for(i=0,a=[];i<x;i++)a[i]=i;return a}

// ES6
x=>Array(x).fill().map((a,i)=>i)

两者都返回一个长度为x的数组,从值0开始,以x-1结尾。

之所以需要.fill()in 的原因是因为仅初始化数组不会让您映射它。也就是说,这样做x=>Array(x).map((a,i)=>i)将返回一个空数组。您还可以使用散布运算符来解决填充需求(并使其更短),如下所示:

x=>[...Array(x)]

使用扩展运算符和.keys()函数,您现在可以将短范围设为0 ... x:

x=>[...Array(x).keys()]

如果您想要一个自定义范围x ... y或一个专门的范围(例如偶数),则可以使用扩展运算符来摆脱它,.keys()而只需使用.map()或使用.filter(),即可:

// Custom range from x...y
(x,y)=>[...Array(y-x)].map(a=>x++)

// Even numbers (using map)
x=>[...Array(x/2)].map((a,i)=>i*2)

// Even numbers (using filter)
x=>[...Array(x).keys()].filter(a=>~a%2)

这是第二个示例的建议:x=>Array(x).fill(i=0).map(a=>i++)另外,我不确定.fill(0)是否需要输入0 。你有没有试过吗?
ETHproductions

@ETHproductions您是对的,我忘记了地图之前的填充中不需要0。但这使它比您建议的字符短了1个字符,因此我将保持这种状态。谢谢!
Mwr247

同样,对于最后一个示例,也a=>a%2-1可以正常工作a=>a%2<1
ETHproductions

1
我学到的一个新技巧:和[...Array(x)]一样好Array(x).fill(),并且缩短2个字节。x=>[...Array(x)].map((a,i)=>i)
ETHproductions 2015年

1
@yonatanmn非常好!仅注释将是1)1/4示例将更简短地写出来[0,0,0,0],以及2)字符串化函数是特定于实现的,因此不会返回可靠的长度(MapChrome中为32字节,而Firefox中为36字节)。
Mwr247

9

箭头函数中的返回值

众所周知,如果箭头函数声明后有一条语句,它将返回该语句的结果:

a=>{return a+3}
a=>a+3

-7个字节

因此,如果可能,将多个语句合并为一个。最容易做到的是用括号将语句括起来并用逗号将它们分开:

a=>{r=0;a.map(n=>r+=n);return r}
a=>(r=0,a.map(n=>r+=n),r)

-8个字节

但是,如果只有两个语句,通常可以(或更短)将它们与&&或组合||

a=>{r=0;a.map(n=>r+=n);return r}

// - Use && because map always returns an array (true)
// - declaration of r moved into unused map argument to make it only 2 statements
a=>a.map(n=>r+=n,r=0)&&r

-9个字节

最后,如果您使用的是map(或类似名称)并且需要返回数字,并且可以保证地图永远不会返回带有数字的1长度数组,则可以使用以下命令返回数字|

a=>{a=b=0;a.map(n=>(a+=n,b-=n));return a/b}

// - {} in map ensures it returns an array of undefined, so the | will make the returned
//   array cast from [ undefined, undefined, undefined ] to ",," to NaN to 0 and 0|n = n,
//   if the map returned [ 4 ] it would cast from [ 4 ] to "4" to 4 and make it 4|n
a=>a.map(n=>{a+=n,b-=n},a=b=0)|a/b

在最后一个示例中,您还需要确保该数字始终是整数。
ETHproductions 16-10-4

8

随机模板字符串黑客

此功能浅滩两个字符串(即转"abc","de""adbec"):

f=(x,y)=>String.raw({raw:x},...y)

请注意,这仅在x大于时起作用y。您问它如何运作?String.raw被设计为模板标记,如下所示:

String.raw`x: ${x}\ny: ${y}\nx + y: ${x + y}`

基本上叫String.raw(["x: ", "\ny: ", "\nx + y: ", ""], x, y, x + y),尽管不是那么简单。模板数组还具有一个特殊raw属性,该属性基本上是数组的副本,但带有原始字符串。String.raw(x, ...args)基本上返回x.raw[0] + args[0] + x.raw[1] + args[1] + x.raw[2] + ...等等,直到x项目用完。

因此,既然我们知道如何String.raw工作,就可以利用它来发挥我们的优势:

f=(x,y)=>String.raw({raw:x},...y)                   // f("abc", "de") => "adbec"
f=x=>String.raw({raw:x},...[...x].keys())           // f("abc") => "a0b1c"
f=(x,y)=>String.raw({raw:x},...[...x].fill(y))      // f("abc", " ") => "a b c"

当然,对于最后一个,f=(x,y)=>x.split``.join(y)它要短得多,但是您明白了。

如果xy长度相等,则有两个筛选函数也可以使用:

f=(x,y)=>String.raw({raw:x.match(/.?/g)},...y)
f=(x,y)=>String.raw({raw:x},...y)+y.slice(-1)  // Only works if x.length == y.length

您可以String.raw 在MDN上了解更多信息。


7

如何进行递归高尔夫

递归虽然不是最快的选择,但通常是最短的。通常,如果解决方案可以简化为挑战的一小部分,则递归最短,尤其是在输入是数字或字符串的情况下。例如,如果f("abcd")可以根据"a"和进行计算f("bcd"),通常最好使用递归。

以阶乘为例:

n=>[...Array(n).keys()].reduce((x,y)=>x*++y,1)
n=>[...Array(n)].reduce((x,_,i)=>x*++i,1)
n=>[...Array(n)].reduce(x=>x*n--,1)
n=>{for(t=1;n;)t*=n--;return t}
n=>eval("for(t=1;n;)t*=n--")
f=n=>n?n*f(n-1):1

在此示例中,递归显然比任何其他选项都短。

字符总和如何:

s=>[...s].map(x=>t+=x.charCodeAt(),t=0)|t
s=>[...s].reduce((t,x)=>t+x.charCodeAt())
s=>[for(x of(t=0,s))t+=x.charCodeAt()]|t  // Firefox 30+ only
f=s=>s?s.charCodeAt()+f(s.slice(1)):0

这个技巧比较棘手,但是我们可以看到,如果正确实现,递归可以节省4个字节.map

现在让我们看一下不同类型的递归:

递归前

这通常是最短的递归类型。输入被分成两个部分a,并b和函数计算的东西af(b)。回到我们的阶乘示例:

f=n=>n?n*f(n-1):1

在这种情况下,anbn-1,返回值为a*f(b)

重要说明:当输入足够小时,所有递归函数都必须有一种方法来停止递归。在阶乘函数中,这由来控制n? :1,即,如果输入为0,则返回1而无需f再次调用。

递归后

后递归与前递归相似,但略有不同。输入被分成两个部分a,并b和函数计算用的东西a,然后调用f(b,a)。第二个参数通常具有默认值(即f(a,b=1))。

当您需要对最终结果做一些特殊的事情时,预递归会很好。例如,如果您想要一个数字的阶乘加1:

f=(n,p=1)=>n?f(n-1,n*p):p+1

即使这样,post-也并不总是比在另一个函数中使用pre-recursion短:

n=>(f=n=>n?n*f(n-1):1)(n)+1

那么什么时候更短?您可能会注意到,此示例中的递归后需要在函数参数周围加上括号,而递归则不需要。通常,如果两个解决方案都需要在参数前后加上括号,则递归后的时间大约要短2个字节:

n=>!(g=([x,...a])=>a[0]?x-a.pop()+g(a):0)(n)
f=([x,...a],n=0)=>a[0]?f(a,x-a.pop()+n):!n

(此处的程序取自此答案

如何找到最短的解决方案

通常,找到最短方法的唯一方法是尝试所有方法。这包括:

  • 循环
  • .map(对于字符串,[...s].maps.replace;对于数字,您可以创建一个范围
  • 数组理解
  • 递归(有时在这些选项中的另一个选项中)
  • 递归后

这些只是最常见的解决方案。最好的解决方案可能是这些的组合,甚至是完全不同的东西。找到最短解决方案的最佳方法是尝试一切


1
+1为它的价值,我想再加上一个+1的动物世界
edc65 '16

7

较短的方法 .replace


如果要用一个字符串中的另一个替换一个确切的子字符串的所有实例,显而易见的方法是:

f=s=>s.replace(/l/g,"y") // 24 bytes
f("Hello, World!")       // -> "Heyyo, Woryd!"

但是,您可以缩短1个字节:

f=s=>s.split`l`.join`y`  // 23 bytes
f("Hello, World!")       // -> "Heyyo, Woryd!"

请注意,如果您想使用除g标志之外的任何正则表达式功能,此功能将不再短。但是,如果要替换变量的所有实例,则通常要短得多:

f=(s,c)=>s.replace(RegExp(c,"g"),"") // 36 bytes
f=(s,c)=>s.split(c).join``           // 26 bytes
f("Hello, World!","l") // -> "Heo, Word!"

有时,您需要映射字符串中的每个字符,用其他字符替换每个字符。我经常发现自己正在这样做:

f=s=>s.split``.map(x=>x+x).join`` // 33 bytes
f=s=>[...s].map(x=>x+x).join``    // 30 bytes
f("abc") // -> "aabbcc"

但是,.replace几乎总是较短:

f=s=>s.replace(/./g,x=>x+x)  // 27 bytes
f=s=>s.replace(/./g,"$&$&")  // Also works in this particular case

现在,如果您想映射字符串中的每个字符但不关心结果字符串,.map通常会更好,因为您可以摆脱.join``

f=s=>s.replace(/./g,x=>t+=+x,t=0)&&t // 36 bytes
f=s=>[...s].map(x=>t+=+x,t=0)&&t     // 32 bytes
f("12345")  // -> 15

对于最后一种情况,如果仅对与正则表达式匹配的某些字符(例如/\w/g)感兴趣,那么使用replace会比本演示中的要好得多。
Shieru Asakoto

6

使用RegEx文字编写 eval

regex构造函数的名称很长,因此可能会非常庞大​​。而是用eval和反引号写一个文字:

eval(`/<${i} [^>]+/g`)

如果变量i等于foo,则将生成:

/<foo [^>]+/g

这等于:

new RegExp("<"+i+" [^>]+","g")

您也String.raw可以避免重复反斜杠\

eval(String.raw`/\(?:\d{4})?\d{3}\d{3}\d{3}\d{3}\d{3}\d{3}\d{4}/g`)

这将输出:

/(?:\d{4})?\d{3}\d{3}\d{3}/g

等于:

RegExp("\\(?:\\d{4})?\\d{3}\\d{3}\\d{3}\\d{3}\\d{3}\\d{3}\\d{4}","g")

记住!

String.raw占用大量字节,除非您至少有 9个反斜杠,String.raw否则它将更长。


您不需要new在那里,因此在第二个示例中使用构造函数实际上更短
Optimizer

5

.forEachvs for循环

始终喜欢.map使用任何for循环。简单,即时节省。


a.map(f)
for(x of a)f(x);
for(i=0;i<a.length;)f(a[i++]);
  • 原始总共8个字节
  • 与for-of相比节省了8个字节(减少了50%
  • 与C风格的循环相比节省了22个字节(减少了73%

a.map(x=>f(x,0))
for(x of a)f(x,0);
for(i=0;i<a.length;)f(a[i++],0);
  • 总共16个字节原始
  • 保存2个字节与for( 11%减少了)
  • 与C样式的循环相比节省了16个字节(减少了50%

a.map((x,i)=>f(x,i,0))
for(i in a)f(a[i],i,0);
for(i=0;i<a.length;)f(a[i],i++,0);
  • 总共22个字节原始
  • 保留了1个字节,而不保留 4%减少了)
  • 与C样式的循环相比节省了11个字节(减少了33%

a.map(x=>f(x)&g(x))
for(x of a)f(x),g(x);
for(i=0;i<a.length;)f(x=a[i++]),g(x);
  • 总共19个字节原始
  • 与for-of相比节省了2个字节(减少了10%
  • 与C样式的循环相比节省了18个字节(减少了49%

5

在递归中使用未初始化的计数器

注意:严格来说,这不是ES6特有的。但是,由于箭头功能的简洁性,在ES6中使用和滥用递归更为合理。


遇到一个递归函数是很常见的,该递归函数使用的计数器k最初设置为零,并在每次迭代时递增:

f = (…, k=0) => [do a recursive call with f(…, k+1)]

在某些情况下,可以省略此类计数器的初始化并替换k+1-~k

f = (…, k) => [do a recursive call with f(…, -~k)]

此技巧通常可以节省2个字节

为什么以及何时起作用?

使成为可能的公式是~undefined === -1。因此,在第一次迭代中,-~k将被评估为1。在下一迭代中,-~k实质上等同于-(-k-1)它等于k+1,至少在范围[整数0 ... 2 31 -1]。

但是,您必须确保k = undefined第一次迭代不会破坏该函数的行为。您应该特别记住,大多数涉及的算术运算undefined都会导致NaN

例子1

给定一个正整数n,此函数将查找k不除的最小整数n

f=(n,k=0)=>n%k?k:f(n,k+1)   // 25 bytes

可以缩短为:

f=(n,k)=>n%k?k:f(n,-~k)     // 23 bytes

这是有效的,因为n % undefinedis NaN,这是虚假的。这是第一次迭代的预期结果。

[链接到原始答案]

范例#2

给定一个正整数n,此函数将查找一个整数p,使得(3**p) - 1 == n

f=(n,p=0,k=1)=>n<k?n>k-2&&p:f(n,p+1,k*3)  // 40 bytes

可以缩短为:

f=(n,p,k=1)=>n<k?n>k-2&&p:f(n,-~p,k*3)    // 38 bytes

之所以有效,p是因为在第一次迭代中根本没有使用过(n<k为false)。

[链接到原始答案]


5

ES6功能

数学

Math.cbrt(x)比保存字符Math.pow(x,1/3)

Math.cbrt(x)
Math.pow(x,1/3)

保存了3个字符

Math.hypot(...args)当您需要args平方和的平方根时很有用。使ES5代码做到这一点比使用内置代码难得多。

该函数Math.trunc(x)没有帮助,因为x|0它更短。(感谢Mwr247!)

ES5中有许多属性需要花费大量代码来完成,但在ES6中则更加容易:

  • Math.acoshasinhatanhcoshsinhtanh。计算三角函数的双曲等效项。
  • Math.clz32。在ES5中可能可以做,但是现在更容易了。计算数字的32位表示形式中的前导零。

还有更多的事情,所以我只是要列出一些:
Math.signMath.froundMath.imulMath.log10Math.log2Math.log1p


Math.trunc(x)是的四倍x|0
Mwr247

@ mwr247:好的,将更新。
ev3commander 2015年

这是我对其中的几个函数了解的最短的ES5等效项:(Math.hypot(a,b) => Math.sqrt(a*a+b*b)更长的3个字节;使用更多的参数变得更长),Math.sign(a) => (a>0)-(a<0)(较短的1个字节,但在某些情况下需要用括号括起来;可能不起作用NaN
ETHproductions

@ETHproductions您需要hypot的arguments数组(的es5解决方法)。并且您确定Math.sign的解决方法适用于-0吗?(它应返回-0)
ev3commander'15

1
@ ev3commander这些只是作为它们各自的ES6等效项的嵌入式替换,因此可以缩小到99%的使用量。真正地重新创建这些功能将需要更多的代码。另外,我认为没有必要为-0加上特殊情况,因为(AFAIK)没有办法获得-0,除非通过手动指定它,并且在代码高尔夫中几乎没有用。但是,感谢您指出这些问题。
ETHproductions

5

优化小常数范围 map()

语境

map()for[0 ..ñ-1个]

for(i = 0; i < 10; i++) {
  do_something_with(i);
}

可以替换为:

[...Array(10).keys()].map(i => do_something_with(i))

或更常见的是:

[...Array(10)].map((_, i) => do_something_with(i))

Array(N)ñ 是一个小常数。

一系列优化 [0 ..ñ-1个],带柜台

下面总结了计数器时较短的替代方法 一世 在回调中使用:

N           | Method                               | Example                         | Length
------------+--------------------------------------+---------------------------------+-------
N ≤ 6       | use a raw array of integers          | [0,1,2,3].map(i=>F(i))          | 2N+10
N = 7       | use either a raw array of integers   | [0,1,2,3,4,5,6].map(i=>F(i))    | 24
            | or a string if your code can operate | [...'0123456'].map(i=>F(i))     | 23
            | with characters rather than integers |                                 |
8 ≤ N ≤ 9   | use scientific notation 1e[N-1]      | [...1e7+''].map((_,i)=>F(i))    | 24
N = 10      | use scientific notation 1e9          | [...1e9+''].map((_,i)=>F(i))    | 24
            | or the ES7 expression 2**29+'4' if   | [...2**29+'4'].map(i=>F(i))     | 23
            | the order doesn't matter and your    |                                 |
            | code can operate with characters     |  (order: 5,3,6,8,7,0,9,1,2,4)   |
            | rather than integers                 |                                 |
11 ≤ N ≤ 17 | use scientific notation 1e[N-1]      | [...1e12+''].map((_,i)=>F(i))   | 25
N = 18      | use the fraction 1/3                 | [...1/3+''].map((_,i)=>F(i))    | 24
N = 19      | use the fraction 1/6                 | [...1/6+''].map((_,i)=>F(i))    | 24
20 ≤ N ≤ 21 | use scientific notation 1e[N-1]      | [...1e20+''].map((_,i)=>F(i))   | 25
N = 22      | use scientific notation -1e20        | [...-1e20+''].map((_,i)=>F(i))  | 26
23 ≤ N ≤ 99 | use Array(N)                         | [...Array(23)].map((_,i)=>F(i)) | 27

NBF(i)不计算回调代码的长度。

优化范围 [1..9],带柜台

如果您想遍历整个范围 [1..9] 而且顺序无关紧要,您可以使用以下ES7表达式(前提是您的代码可以使用字符而不是整数进行操作):

[...17**6+'8'].map(i=>F(i))  // order: 2,4,1,3,7,5,6,9,8; length: 23

无计数器优化

如果只需要迭代,可以使用以下方法 ñ 次,无需使用计数器:

N           | Method                               | Example                         | Length
------------+--------------------------------------+---------------------------------+-------
N ≤ 5       | use a raw array of integers          | [0,0,0,0].map(_=>F())           | 2N+10
6 ≤ N ≤ 10  | use scientific notation 1e[N-1]      | [...1e7+''].map(_=>F())         | 20
11 ≤ N ≤ 17 | use scientific notation 1e[N-1]      | [...1e12+''].map(_=>F())        | 21
N = 18      | use the fraction 1/3                 | [...1/3+''].map(_=>F())         | 20
N = 19      | use the fraction 1/6                 | [...1/6+''].map(_=>F())         | 20
20 ≤ N ≤ 21 | use scientific notation 1e[N-1]      | [...1e20+''].map(_=>F())        | 21
N = 22      | use scientific notation -1e20        | [...-1e20+''].map(_=>F())       | 22
23 ≤ N ≤ 99 | use Array(N)                         | [...Array(23)].map(_=>F())      | 23

NBF()不计算回调代码的长度。


2**26应该2**29吗?
毛茸茸的

@Shaggy Heck。接得好!
Arnauld

不想自己编辑,因为我有代码盲点!:D
毛茸茸的

使用.keys(),您不需要lambda:[...Array(10).keys()].map(do_something_with)
long-lazuli

@ long-lazuli如果您不需要lambda只是想要一个范围,那么您也可能不需要地图...
Arnauld

4

销毁工作

ES6引入了用于破坏分配的新语法,即将一个值切成段并将每个段分配给一个不同的变量。这里有一些例子:

字符串和数组

a=s[0];b=s[1];       // 14 bytes
[a,b]=s;             //  8 bytes

a=s[0];s=s.slice(1); // 20 bytes
a=s.shift();         // 12 bytes, only works if s is an array
[a,...s]=s;          // 11 bytes, converts s to an array

对象

a=o.asdf;b=o.bye;c=o.length; // 28 bytes
{asdf:a,bye:b,length:c}=o;   // 26 bytes

a=o.a;b=o.b;c=o.c; // 18 bytes
{a,b,c}=o;         // 10 bytes

这些分配也可以在功能参数中使用:

f=a=>a[0]+a[1]+a[2]
f=([a,b,c])=>a+b+c

f=b=>b[1]?b[0]+f(b.slice(1)):b[0]*2
f=b=>b[1]?b.shift()+f(b):b[0]*2
f=([a,...b])=>b[0]?a+f(b):a*2

4

避免的另一种方法 return

您知道应该对带有多个语句和return的箭头函数使用eval。在某些特殊情况下,您可以使用内部子功能保存更多内容。

我说不寻常是因为

  1. 返回的结果不能是循环中最后一个值

  2. 循环之前必须(至少)进行2次不同的初始化

在这种情况下,您可以使用内部子函数而无需返回,只需将初始值之一作为参数传递即可。

示例 查找a到b范围内的值的exp函数之和的倒数。

很长的路要走-55个字节

(a,b)=>{for(r=0,i=a;i<=b;i++)r+=Math.exp(i);return 1/r}

带有eval-54个字节

(a,b)=>eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i);1/r")

具有内部功能-53字节

(a,b)=>(i=>{for(r=0;i<=b;i++)r+=Math.exp(i)})(a)||1/r

注意,不需要较低的范围限制a,我可以合并i和r的初始化,而eval版本较短。


在您的样本中,无需保留a
l4m2,18年

@ l4m2我无法理解您的意思,请帮助...
edc65 '18

(i,b)=>{for(r=0;i<=b;i++)r+=Math.exp(i);return 1/r}
l4m2

@ l4m2嗯,对, return a/r这将是一个更好的示例
edc65

1
评估仍然更好(a,b)=>1/eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i)"),在这种情况下(i,b)=>1/eval("for(r=0;i<=b;)r+=Math.exp(i++)")
JayXon

4

对二元和递归函数使用currying语法

二进位函数

每当函数正好接受两个没有默认值的参数时,使用currying语法将节省一个字节。

之前

f =
(a,b)=>a+b  // 10 bytes

f(a,b)

f =
a=>b=>a+b   // 9 bytes

f(a)(b)

注意事项此Meta帖子确认了此语法的有效性。

递归函数

当递归函数接受多个参数,但每次迭代之间只需要更新其中一些参数时,使用currying语法还可以节省一些字节。

以下函数计算范围内所有整数的总和[a,b]

f=(a,b)=>a>b?0:b+f(a,b-1)   // 25 bytes

因为a在整个过程中保持不变,所以我们可以使用以下方法节省3个字节:

f =                         // no need to include this assignment in the answer anymore
a=>F=b=>a>b?0:b+F(b-1)      // 22 bytes

注意:正如Neil在评论中所指出的,没有将参数显式传递给递归函数这一事实并不意味着应将其视为不可变的。如果需要,我们可以修改a与功能代码中a++a--或任何类似的语法。


最后一个示例可以写成a=>F=b=>a>b?0:a+++F(b)a针对每个递归调用进行修改。在这种情况下这无济于事,但是如果参数更多,它可能会节省字节。
尼尔(Neil)

呵呵,我只是想为此写一条小贴士:-)
ETHproductions'Jan

4

原始性测试功能

以下28字节函数返回true质数和false非质数:

f=(n,x=n)=>n%--x?f(n,x):x==1

可以很容易地对其进行修改以计算其他内容。例如,此39字节的函数计算小于或等于一个数字的质数:

f=(n,x=n)=>n?n%--x?f(n,x):!--x+f(n-1):0

如果已经具有n要检查素数的变量,则可以将素数函数简化很多:

(f=x=>n%--x?f(x):x==1)(n)

这个怎么运作

f = (         // Define a function f with these arguments:
  n,          //   n, the number to test;
  x = n       //   x, with a default value of n, the number to check for divisibility by.
) =>
  n % --x ?   //   If n is not divisible by x - 1,
  f(n, x)     //     return the result of f(n, x - 1).
              //   This loops down through all numbers between n and 0,
              //     stopping when it finds a number that divides n.
  : x == 1    //   Return x == 1; for primes only, 1 is the smallest number
              //     less than n that divides n.
              //   For 1, x == 0; for 0, x == -1.

注意:当使用足够大的输入(例如12345)调用时,这将失败并显示“太多递归”错误。您可以通过循环解决此问题:

f=n=>eval('for(x=n;n%--x;);x==1')

1
但由于输入少至12345 而对太多输入进行递归失败
edc65 '16

x==1可能是x<2为了节省。
CalculatorFeline

@CalculatorFeline谢谢,但是随后由于10(因为分别x0-1)而失败
ETHproductions'Jul

在某些情况下可能有用。同样,!~-x对于-0字节。
CalculatorFeline

3

Array#concat() 和点差算子

这在很大程度上取决于情况。


组合多个数组。

除非克隆,否则最好使用concat函数。

0字节保存

a.concat(b)
[...a,...b]

浪费了3个字节

a.concat(b,c)
[...a,...b,...c]

保存3个字节

a.concat()
[...a]

保存6个字节

// Concatenate array of arrays
[].concat.apply([],l)
[].concat(...l)

首选使用现有数组 Array#concat()

轻松保存4个字节

[].concat(a,b)
a.concat(b)

3

返回中间结果

您知道,使用逗号运算符可以执行返回最后一个值的一系列表达式。但是滥用文字数组语法,您可以返回任何中间值。例如,它在.map()中很有用。

// capitalize words
// f is a flag indicating if prev char is space
[...x].map(c=>(f?c=c.toUpperCase():0,f=c<'!',c),f=1).join('')

// shortened to ...
[...x].map(c=>[f?c.toUpperCase():c,f=c<'!'][0],f=1).join('')

3
请记住,当然.join('')可以是.join``
Cyoce '16

3

设置功能参数默认值

($,a,b,_)=>_!=undefined?'asdf':_ // before
($,a,b,_)=>_!=[]._?'asdf':_ // before, but a bit golfed
($,a,b,_='asdf')=>_ // after

这个真的很有用...

但是,请务必了解,_=>_||'asdf'仅在将一个(有用的)参数传递给该函数时,类似的东西会更短。


1
我会指出,_=>_||'asdf'在大多数情况下,使用OR的时间通常更短
Downgoat 2015年

@Downgoat我要注意的是,返回"asdf"的输入是""(空字符串)。
ETHproductions 2015年

2
请注意undefined,即使您显式传递了该值,只要该参数为,都会对默认值进行评估。例如,[...Array(n)].map((a,b,c)=>b)总是通过undefineda,因此,你可以为它的默认值(虽然不是而言b)。
尼尔

3

使用eval代替括号代替箭头功能

箭头功能很棒。它们采用的形式x=>y,其中x是参数,y是返回值。但是,如果您需要使用诸如的控制结构,则必须使用while大括号,例如=>{while(){};return}。但是,我们可以解决这个问题。幸运的是,该eval函数接受一个字符串,将该字符串作为JS代码求值,并返回最后一个求值的expression。例如,比较这两个:

x=>{while(foo){bar};return baz} // before
x=>eval('while(foo){bar};baz')  // after
//                            ^

我们可以使用此概念的扩展来进一步缩短代码:在的眼中eval,控制结构还返回它们的最后一个评估表达式。例如:

x=>{while(foo)bar++;return bar} // before
x=>eval('while(foo)++bar')      // after
//                        ^^^^^

3

ES6中的高尔夫逻辑运算

“ GLOE(S6)”

通用逻辑

假设您已经构造了语句st。查看是否可以使用以下任何替代产品:

Traditional conjuction: s&&t
Equivalent conjuction: s*t OR s&t

Traditional disjunction: s||t
Equivalent disjunction: s+t OR s|t

(如果顺序错误,则这些顺序可能不起作用;即+*其优先级比||和顺序低&&。)

另外,这是一些方便的逻辑表达式:

  • 要么s或者t是真/ XOR:s^t
  • s并且t具有相同的真值:!s^ts==t

阵列逻辑

所有a满足条件的成员p

a.every(p)                             // 10 bytes (11 bytes saved)
a.map(x=>c&=p(x),c=1)                  // 21 bytes (16 bytes saved)
for(i=0,c=1;i<a.length;c&=p(a[i++]));  // 37 bytes (hideously long)

至少a满足以下条件之一p

a.some(p)                            // 9  bytes (13 bytes saved)
a.map(x=>c|=p(x),c=0)                // 21 bytes (14 bytes saved)
for(i=c=0;i<a.length;c|=p(a[i++]));  // 35 bytes (just please no)

没有成员a满足条件p!a.some(p)

元素e存在于数组中a

a.includes(e)                        // 13 bytes, standard built-in
~a.indexOf(e)                        // 13 bytes, "traditional" method
a.find(x=>e==x)                      // 15 bytes, find (ES6)
a.some(x=>x==e)                      // 15 bytes, some (ES5)
(a+"").search(e)                     // 16 bytes, buggy
a.filter(t=>t==e).length             // 24 bytes, no reason to use this
for(i=c=0;i<a.length;c+=e==a[i++]);  // 35 bytes, super-traditional

元素在array e存在a

!a.includes(e)
!~a.indexOf(e)
a.every(t=>t!=e)
!a.filter(t=>t==e).length
for(i=0,c=1;i<a.length;c*=e!=a[i++]);

我通常分别使用&&||as x?y:xx?x:y。但是我可以看到这在更多基于逻辑的程序中如何有用。一个问题+是例如3-3都是真实的,但事实3+-3并非如此。
ETHproductions 2016年

@ETHproductions啊,你是对的;那是一个极端的情况。-如果也可以工作s != t
科纳·奥布莱恩

a.filter(t=>t==e).length==a.length是不正确的。应该是!a.filter(t=>t==e).length
ETHproductions's

@ETHproductions对,你就是!
科纳·奥布莱恩

3

缩短重复的函数调用

如果您重复调用了一个长名称的函数,例如画布操作:

c.lineTo(0,100);c.lineTo(100,100);c.lineTo(100,0);c.lineto(0,0);c.stroke()

缩短它的传统方法是给函数名起别名:

c[l='lineTo'](0,100);c[l](100,100);c[l](100,0);c[l](0,0);c.stroke()

如果您有足够的调用,更好的方法是创建一个为您完成工作的函数:

l=(x,y)=>c.lineTo(x,y);l(0,100);l(100,100);l(100,0);l(0,0);c.stroke()

如果大多数函数调用都是链接的,则可以使函数返回自身,从而允许您在每个后续调用中减少两个字节:

l=(x,y)=>c.lineTo(x,y)||l;l(0,100)(100,100)(100,0)(0,0);c.stroke()

实例:12


1
您可以使用bind操作符来缩短:(l=::c.lineTo)(0,100)(100,100)(100,0)(0,0);c.stroke()
Downgoat

@Downgoat谢谢,哪些浏览器支持该功能?(此外,从我所看到的情况来看,第二次调用会出错,因为c.lineTo它不会自然地返回自身)
ETHproductions 2017年

您必须通过babel对其进行摩擦,因为它具有ES7功能
Downgoat

3

绑定运算符 ::

绑定运算符可用于帮助缩短重复函数的字节数:

(x='abc'.search(a))+x.search(b) // Before
(x=::'abc'.search)(a)+x(b)      // 5 bytes saved

另外,如果您想将函数与其他函数一起使用,this例如:

s[r='replace'](/a/g,'b')+s[r](/c/g,'d') // Before
(r=s.replace)(/a/g,'b')+s::r(/c/g,'d')  // 1 byte saved

3

存储大量数据时避免逗号

如果您需要将大量数据(即索引,字符等)存储在数组中,那么最好不要使用所有逗号。如果每条数据具有相同的字符串长度,这将是最佳方法,显然1是最佳长度。

43个字节(基准)

a=[[3,7,6,1,8,9,4,5,2],[5,4,3,2,7,6,5,4,3]]

34个字节(无逗号)

a=[[..."376189452"],[..."543276543"]]

如果您愿意更改阵列访问权限,则可以进一步减少它,并存储相同的值,如下所示:

27字节(相同数据,仅更改阵列访问)

a=[..."376189452543276543"]

为什么只突出显示最后一个块?
CalculatorFeline

@CalculatorFeline谢谢,已修复。
Chiru
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.