# 表达式

### 表达式

表达式是 javascript 中的一个短语，javascript 解释器会将其计算出一个结果。程序中的常量是最简单的一类表达式。变量名也是一种简单的表达式。

### 运算符

将简单的表达式组合成复杂表达式最常用的方达就是使用运算符。

### 原始表达式

最简单的表达式是“原始表达式”。原始表达式是表达式的最小单位 - 它们不再包含其他表达式。javascript 中的原始值表达式包含常量或者直接量、关键字和变量。

### 属性访问表达式

属性访问表达式运算得到一个对象属性或一个数组元素的值。javascript 为属性访问定义了两种语法：

```javascript
expression.identifier
expression[expression]
```

`.identifier` 的写法更为简单，但这种方式只适用于要访问的属性名称是合法的标识符，并且需要知道要访问的属性的名字。如果属性名称是一个保留字或者包含空格和标点符号，或者是一个数字(对于数组来说)，则必须使用方括号的写法。当属性名是通过运算得出的值而不是固定的值的时候，这时必须使用方括号写法。

### 对象创建表达式

对象创建表达式创建一个对象并调用一个函数初始化新对象的属性。对象创建表达式和函数调用表达式非常类似，只是对象创建表达式之前多了一个关键字 new：

```javascript
new Objext()
new Point(2, 3)
```

如果一个对象创建表达式不要传入任何参数给构造函数的话，那么这对空圆括号是可以省略掉的：

```javascript
new Object()
new Date()
```

当计算一个对象创键表达式的值时，和对象初始化表达式通过`{ }`创建对象的做法一样，javascript 首先创建一个新的空对象，然后，javascript 通过传入指定的参数并将这个新对象当做 `this` 的值来调用一个指定的函数。这个函数可以使用 `this` 来初始化这个新创建对象的属性。那些被当成构造函数的函数不会返回一个值，并且这个新创建并被未初始化后的对象就是整个对象创建表达式的值。如果一个构造函数确实返回了一个对象值，那么这个对象就作为整个对象创建表达式的值，而新创建的对象就废弃了。

### 运算符概述

#### 左值

`lval` 是 `left-value` 的简写，即左值。左值是一个古老的术语，它是指“表达式只能出现赋值运算符的左侧”。在 javascript 中，变量、对象属性和数组元素均是左值。ECMAScript 规范允许内置函数返回一个左值，但自定义函数则不能返回左值。下面内容读取引用自 C++,左值与右值并不是通常讲的在操作符左边就是左值，在操作符右边就是右值，这是错误的。左值与右值是针对表达式而言的。通俗的解释一下，左值就是能取地址，右值就是只能取值的。左值是长久存在的对象，右值是临时的对象。

#### 运算顺序

在表达式 `z` 中，如果变量 `x` 自增 `1`，那实际上是先计算出 `x` 的值在计算 `z` 的值：

```javascript
a = 1
b = a++ + a //结果为3，后一个a的值为2
```

### 算术表达式

运算符`%`计算的是第一个操作数对第二个操作数的摸。结果的符号和第一个操作数的符号保持一致。例如：

```javascript
5%2 = 1;
-5%2 = -1;
```

求余运算符的操作数通常都是整数，但也使用于浮点数，比如，`6.5%2.1` 的结果为`0.2`。

### “+”运算符

加法操作符的表现行为：

1. 如果其中一个操作数是对象，则对象会转换为原始值类型：日期对象通过 `toString()`方法执行转换，其他对象则通过 `valueOf()`方法执行转换。由于多数对象都不具备可用的 `valueOf()`方法，因此它们会通过 `toString()`方法来执行转换。
2. 在进行了对象到原始值的转换后，如果其中一个操作数是字符串的话，另一个操作数也会转换为字符串，然后进行字符串连接。
3. 否则，两个操作数都将转换为数字或者 `NaN`，然后进行加法操作。

例如：

```javascript
1 + {} // ="1[object Object]"
true + true // =2
2 + undefined // =NaN：undefined转换为了NaN
```

### 一元算术运算符

一元运算符作用于一个单独的操作数，并产生一个新值。在 javascript 中，一元运算符具有很高的优先级，而且都是右结合。

递增(++)

