目录

表达式和操作符

表达式是JavaScript的一个短语,JavaScript解释器可以对其进行求值。

主表达式

最简单的表达式,称为主表达式,是那些独立的表达式——它们不包含任何简单的表达式。JavaScript中的主要表达式是常量或文字值、特定语言关键字和变量引用。

1
2
3
4
5
6
7
true // Evalutes to the boolean true value
false // Evaluates to the boolean false value
null // Evaluates to the null value
this // Evaluates to the "current" object
i // Evaluates to the value of the variable i.
sum // Evaluates to the value of the variable sum.
undefined // undefined is a global variable, not a keyword like null.

当任何标识符单独出现在程序中时,JavaScript假设它是一个变量,并查找它的值。如果不存在具有该名称的变量,则表达式计算为未定义的值。然而,在ES5的严格模式中,计算不存在的变量会抛出一个ReferenceError。

对象和数组初始化式

数组初始化式是方括号中包含的以逗号分隔的表达式列表。数组初始化式的值是一个新创建的数组。这个新数组的元素被初始化为逗号分隔表达式的值:

1
2
[] // An empty array: no expressions inside brackets means no elements
[1+2,3+4] // A 2-element array. First element is 3, second is 7

每次计算数组初始化式时,其中的元素表达式都会被计算。这意味着数组初始化式的值在每次求值时可能不同。通过在逗号之间省略值,可以将未定义的元素包含在数组字面值中:

1
var sparseArray = [1,,,,5];

在数组初始化式最后一个表达式后面允许有一个逗号,并且不会创建未定义的元素。

对象初始化表达式类似于数组初始化表达式,但是方括号被大括号替换,并且每个子表达式的前缀都有一个属性名和一个冒号:

1
2
3
var p = { x:2.3, y:-1.2 }; // An object with 2 properties
var q = {}; // An empty object with no properties
q.x = 2.3; q.y = -1.2; // Now q has the same properties as p

函数定义表达式

函数定义表达式定义了一个JavaScript函数,该表达式的值是新定义的函数。

1
2
// This function returns the square of the value passed to it.
var square = function(x) { return x * x; }

属性访问表达式

属性访问表达式求值为对象属性或数组元素的值。JavaScript为属性访问定义了两种语法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
expression.identifier
expression[expression]

var o = {x:1,y:{z:3}}; // An example object
var a = [o,4,[5,6]]; // An example array that contains the object
o.x // => 1: property x of expression o
o.y.z // => 3: property z of expression o.y
o["x"] // => 1: property x of object o
a[1] // => 4: element at index 1 of expression a
a2 // => 6: element at index 1 of expression a[2]
a[0].x // => 1: property x of expression a[0]

