深入理解分组运算符 ( )

256 阅读3分钟

分组()运算符控制表达式中求值的优先级。它还充当某些语法结构中任意表达式的容器,否则会发生歧义或语法错误。

优先级

3 * (2 + 1) // 9

请注意,在上述示例中,运算符计算的顺序已更改,但操作数计算的顺序没有更改。例如,在此代码中,在考虑运算符顺序之前,函数调用a()、b()和c()从左到右计算(正常计算顺序)。

a() * (b() + c());

* + 表示运算符,3 2 1a() b() c() 表示操作数。

() 为所有运算符中最高优先级,在你不明确运算符的优先级时,可以用 () 显式的标明优先级。

使用分组运算符消除解析歧义

表达式语句不能以关键字函数开头,因为解析器会将其视为函数声明的开始。

无论这样写

// 这样写是错误的
function () {
  // code
}();

还是

// 这样写是错误的
function(){
  // code
}

都是会报错的。因为js的引擎会把这里的 function 看成是函数声明,而函数声明不允许没有函数名,因此会对匿名函数报错。

匿名函数只允许以表达式的形式存在,例如:

setTimeout(function(){
    /* 代码 */
}, 1000);

这里的匿名函数就是作为 setTimeout 的一个参数,是表达式,这种写法是允许的。

或者:

const foo = function(){
    /* 代码 */
};

把一个匿名函数赋值给一个变量,这是一个const语句,而匿名函数在该语句中充当函数表达式的角色。

如果这里的函数有名字呢?不会报错,但语义会发生变化。例如:

function foo(x){
    /* 代码 */
}(1);

控制台里会输出“1”。

原因是js引擎会认为前面的函数是一个函数声明的语句,而后面的(1)是另一个单独的语句,于是执行后面的语句,在控制台输出1。它实际上等价于:

function foo(x){
    /* 代码 */
};
(1);

分组运算符可用于消除这种歧义,因为当解析器看到左括号时,它知道后面的内容必须是表达式而不是声明。

(function () {
  // code
})();

或者使用一元运算符,void 提供了最好的语义,因为它明确表示函数调用的返回值应该被丢弃。

void function() {}()

()()的理解

(function (a, b) { 
  // code 
})(1, 2);

可以尝试把匿名函数赋值给 foo ,再执行 foo(1,2)

const foo = function(a,b) {}
foo(1, 2)

一道面试题

var a, b = 2
;(function() {
    console.log(a)
    console.log(b)
    a = (b = 5)
    var b
    console.log(a)
    console.log(b)
})()
console.log(a)

答案

undefined
undefined
5
5
5
2

解析:

var a, b = 2 //声明全局变量a、b,并给b赋值为2
;(function() {
    console.log(a) // undefined,a为全局变量没有被赋值
    console.log(b) // undefined,
    // 这里b为局部变量,因为在匿名函数中也声明了变量b并赋值为3,
    // 但var存在变量提升,只是声明提升,所以为undefined,并不会报错
    var b = 3
    a = (b = 5)
    console.log(a) // 5
    console.log(b) // 5
})()
console.log(a) // 5 a为全局变量,在匿名函数中被赋值为5
console.log(b) // 2 匿名函数中重新声明了局部变量b,匿名函数中对b的操作,不影响全局变量b的值

赋值运算符是右结合的,所以:

a = b = 5; // 相当于 a = (b = 5);

预期结果是 a 和 b 的值都会成为 5。这是因为赋值运算符的返回结果就是赋值运算符右边的那个值,具体过程是:首先 b 被赋值为 5,然后 a 也被赋值为 b = 5 的返回值,也就是 5。