递增`++`运算符的返回值依赖于它相对操作数的位置。当运算符在操作数之前，称为“前增量”，它对操作数进行增量计算，并返回计算后的值。当运算符在操作符之后，称为“后增量”，它对操作数进行增量计算，但返回未做增量计算的值。表达式`++x` 并不总和 `x=x+1` 完全一样，`++`运算符从不进行字符串连接操作，而将操作数转换为数字并增 `1`.

### 位运算符

位运算符要求它的操作符是整数，这些整数表示为 `32` 位整数型。必要时，位运算符首先将操作符转换为数字，需注意位运算符会将 `NaN`、`Infinity` 和`-Infinity` 都转换为 `0`。

#### 按位与(&)

全一出一，有零出零

#### 按位或 (|)

有一出一，全零出零

#### 按位异(^)

同则 `0`，异则 `1`

#### 按位非(\~)

位于一个整型参数之前，它将操作数的所有位取反。对一个值使用`~`运算符相当于改变人它的符号并减 `1`。

#### 左移(<<)

将第一个操作数的所有二进制位进行左移操作，移动的位数由第二个操作数指定。在 `a<<1` 中，`a` 的第一位变成了第二位，新的一位用 `0` 补充。将一个值左移 `1` 位相当于它乘以 `2`。

#### 带符号右移(>>)

运算符`>>`将第一个操作数的所有位进行右移操作，移动的位数由第二个操作数指定。如果第一个操作数是正数，位移后用 `0` 填补最高位，如果第一个操作数是负的，就用 `1` 填补高位。将一个值右移一位，相当于它除以 `2`(忽略余数)。

#### 无符号右移(>>>)

运算符`>>>`和运算符`>>`一样，只是左边的高位总是填补 `0`。

### 关系表达式

#### 相等和不等运算符

严格相等运算符`===`首先计算其操作数的值，然后比较这两个值，比较过程没有任何类型转换：

1. 如果两个值类型不相同，则他们不相等。
2. 如果两个值都是 `null` 或者是 `undefined`，则他们不相等(在当前谷歌浏览器中，打印`null === null`，值为 `true`)。
3. 如果两个值都是布尔值 `true` 或者都是 `false`，则它们不相等(d)。
4. 如果其中一个值是 `NaN`，或者两个值都是 `NaN`，则它们不相等。
5. 如果两个值为数字且数值相等，则它们相等。
6. 如果两个值为字符串，且所含的对应位上 `16` 位数完全相等则它们相等。两个字符串可能含义完全一样且显示的字符也一样，但具有不同的编码的 `16` 位值。javascript 并不对 `Unicode` 进行标准化的转换，因此这样的字符串通过`===`和`==`进行比较结果也不相等。
7. 如果两个引用值指向同一对象、数组或对象，则它们是相等的。如果指向不同的对象，则它们是不等的，尽管两个对象具有完全一样的属性。

相等运算符`==`和恒等运算符相似，但相等运算符的比较并不严格。如果两个操作数不是同一类型，那么相等运算符会尝试进行一些类型转换，然后进行比较：

1. 如果两个操作数的类型相同，则和上文所述的严格相等的比较规则一样。如果严格相等，那么比较结果为相等。否则，比较结果不相等。
2. 如果两个操作数类型不同，`==`相等操作符也可能会认为他们相等。检测相等将会遵守如下规则和类型转换：如果一个值是 `null`，另一个是 `undefined`，则他们相等；如果一个值是数字，另一个是字符串，先将字符串转换为数字，然后使用转换后的值进行比较；如果其中一个值是 `true`，则将其转化为 `1` 再进行比较。如果其中一个值是 `false`，则将其转换为 `0` 再进行比较；如果一个值是对象，另一个是数字或者是字符串，则将其转换为原始值，再进行比较；其他不同类型之间的比较均不相等。

#### 比较运算符

比较操作符的操作符可能是任意类型。然而，只有数字和字符串才能真正执行比较操作，因此那些不是数字和字符串的操作数都将进行类型转换，注意，字符串比较是区分大小写的，所有的大写 `ASCII` 字母都“小于”小写的 `ASCII` 字母，类型转换规则如下：

1. 如果操作数为对象：如果 `valueOf()`返回一个原始值，那么直接使用这个原始值。否则，使用 `toString()`的转换结果进行比较。
2. 在对象转换为原始值之后，如果两个操作数都是字符串，那么将依照字母表的顺序对两个字符串进行比较，即组成这个字符串的 `16` 位 `Unicode` 的索引顺序。
3. 在转换为原始值之后，如果至少有一个操作数不是字符串，那么两个操作数都将转转换为数字进行数值比较。`0` 和`-0`是相等的。`Infinity`比其他任何数字都大(除了本身)，`-Infinity` 比其他任何数字都小(除了自身)。如果其中一个操作数是 `NaN`，则总是返回 `false`。

