跳到主要内容

表达式和运算符

表达式:JavaScript 解释器会将其计算(evaluate)出一个结果。

  • 常量:最简单的一类表达式。
  • 变量名:也是一种简单的表达式,它的值就是赋给变量的值。

复杂表达式:由简单表达式通过运算符(或其它符号,如数组元素访问或函数调用)组合而成。

原始表达式

原始表达式:最简单的表达式,也是表达式的最小单位——不再包含其它表达式。常量、直接量、关键字、变量都是原始表达式。

1.23 // 数字直接量
'hello' // 字符串直接量
/pattern/ // 正则表达式直接量

一些保留字也构成原始表达式:

true // 返回布尔值:真
false // 返回布尔值:假
null // 返回值:空
this // 返回“当前”对象

还有一种原始表达式就是变量:

i // 返回变量 i 的值
undefined // undefined 是全局变量,而 null 是一个关键字,它俩是不一样的

JavaScript 代码中出现了标识符的话,就会将其当作变量去查找它的值。如果变量名不存在,表达式的运算结果就是 undefined。在 ES5 严格模式下的话,对不存在的变量名进行求值就会抛出一个引用错误异常。

对象和数组的初始化表达式

对象和数组的初始化表达式其实是新建的对象和数组,有时候也称作“对象直接量”和“数组直接量”。但是,它们不是原始表达式,因为它们所包含的成员或者元素都是子表达式。

[] // 空数组
[1 + 2, 3 + 4] // 数组拥有两个元素,3 和 7
var matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; // 嵌套表达式

JavaScript 对数组初始化表达式进行求值的时候,表达式中的元素表达式也会各计算一次。也就是说,数组初始化表达式每次计算的值有可能是不同的。

var i = 0;
var a = [++i, ++i, ++i]; // [1, 2, 3]

数组直接量中的逗号间的元素可省略,会填充为 undefined。但是如果元素列表结尾处有一个逗号,逗号后就不会再创建一个 undefined 元素了。不过访问数组元素时,数组索引表达式的值大于数组最后一个元素的索引的话,结果肯定就是 undefined 了。

var a = [1,,,,3]; // => [1, empty × 3, 3]
var b = [1,]; // => [1]

对象的初始化和数组非常类似。

var p = { x: 1, y: 2 }; // 拥有两个属性成员的对象
var emp = {}; // 空对象
var rectangle = { upperLeft: { x: 2, y: 2 },
lowerRight: { x: 4, y: 5 } }; // 也可以嵌套

和数组一样,求对象初始化表达式的值的时候,对象表达式也会各自计算一次。

var side = 1;
var square = { 'upperLeft': { x: p.x, y: p.y }, 'lowerRight': { x: p.x + side, y: p.y + side } };
// => { upperLeft: {x: 2.3, y: -1.2}, lowerRight: {x: 3.3, y: -0.19999999999999996} }

函数定义表达式

函数定义表达式的值就是新定义的函数,也可以叫做函数直接量。

var square = function(x) { return x * x; };

属性访问表达式

属性访问表达式会计算得到一个对象属性或一个数组元素的值。

expression.identifier
expression[expression]

获取数组元素时只能用上面的第二种写法,而对象则两种都可以用。

