JS 代码高尔夫——如何毁掉每个人的一天
基础知识
简洁的混淆
表达式求值
充分利用
节省开支
结束语
这篇文章的标题有点儿讽刺意味。
“代码高尔夫”这个词指的是用尽可能少的代码来获得尽可能少的“分数”(就像打高尔夫球一样)。
JavaScript 是一门非常适合代码高尔夫的语言,因为它具有向后兼容性、一些特性、高级语言的特性以及各种强制转换功能。我们将介绍一些极致的 JS 代码高尔夫示例,并解释它们的工作原理和原理。
虽然过度使用代码高尔夫会很快让你的代码库变得难以阅读,但偶尔,代码高尔夫的某些技巧可以让一些看起来更复杂的代码更具可读性。理解代码高尔夫也能让你更好地理解 JS 中的一些概念。
基础知识
下面我们将介绍一些你肯定见过的代码高尔夫技巧。包括:
- 缩短变量名称。
- 不要对简单操作使用中间变量。
- 尽可能避免使用块(if、while 和 for 语句)。
- 不要
else
在返回后不必要地使用语句。 - 尽可能使用箭头函数。
让我们给出一个代码片段,然后使用这 5 条基本规则将其缩短。
// 345 characters
function getNumOneOrZeroOff(baseNumber, shouldTryToGoUp, falsyOrTruthyVal) {
const myBoolean = Boolean(falsyOrTruthyVal);
const oneOrZero = Number(myBoolean);
if (shouldTryToGoUp) {
const numberPlus = baseNumber + oneOrZero;
return numberPlus;
} else {
const numberPlus = baseNumber - oneOrZero;
return numberPlus;
}
}
除了这是一个相当无用的功能之外,它还没有必要那么长。
让我们实施前 5 条基本规则来稍微缩短一下。
// 164 characters
const getNewNum = (num, goUp, arbitraryVal) => {
const oneOrZero = Number(Boolean(arbitraryVal));
if (goUp) return num + oneOrZero;
return num - oneOrZero;
}
删除了缩短的变量名const myBoolean = ;{}else{}
、空格以及第 7、10 行
哇,尽管我们删除了很多非常具体的变量名,但它实际上更容易阅读了。让我们再添加 4 条基本规则,让它更简洁一些。
- 更喜欢一元
!!
而不是Boolean()
。 - 更喜欢一元
+
而不是Number()
。 - 如果可能的话,优先使用三元语句而不是
if
语句。 - 将三元表达式缩短为两个表达式的差值。
// 136 characters
const getNewNum = (num, goUp, arbitraryVal) => {
const oneOrZero = +!!arbitraryVal;
return num + (goUp ? oneOrZero : -oneOrZero);
}
删除
Number(Boolean())ifreturn;num
并
添加空白+!!?:
第 4 项可能有点令人困惑。这意味着return goUp ? num + oneOrZero : num - oneOrZero
我们不应该执行 ,而应该执行 ,return num + (goUp ? oneOrZero : -oneOrZero)
因为它们都有一个共同的因数 ( num
)。delta 是两个表达式的差值——唯一改变的是它。
在我们的例子中,只有 3 个字符的差异,但这些差异加起来可以提高可读性。我们也可以去掉括号,这样可以少写 2 个字符,但这样会比较难读。
现在我们先不要关心可读性。
简洁的混淆
重复使用前面的代码片段,我们可以采用更多的规则。
- 删除不必要的空格。
- 删除不必要的括号。
- 删除不必要的分号
- 使用单字符变量
let
更喜欢const
。
// 43 characters
let f=(a,b,c)=>{let d=+!!c;return a+(b?d:-d)}
删除缩短的变量名称constconst;()
并
添加空格letlet
说到这儿,所有关于“代码高尔夫”能提高代码可读性的讨论都泡汤了。代码几乎无法解读。
如果我们不再关心性能会怎么样?
- 如果可以节省空间,则优先重新评估表达式
- 优先创建全局变量,而不是使用、或定义
let
(const
假设var
没有“use strict”)
// 26 characters
f=(a,b,c)=>a+(b?+!!c:-!!c)
删除
let{}returnletdd+
并
添加空格!!c
通过将 放在+!!
三元运算符内部,我们可以移除变量赋值,从而实现一行返回。在仅包含 return 语句的箭头函数中,我们可以移除括号。
我们还可以使用-!!
而不是-+!!
因为存在一元否定。
因此,我们几乎抛弃了所有可读性和最佳实践,将函数长度从 345 个字符缩减到了 26 个字符——不到原始大小的 8%!哇!
让我们更进一步,揭示一些不常用的 JS 技巧。
表达式求值
从某种意义上说,所有函数和赋值都是表达式。很多时候函数会返回undefined
,但它仍然是。这给了我们很大的力量来缩短代码。
是时候深入了解更多片段了!
// empty function
const myFunc = () => {};
myFunc() // -> undefined
!!myFunc() // -> false
4 + +!myFunc() // -> 5
// assignments as expressions
let a = 1;
let b = 1;
let c = 1;
c += b += a += 1;
a // -> 2
b // -> 3
c // -> 4
// 2 operations at once
let i = 0;
console.log(i++); // logs 0 and now i is 1
console.log(++i); // logs 2 and now i is 2
需要注意的是,声明不会返回任何内容(甚至包括 undefined),因此它不是表达式。你不能let a = 3
在表达式的任何地方记录或使用它(但你可以这样做let a = b = c
)。
既然知道这些都是可以表达的(新词),让我们来回顾一下一段经常被遗忘的 JS 代码。以下是一段有效的 JS 代码:
// ignore this. Just initializations.
let a = 0, b = 0, myFunc = (num) => console.log(num);
let c = (a++, b+=a, myFunc(2), a+=b, b + a); // > 2
c // -> 3
刚才发生了什么?如果你用过 C++,这种行为可能很熟悉。在 JS 中,我们可以在括号内编写逗号分隔的表达式。这些表达式会从左到右进行求值,然后返回最右边的表达式。
就我们而言,我们做了很多本来可以在他们自己的生产线上完成的事情。
这在什么时候有用?嗯,在大多数情况下它用处不大,因为我们可以直接去掉括号,用分号代替。最有用的地方是在while
循环、for
循环和缩短箭头函数中。
// prints 0-9 inclusive
let i = 0;
while (console.log(i++), i < 10);
// prints 0-9 inclusive
for (j = 0; console.log(j++), j < 10;);
// 32 characters
a=>{r=0;a.map(n=>r+=n);return r}
// 25 characters
a=>(r=0,a.map(n=>r+=n),r)
// a reduce would do the same thing and be only 23 characters, I know
就循环而言,我们甚至不需要括号;它们是可选的。我们已经创建了功能齐全、没有循环主体的 for 和 while 循环。请务必使用分号,以免循环意外地在其下方循环出一些随机语句。
需要注意的是,只要 for 循环的括号包含两个分号,我们也可以省略 for 循环的某些部分。由于我们的表达式,只要最后一个表达式是布尔值(或求值/强制转换为布尔值),括号的最后一部分(第二个分号之后)基本上是无用的。
我们也可以使用 evil 将非表达式转换为表达式eval()
。通常建议避免使用 evil,但也有一些用例,例如代码高尔夫。它总是返回最后一个表达式,因此我们可以用它将箭头函数的长度减少 3 个字符。目前我们节省了一些钱,但从长远来看,这可能是值得的。
// 43 characters
a=>{for(var o="",i=0;i<a;i++)o+=i;return o}
// 40 characters
a=>eval('for(let o="",i=0;i<a;i++)o+=i')
充分利用
JS 中有很多巧妙而奇特的地方,它们揭示了一些非常有趣的行为。我们可以利用这些行为来缩短代码。
这些技巧中更常见的是使用按位运算符将浮点数强制转换为整数。通常我们会使用诸如 、 或 之类的运算符Math.floor()
,Math.ceil()
但Math.round()
这些运算符占用太多字符。
我们可以使用位运算符(其副作用是截断小数)来实现类似的操作,但只需要 2 个字符。使用~~
或运算符,|0
我们可以执行不执行任何操作的位运算。由于它们在计算结果时会截断小数,因此我们得到一个不带小数的数字。
// 31 characters
Math.floor(Math.random() * 100)
// 21 characters
~~(Math.random()*100)
// 19 characters
Math.random()*100|0
需要注意两点:1. 截断的结果与处理负数时的结果不同Math.floor()
。2. 位运算符的执行顺序与乘法相同。可以参考 PEMDAS,但在 M 或 D 旁边加上一个 B。这就是为什么第二个位运算符示例不需要括号,而第一个需要的原因。
另一个你可能熟悉的行为是短路求值。它处理与运算&&
符||
,并允许我们节省大量空间。
// logs thing and then returns it
const logIt = thing => (console.log(thing), thing)
logIt(0) || logIt('hey') // logs both since 0 is falsy
logIt('hey') || logIt(0) // only logs 'hey' since 'hey' is truthy
logIt('hey') && logIt(0) // logs both since 'hey' is truthy
logIt(0) && logIt('hey') // only logs 0 since 0 is falsy
它被多次使用,根据第一个函数的返回值来执行一个或两个函数。如果你希望第二个函数仅在函数为真时执行,请使用&&
。如果你希望第二个函数仅在函数为假时执行,请使用||
。
&&
并且||
还可以用来检索假值或真值。
const theFalsyOne = '' && 100; // it is ''
const theTruthyOne = '' || 100; // it is 100
如果两个都是真值,&&
则返回第二个值并||
返回第一个值。如果两个都是假值,&&
则返回第一个值并||
返回第二个值。此行为也是由于短路求值造成的。
最后的行为围绕着valueOf
。有一个有趣的问题,关于 if (a==1 && a==2 && a==3) 是否可以求值为true
,答案valueOf
也与 有关。
我们可以创建在连接和数学运算中看起来像原始值的对象。如果我们在这些情况下使用对象,JS 会检查其valueOf
属性并将其强制转换为原始值。我们可以用它做一些很酷的事情,但我发现最常见的用法是Math.random()
const makeEllipse = (x, y, width, height) => {
// do stuff
}
// 91 characters
makeEllipse(Math.random() * 50, Math.random() * 50, Math.random() * 10, Math.random() * 10)
// 60 characters
let r={valueOf:Math.random}
makeEllipse(r*50,r*50,r*10,r*10)
当您必须定义一个新对象并包含原始函数时,显然需要权衡,但如果您充分使用它,它有助于缩短它。
节省开支
在真正的代码高尔夫比赛中,每个字符都至关重要。如果可以删掉一个字符,那就删掉吧。以下是一些可以节省字符的技巧。
${}
当两个字符串之间有空格时,使用而不是连接++
。这样可以节省一个字符。
// 27 characters
'You are '+age+' years old'
// 26 characters
`You are ${age} years old`
你可以将常规函数用作带标签的模板字面量,只要该函数将其请求的字符串用作字符串即可。这样可以节省 2 个字符。
// 16 characters
myArr.split('.')
// 14 characters
myArr.split`.`
这不适用于诸如此类的情况eval
,因为它们不将输入视为字符串。您还必须尝试不包含任何内容,${}
因为标记模板文字会接收来自不同参数的变量。
如果有无限循环,请使用for(;;i++)
over while(true)i++
。这样可以节省 4 个字符。i++
在本例中, 是每次迭代都会调用的表达式或函数。
最后,数字也可以用科学计数法来表示。除了1000
,您还可以使用 ,1e3
其计算结果相同。这样可以节省 1000 个字符,但随着数字的增加,ROI 会迅速上升。
结束语
玩代码高尔夫很有趣。练习也能让你学到很多关于这门语言的知识。
我当然没法涵盖所有 JS 代码高尔夫技巧,但希望我已经涵盖了相当一部分。强烈建议大家看看这个帖子,了解更多 JS 代码高尔夫技巧。
如果您想开始代码高尔夫,我强烈建议您查看https://codegolf.tk/和https://www.dwitter.net/
这些网站使用代码高尔夫和画布以极少的字符创建精美的可视化效果。
感到困惑?还有其他技巧想分享吗?欢迎留言告诉我!
鏂囩珷鏉ユ簮锛�https://dev.to/emnudge/js-code-golfing-how-to-ruin-everyone-s-day-40h3