`String.localCompare()`提供了另外一种更为健壮的字符串的方法。这个方法参照本地语言的字母定义表的字符次序。

对于数字和字符串操作符来说，加号运算符和比较运算符的行为都有所不同，如果其中一个操作数是字符串则进行字符串连接操作，前者更偏爱字符串。而运算符更偏爱数字，只有在两个操作数都是字符串的时候，才会进行字符串的比较。

需要注意的是，`<=`和`>=`运算符在判断相等的时候，并不依赖于相等运算和严格运算符的比较规则。相反，小于等于只是简单的“不大于”，大于等于运算也只是“不小于”。只有一个例外，当一个操作数为 `NaN` 时，所有的 `4` 个比较运算符均返回 `false`。

#### in 运算符

`in` 运算符希望它的左操作数是一个字符串或者可以转换为字符串，希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数的属性名，则返回 `true`，例如

```javascript
var point = { x: 1, y: 1 }
'x' in point // =true
'toString' in point // =true：继承而来

var data = [7, 8, 9]
'0' in data // =true
3 in data // =false：没有索引为3的元素
```

#### instanceof 运算符

`instanceof` 运算符希望左操作数是一个对象，右操作数表示对象的类。如果左侧的对象是右侧类的实例，则表达式返回 `true`。比如：

```javascript
var d = new Date()
d instanceof Date // true
d instanceof Number // false

var a = [1, 2, 3]
a instanceof Array // true
a instanceof Object // true：所有数组都是对象
```

当通过 `instanceof` 判断一个对象是否是一个类的实例的时候，这个判断也会包含对“父类”的检测。如果 `instancof` 的左操作数不是对象的话，`instanceof` 返回 `false`。如果右操作数不是函数，则抛出一个类型错误异常。

### 逻辑表达式

#### 逻辑与(&&)

`&&`常用来连接两个关系表达式：

```javascript
x == 0 && y == 0 // 只有在x和y都是0的时候，才返回true
```

运算符首先计算左操作数的值，即首先计算`&&`左侧的表达式，如果计算结果是假值，那么整个表达式的结果一定是假值，因此`&&`这时简单的返回左操作数的值，而不会对右操作数进行计算。

左操作数是真值的这种情况下，如果右操作数是真值，那么整个表达式的值一定是真值；如果右操作数为假值，那么表达式为假值。因此，当左操作数是真值时，`&&`运算将计算操作数的值并将其返回作为整个表达式的计算结果。

`&&`的行为有时称作“短路”，我们可以利用这一特性来有条件地执行代码。例如，下面两行代码完全等价：

```javascript
if (a == b) stop() //只有在 a==b 的时候才调用 stop()
a == b && stop() //同上
```

#### 逻辑或(||)

`||`运算符对两个操作数作布尔或(OR)运算。如果其中一个或者两个操作数是真值，它返回一个真值。如果两个操作数都是假值，它返回一个假值。和`&&`一样，它也具有一些更为复杂的行为，他首先计算第一个操作数的值，也就是说会首先计算左值的表达式，如果计算结果为真值，那么返回这个真值。否则，再计算第二个操作数的值，即计算右侧的表达式，并返回这个表达式的计算结果。

### 赋值表达式

赋值操作的结合性是从右至左，也就是说，如果一个表达式中出现多个赋值运算符，运算顺序是从右到左。因此，可以通过如下方式来对多个变量赋值：

```javascript
i = j = k = 0 //把三个变量初始化为0
```

### 带操作的赋值运算

通用表达式：

```javascript
a op= b      // a+=b
```

这里 `op` 代表一个运算符(+,-,/等等)，这个表达式和下面的表达式等价：

```javascript
a=a op b    // a=a+b
```

### 表达式计算

#### eval()

`eval()`只有一个参数。如果传入的参数不是字符串，它直接返回这个参数。如果参数是字符串，它会将字符串当成 javascript 代码进行编译(编译不包括代码的执行)，如果编译失败则抛出一个语法错误异常。如果编译成功，则开始执行这段代码，并返回组返回字符串最后一个表达式或语句的值，如果最后一个表达式或语句没有值，则最终返回 `undefined`。如果字符串抛出一个异常，这个异常将把该调用传递给 `eval()`。

