4.7 运算符概述
JavaScript 中的运算符用于算术表达式、比较表达式、逻辑表达式、赋值表达式等。下表简单列出了 JavaScript 中的运算符,作为一个方便的参照。
需要注意的是,大多数运算符都是由标点符号表示的,比如“+”和“
=
”。而另外一些运算符则是由关键字表示的,比如 delete 和 instanceof。关键字运算符和标点符号所表示的运算符一样都是正规的运算符,它们的语法都非常言简意赅。
下表是按照运算符的优先级排序的,前面的运算符优先级要高于后面的运算符优先级。被水平分割线分隔开来的运算符具有不同的优先级。标题为 A的列表示运算符的结合性,L (从左至右)或R (从右至左),标题为N的列表示操作数的个数。标题为“类型”的列表示期望的操作数类型,以及运算符的结果类型(在“→”符号之后)。下表之后的段落会解释优先级、结合性和操作数类型的概念。下表只对运算符做单独讨论。
表4-1:JavaScript运算符
4.7.1 操作数的个数
运算符可以根据其操作数的个数进行分类。JavaScript 中的大多数运算符(比如“
*
”乘法运算符)是一个二元运算符(binary operator),将两个表达式合并成一个稍复杂的表达式。换言之,它们的操作数均是两个。JavaScript同样支持一些一元运算符(unary operator),它们将一个表达式转换为另一个稍复杂的表达式。表达式-x中的运 算符就是一个一元运算符,是将操作数x求负值。最后,JavaScript 支持一个三元运算符(ternary operator),条件判断运算符“?:”,它将三个表达式合并成一个表达式。
4.7.2 操作数类型和结果类型
一些运算符可以作用于任何数据类型,但仍然希望它们的操作数是指定类型的数据,并且大多数运算符返回(或计算出)一个特定类型的值。在表(JavaScript运算符)标题为“类型”的列中列 出了运算符操作数的类型(箭头前)和运算结果的类型(箭头后)。
JavaScript 运算符通常会根据需要对操作数进行类型转换。乘法运算符 “
*
”希望操作数为数字,但表达式却是合法的,因为 JavaScript 会将操作数转换为数宇。这个表达式的值是数字15,而不是字符串“15”。之前也提到过,JavaScript 中的所有值不是真值就是假值,因此对于那些希望操作数是布尔类型的操作符来说,它们的操作数可以是任意类型。
有一些运算符对操作数类型有着不同程度的依赖。最明显的例子是加法运算符,“+” 运算符可以对数字进行加法运算,也可以对字符串作连接。同样,比如“<”比较运算符可以根据操作数类型的不同对数字进行大小值的比较,也可以比较字符在字母表中的次序先后。单个运算符的描述充分解释了它们对类型有着怎样的依赖以及对操作数进行怎样的类型转换。
4.7.3 左值
你可能会注意到, 表4-1中的赋值运算符和其他少数运算符期望它们的操作数是lval类型。左值(lvalue)是一个古老的术语,它是指“表达式只能出现在赋值运算符的左侧”。在 JavaScript 中,变量、对象属性和数组元素均是左值。ECMAScript 规范允许内置函数返回一个左值,但自定义的函数则不能返回左值。
4.7.4 运算符的副作用
计算一个简单的表达式(比如2*3)不会对程序的运行状态造成任何影响,程序后续执行的计算也不会受到该计算的影响。而有一些表达式则具有很多副作用,前后的表达式运算会相互影响。赋值运算符是最明显的一个例子:如果给一个变量或属性赋值,那么那些使用这个变量或属性的表达式的值都会发生改变。"++”和递增和递减运算符与此类似,因为它们包含隐式的赋值。delete 运算符同样有副作用:删除一个属性就像(但不完全一样)给这个属性赋值undefined。
其他的 JavaScript 运算符都没有副作用,但函数调用表达式和对象创建表达式有些特别,在函数体或者构造函数内部运用了这些运算符并产生了副作用的时候,我们说函数调用表达式和对象创建表达式是有副作用的。
4.7.5 运算符优先级
表4-1中所示的运算符是按照优先级从高到低排序的,每个水平分割线内的一组运算符具有相同的优先级。运算符优先级控制着运算符的执行顺序。优先级高的运算符(表格的顶部)的执行总是先于优先级低(表格的底部)的运算符。
看一下下面这个表达式:
w = x + y*z;
var hzhW;
var hzhX = 1;
var hzhY = 2;
var hzhZ = 3;
console.log("计算hzhW的值:");
console.log(hzhW = hzhX + hzhY*hzhZ);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
计算hzhW的值:
7
[Done] exited with code=0 in 0.192 seconds
乘法运算符“*”比加法运算符“+”具有更高的优先级,所以乘法先执行,加法后执行。然后,由于赋值运算符“=”具有最低的优先级,因此赋值操作是在右侧的表达式计算出结果后进行的。
运算符的优先级可以通过显式使用圆括号来重写。为了让加法先执行,乘法后执行,可以这样写:
w = (x + y)*z;
需要注意的是,属性访问表达式和调用表达式的优先级要比表4-1中列出的所有运算符都要高。看一下这个例子:
typeof my.functions[x](y)
尽管 typeof 是优先级最高的运算符之一,但 typeof 也是在两次属性访问和函数调用之后执行的。
实际上,如果你真的不确定你所使用的运算符的优先级,最简单的方法就是使用圆括号来强行指定运算次序。有些重要规则需要熟记:乘法和除法的优先级高于加法和减法, 赋值运算的优先级非常低,通常总是最后执行的。
4.7.6 运算符的结合性
在表4-1中标题为A的列说明了运算符的结合性。L指从左至右结合,R指从右至左结合。
结合性指定了在多个具有同样优先级的运算符表达式中的运算顺序。从左至右是指运算的执行是按照由左到右的顺序进行。例如,减法运算符具有从左至右的结合性,因此:
w = x - y - z;
var hzhW;
var hzhX = 3;
var hzhY = 2;
var hzhZ = 1;
console.log("计算hzhW的值:");
console.log(hzhW = hzhX - hzhY - hzhZ);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
计算hzhW的值:
0
[Done] exited with code=0 in 0.471 seconds
和这段代码一模一样:
W = ((X - y) - z);
var hzhW;
var hzhX = 3;
var hzhY = 2;
var hzhZ = 1;
console.log("计算hzhW的值:");
console.log(hzhW = ((hzhX - hzhY) - hzhZ));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
计算hzhW的值:
0
[Done] exited with code=0 in 0.219 seconds
反过来讲,下面这个表达式:
x = ~-y;
w = x = y = z;
q = a?b:c?d:e?f:g;
和这段代码一模一样:
x = ~(-y); w = (x = (y = z)); q =
a?b:(c?d:(e?f:g));
var hzh1 = 1;
var hzhX = ~-hzh1;
var hzhY = ~(-hzh1);
console.log("判断hzhX和hzhY是否相等:");
console.log(hzhX == hzhY);
console.log("");
var hzh2 = 2;
var hzh3 = 3;
var hzh4 = 4;
var hzhA = hzh2 = hzh3 = hzh4;
var hzhB = (hzh2 = (hzh3 = hzh4));
console.log("判断hzhA和hzhB是否相等:");
console.log(hzhA == hzhB);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
判断hzhX和hzhY是否相等:
true
判断hzhA和hzhB是否相等:
true
[Done] exited with code=0 in 0.338 seconds
因为一元操作符、赋值和三元条件运算符都具有从右至左的结合性。
4.7.7 运算顺序
运算符的优先级和结合性规定了它们在复杂的表达式中的运算顺序,但并没有规定子表达式的计算过程中的运算顺序。JavaScript 总是严格按照从左至右的顺序来计算表达式。 例如,在表达式 w=x+y*z 中,将首先计算子表达式 w,然后计算 x、y 和 z,然后,y 的值和 z 的值相乘,再加上 x 的值,最后将其赋值给表达式 w 所指代的变量或属性。给表达式添加圆括号将会改变乘法、加法和赋值运算的关系,但从左至右的顺序是不会改变的。
只有在任何一个表达式具有副作用而影响到其他表达式的时候,其求值顺序才会和看上去有所不同。如果表达式 x 中的一个变量自增1,这个变量在表达式 z 中使用,那么实际上是先计算出了 x 的值再计算 z 的值,这一点非常重要。
注
假设存在a=1,那么"b=(a++)+a;”将如何计算结果呢?按照正文所述,顺序应该是,1)计算b,2)计算a++ (假设值为c), 3))计算a,4)计算c+a,,5)将c+a的结果赋值给b。按照"++”的定义,第2)步中a++的结果依然是1,即c为1,随后a立即增1,因此在执行第3)步时,a的值已经是2。所以b的结果为3。很多初学者会误认为a增1的操作是在表达式计算完毕后执行的。