什么时候使用逗号运算符有用?

115

我读了有关表达式中“逗号运算符”(,)和MDN文档这个问题,但我无法想到什么场景它是有用的。

那么,在什么情况下逗号运算符是有用的呢?


2
var i, j, k;var i; var j, var k 有什么区别? - Salman A
21
我不确定这与,运算符有任何关系。那行代码在C#中也是有效的,但是在C#中不存在,运算符。 - gdoron
2
@SalmanA。我找了,没有找到,请指教... - gdoron
6
在C#中,逗号(,)不总是逗号操作符,而且它从不是逗号操作符。因此,即使没有逗号操作符,C#仍然可以自由地使用逗号作为语法的一部分。请注意,这里不需要写解释。 - Seth Carnegie
9
这里的回答已经总结了“,”并不常用(而且并非每个“,”都是逗号运算符)的事实。但你可以借助它和数组来进行内联变量交换而不创建临时变量。假设你想要交换 ab 的值,你可以这样写:a = [b][b = a,0]。这会把当前的 b 放到数组中。第二个“[]”是属性访问符号。被访问的索引是 0,但是在将 a 分配给 b 之前,这不会发生,因为现在 b 被保留在数组中。逗号运算符让我们在“[]”中执行多个表达式。 - user1106925
显示剩余6条评论
15个回答

159

以下内容可能对自己编写代码没有太大用处,但压缩程序可以使用逗号操作符来缩小代码。例如:

if(x){foo();return bar()}else{return 1}

将成为:

return x?(foo(),bar()):1

现在可以使用三元运算符? :,因为逗号运算符(在某种程度上)允许将两个语句写成一个语句。

这样做的好处在于它可以实现一些整洁的压缩(39->24字节)。


我想强调的是,在var a, b中的逗号不是逗号运算符,因为它不存在于一个表达式中。在var 语句中,逗号具有特殊含义。a, b在表达式中将引用这两个变量,并且求值为b,而在var a, b中不是这种情况。


3
你是怎么想到的?你在哪里读到的?真的有人使用吗? - gdoron
22
前几天我玩了一下闭包编译器,想了解它的实际作用,然后我注意到了这种替换。 - pimvdb
3
我认为在你的代码中有一个类似的用法很有用,可以用于在一行if语句中同时赋值给多个变量。例如:if (条件) 变量1 = 值1, 变量2 = 值2; 我个人认为,在可能的情况下避免使用括号可以使代码更易读。 - Aidiakapi
2
关于逗号运算符,我唯一使用它的时候是在表达式中添加日志语句(foo => (console.log('foo', foo), foo)),或者当我在使用reduce迭代器时变得过于聪明时。(pairs.reduce((acc, [k, v]) => (acc[k] = v, acc), {})) - Joseph Sikorski

51

逗号运算符允许您在需要一个表达式的地方放置多个表达式。 由逗号分隔的多个表达式的结果值将是最后一个表达式的值。

我个人并不经常使用它,因为很少有情况需要多个表达式,并且没有比使用逗号运算符更清晰的编写代码的方法。一个有趣的可能性是在 for 循环结束时,当您想要增加多个变量时使用逗号运算符:

// j is initialized to some other value
// as the for loop executes both i and j are incremented
// because the comma operator allows two statements to be put in place of one
for (var i = 0; i < items.len; i++, j++) {
    // loop code here that operates on items[i] 
    // and sometimes uses j to access a different array
}

在这里,您可以看到i ++,j ++可以放置在允许一个表达式的位置。 在这种特殊情况下,多个表达式用于副作用,因此复合表达式采用最后一个表达式的值并不重要,但在其他情况下可能会更加重要。


47
逗号运算符在JavaScript编写功能代码时非常有用。考虑我之前为单页应用程序编写的以下代码:
const actions = _.chain(options)
                 .pairs() // 1
                 .filter(selectActions) // 2
                 .map(createActionPromise) // 3
                 .reduce((state, pair) => (state[pair[0]] = pair[1], state), {}) // 4
                 .value();

这是一个相当复杂但真实的情景。在我解释发生了什么的过程中,请耐心等待,并借此说明逗号运算符的用例。
这里使用Underscore的链式调用来:
  1. 使用 pairs 把传递给该函数的所有选项拆分开,将{a: 1, b: 2}变成[['a', 1], ['b', 2]]

  2. 此属性对数组被过滤为系统中被视为“操作”的属性。

  3. 然后,使用map将数组中第二个索引替换为表示该操作的 Promise 的函数

  4. 最后,调用reduce将每个 "property array" (['a', 1])合并回一个最终对象中。

