9月阅读周·JavaScript权威指南:表达式和运算符,运算符篇
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读八个月。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》。
当前阅读周书籍:《JavaScript权威指南》。
运算符
JavaScript中的运算符用于算术表达式、比较表达式、逻辑表达式、赋值表达式等。
需要注意的是,大多数运算符都是由标点符号表示的,比如“+”和“=”。而另外一些运算符则是由关键字表示的,比如delete和instanceof。关键字运算符和标点符号所表示的运算符一样都是正规的运算符,它们的语法都非常言简意赅。
表1-1是按照运算符的优先级排序的,前面的运算符优先级要高于后面的运算符优先级。被水平分割线分隔开来的运算符具有不同的优先级。标题为A的列表示运算符的结合性,L(从左至右)或R(从右至左),标题为N的列表示操作数的个数。标题为“类型”的列表示期望的操作数类型,以及运算符的结果类型(在“→”符号之后)。表1-1之后的段落会解释优先级、结合性和操作数类型的概念。表1-1只对运算符做单独讨论。
操作数的个数
运算符可以根据其操作数的个数进行分类。JavaScript中的大多数运算符(比如“*”乘法运算符)是一个二元运算符(binary operator),将两个表达式合并成一个稍复杂的表达式。换言之,它们的操作数均是两个。JavaScript同样支持一些一元运算符(unary operator),它们将一个表达式转换为另一个稍复杂的表达式。表达式-x中的“-”运算符就是一个一元运算符,是将操作数x求负值。最后,JavaScript支持一个三元运算符(ternary operator),条件判断运算符“?:”,它将三个表达式合并成一个表达式。
操作数类型和结果类型
一些运算符可以作用于任何数据类型,但仍然希望它们的操作数是指定类型的数据,并且大多数运算符返回(或计算出)一个特定类型的值。在表1-1标题为“类型”的列中列出了运算符操作数的类型(箭头前)和运算结果的类型(箭头后)。
JavaScript运算符通常会根据需要对操作数进行类型转换。乘法运算符“*”希望操作数为数字,但表达式"3"*"5"却是合法的,因为JavaScript会将操作数转换为数字。这个表达式的值是数字15,而不是字符串“15”。之前也提到过,JavaScript中的所有值不是真值就是假值,因此对于那些希望操作数是布尔类型的操作符来说,它们的操作数可以是任意类型。
有一些运算符对操作数类型有着不同程度的依赖。最明显的例子是加法运算符,“+”运算符可以对数字进行加法运算,也可以对字符串作连接。同样,比如“<”比较运算符可以根据操作数类型的不同对数字进行大小值的比较,也可以比较字符在字母表中的次序先后。单个运算符的描述充分解释了它们对类型有着怎样的依赖以及对操作数进行怎样的类型转换。
左值
你可能会注意到,表1-1中的赋值运算符和其他少数运算符期望它们的操作数是lval类型。左值(lvalue)是一个古老的术语,它是指“表达式只能出现在赋值运算符的左侧”。在JavaScript中,变量、对象属性和数组元素均是左值。ECMAScript规范允许内置函数返回一个左值,但自定义的函数则不能返回左值。
运算符的副作用
计算一个简单的表达式(比如2*3)不会对程序的运行状态造成任何影响,程序后续执行的计算也不会受到该计算的影响。而有一些表达式则具有很多副作用,前后的表达式运算会相互影响。赋值运算符是最明显的一个例子:如果给一个变量或属性赋值,那么那些使用这个变量或属性的表达式的值都会发生改变。“++”和“--”递增和递减运算符与此类似,因为它们包含隐式的赋值。delete运算符同样有副作用:删除一个属性就像(但不完全一样)给这个属性赋值undefined。
其他的JavaScript运算符都没有副作用,但函数调用表达式和对象创建表达式有些特别,在函数体或者构造函数内部运用了这些运算符并产生了副作用的时候,我们说函数调用表达式和对象创建表达式是有副作用的。
运算符优先级
表1-1中所示的运算符是按照优先级从高到低排序的,每个水平分割线内的一组运算符具有相同的优先级。运算符优先级控制着运算符的执行顺序。优先级高的运算符(表格的顶部)的执行总是先于优先级低(表格的底部)的运算符。
看一下下面这个表达式:
w=x+y*z;
乘法运算符“*”比加法运算符“+”具有更高的优先级,所以乘法先执行,加法后执行。然后,由于赋值运算符“=”具有最低的优先级,因此赋值操作是在右侧的表达式计算出结果后进行的。
运算符的优先级可以通过显式使用圆括号来重写。为了让加法先执行,乘法后执行,可以这样写:
w=(x+y)*z;
需要注意的是,属性访问表达式和调用表达式的优先级要比表1-1中列出的所有运算符都要高。看一下这个例子:
typeof my.functions[x](y)
尽管typeof是优先级最高的运算符之一,但typeof也是在两次属性访问和函数调用之后执行的。
实际上,如果你真的不确定你所使用的运算符的优先级,最简单的方法就是使用圆括号来强行指定运算次序。有些重要规则需要熟记:乘法和除法的优先级高于加法和减法,赋值运算的优先级非常低,通常总是最后执行的。
运算符的结合性
在表1-1中标题为A的列说明了运算符的结合性。L指从左至右结合,R指从右至左结合。结合性指定了在多个具有同样优先级的运算符表达式中的运算顺序。从左至右是指运算的执行是按照由左到右的顺序进行。例如,减法运算符具有从左至右的结合性,因此:
w=x-y-z;
和这段代码一模一样:
w=((x-y)-z);
反过来讲,下面这个表达式:
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));
因为一元操作符、赋值和三元条件运算符都具有从右至左的结合性。
总结
运算符的优先级和结合性规定了它们在复杂的表达式中的运算顺序,但并没有规定子表达式的计算过程中的运算顺序。JavaScript总是严格按照从左至右的顺序来计算表达式。例如,在表达式w=x+y*z中,将首先计算子表达式w,然后计算x、y和z,然后,y的值和z的值相乘,再加上x的值,最后将其赋值给表达式w所指代的变量或属性。给表达式添加圆括号将会改变乘法、加法和赋值运算的关系,但从左至右的顺序是不会改变的。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)