对于任一种属性访问表达式,.或[之前的表达式先计算。如果其值为null或undefined,抛出TypeError。如果指定的属性不存在,则属性访问表达式的值为undefined。

调用表达式

调用表达式用于JavaScript调用函数或方法。

1
2
3
f(0) // f is the function expression; 0 is the argument expression.
Math.max(x,y,z) // Math.max is the function; x, y and z are the arguments.
a.sort() // a.sort is the function; there are no arguments.

当对调用表达式求值时,首先对函数表达式求值,然后对参数表达式求值,以生成参数值列表。如果函数表达式的值不是可调用对象,则抛出TypeError。如果函数使用return语句返回值,则该值成为调用表达式的值。否则,调用表达式的值是undefined。

每一个调用表达式都包含一对括号和括号之前的表达式。如果该表达式是属性访问表达式,则调用称为方法调用。在方法调用中,对象或数组成为函数体执行时this参数的值。

非方法调用的调用表达式通常使用全局对象作为this关键字的值。在ES5中严格模式下定义的函数被调用时,this的值为undefined,而不是全局对象。

对象创建表达式

对象创建表达式创建一个新对象并调用一个函数(称为构造函数)来初始化该对象的属性。对象创建表达式类似于调用表达式,只是它们的前缀是关键字new:

1
2
new Object()
new Point(2,3)

如果在对象创建表达式中没有参数传递给构造函数,则可以省略括号:

1
2
new Object
new Date

当计算对象创建表达式时,JavaScript首先创建一个新的空对象,就像{}创建的对象一样。接下来,它使用指定的参数调用指定的函数,将新对象传递给this参数。然后函数可以使用this来初始化新创建对象的属性。构造函数不返回值,且对象创建表达式的值是新创建和初始化的对象。如果构造函数返回一个对象值,该值将成为对象创建表达式的值,新创建的对象将被丢弃。

操作符概述

/images/2018/10/28/operators.jpg

左值

左值是一个历史术语,意思是“可以合法地出现在赋值表达式左边的表达式”。在JavaScript中,变量、对象属性和数组元素都是lvalue。ECMAScript规范允许内置函数返回左值,但没有任何以这种方式运行的函数。

操作符的副作用

有些表达有副作用,它们的计算会影响到将来计算的结果。赋值操作符是最明显的例子:如果将值赋给变量或属性,则会更改使用该变量或属性的任何表达式的值。++和–操作符类似,因为它们执行隐式赋值。delete操作符也有副作用:删除一个属性就像给这个属性赋值undefined。

没有其他JavaScript操作符有副作用,但是如果函数或构造函数中使用的任何操作符有副作用,那么函数调用和对象创建表达式就会有副作用。

运算符优先级

运算符优先控制执行操作的顺序。优先级较高的操作符在优先级较低的操作符之前执行。可以通过显式使用括号覆盖运算符优先级。属性访问和调用表达式的优先级高于上表任何操作符。

运算符结合性

运算符的结合性指定执行相同优先级的操作的顺序。从左到右的结合律意味着操作是从左到右执行的。

求值顺序

运算符优先级和结合性指定在复杂表达式中执行操作的顺序,但它们没有指定计算子表达式的顺序。JavaScript总是严格按照从左到右的顺序计算表达式。

算术表达式

基本的算术运算符是*、/、%、+和-。我们将在后面单独讨论+运算符。其他四个运算符只是计算它们的操作数,如果需要的话,将值转换为数字,然后计算。不能转换为数字的操作数转换为NaN。如果操作数是NaN,则结果也是NaN。

在JavaScript中,所有数字都是浮点数,因此所有除法操的结果都是浮点数。除以0得到正无穷或负无穷,而0/0得到NaN。

运算符%计算第一个操作数对第二个操作数的取模,结果的符号与第一个操作数的符号相同。模运算符通常与整数操作数一起使用,但它也适用于浮点值。

1
2
3
5 % 2 //=> 1
-5 % 2 //=> -1
6.5 % 2.1 //=> 0.2

+运算符

如果有一个操作数是对象,使用对象到原始类型转换:日期对象使用toString()方法,其他对象使用valueOf()方法。大多数对象没有一个有用的valueOf()方法,因此它们也通过toString()进行转换。

  • 如果任何一个操作数是字符串,那么另一个操作数将转换为字符串,并执行连接
  • 将两个操作数转换为数字(或NaN)并执行加法。
1
2
3
4
5
6
7
1 + 2 // => 3: addition
"1" + "2" // => "12": concatenation
"1" + 2 // => "12": concatenation after number-to-string
1 + {} // => "1[object Object]": concatenation after object-to-string
true + true // => 2: addition after boolean-to-number
2 + null // => 2: addition after null converts to 0
2 + undefined // => NaN: addition after undefined converts to NaN

一元运算符

在JavaScript中,一元运算符的优先级都很高,且都是右关联的。如果需要,算术一元运算符(+、-、++和–)都将其操作数转换为数字。

位运算符

位运算符将其操作数转换为数字,通过删除小数部分和超过32位的部分,将数值强制转换为32位整数。NaN,Infinity和-Infinity都转换为0。

&运算符对操作数每个位执行与操作。只有两位都为1,结果才为1。

|运算符对操作数每个位执行或操作。只有两位都为0,结果才为0。

^运算符对操作数每个位执行异或操作。两位相同结果为0,两位不相同结果为1。

~按位取反,相当于更改符号并减去1。

<<左移运算符,将其第一个操作数中的所有位向左移动,移动的位置是第二个操作数指定的位数。

>>右移操作符,将其第一个操作数中的所有位向右移动,移动的位置是第二个操作数指定的位数,左边填充符号位。

>>>类似于>>,只是左边总是填充0。

1
2
3
4
5
6
7
0x1234 & 0x00FF // 0x0034
0x1234 | 0x00FF // 0x12FF
0xFF00 ^ 0xF0F0 // 0x0FF0
~0x0F // 0xFFFFFFF0
7 << 2 // 28
7 >> 1 // −4
-1 >>> 4 // 0x0FFFFFFF

关系表达式

相等和不等操作符

JavaScript对象通过引用而不是值进行比较。严格相等运算符===计算其操作数,然后按照如下方式比较这两个值,不执行类型转换:

  • 如果这两个值有不同的类型,它们就不相等
  • 如果两个值都为null或undefined ,则它们相等。
  • 如果两个值都是布尔值true或都是布尔值false,则它们相等。
  • 如果一个或两个值都是NaN,则它们不相等。NaN值永远不等于任何其他值,包括它自己。
  • 如果两个值都是数字且值相同,则它们相等。0和-0相等。
  • 如果两个值是字符串且长度和内容相同则它们相等,否则它们不相等。
  • 如果两个值引用相同的对象、数组或函数,则它们相等。如果它们引用不同的对象,它们就不相等,即使两个对象具有相同的属性。

==运算符没有===严格。如果两个操作数的值不是相同的类型,它会尝试一些类型转换并再次比较:

  • 如果两个值具有相同的类型,则用===比较它们。

  • 如果两个值的类型不相同,使用以下规则和类型转换检查是否相等

    1. 如果一个是null,另一个是undefined,它们相等。
    2. 如果一个是数字,另一个是字符串,将字符串转换为数字,再使用转换后的值进行比较
    3. 如果任一值为true或false,将其转换数字再进行比较。
    4. 如果一个是对象,另一个是数字或字符串,将对象转换原始类型再进行比较。
  • 任何其他值的组合都不相等

比较运算符(<,>,<=,>=)

这些比较运算符的操作数可以是任何类型的。但是,比较只能在数字和字符串上执行,因此转换的操作数不是数字或字符串。比较和转换如下所示:

  • 如果操作数是一个对象,将该对象转换为原始值。如果valueOf()方法返回一个原始值,则使用该值。否则使用toString()方法返回的值。
  • 如果进行对象到原始值转换之后,两个操作数都是字符串,那么使用字母顺序比较这两个字符串。
  • 如果进行对象到原始值转换之后,至少有一个操作数不是字符串,那么两个操作数都转换为数字再进行数值比较。如果有一个是NaN,总是返回false。

+偏爱字符串,如果任何一个操作数是字符串,它都会执行连接。比较运算符更喜欢数字,如果两个操作数都是字符串,才进行字符串比较。

1
2
3
4
5
6
7
1 + 2 // Addition. Result is 3.
"1" + "2" // Concatenation. Result is "12".
"1" + 2 // Concatenation. 2 is converted to "2". Result is "12".
11 < 3 // Numeric comparison. Result is false.
"11" < "3" // String comparison. Result is true.
"11" < 3 // Numeric comparison. "11" converted to 11. Result is false.
"one" < 3 // Numeric comparison. "one" converted to NaN. Result is false.

in操作符

in操作符左边是字符串,右边是一个对象。如果左侧值是右侧对象的属性名,则结果为true:

1
2
3
4
5
6
7
8
var point = { x:1, y:1 }; // Define an object
"x" in point // => true: object has property named "x"
"z" in point // => false: object has no "z" property.
"toString" in point // => true: object inherits toString method
var data = [7,8,9]; // An array with elements 0, 1, and 2
"0" in data // => true: array has an element "0"
1 in data // => true: numbers are converted to strings
3 in data // => false: no element 3

instanceof操作符

instanceof操作符左侧是一个对象,右侧是一个类。如果左边的对象是右侧类的实例,则结果为true:

1
2
3
4
5
6
7
8
var d = new Date(); // Create a new object with the Date() constructor
d instanceof Date; // Evaluates to true; d was created with Date()
d instanceof Object; // Evaluates to true; all objects are instances of Object
d instanceof Number; // Evaluates to false; d is not a Number object
var a = [1, 2, 3]; // Create an array with array literal syntax
a instanceof Array; // Evaluates to true; a is an array
a instanceof Object; // Evaluates to true; all arrays are objects
a instanceof RegExp; // Evaluates to false; arrays are not regular expressions

注意,所有对象都是Object的实例。instanceof在决定对象是否为类的实例考虑的是基类。如果instanceof的左边操作数不是对象,将返回false。如果右边不是函数,它会抛出TypeError 。

逻辑表达式(&&,||,!)

&&和||具有短路求值的特性:

1
2
3
4
(a == b) && stop();
function copy(o, p) {
	p = p || {}; // If no object passed for p, use a newly created object.
}

赋值表达式

赋值运算符左侧是一个左值,右侧可以是任意类型的值。赋值表达式的值就是右侧操作数的值。作为副作用,赋值运算符将右侧的值赋给左侧的变量或属性。赋值运算符是右结合的。

大多数情况下,a op= b等价于a = a op b。只有当a包含了诸如函数调用或递增操作符等副作用时,它们不等价。

估值表达式

像许多解释语言一样,JavaScript能够解释JavaScript源代码字符串,并对其进行计算。JavaScript通过全局函数eval()来实现的。

eval()

如果传递一个字符串,eval()将尝试解析该字符串为JavaScript代码,如果失败则抛出SyntaxError。如果成功,则返回字符串中最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则返回undefined。如果该串代码抛出异常,eval()传播该异常。

如果传递其他值,则返回该值。

eval()会使用当前代码作用域的变量,也可以像本地代码一样定义变量和函数。

1
2
3
4
x = 1
eval('x') // 1
eval("function f() { return x+1; }");
f() // 2

如果从顶级代码调用eval(),它将对全局变量和全局函数进行操作。

全局eval()

eval()更改本地变量的能力对JavaScript优化器来说是一个很大的问题。解释器对任何调用eval()的函数只做较少的优化。

ES5废弃了EvalError并标准化了eval()的行为。直接调用eval()使用调用上下文的变量环境。任何其他调用都使用全局对象作为其变量环境,不能读写或定义局部变量和函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var geval = eval; // Using another name does a global eval
var x = "global", y = "global"; // Two global variables
function f() { // This function does a local eval
    var x = "local"; // Define a local variable
    eval("x += 'changed';"); // Direct eval sets local variable
    return x; // Return changed local variable
}
function g() { // This function does a global eval
    var y = "local"; // A local variable
    geval("y += 'changed';"); // Indirect eval sets global variable
    return y; // Return unchanged local variable
}
console.log(f(), x); // "localchanged global"
console.log(g(), y); // "local globalchanged"

在IE9之前,当使用不同的名称调用eval()时,它不会执行全局eval。也不会抛出一个EvalError,它只做局部运算。但IE定义了一个execScript()的全局函数,它会像执行顶级脚本一样执行字符串参数。然而,与eval()不同的是,execScript()总是返回null。

严格eval()

ES5严格模式进一步限制eval()函数的行为,甚至是标识符eval的使用。当在严格模式中调用eval()时,eval()用私有变量环境执行本地eval。这意味着在严格模式下,求值代码可以查询和设置局部变量,但不能在局部作用域中定义新的变量或函数。

此外,严格eval()更像操作符。不允许使用新值覆盖eval()函数,且不允许使用“eval”名称声明变量、函数、函数参数或catch块参数。

其他运算符

条件运算符(?:)

条件运算符的操作数可以是任何类型的。如果第一个操作数的值为真,则计算第二个操作数,并返回其值。否则,计算第三个操作数并返回其值。

typeof运算符

/images/2018/10/28/typeof.png

JavaScript在函数和“可调用对象”之间做了细微的区分。所有的函数都是可调用的,但也有可能一个可调用对象不是一个真正的函数。ES3规范说所有可调用的本机对象返回"function"。ES5规范对此进行了扩展,要求所有可调用对象都返回"function"。

delete运算符

delete是一个一元运算符,它删除指定对象的属性或数组元素。与赋值、递增和递减运算符一样,使用delete通常是为了其副作用,而不是返回的值。

1
2
3
4
5
6
var o = { x: 1, y: 2}; // Start with an object
delete o.x; // Delete one of its properties
"x" in o // => false: the property does not exist anymore
var a = [1,2,3]; // Start with an array
delete a[2]; // Delete the last element of the array
a.length // => 2: array only has two elements now

删除属性或数组元素不等同于将其值设为undefined。当一个属性被删除时,该属性就不存在了。尝试读取不存在的属性将返回未定义的值,但是可以使用in操作符测试属性的是否存在。

delete的操作数是一个左值。如果它不是左值,delete不采取任何操作并返回true。否则,delete尝试删除该左值。如果d删除成功,则返回true。并不是所有的属性都可以删除:一些内置的核心属性和客户端属性不受删除的影响,并且不能删除使用var语句声明的用户定义的变量。由function声明的函数和参数也不能删除。

在ES5严格模式下,如果delete的操作数是一个非限定的标识符(如变量、函数或函数参数),那么它抛出SyntaxError错误,只有当操作数是一个属性访问表达式时,它才会工作。如果删除任何不可配置属性,将抛出TypeError。在严格模式之外,这些情况下不会发生异常,delete返回false表示操作数无法删除:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var o = {x:1, y:2}; // Define a variable; initialize it to an object
delete o.x; // Delete one of the object properties; returns true
typeof o.x; // Property does not exist; returns "undefined"
delete o.x; // Delete a nonexistent property; returns true
delete o; // Can't delete a declared variable; returns false.
// Would raise an exception in strict mode.
delete 1; // Argument is not an lvalue: returns true
this.x = 1; // Define a property of the a global object without var
delete x; // Try to delete it: returns true in non-strict mode
		  // Exception in strict mode. Use 'delete this.x' instead
delete x; // Runtime error: x is not defined

void运算符

void是一个一元运算符,其操作数可以是任何类型。它计算并丢弃其操作数的值,然后返回undefined。由于操作数的值被丢弃,所以只有当操作数有副作用时,使用void运算符才有意义。

这个操作符最常见的用法是在客户端javascript的URL中,它允许你为了表达式的副作用,而不需要浏览器显示表达式的值。

1
<a href="javascript:void window.open();">Open New Window</a>

逗号运算符

逗号操作符是一个二进制操作符,其操作数可以是任何类型。它从左到右计算操作数,然后返回右操作数的值。