最终结果是转换后的options参数版本,其中只包含适当的键,其值可以被调用函数使用。


仅看

.reduce((state, pair) => (state[pair[0]] = pair[1], state), {})

你可以看到,reduce函数从一个空的状态对象state开始,对于每对表示键和值的元素,该函数返回相同的state对象,在将属性添加到与键/值对应的对象后。由于ECMAScript 2015的箭头函数语法,函数体是一个表达式,因此逗号运算符允许一个简洁且有用的"迭代器"函数。

就我个人而言,在使用ECMAScript 2015 +箭头函数以更功能化的方式编写JavaScript时,我遇到了许多案例。话虽如此,在遇到箭头函数之前(例如在编写问题时),我从未有意识地使用过逗号运算符。


3
这是关于程序员如何/何时使用逗号运算符的唯一真正有用的答案。尤其在“reduce”中非常有用。 - hgoebl
5
不错的回答,但如果我可以建议你稍微改进一下,让它更容易阅读:.reduce((state, [key, value]) => (state[key] = value, state), {})。我知道这样做有违回答本身的用意,但是.reduce((state, [key, value]) => Object.assign(state, { [key]: value }), {})可以消除逗号运算符的需要。 - Patrick Roberts
2
虽然现在Object.assign可能更常用,甚至只是使用展开运算符,但我不确定它们在当时是否被广泛使用。我也指出,虽然逗号操作符有点更晦涩,但在数据集非常大的情况下,这可能会产生更少的垃圾。解构肯定会提高可读性! - Syynth

21

逗号运算符的另一个用途是在repl或控制台中隐藏不关心的结果,仅出于方便而这样做。

例如,如果您在repl或控制台中评估myVariable = aWholeLotOfText,它将打印您刚刚分配的所有数据。 这可能是页数和页数,如果您不想看到它,您可以改为评估myVariable = aWholeLotOfText,'done',则 repl/console 将只打印'done'。

Oriel正确指出,自定义的toString()get()函数甚至可能使这个方法更有用。


2
哈,非常好的想法!(终于有一个回答实际上回答了问题,而不像几乎所有的答案{还有3个已删除的答案,需要20K声望才能看到...}) - gdoron
1
如果分配的值是一个对象,控制台可能会尝试以漂亮的方式显示它。为了做到这一点,它可能会调用getter,这可能会改变对象的状态。使用逗号可以防止这种情况发生。 - Oriol
1
@Oriol - 很好!你完全正确。不知怎么的,这个可能有用的感觉让人有点失望 :) - Julian de Bhal

14

逗号运算符不仅仅是JavaScript特有的,它在其他语言中也可用,例如C和C++。作为一个二元操作符,当第一个操作数通常是一个表达式时,它非常有用,因为第一个操作数具有第二个操作数所需的期望副作用。维基百科上的一个例子如下:

i = a += 2, a + b;

显然,你可以编写两行不同的代码,但使用逗号是另一种选项,有时更易读。


1
认为这是一种替代方案,尽管好的定义可能因人而异。然而,我找不到任何必须使用逗号的例子。另一个类似的事情是三元 ?: 运算符。它总是可以被 if-else 替换,但有时 ?: 使代码比 if-else 更易读。逗号的概念也是如此。 - taskinoor
顺便说一下,我不考虑在变量声明中使用逗号或在循环中初始化多个变量。在这些情况下,逗号通常更好。 - taskinoor
3
这看起来非常令人困惑...什么鬼。 - Timmerz
“更易读”:可以举个例子吗? - Philippe-André Lorin

8
我不同意Flanagan的观点,逗号在编程中非常有用,可以使代码更易读、更优雅,特别是当你知道自己在做什么时:
这里有一篇非常详细的文章介绍了逗号的使用方法:
以下是其中几个例子以证明逗号的实用性:
function renderCurve() {
  for(var a = 1, b = 10; a*b; a++, b--) {
    console.log(new Array(a*b).join('*'));
  }
}

