表达式和操作符
表达式是JavaScript的一个短语,JavaScript解释器可以对其进行求值。
主表达式
最简单的表达式,称为主表达式,是那些独立的表达式——它们不包含任何简单的表达式。JavaScript中的主要表达式是常量或文字值、特定语言关键字和变量引用。
|
|
当任何标识符单独出现在程序中时,JavaScript假设它是一个变量,并查找它的值。如果不存在具有该名称的变量,则表达式计算为未定义的值。然而,在ES5的严格模式中,计算不存在的变量会抛出一个ReferenceError。
对象和数组初始化式
数组初始化式是方括号中包含的以逗号分隔的表达式列表。数组初始化式的值是一个新创建的数组。这个新数组的元素被初始化为逗号分隔表达式的值:
|
|
每次计算数组初始化式时,其中的元素表达式都会被计算。这意味着数组初始化式的值在每次求值时可能不同。通过在逗号之间省略值,可以将未定义的元素包含在数组字面值中:
|
|
在数组初始化式最后一个表达式后面允许有一个逗号,并且不会创建未定义的元素。
对象初始化表达式类似于数组初始化表达式,但是方括号被大括号替换,并且每个子表达式的前缀都有一个属性名和一个冒号:
|
|
函数定义表达式
函数定义表达式定义了一个JavaScript函数,该表达式的值是新定义的函数。
|
|
属性访问表达式
属性访问表达式求值为对象属性或数组元素的值。JavaScript为属性访问定义了两种语法:
|
|
对于任一种属性访问表达式,.或[之前的表达式先计算。如果其值为null或undefined,抛出TypeError。如果指定的属性不存在,则属性访问表达式的值为undefined。
调用表达式
调用表达式用于JavaScript调用函数或方法。
|
|
当对调用表达式求值时,首先对函数表达式求值,然后对参数表达式求值,以生成参数值列表。如果函数表达式的值不是可调用对象,则抛出TypeError。如果函数使用return语句返回值,则该值成为调用表达式的值。否则,调用表达式的值是undefined。
每一个调用表达式都包含一对括号和括号之前的表达式。如果该表达式是属性访问表达式,则调用称为方法调用。在方法调用中,对象或数组成为函数体执行时this参数的值。
非方法调用的调用表达式通常使用全局对象作为this关键字的值。在ES5中严格模式下定义的函数被调用时,this的值为undefined,而不是全局对象。
对象创建表达式
对象创建表达式创建一个新对象并调用一个函数(称为构造函数)来初始化该对象的属性。对象创建表达式类似于调用表达式,只是它们的前缀是关键字new:
|
|
如果在对象创建表达式中没有参数传递给构造函数,则可以省略括号:
|
|
当计算对象创建表达式时,JavaScript首先创建一个新的空对象,就像{}创建的对象一样。接下来,它使用指定的参数调用指定的函数,将新对象传递给this参数。然后函数可以使用this来初始化新创建对象的属性。构造函数不返回值,且对象创建表达式的值是新创建和初始化的对象。如果构造函数返回一个对象值,该值将成为对象创建表达式的值,新创建的对象将被丢弃。
操作符概述
左值
左值是一个历史术语,意思是“可以合法地出现在赋值表达式左边的表达式”。在JavaScript中,变量、对象属性和数组元素都是lvalue。ECMAScript规范允许内置函数返回左值,但没有任何以这种方式运行的函数。
操作符的副作用
有些表达有副作用,它们的计算会影响到将来计算的结果。赋值操作符是最明显的例子:如果将值赋给变量或属性,则会更改使用该变量或属性的任何表达式的值。++和–操作符类似,因为它们执行隐式赋值。delete操作符也有副作用:删除一个属性就像给这个属性赋值undefined。
没有其他JavaScript操作符有副作用,但是如果函数或构造函数中使用的任何操作符有副作用,那么函数调用和对象创建表达式就会有副作用。
运算符优先级
运算符优先控制执行操作的顺序。优先级较高的操作符在优先级较低的操作符之前执行。可以通过显式使用括号覆盖运算符优先级。属性访问和调用表达式的优先级高于上表任何操作符。
运算符结合性
运算符的结合性指定执行相同优先级的操作的顺序。从左到右的结合律意味着操作是从左到右执行的。
求值顺序
运算符优先级和结合性指定在复杂表达式中执行操作的顺序,但它们没有指定计算子表达式的顺序。JavaScript总是严格按照从左到右的顺序计算表达式。
算术表达式
基本的算术运算符是*、/、%、+和-。我们将在后面单独讨论+运算符。其他四个运算符只是计算它们的操作数,如果需要的话,将值转换为数字,然后计算。不能转换为数字的操作数转换为NaN。如果操作数是NaN,则结果也是NaN。
在JavaScript中,所有数字都是浮点数,因此所有除法操的结果都是浮点数。除以0得到正无穷或负无穷,而0/0得到NaN。
运算符%计算第一个操作数对第二个操作数的取模,结果的符号与第一个操作数的符号相同。模运算符通常与整数操作数一起使用,但它也适用于浮点值。
|
|
+运算符
如果有一个操作数是对象,使用对象到原始类型转换:日期对象使用toString()方法,其他对象使用valueOf()方法。大多数对象没有一个有用的valueOf()方法,因此它们也通过toString()进行转换。
- 如果任何一个操作数是字符串,那么另一个操作数将转换为字符串,并执行连接
- 将两个操作数转换为数字(或NaN)并执行加法。
|
|
一元运算符
在JavaScript中,一元运算符的优先级都很高,且都是右关联的。如果需要,算术一元运算符(+、-、++和–)都将其操作数转换为数字。
位运算符
位运算符将其操作数转换为数字,通过删除小数部分和超过32位的部分,将数值强制转换为32位整数。NaN,Infinity和-Infinity都转换为0。
&
运算符对操作数每个位执行与操作。只有两位都为1,结果才为1。
|
运算符对操作数每个位执行或操作。只有两位都为0,结果才为0。
^
运算符对操作数每个位执行异或操作。两位相同结果为0,两位不相同结果为1。
~
按位取反,相当于更改符号并减去1。
<<
左移运算符,将其第一个操作数中的所有位向左移动,移动的位置是第二个操作数指定的位数。
>>
右移操作符,将其第一个操作数中的所有位向右移动,移动的位置是第二个操作数指定的位数,左边填充符号位。
>>>
类似于>>
,只是左边总是填充0。
|
|
关系表达式
相等和不等操作符
JavaScript对象通过引用而不是值进行比较。严格相等运算符===计算其操作数,然后按照如下方式比较这两个值,不执行类型转换:
- 如果这两个值有不同的类型,它们就不相等
- 如果两个值都为null或undefined ,则它们相等。
- 如果两个值都是布尔值true或都是布尔值false,则它们相等。
- 如果一个或两个值都是NaN,则它们不相等。NaN值永远不等于任何其他值,包括它自己。
- 如果两个值都是数字且值相同,则它们相等。0和-0相等。
- 如果两个值是字符串且长度和内容相同则它们相等,否则它们不相等。
- 如果两个值引用相同的对象、数组或函数,则它们相等。如果它们引用不同的对象,它们就不相等,即使两个对象具有相同的属性。
==运算符没有===严格。如果两个操作数的值不是相同的类型,它会尝试一些类型转换并再次比较:
-
如果两个值具有相同的类型,则用===比较它们。
-
如果两个值的类型不相同,使用以下规则和类型转换检查是否相等
- 如果一个是null,另一个是undefined,它们相等。
- 如果一个是数字,另一个是字符串,将字符串转换为数字,再使用转换后的值进行比较
- 如果任一值为true或false,将其转换数字再进行比较。
- 如果一个是对象,另一个是数字或字符串,将对象转换原始类型再进行比较。
-
任何其他值的组合都不相等
比较运算符(<,>,<=,>=)
这些比较运算符的操作数可以是任何类型的。但是,比较只能在数字和字符串上执行,因此转换的操作数不是数字或字符串。比较和转换如下所示:
- 如果操作数是一个对象,将该对象转换为原始值。如果valueOf()方法返回一个原始值,则使用该值。否则使用toString()方法返回的值。
- 如果进行对象到原始值转换之后,两个操作数都是字符串,那么使用字母顺序比较这两个字符串。
- 如果进行对象到原始值转换之后,至少有一个操作数不是字符串,那么两个操作数都转换为数字再进行数值比较。如果有一个是NaN,总是返回false。
+偏爱字符串,如果任何一个操作数是字符串,它都会执行连接。比较运算符更喜欢数字,如果两个操作数都是字符串,才进行字符串比较。
|
|
in操作符
in操作符左边是字符串,右边是一个对象。如果左侧值是右侧对象的属性名,则结果为true:
|
|
instanceof操作符
instanceof操作符左侧是一个对象,右侧是一个类。如果左边的对象是右侧类的实例,则结果为true:
|
|
注意,所有对象都是Object的实例。instanceof在决定对象是否为类的实例考虑的是基类。如果instanceof的左边操作数不是对象,将返回false。如果右边不是函数,它会抛出TypeError 。
逻辑表达式(&&,||,!)
&&和||具有短路求值的特性:
|
|
赋值表达式
赋值运算符左侧是一个左值,右侧可以是任意类型的值。赋值表达式的值就是右侧操作数的值。作为副作用,赋值运算符将右侧的值赋给左侧的变量或属性。赋值运算符是右结合的。
大多数情况下,a op= b
等价于a = a op b
。只有当a包含了诸如函数调用或递增操作符等副作用时,它们不等价。
估值表达式
像许多解释语言一样,JavaScript能够解释JavaScript源代码字符串,并对其进行计算。JavaScript通过全局函数eval()来实现的。
eval()
如果传递一个字符串,eval()将尝试解析该字符串为JavaScript代码,如果失败则抛出SyntaxError。如果成功,则返回字符串中最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则返回undefined。如果该串代码抛出异常,eval()传播该异常。
如果传递其他值,则返回该值。
eval()会使用当前代码作用域的变量,也可以像本地代码一样定义变量和函数。
|
|
如果从顶级代码调用eval(),它将对全局变量和全局函数进行操作。
全局eval()
eval()更改本地变量的能力对JavaScript优化器来说是一个很大的问题。解释器对任何调用eval()的函数只做较少的优化。
ES5废弃了EvalError并标准化了eval()的行为。直接调用eval()使用调用上下文的变量环境。任何其他调用都使用全局对象作为其变量环境,不能读写或定义局部变量和函数:
|
|
在IE9之前,当使用不同的名称调用eval()时,它不会执行全局eval。也不会抛出一个EvalError,它只做局部运算。但IE定义了一个execScript()的全局函数,它会像执行顶级脚本一样执行字符串参数。然而,与eval()不同的是,execScript()总是返回null。
严格eval()
ES5严格模式进一步限制eval()函数的行为,甚至是标识符eval的使用。当在严格模式中调用eval()时,eval()用私有变量环境执行本地eval。这意味着在严格模式下,求值代码可以查询和设置局部变量,但不能在局部作用域中定义新的变量或函数。
此外,严格eval()更像操作符。不允许使用新值覆盖eval()函数,且不允许使用“eval”名称声明变量、函数、函数参数或catch块参数。
其他运算符
条件运算符(?:)
条件运算符的操作数可以是任何类型的。如果第一个操作数的值为真,则计算第二个操作数,并返回其值。否则,计算第三个操作数并返回其值。
typeof运算符
JavaScript在函数和“可调用对象”之间做了细微的区分。所有的函数都是可调用的,但也有可能一个可调用对象不是一个真正的函数。ES3规范说所有可调用的本机对象返回"function"。ES5规范对此进行了扩展,要求所有可调用对象都返回"function"。
delete运算符
delete是一个一元运算符,它删除指定对象的属性或数组元素。与赋值、递增和递减运算符一样,使用delete通常是为了其副作用,而不是返回的值。
|
|
删除属性或数组元素不等同于将其值设为undefined。当一个属性被删除时,该属性就不存在了。尝试读取不存在的属性将返回未定义的值,但是可以使用in操作符测试属性的是否存在。
delete的操作数是一个左值。如果它不是左值,delete不采取任何操作并返回true。否则,delete尝试删除该左值。如果d删除成功,则返回true。并不是所有的属性都可以删除:一些内置的核心属性和客户端属性不受删除的影响,并且不能删除使用var语句声明的用户定义的变量。由function声明的函数和参数也不能删除。
在ES5严格模式下,如果delete的操作数是一个非限定的标识符(如变量、函数或函数参数),那么它抛出SyntaxError错误,只有当操作数是一个属性访问表达式时,它才会工作。如果删除任何不可配置属性,将抛出TypeError。在严格模式之外,这些情况下不会发生异常,delete返回false表示操作数无法删除:
|
|
void运算符
void是一个一元运算符,其操作数可以是任何类型。它计算并丢弃其操作数的值,然后返回undefined。由于操作数的值被丢弃,所以只有当操作数有副作用时,使用void运算符才有意义。
这个操作符最常见的用法是在客户端javascript的URL中,它允许你为了表达式的副作用,而不需要浏览器显示表达式的值。
|
|
逗号运算符
逗号操作符是一个二进制操作符,其操作数可以是任何类型。它从左到右计算操作数,然后返回右操作数的值。