var o = { x: 1, y: { z: 3 } }; // 示例对象
var a = [o, 4, [5, 6]]; // 包含示例对象的数组
o.x // => 1: 表达式 o 的属性 x
o.y.z // => 3: 表达式 o.y 的属性 z
o["x"] // => 1: 表达式 o 的属性 x
a[1] // => 4: 表达式 a 中索引为 1 的元素
a[2]["1"] // => 6: 表达式 a[2] 中索引为 1 的元素
a[0].x // => 1: 表达式 a[0] 的属性 x
  • 对于上面两种属性访问表达式,在 . 或者 [ 之前的表达式都会首先计算。

  • 如果计算结果是 null 或 undefined,表达式会抛出一个类型错误异常,因为这两个值都不能包含任何属性。

  • 如果运算结果不是对象(或数组),JavaScript 会先将其转换为对象。

  • 如果表达式后跟句点和标识符,则会查找标识符所指定的属性的值,并作为整个表达式的值返回。

  • 如果表达式后跟一对方括号,则先计算方括号内的表达式的值并转换为字符串,然后查找字符串所指定的属性的值并返回。

  • 如果查找的属性不存在,则整个属性访问表达式的值就是 undefined。

  • 虽然用标识符访问属性的写法更简单(a.b),但这种写法只适用于属性名是合法标识符,并且已经知道属性名的情况。

  • 如果属性名包含空格或标点符号,或者是数字(对于数组而言),就必须使用方括号。

  • 如果属性名不是固定值而是计算得出的值,也必须用方括号。

调用表达式

调用表达式(invocation expression):调用(或者执行)函数或方法的语法表示,它由函数表达式和紧邻其后的一对圆括号组成。

f(0) // f 是函数表达式,0 是参数表达式
Math.max(x, y, z) // Math.max 是函数,x、y 和 z 是参数
a.sort() // a.sort 是函数,没有参数

对调用表达式求值:

  • 先计算函数表达式,然后计算参数表达式,得到一组参数值。
  • 如果函数表达式不是一个可调用的对象,则抛出一个类型错误异常。
  • 函数表达式可调用,将实参的值依次赋给形参,然后执行函数体。
  • 函数使用 return 语句给出返回值的话,这个值就是整个调用表达式的值。
  • 否则,调用表达式的值就是 undefined。

方法调用(method invocation):调用表达式是属性访问表达式的情况。在方法调用中,执行函数体的时候,作为属性访问主体的对象和数组,就是其调用方法内 this 的指向。借由这种特性,在面向对象编程中,函数可以调用其宿主对象。

调用表达式不属于方法调用时,通常将全局对象作为 this 的值。但是,在 ES5 严格模式中定义的函数,this 的值就是 undefined,而不是全局对象了。

对象创建表达式

对象创建表达式:创建对象并调用函数(构造函数)来初始化新对象的属性。和函数调用表达式很类似,只是多了一个必需的关键字 new

new Object()
new Point(2, 3)

如果对象创建表达式不需要传入参数的话,这对空的圆括号也可以省略掉。

new Object
new Date

计算对象创建表达式的值:

  • 先创建一个新的空对象。
  • 将参数传入指定的函数,并将新对象当作这个函数里的 this 的值,函数可以使用 this 来初始化这个新创建的对象的属性。
  • 如果构造函数不返回值,那么对象创建表达式的值就是这个新创建的并且被初始化了的对象。
  • 如果构造函数返回对象,那么这个返回的对象就是对象创建表达式的值,新创建的对象就被丢弃了。

运算符概述

绝大多数运算符都是标点符号,比如 +=,只有少数是由关键字表示的,比如 deleteinstanceof

操作数的个数

JavaScript 中大部分运算符都是二元运算符(也就是有两个操作数),将两个表达式合并成一个稍微复杂一点的表达式。也有一少部分运算符是一元运算符,将一个表达式转换为另一个稍微复杂一点的表达式。唯一的一个三元运算符是条件判断运算符 ? :,它将三个表达式合并成一个表达式。

操作数类型和结果类型

虽然有些运算符可以用于所有数据类型,但操作数是指定类型数据的话就更好了;并且大多数运算符返回(或计算出)的也是一个特定类型的值。比如逻辑非运算符 ! 期望的操作数就是 bool 类型,如果不是,则会先将其转换为该类型。

左值

赋值及其它几个运算符期望的操作数是 lval 类型,表示该操作数(表达式)只能出现在赋值运算符的左侧。在 JavaScript 中,变量、对象属性和数组元素都是左值。

ES规范允许内置函数返回一个左值,但自定义函数则不能返回左值:只有内置函数才可以返回变量、对象属性或数组元素。

TODO: 再次看这个概念,还是不太明白。

运算符的副作用

计算简单的表达式(比如 2*3)不会影响程序的运行状态,后续执行的状态也不会受影响。但有些表达式就会对程序有影响了,比如赋值运算符:给一个变量或属性赋值的话,后续使用这个变量或属性的表达式的值都会发生变化。自增和自减运算符也是这样,因为它们包含隐式的赋值。delete 运算符也是如此:删除一个属性就类似于(但不等于)给这个属性赋值 undefined

其它的 JavaScript 运算符都没有副作用,除了函数调用表达式和对象创建表达式:在函数体或者构造函数内部运用了这些运算符并产生了副作用的时候,就说函数调用表达式和对象创建表达式是有副作用的(TODO: 也没太看懂……)。

运算符优先级

属性访问表达式和调用表达式的优先级要比所有其它运算符都高:

typeof ( my.function )[x](y)

虽然 typeof 是优先级最高的运算符之一,但也是在属性访问和函数调用之后执行的。

最保险的保证优先级的方法,就是用圆括号来强行指定。

注意:乘法和除法的优先级高于加法和减法,赋值运算符的优先级非常低,通常总是最后执行的。

运算符的结合性

结合性:多个具有同样优先级的运算符表达式中的运算顺序。比如减法运算符具有从左至右的结合性,因此下面两行代码的执行效果是一样的:

w = x - y - z;
w = ((x - y) - z);

运算顺序

运算符的优先级和结合性规定了它们在复杂的表达式中的运算顺序,但并没有规定子表达式在计算过程中的运算顺序 —— JavaScript 总是严格按照从左至右的顺序来计算表达式。

w = x + y * z

比如在上面的表达式中,先依次计算表达式 w、x、y 和 z,然后计算 y * z,再计算 x + y * z,最后将其赋值给 w 所代表的变量或属性。

给表达式添加括号可以改变乘法、加法和赋值运算等运算符的关系,但从左至右计算子表达式的顺序是不会改变的。

只有在任一表达式具有副作用而影响到其它表达式的时候,其求值顺序才会和看上去有所不同。

var a = 1;
var b = (a++)+a;

上面的表达式计算顺序是这样的:

  1. 先计算子表达式 b 的值;
  2. 计算 a++,子表达式的计算结果为 1,但是计算完成后 a 的值已经是 2 了;
  3. 计算 a,值为 2;
  4. 计算 (a++)+a,值为 3;
  5. 将该表达式的结果 3 赋给 b。

算术表达式

基本的算术运算符:*(乘法)、/(除法)、%(取余)、+(加法)和 -(减法)。

加法之外的运算符都是在必要的时候将操作数转换为数字,然后求积、商、余数和差。无法转换为数字的操作数,都转换为 NaN。如果操作数是 NaN,算术运算的结果也是 NaN。

对于 JavaScript 来说,所有的数字都是浮点数,所以除法运算的结果总是浮点型。除数为 0 的时候,运算结果为正/负无穷大,0/0 的结果则是 NaN,所有这些运算都有结果,都不会报错。

取余运算符不限于整数,浮点数也可以,比如 6.5 % 2.1 === 0.2(实际输出 0.19999999999999973)。

+ 运算符

该运算符既可以将两个数字相加,也可以连接两个字符串。

一般来说,优先进行字符串连接。只要其中一个操作数是字符串或者转换为字符串的对象,另外一个操作数也会转换为字符串,然后加法运算符连接这两个字符串。

只有两个操作数都不是类字符串(string-like)的时候,才会进行算术加法运算。

加法运算符的表现如下:

  1. 其中一个操作数是对象的话,就会遵循对象到原始值的转换规则进行转换:日期对象通过 toString() 方法转换,其它对象则通过 valueOf() 方法转换(如果 valueOf() 方法返回一个原始值的话)。因为大部分对象都没有可用的 valueOf() 方法,所以这些对象其实还是会通过 toString() 方法进行转换。
  2. 对象转换到原始值后,如果其中一个操作数是字符串,则另一个操作数也会转换为字符串,然后加号运算符连接这两个字符串。
  3. 两个操作数都不是对象的话,就会都转换为数字(或者 NaN),然后相加。
1 + 2 // => 3: 加法
'1' + '2' // => '12': 字符串连接
'1' + 2 // => '12': 数字转换为字符串之后连接字符串
1 + {} // => '1[object Object]': 对象转换为字符串后连接字符串
true + true // => 2: 布尔值转换为数字后做加法
2 + null // => 2: null 转换为 0 之后做加法
2 + undefined // => NaN: undefined 转换为 NaN 之后做加法

另外,加号运算符和数字一起使用的时候,还要注意加法的结合性对运算顺序的影响。

1 + 2 + ' blind mice' // => '3 blind mice'
1 + (2 + 'blind mice') // => '12blind mice'

一元算术运算符

一元运算符作用于一个操作数并产生一个新值。JavaScript 中的一元运算符都有很高的优先级,并且都是右结合。一元运算符在必要的时候会将操作数转换为数字。

一元加法(+):该运算符把操作数转换为数字或者 NaN,并返回转换后的数字。如果操作数本身就是数字的话则直接返回(该运算符并不会改变操作数的正负号 => +(-5) === -5)。 一元减法(-):该运算符根据需要把操作数转换为数字,然后改变运算结果的符号。

递增(++):运算符将操作数(变量、数组元素或对象属性)转换为数字之后加 1,并将加 1 后的数值重新赋给变量、数组元素或对象属性。该运算符的返回值依赖于它相对于操作数的位置。运算符在操作数之前时,称为“前增量”,它将操作数加一之后,返回计算后的值。而运算符在操作数之后的话,称为“后增量”运算符,它对操作数进行加一运算,但返回的是加一之前的值。

var i = 1, j = ++i; // => i: 2, j: 2
var i = 1, j = i++; // => i: 2, j: 1

这里还要注意区分 ++xx = x + 1++运算符只执行数值运算操作,而 + 运算符有可能执行字符串连接操作。

var x = '1'; ++x; // => 2
var x = '1'; x = x + 1; // => '11'

此外,由于 JavaScript 会自动进行分号补全,所以不能在后增量运算符和操作数之间插入换行符。如果插入了换行符,JavaScript 会把操作数当作一条单独的语句,并在其之前补上一个分号。

var x = 1;
x
++ // => x: 1

递减(--):与递增运算符相同。

位运算符

先 pass 这一小节。

关系表达式

关系运算符用于测试两个值之间的关系(比如“相等”、“小于”,或者“是……的属性”),并根据关系是否存在而返回 true 或者 false。

相等和不等运算符

===== 运算符都用于比较两个值是否相等,都允许任意类型的操作数。

=== 也称为“严格相等运算符”,

!=!== 运算符是 ===== 运算符的求反:如果两个值通过 ===== 比较的结果为 true,则 !=!== 的比较结果则为 false,反之亦然。

在 JavaScript 中,比较两个对象时,比较的是引用而不是值。对象只和它本身相等,和其它任何对象都不相等:即使具有相同数量的属性、相同的属性名和值也依然是不相等的。数组也是如此。

严格相等运算符 === 的比较流程如下:

  • 如果两个值的类型不相等,则它们不相等。
  • 如果两个值都是 null 或者 undefined ,则它们不相等。
  • 如果两个值都是布尔值 true 或 false,则它们相等。
  • 只要有一个值是 NaN,它们就不相等:NaN 和自身也不相等。所以可以用 x !== x 来判断是否为 NaN。
  • 如果两个值为数字且相等,则它们相等。0 和 -0 也相等。
  • 如果两个值为字符串,且各个对应位上的 16 位数完全相等,则它们相等。如果它们的长度或内容不同,则不相等。具有不同编码的 16 位值的两个字符串,所显示的字符可能是一样的。JavaScript 并不对 Unicode 进行标准化的转换,所以这样的字符通过 ===== 的比较结果也是不相等的。(TODO: 这里需要进一步了解,貌似有个有关特殊网址的 0day 漏洞应用的就是这个知识点?)
  • 如果两个引用值均指向同一个对象、数组或函数,则它们相等。如果指向的是不同的对象,肯定不相等。

相等运算符 == 的判断就没那么严格了,如果两个操作数类型不同,相等运算符会先转换为相同类型,再进行比较:

  • 如果两个操作数类型相同,则和上文所述的严格相等的比较规则和结果一样。
  • 如果两个操作数类型不相同,则依据如下规则进行转换和比较:
    • 如果一个值是 null,另一个是 undefined,则它们相等。
    • 如果一个值是数字,另一个是字符串,则先将字符串转换为数字,然后比较两个数字。
    • 如果一个值是 true,则将其转换为 1 再进行比较;为 false 的话,则转换为 0 再进行比较。
    • 如果一个值是对象,另一个值是数字或字符串,则按照对象转换为原始值的规则,先将对象转换为原始值,再进行比较。JavaScript 语言核心的内置类中,只有对象使用 toString() 转换,其余类都是先尝试使用 valueOf() 转换,再尝试使用 toString() 转换。非核心的对象,则通过各自定义的方法转换为原始值。
    • 其它不同类型之间的比较均不相等。

'1' == true 为例:布尔值 true 首先转换为数字 1,然后字符串 '1' 也转换为数字 1,最后两个数字 1 相等,因此比较结果为 true。

比较运算符

虽然比较运算符的操作数可以是任意类型,但只有数字和字符串才能真正进行比较。其它类型的操作数的转换规则如下:

  • 操作数为对象,则首先尝试用 valueOf() 转换成一个原始值,否则再用 toString() 的转换结果进行比较。
  • 对象转换为原始值之后,如果两个操作数都是字符串,则依照字母表的顺序对两个字符串进行比较。这里的字母表顺序,是指组成字符串的 16 位 Unicode 字符的索引顺序。
  • 对象转换为原始值之后,如果至少有一个操作数不是字符串,则两个操作数都转换为数字。0 和 -0 相等,Infinity 大于任何其它数(除了自己),-Infinity 小于任何其它数(除了自己)。如果其中一个操作数是(或者转换后是)NaN,则比较结果为 false。

注意:由于 JavaScript 字符串是由 16 位整数值组成的序列,所以字符串的比较就是这些数值的比较。而 Unicode 字符编码的顺序和传统字符编码顺序不太一样,其中所有大写的 ASCII 字母都“小于”小写字母。而 String.localCompare() 则使用本地语言的字母表中定义的字符次序进行比较,更符合用户习惯。

另外,对于数字和字符串来说,加号运算符和比较运算符也有所不同:加号运算符更偏爱字符串,至少有一个操作数是字符串,它就会进行字符串连接操作;比较运算符则更偏爱数字,只要有一个操作数是数字,它就进行数学加法操作。

1 + 2 // => 3
'1' + '2' // => '12'
'1' + 2 // => '12'
11 < 3 // => false
'11' < '3' // => true
'11' < 3 // => false
'one' < 3 // => false

最后,<=>= 则判断相等的时候,只是简单的“不大于”和“不小于”,并不依赖于相等运算符和严格相等运算符的比较规则。仅有一个例外:当其中一个操作数是(或者转换后是)NaN 的时候,所有 4 个比较运算符均返回 false。

in 运算符

该运算符检查右侧的对象是否拥有一个名为左侧操作数的值的属性名,或者检查右侧的数组是否包含左侧的索引值。

var point = { x: 1, y: 1 };
'x' in point // => true: 对象包含属性 x
'z' in point // => false: 对象不包含属性 z
'toString' in point // => true: 对象继承了 toString() 方法

var data = [7, 8, 9];
0 in data // => true: 数组包含索引为 0 的元素
'1' in data // => true: 数组包含索引为 1 的元素
3 in data // => false: 数组不包含索引为 3 的元素
7 in data // => false: 数组不包含索引为 7 的元素

instanceof 运算符

该运算符检查左操作数对象是否为右操作数构造函数(不含函数名之后的括号 ())的实例。

var d = new Date(); // => 通过 Date() 构造函数来新建一个对象
d instanceof Date // => true: d 是由 Date() 构造函数创建的,所以是 Date 这个构造函数的实例
d instanceof Date() // => VM224:1 Uncaught TypeError: Right-hand side of 'instanceof' is not an object
d instanceof Object // => true: 所有的对象都是 Object 的实例
d instanceof Number // => false

所有的对象都是 Object 的实例,而通过 instanceof 判断一个对象是否是一个类的实例的时候,这个判断也会包含对“父类”(superclass)的检测(TODO: 没太看懂……)。

instanceof 的左操作数不是对象的话,则返回 false;右操作数不是对象所属的类的话,就会抛出一个类型异常,看上面代码的第三就行(TODO: 不正确,待修正)。

要理解 instanceof 是如何工作的,必须首先理解“原型链”(prototype chain)——即 JavaScript 的继承机制。

计算表达式 o instanceof f 时,JavaScript 首先计算 f.prototype,然后在原型链中查找 o。如果能找到,则 of(或者 f 的子类)的一个实例,表达式返回 true;否则就说明 o 不是 f 的实例,表达式返回 false。

逻辑表达式

逻辑与(&&)运算符

该运算符可以对两个布尔值执行布尔与(AND)操作,也可以对真值和假值进行布尔与操作。在对真值/假值进行布尔与操作的时候,会先计算左操作数的值,只有左操作数为真值时,才会进一步计算右操作数。这个规则,也叫“短路”(short circuiting)。

逻辑或(||)运算符

该运算符也有类似的表现:如果左操作数为真,就不会再计算右操作数了。它的一种常用方式,是从一组备选表达式中选出第一个真值表达式:

// 将 o 的成员属性复制到 p 中,并返回 p
function copy(o, p) {
p = o || {}; // 如果未向参数 p 传入对象,则使用一个新建的空对象。
// 函数体主逻辑
}

逻辑非(!)运算符

该运算符首先将操作数转换为布尔值,然后再对布尔值求反。结合这个特性,就可以用 !! 求出操作数的等价布尔值。

赋值表达式

其左操作数是一个左值:变量或者对象属性(或数组元素),右操作数可以是任意类型的任意值,且赋值表达式的值就是右操作数的值。

赋值操作符的结合性是从右至左,也就是说,下面的代码,先给 k 赋值,再给 j 赋值,最后给 i 赋值。

i = j = k = 0;
i++ = ++j = 0; // => 报错:自增表达式不能作为左值

带操作的赋值运算

下面的两个表达式是等价的:

total += sales_tax
total = total + sales_tax

所有带操作的赋值运算:+=-=*=/=%=<<=>>=>>>=&=|=^=

a op= b
a = a op b

在上面的两个表达式中,第一行的 a 只计算了一次,而第二行的 a 计算了两次。如果 a 有副作用(比如函数调用和赋值),在使用该表达式的时候就需要注意一下。

表达式计算

TODO: 本小节暂时跳过。

其它运算符

条件运算符(?:)

该运算符常常可用来判断变量是否已定义,如果已定义则使用它,否则使用一个默认值:

var greeting = 'hello ' + (username ? username : 'there');

typeof 运算符

该运算符获取操作数的类型。

xtypeof x
undefined'undefined'
null'object'
true / false'boolean'
任意数字或 NaN'number'
任意字符串'string'
任意函数'function'
任意内置对象(非函数)'object'
任意宿主对象由编译器各自实现的字符串,但不是 'undefined'、'boolean'、'number' 或者 'string'

常见用法:(typeof value == 'string') ? '"' + value + '"' : value

typeof 运算符也可以像函数一样带上圆括号,带不带括号,运行结果都是一样的。

另外,由于 typeof null 的返回结果也是 object,所以需要区分 null 和对象的时候,还要用 !!value 单独判断一下。

此外,客户端 JavaScript 中的大多数宿主对象都是 object 类型。

如果想进一步区分对象的类,就需要使用 instanceof 运算符、class 特性以及 constructor 属性。(TODO: 这个话题可以把相关的内容总结一下,形成一篇文章。)

delete 运算符

该运算符用来删除对象的属性或者数组的元素。删除后的属性或者元素将不再存在,可以用 in 运算符来检查属性(名称)或者数组元素(索引)是否还存在。

var o = { x: 1, y: 2 };
delete o.x; // => true: 成功删除属性
'x' in o; // => false: 属性不再存在
'y' in o; // => true: 该属性还存在

var a = [4, 5, 6];
delete a[2]; // => true: 成功删除元素
2 in a; // => false: 元素索引不再存在
1 in a; // => true: 该元素索引还存在
a.length // => 3: 数组长度未变!删除操作虽然删除了元素,但是并没有修改数组长度,这样就产生了一个稀疏数组。所以该运算符在操作数组元素时要慎用。

如果 delete 的操作数不是左值,则运算符将不作任何操作并返回 true;否则,运算符将试图删除这个左值,成功的话返回 true。诸如内置核心属性、客户端属性、var 语句声明的属性、function 语句定义的函数和函数参数都不能删除。

在 ES5 严格模式中,如果 delete 的操作数是变量、函数参数或函数参数之类的非法操作数,则该操作将抛出一个语法错误(SyntaxError)异常;在删除不可配置的属性时,会抛出一个类型错误异常,只有操作数是属性访问表达式时才可删除。在非严格模式下,这些操作都不再报错,而只是简单地返回 false。

var o = { x: 1, y: 2 };
delete o.x; // => true: 成功删除对象属性
typeof o.x; // => "undefined": 属性不再存在
delete o.x; // => true: 删除不存在的属性也返回 true
delete o; // => false: 不能删除 var 语句声明的变量
delete 1; // => true: 参数不是左值,删除失败,但也返回 true
this.x = 1; // => 1: 给全局对象定义一个属性
delete x; // => true: 非严格模式下可删除,严格模式下需改成 delete this.x
x // => 运行时错误,x 未定义
Uncaught ReferenceError: x is not defined

void 运算符

一元运算符 void 出现在任意类型的操作数之前,操作数会照常计算,但表达式永远返回 undefined。由于 void 会忽略操作数,所以操作数有副作用的时候可以搭配该运算符使用。

该运算符常常用在客户端的 URL——javascript:URL 中,在 URL 可以放心地写带有副作用的表达式,void 则用来让浏览器不显示表达式的计算结果。

<a href="javascript:void window.open();">打开一个新窗口</a>

但是在实际开发中其实不会这么写,而是给该节点的 onclick 绑定一个事件处理程序。

逗号运算符(,)

逗号运算符是二元运算符,它依次计算左操作数和右操作数的值,然后返回右操作数的值。该运算符最常用的场景在 for 循环中:

for ( var i = 0, j = 10; i < j; i++, j--) {
console.log(i + j);
}