一个斐波那契数列生成器:
for (
    var i=2, r=[0,1];
    i<15;
    r.push(r[i-1] + r[i-2]), i++
); 
// 0,1,1,2,3,5,8,13,21,34,55,89,144,233,377

查找第一个父元素,类似于jQuery的.parent()函数:

function firstAncestor(el, tagName) {
    while(el = el.parentNode, el && (el.tagName != tagName.toUpperCase()));
    return el;
}

//element in http://ecma262-5.com/ELS5_HTML.htm
var a = $('Section_15.1.1.2'); 

firstAncestor(a, 'div'); //<div class="page">

9
我不确定我是否会说任何一种方法都更容易阅读,但肯定很漂亮,所以加一分。 - Chris Marisic
2
在最后一个例子中,while循环不需要逗号,while ((el = el.parentNode) && (el.tagName != tagName.toUpperCase()))在上下文中完全可以。 - PitaJ

7
我发现逗号操作符在编写此类辅助程序时非常有用。
const stopPropagation = event => (event.stopPropagation(), event);
const preventDefault = event => (event.preventDefault(), event);
const both = compose(stopPropagation, preventDefault);

您可以将逗号替换为||或&&,但是您需要知道函数返回值。

更重要的是,逗号分隔符传达了意图 - 代码不关心左操作数的求值结果,而其他选择可能有其存在的原因。这反过来使得代码更易于理解和重构。如果函数返回类型发生更改,则上面的代码不会受到影响。

当然,您可以以其他方式实现相同的效果,但不如逗号运算符简洁。如果||和&&在常用情况下被使用,那么逗号运算符也可以。


2
类似于 Ramda\Lodash 中的 tap 函数(https://ramdajs.com/docs/#tap)所做的事情。本质上,您执行了一个副作用,然后返回初始值;在函数式编程中非常有用 :) - avalanche1

6

除此之外,我还没有发现它的实际用途,但是这里有一个场景,James Padolsey 很好地运用了这个技巧来检测IE在while循环中:

var ie = (function(){

    var undef,
        v = 3,
        div = document.createElement('div'),
        all = div.getElementsByTagName('i');

    while ( // <-- notice no while body here
        div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
        all[0]
    );

    return v > 4 ? v : undef;

}());

这两行代码必须执行:
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
all[0]

在逗号操作符内部,虽然可以将它们分为两个语句,但两者都会被评估。

4
这本来可以用“do-while”循环实现。 - Casey Chu

6

在JavaScript中有一种“奇怪”的方法,可以通过使用逗号运算符间接调用函数。

这里有一个详细的描述: JavaScript中的间接函数调用

通过使用以下语法:

(function() {
    "use strict";
  
    var global = (function () { return this || (1,eval)("this"); })();
    console.log('Global === window should be true: ', global === window);
  
    var not_global = (function () { return this })();
    console.log('not_global === window should be false: ', not_global === window);
  
  }());

您可以访问全局变量,因为eval在直接调用和间接调用时的工作方式不同。


2

我今天刚看到这个,看了一下管道操作符的提议和部分应用程序......

此外,即使今天不引入新的语法,Hack风格的管道也已经是可行的:
let $; // Hack-style topic variable
let result = (
  $= books,
  $= filter($, _ => _.title = "..."),
  $= map($, _ => _.author),
  $);

这里使用逗号表达式可以模拟尚未出现在语言中的管道运算符。
消除 $= 之间的空格可以模拟正确的管道符号 |> 的感觉。请注意,“主题”变量 $ 可以是任何内容,它只是缩写,表示反复覆盖变量。因此,更像是...
// blocking inside an IIFE
let result = (() => {
  let $;
  $ = books;
  $ = filter($, _ => _.title = "..."),
  $ = map($, _ => _.author),
  return $;
})()

“逗号”版本成功地削减了一些噪音,使你更接近于提案的内容:
let result = books
  |> filter($, _ => _.title = "..."
  |> map($, _ => _.author)

这是另一个例子,展示如何使用它来组合函数:

const double = (x) => 2 * x;
const add = (x, y) => x + y;
const boundScore = (min, max, score) => Math.max(min, Math.min(max, score));


const calculateScore = ($) => (
  $= double($),
  $= add($, 20),
  $= boundScore(0, 100, $),
  (console.log($), $)
)

const score = calculateScore(28)


1
在最后一行,您不需要使用 return 或单个 $,默认情况下将返回最后分配的值。 - icetbr

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