我读过这个关于表达式 ( ,
) 中“逗号运算符”的问题和关于它的MDN 文档,但我想不出它有用的场景。
那么,逗号运算符什么时候有用呢?
以下内容可能不是很有用,因为您不是自己编写的,但是缩小器可以使用逗号运算符缩小代码。例如:
if(x){foo();return bar()}else{return 1}
会成为:
return x?(foo(),bar()):1
现在可以使用该? :
运算符,因为逗号运算符(在一定程度上)允许将两个语句写成一个语句。
这很有用,因为它允许进行一些简洁的压缩(此处为 39 -> 24 字节)。
我想强调一个事实,逗号 invar a, b
不是逗号运算符,因为它不存在于expression中。var
逗号在语句中具有特殊含义。a, b
在表达式中将引用两个变量并计算为b
,而var a, b
.
用 Javascript 编写函数式代码时,逗号运算符经常很有用。
考虑一下我不久前为 SPA 编写的这段代码,其内容类似于以下内容
const actions = _.chain(options)
.pairs() // 1
.filter(selectActions) // 2
.map(createActionPromise) // 3
.reduce((state, pair) => (state[pair[0]] = pair[1], state), {}) // 4
.value();
这是一个相当复杂但真实的场景。在我解释正在发生的事情时请耐心等待,并在此过程中为逗号运算符提出理由。
拆开传递给此函数的所有选项,使用pairs
which 将{ a: 1, b: 2}
变成[['a', 1], ['b', 2]]
该属性对数组通过哪些属性对被视为系统中的“动作”进行过滤。
然后数组中的第二个索引被一个函数替换,该函数返回一个代表该动作的承诺(使用map
)
最后,调用reduce
将把每个“属性数组”(['a', 1]
)合并回一个最终对象。
最终结果是options
参数的转换版本,它仅包含适当的键,并且其值可由调用函数使用。
看着只是
.reduce((state, pair) => (state[pair[0]] = pair[1], state), {})
你可以看到reduce函数以一个空的状态对象开始state
,并且对于代表一个键和值的每一对,该函数在state
为键/值对对应的对象添加一个属性后返回相同的对象。由于 ECMAScript 2015 的箭头函数语法,函数体是一个表达式,因此,逗号运算符允许一个简洁有用的“迭代”函数。
就我个人而言,我在使用 ECMAScript 2015 + 箭头函数以更实用的风格编写 Javascript 时遇到了许多案例。话虽如此,在遇到箭头函数之前(例如在撰写问题时),我从未以任何故意的方式使用逗号运算符。
逗号运算符允许您将多个表达式放在需要一个表达式的位置。 由逗号分隔的多个表达式的结果值将是最后一个逗号分隔的表达式的值。
我个人并不经常使用它,因为在很多情况下需要多个表达式,并且没有比使用逗号运算符更容易编写代码的方式。一种有趣的可能性是在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++
可以放在允许一个表达式的地方。在这种特殊情况下,多个表达式用于副作用,因此复合表达式采用最后一个的值并不重要,但在其他情况下这可能实际上很重要。
逗号运算符的另一个用途是在 repl 或控制台中隐藏您不关心的结果,纯粹是为了方便。
例如,如果您myVariable = aWholeLotOfText
在 repl 或控制台中进行评估,它将打印您刚刚分配的所有数据。这可能是页面和页面,如果您不想看到它,您可以改为评估myVariable = aWholeLotOfText, 'done'
,并且 repl/console 只会打印“完成”。
Oriel 正确地指出†</sup>自定义toString()
或get()
函数甚至可能使它有用。
逗号运算符不是 JavaScript 特有的,它在C 和 C++等其他语言中可用。作为二元运算符,当第一个操作数(通常是表达式)具有第二个操作数所需的副作用时,这很有用。来自维基百科的一个例子:
i = a += 2, a + b;
显然,您可以编写两行不同的代码,但使用逗号是另一种选择,有时更具可读性。
我不同意弗拉纳根的观点,并说逗号非常有用,可以编写更易读、更优雅的代码,尤其是当你知道自己在做什么时:
这是关于逗号用法的非常详细的文章:
来自那里的几个示例用于演示证明:
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">
除此之外,我还没有发现它的实际用途,但这是James Padolsey在 while 循环中很好地使用这种技术进行 IE 检测的一个场景:
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]
在逗号运算符内部,虽然可以以某种方式将它们分开声明,但两者都被评估。
在 JavaScript 中通过使用逗号运算符间接调用函数可以完成一些“奇怪”的事情。
这里有很长的描述: Indirect function call in 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
直接调用与间接调用时的工作方式不同。
在编写这样的助手时,我发现逗号运算符最有用。
const stopPropagation = event => (event.stopPropagation(), event);
const preventDefault = event => (event.preventDefault(), event);
const both = compose(stopPropagation, preventDefault);
您可以将逗号替换为 || 或 &&,但是你需要知道函数返回什么。
更重要的是,逗号分隔符传达了意图——代码不关心左操作数的计算结果,而替代方案可能有另一个原因。这反过来又使它更容易理解和重构。如果函数返回类型发生变化,上面的代码不会受到影响。
当然,您可以通过其他方式实现相同的目标,但不是那么简洁。如果 || && 找到了一个常用的地方,逗号运算符也可以。
我最终使用它的一个典型案例是在可选参数解析期间。我认为它使它更具可读性和简洁性,因此参数解析不会支配函数体。
/**
* @param {string} [str]
* @param {object} [obj]
* @param {Date} [date]
*/
function f(str, obj, date) {
// handle optional arguments
if (typeof str !== "string") date = obj, obj = str, str = "default";
if (obj instanceof Date) date = obj, obj = {};
if (!(date instanceof Date)) date = new Date();
// ...
}
假设您有一个数组:
arr = [];
当您push
进入该数组时,您很少对push
的返回值感兴趣,即数组的新长度,而是数组本身:
arr.push('foo') // ['foo'] seems more interesting than 1
使用逗号运算符,我们可以压入数组,将数组指定为逗号的最后一个操作数,然后将结果(数组本身)用于后续数组方法调用,一种链接:
(arr.push('bar'), arr.push('baz'), arr).sort(); // [ 'bar', 'baz', 'foo' ]
它使您免于return
在嵌套条件中使用,并且非常方便,尤其是使用三元运算符。如;
function insert(v){
return this.node > v ? this.left.size < this.right.size ? ( this.left.insert(v)
, this
)
: ( this.left.insert(this.node)
, this.node = this.right.popmin()
, this.insert(v)
, this
)
: this.left.size < this.right.size ? ( this.right.insert(this.node)
, this.node = this.left.popmax()
, this.insert(v)
, this
)
: ( this.right.insert(v)
, this
)
}
我今天刚刚在查看管道运营商提案和部分申请的提案时遇到了这个......
此外,在不引入新语法的情况下,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)
逗号运算符 (,) 计算其每个操作数(从左到右)并返回最后一个操作数的值。这使您可以创建一个复合表达式,在其中计算多个表达式,复合表达式的最终值是其最右边的成员表达式的值。这通常用于为 for 循环提供多个参数。
让 x = 1;
x = (x++, x);
控制台.log(x); // 预期输出:2
x = (2, 3);
控制台.log(x); // 预期输出:3
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
可以使用逗号运算符的另一个领域是代码混淆。
假设开发人员编写了一些这样的代码:
var foo = 'bar';
现在,她决定混淆代码。使用的工具可能会像这样更改代码:
var Z0b=(45,87)>(195,3)?'bar':(54,65)>(1,0)?'':'baz';// Z0b == 'bar'
演示:http: //jsfiddle.net/uvDuE/