关于 `eval()`最重要的是，它使用了调用它的变量作用域环境。也就是说，它查找变量的值和定义新变量和函数的操作和局部作用域中的代码完全一样。例如：

```javascript
var foo = function(a) {
  eval(a)
}
foo('return; ')
```

按原文的意思，这段代码中执行 `eval(a)`的上下文是全局的，在全局上下文中使用 `return` 会抛出语法错误：`return not in function`。

#### 全局 eval()

ECMAScript 5 是反对使用 `EvalError` 的，并且规范了 `eval()`的行为。当直接使用非限定的`eval`名称来调用 `eval()`函数时，通常称为“直接 eval”。直接调用 `eval()`时，它总是在调用它的上下作用域内执行。其他的间接调用则会使用全局对象作为其上下文作用域，并且无法读、写、定义局部变量和函数。例如：

```javascript
var geval = eval
var x = 'global',
  y = 'global'
function f() {
  var x = 'local'
  eval("x += 'changed';")
  return x
}
function g() {
  var y = 'local'
  geval("y += 'changed';")
  return y
}
console.log(f(), x) //更改了局部变量："local changed global"
console.log(f(), y) //更改了全局变量："local globalchanged"
```

#### 严格 eval()

ECMAScript 严格模式对 `eval()`函数的行为施加了更多的限制。当在严格模式下调用 `eval()`时，或者 `eval()`执行的代码段以`use strict`指令开始，这里的 `eval()`是私有上下文环境中的局部 `eval`。也就是说，在严格模式下。`eavl` 执行的代码段可以查询或者更改局部变量，但不能在局部作用域中定义新的变量或者函数。

### 其他运算符

#### typeof 运算符

`typeof` 是一元运算符，放在其单个操作数的前面，操作数可以是任意类型。返回值为表示操作数类型的一个字符串。例如：

```javascript
x             typeof x
undefined     "undefined"
null          "object"
任意宿主对象   由编译器各自实现的字符串
```

`typeof` 运算符可以带上圆括号，使其看起来像一个函数名，而不是一个运算符关键字：

```javascript
typeof i
```

对于宿主对象来说，`typeof` 有可能并不返回`object`。而返回字符串。但实际上客户端 javascript 中的大多数宿主对象都是`object`类型。

#### delete 运算符

`delete` 是一元操作符，它用来删除对象属性或者数组元素。就像赋值、递增、递减运算符一样，`delete` 也是具有副作用的，它是用来做删除操作的，不是用来返回一个值的，例如：

```javascript
var o = { x: 1, y: 2 }
delete o.x
'x' in o // =false

var a = [1, 2, 3]
delete a[2]
2 in a // =? false
a.length // =3
```

这里数组长度并没有改变，尽管上一行代码删除了这个元素，但删除操作留下了一个“洞”，实际上并没有修改数组的长度，因此 `a` 的长度仍然为 `3`。

`delete` 希望他的操作数是一个左值，如果他不是左值，那么 `delete` 将不进行任何操作同时返回 `true`。否则，`delete` 将试图删除这个指定的左值。如果删除成功，`delete` 将返回 `true`。一些内置核心和客户端属性是不能删除的，用户通过 `var` 语句声明的变量不能删除。同样，通过 `function` 语句定义的函数和函数参数也不能删除。

#### void 运算符

`void` 是一元运算符，它出现在操作数之前，操作数可以是任意类型。这个运算符并不是经常使用：操作数会照常计算，但忽略计算结果并返回 `undefined`。由于 `void` 会忽略操作数的值，因此在操作数具有副作用的时候使用 `void` 来让程序更有语义。

#### 逗号运算符(,)

逗号运算符是二元运算符，它的操作数可以是任意类型。它首先计算左操作数，然后计算右操作数，最后返回右操作数的值，例如：

```javascript
;(i = 0), (j = 1), (k = 2) //计算结果为2
```

逗号运算符最常用的场景是在 `for` 循环中，这个 `for` 循环通常具有多个循环变量：

```javascript
//for循环中的第一个逗号是var语句的一部分
//第二个逗号是逗号运算符
//它将两个表达式(i++和j--)放在一条(for循环中)语句中
for (var i = 0, j = 10; i < j; i++, j--) console.log(i + j)
```
