为什么要理解表达式,运算符和语句呢?抛开可以加深你对计算机语言语法的理解这种有一些虚幻缥缈的原因,最直接的原因是我们在面试题上会遇到,它们经常会把语句写的长长的,错综复杂,不加分号和空格,然后问你运行结果,这时候就考你什么是语句,表达式,什么会触发计算,怎么把它断句,组成正确的运行顺序。
概念(掌握)
表达式
表达式(expression)JavaScript 中的一个短语,JavaScript 解释器会将其计算(evaluate)出一个结果。
程序中的常量是最简单的一类表达式。变量名也是一种简单的表达式,它的值就是赋值给变量的值。
复杂表达式是由简单表达式组成的。比如,数组访问表达式是由一个表示数组的表达式、左方括号、一个整数表达式和右方括号构成。它们所组成的新的表达式的运算结果是该数组的特定位置的元素值。同样的,函数调用表达式由一个表示函数对象的表达式和 0 个或多个参数表达式构成。
运算符
将简单表达式组合成复杂表达式最常用的方法就是使用运算符(operator)。运算符按照特定的运算规则对操作数(通常是两个)进行运算,并计算出新值。
乘法运算符“*”
是比较简单的例子。表达式 x*y 是对两个变量表达式 x 和 y 进行运算并得出结果。有时我们更愿意说运算符返回了一个值而不是“计算”出了一个值。
表达式(掌握)
原始表达式
原始表达式是表达式的最小单位——它们不再包含其他表达式。JavaScript 中的原始表达式包含常量或直接量、关键字和变量。
1.23 //数字直接量
"hel1o" //字符串直接量
/pattern/ //正则表达式直接量
2
3
JavaScript 中的一些保留字构成了原始表达式:
true; //返回一个布尔值:真
false; //返回一个布尔值:假
null; //返回一个值:空
this; //返回“当前“对象
2
3
4
最后,第三种原始表达式是变量:
i; //返回变量i的值
sum; //返回sum的值
undefined; //undefined是全局变量,和null不同,它不是一个关键字
2
3
对象和数组的初始化表达式
对象和数组初始化表达式实际上是一个新创建的对象和数组。这些初始化表达式有时称做“对象直接量”和“数组直接量”。然而和布尔直接量不同,它们不是原始表达式,因为它们所包含的成员或者元素都是子表达式。
数组初始化表达式
数组初始化表达式是通过一对方括号和其内由逗号隔开的列表构成的。初始化的结果是一个新创建的数组。数组的元素是逗号分隔的表达式的值:
[] //一个空数组:[]内留空即表示该数组没有任何元素
[1+2,3+4] //拥有两个元素的数组,第一个是3,第二个是7
2
数组直接量中的列表逗号之间的元素可以省略,这时省略的空位会填充值 undefined。例如,下面这个数组包含 5 个元素,其中三个元素是 undefined:
var sparseArray = [1, , , , 5];
数组直接量的元素列表结尾处可以留下单个逗号,这时并不会创建一个新的值为 undefined 的元素。
对象初始化表达式
对象初始化表达式和数组初始化表达式非常类似,只是方括号被花括号代替,并且每个子表达式都包含一个属性名和一个冒号作为前缀:
var p={x:2.3,y:-1.2};//一个拥有两个属性成员的对象
var q={};//一个空对象
q.x=2.3;q-y=-1.2;//q的属性成员和p的一样
2
3
对象直接量中的属性名称可以是字符串而不是标识符
var side = 1;
var square = {
upperLeft: { x: p.x, y: p.y },
lowerRight: { x: p.x + side, y: p.y + side }
};
2
3
4
5
函数定义表达式
函数定义表达式定义一个 JavaScript 函数。表达式的值是这个新定义的函数。从某种意义上讲,函数定义表达式可称为“函数直接量”。
一个典型的函数定义表达式包含关键字 function,跟随其后的是一对圆括号,括号内是一个以逗号分割的列表,列表含有 0 个或多个标识符(参数名),然后再跟随一个由花括号包裹的 JavaScript 代码段(函数体),例如:
//这个函数返回传入参数值的平方
var square = function(x) {
return x * x;
};
2
3
4
函数定义表达式同样可以包含函数的名字。函数也可以通过函数语句来定义,而不是函数表达式。更多详情会在后面中讨论。
属性访问表达式
属性访问表达式运算得到一个对象属性或一个数组元素的值。JavaScript 为属性访问定义了两种语法:
expression.identifier;
expression[expression];
2
第一种写法是一个表达式后跟随一个句点和标识符。表达式指定对象,标识符则指定需要访问的属性的名称。
第二种写法是使用方括号,方括号内是另外一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或者代表要访问数组元素的索引。
不管使用哪种形式的属性访问表达式,在“.”和“[”之前的表达式总是会首先计算。
- 如果计算结果是 null 或者 undefined,表达式会抛出一个类型错误异常,因为这两个值都不能包含任意属性。
- 如果运算结果不是对象(或者数组),JavaScript 会将其转换为对象。
- 如果对象表达式后跟随句点和标识符,则会查找由这个标识符所指定的属性的值,并将其作为整个表达式的值返回。
- 如果对象表达式后跟随一对方括号,则会计算方括号内的表达式的值并将它转换为字符串。
- 不论哪种情况,如果命名的属性不存在,那么整个属性访问表达式的值就是 undefined。
显然.identifier 的写法更加简单,但需要注意的是,这种方式只适用于要访问的属性名称是合法的标识符,并且需要知道要访问的属性的名字。
如果属性名称是一个保留字或者包含空格和标点符号,或是一个数字(对于数组来说),则必须使用方括号的写法。
当属性名是通过运算得出的值而不是固定的值的时候,这时必须使用方括号写法。
调用表达式
JavaScript 中的调用表达式(invocation expression)是一种调用(或者执行)函数或方法的语法表示。它以一个函数表达式开始,这个函数表达式指代了要调用的函数。函数表达式后跟随一对圆括号,括号内是一个以逗号隔开的参数列表,参数可以有 0 个也可有多个,例如:
f(0)//f是一个函数表达式;0是一个参数表达式
Math.max(x,y,z)//Math.max是一个函数;x,y和z是参数
a.sort()//a.sort是一个函数,它没有参数
2
3
当对调用表达式进行求值的时候,首先计算函数表达式,然后计算参数表达式,得到一组参数值。
如果函数表达式的值不是一个可调用的对象,则抛出一个类型错误异常(所有的函数都是可调用的,即使宿主对象不是函数它也有可能被调用,这里的区别将在后面讲述)。
然后,实参的值被依次赋值给形参,这些形参是定义函数时指定的,接下来开始执行函数体。如果函数使用 return 语句给出一个返回值,那么这个返回值就是整个调用表达式的值。否则,调用表达式的值就是 undefined。
如果这个表达式是一个属性访问表达式,那么这个调用称做“方法调用”(method invocation)。在方法调用中,执行函数体的时候,作为属性访问主题的对象和数组(也就是"."的对象或数组)便是其调用方法内 this 的指向。这种特性使得在面向对象编程范例中,函数(其 OO 名称为“方法”)可以调用其宿主对象。
在 ECMAScript 5 中,那些通过严格模式定义的函数在调用时将使用 undefined 作为 this 的值,this 不会指向全局对象。
对象创建表达式
对象创建表达式(object creation expression)创建一个对象并调用一个函数(这个函数称做构造函数)初始化新对象的属性。对象创建表达式和函数调用表达式非常类似,只是对象创建表达式之前多了一个关键字 new:
new Object();
new Point(2, 3);
2
如果一个对象创建表达式不需要传入任何参数给构造函数的话,那么这对空圆括号是可以省略掉的:
new Object();
new Date();
2
JavaScript 首先创建一个新的空对象,然后,JavaScript 通过传入指定的参数并将这个新对象当做 this 的值来调用一个指定的函数。这个函数可以使用 this 来初始化这个新创建对象的属性。
那些被当成构造函数的函数不会返回一个值,并且这个新创建并被初始化后的对象就是整个对象创建表达式的值。如果一个构造函数确实返回了一个对象值,那么这个对象就作为整个对象创建表达式的值,而新创建的对象就废弃了。
运算符(掌握)
运算符概述
需要注意的是,大多数运算符都是由标点符号表示的,比如“+”和“=”。而另外一些运算符则是由关键字表示的,比如 delete 和 instanceof。关键字运算符和标点符号所表示的运算符一样都是正规的运算符,它们的语法都非常言简意赅。
下表是按照运算符的优先级排序的,前面的运算符优先级要高于后面的运算符优先级。
被水平分割线分隔开来的运算符具有不同的优先级。标题为 A 的列表示运算符的结合性,L(从左至右)或 R(从右至左),标题为 N 的列表示操作数的个数。标题为“类型”的列表示期望的操作数类型,以及运算符的结果类型(在“一”符号之后)。下表之后的段落会解释优先级、结合性和操作数类型的概念。
操作数的个数
运算符可以根据其操作数的个数进行分类。JavaScript 中的大多数运算符(比如“*”乘法运算符)是一个二元运算符(binary operator),将两个表达式合并成一个稍复杂的表达式。换言之,它们的操作数均是两个。
JavaScript 同样支持一些一元运算符(unary operator),它们将一个表达式转换为另一个稍复杂的表达式。表达式一 x 中的“-”运算符就是一个一元运算符,是将操作数 x 求负值。
最后,JavaScript 支持一个三元运算符(ternary operator),条件判断运算符“?:”,它将三个表达式合并成一个表达式。
操作数类型和结果类型
一些运算符可以作用于任何数据类型,但仍然希望它们的操作数是指定类型的数据,并且大多数运算符返回(或计算出)一个特定类型的值。在上表标题为“类型”的列中列出了运算符操作数的类型(箭头前)和运算结果的类型(箭头后)。
左值
上表中的赋值运算符和其他少数运算符期望它们的操作数是 1va1 类型。左值(lvalue)是一个古老的术语,它是指“表达式只能出现在赋值运算符的左侧”。在 JavaScript 中,变量、对象属性和数组元素均是左值。ECMAScript 规范允许内置函数返回一个左值,但自定义的函数则不能返回左值。
运算符的副作用
计算一个简单的表达式(比如 2*3)不会对程序的运行状态造成任何影响,程序后续执行的计算也不会受到该计算的影响。而有一些表达式则具有很多副作用,前后的表达式运算会相互影响。
赋值运算符是最明显的一个例子:如果给一个变量或属性赋值,那么那些使用这个变量或属性的表达式的值都会发生改变。
“++”和“--”递增和递减运算符与此类似,因为它们包含隐式的赋值。delete 运算符同样有副作用:删除一个属性就像(但不完全一样)给这个属性赋值 undefined。
其他的 JavaScript 运算符都没有副作用,但函数调用表达式和对象创建表达式有些特别,在函数体或者构造函数内部运用了这些运算符并产生了副作用的时候,我们说函数调用表达式和对象创建表达式是有副作用的。
运算符优先级
上表中所示的运算符是按照优先级从高到低排序的,每个水平分割线内的一组运算符具有相同的优先级。运算符优先级控制着运算符的执行顺序。优先级高的运算符(表格的顶部)的执行总是先于优先级低(表格的底部)的运算符。
运算符的优先级可以通过显式使用圆括号来重写。
需要注意的是,属性访问表达式和调用表达式的优先级要比上表中列出的所有运算符都要高。看一下这个例子:
typeof my.functions[x](y);
尽管 typeof 是优先级最高的运算符之一,但 typeof 也是在两次属性访问和函数调用之后执行的。
实际上,如果你真的不确定你所使用的运算符的优先级,最简单的方法就是使用圆括号来强行指定运算次序。有些重要规则需要熟记:乘法和除法的优先级高于加法和减法,赋值运算的优先级非常低,通常总是最后执行的。
运算符的结合性
结合性指定了在多个具有同样优先级的运算符表达式中的运算顺序。在上表中标题为 A 的列说明了运算符的结合性。L 指从左至右结合,R 指从右至左结合。
一元操作符、赋值和三元条件运算符都具有从右至左的结合性。
运算顺序
运算符的优先级和结合性规定了它们在复杂的表达式中的运算顺序,但并没有规定子表达式的计算过程中的运算顺序。
JavaScript 总是严格按照从左至右的顺序来计算表达式。
例如,在表达式 w=x+y*z 中,将首先计算子表达式 w,然后计算 x、y 和 z,然后,y 的值和 z 的值相乘,再加上 x 的值,最后将其赋值给表达式 w 所指代的变量或属性。给表达式添加圆括号将会改变乘法、加法和赋值运算的关系,但从左至右的顺序是不会改变的。
只有在任何一个表达式具有副作用而影响到其他表达式的时候,其求值顺序才会和看上去有所不同。
如果表达式 x 中的一个变量自增 1,这个变量在表达式 z 中使用,那么实际上是先计算出了 x 的值再计算 z 的值,这一点非常重要。
作者在这里揭示了一种很容易忽略的现象。假设存在 a=1,那么“b=(a++)+a;”将如何计算结果呢?按照正文所述,顺序应该是,1)计算 b,2)计算 a++(假设值为 c),3)计算 a,4)计算 c+a,5)将 c+a 的结果赋值给 b。按照“+ +”的定义,第 2)步中 a++的结果依然是 1,即 c 为 1,随后 a 立即增 1,因此在执行第 3)步时,a 的值已经是 2。所以 b 的结果为 3。很多初学者会误认为 a 增 1 的操作是在表达式计算完毕后执行的。
算术表达式
“+”运算符
二元加法运算符“+”可以对两个数字做加法,也可以做字符串连接操作。
加号的转换规则优先考虑字符串连接,如果其中一个操作数是字符串或者转换为字符串的对象,另外一个操作数将会转换为字符串,加法将进行字符串的连接操作。如果两个操作数都不是类字符串(string-like)的,那么都将进行算术加法运算。
- 如果其中一个操作数是对象,则对象会遵循对象到原始值的转换规则转换为原始类值:日期对象通过 toString()方法执行转换,其他对象则通过 valueOf()方法执行转换(如果 value0f()方法返回一个原始值的话)。由于多数对象都不具备可用的 valueof()方法,因此它们会通过 tostring()方法来执行转换。
- 在进行了对象到原始值的转换后,如果其中一个操作数是字符串的话,另一个操作数也会转换为字符串,然后进行字符串连接。
- 否则,两个操作数都将转换为数字(或者 NaN),然后进行加法操作。
1 + {}; //=>"1[object Object]":对象转换为字符串后进行字符串连接
2 + undefined; //=>NaN:undefined转换为NaN后做加法
2
一元算术运算符
一元运算符作用于一个单独的操作数,并产生一个新值。在 JavaScript 中,一元运算符具有很高的优先级,而且都是右结合(right-associative)。
一元加法(+)
一元加法运算符把操作数转换为数字(或者 NaN),并返回这个转换后的数字。如果操作数本身就是数字,则直接返回这个数字。
一元减法(-)
当“-”用做一元运算符时,它会根据需要把操作数转换为数字,然后改变运算结果的符号。
递增(++)
递增“+ +”运算符对其操作数进行增量(加一)操作,操作数是一个左值(lvalue)(变量、数组元素或对象属性)。运算符将操作数转换为数字,然后给数字加 1,并将加 1 后的数值重新赋值给变量、数组元素或者对象属性。
递增“+ +”运算符的返回值依赖于它相对于操作数的位置。当运算符在操作数之前,称为“前增量”(pre-increment)运算符,它对操作数进行增量计算,并返回计算后的值。当运算符在操作数之后,称为“后增量”(post-increment)运算符,它对操作数进行增量计算,但返回未做增量计算的(unincremented)值。
需要注意的是,表达式++x 并不总和 x=x+1 完全一样,“+ +”运算符从不进行字符串连接操作,它总是会将操作数转换为数字并增 1。如果 x 是字符串“1”,++x 的结果就是数字 2,而 x+1 是字符串“11”。
同样需要注意的是,由于 JavaScript 会自动进行分号补全,因此不能在后增量运算符和操作数之间插入换行符。如果插入了换行符,JavaScript 将会把操作数当做一条单独的语句,并在其之前补上一个分号。
递减(--)
递减“一”运算符的操作数也是一个左值。它把操作数转换为数字,然后减 1,并将计算后的值重新赋值给操作数。和“++”运算符一样,递减“--”运算符的返回值依赖于它相对操作数的位置,当递减运算符在操作数之前,操作数减 1 并返回减 1 之后的值。当递减运算符在操作数之后,操作数减 1 并返回减 1 之前的值。当递减运算符在操作符的右侧时,运算符和操作数之间不能有换行符。
位运算符
位运算符可以对由数字表示的二进制数据进行更低层级的按位运算。尽管它们并不是传统的数学运算,但这里也将其归类为算术运算符,因为它们作用于数值类型的操作数并返回数字。这些运算符在 JavaScript 编程中并不常用,如果你对十进制整数的二进制表示并不熟悉的话,你可以跳过本节内容。所以我跳过了。。。。
关系表达式
关系运算符用于测试两个值之间的关系(比如“相等”,“小于”,或“是.的属性”),根据关系是否存在而返回 true 或 false。关系表达式总是返回一个布尔值。
相等和不等运算符
“= =”和“= = =”运算符用于比较两个值是否相等,两个运算符允许任意类型的操作数,如果操作数相等则返回 true,否则返回 false。
“= = =”也称为严格相等运算符(strict equality)(有时也称做恒等运算符(identity operator)),它用来检测两个操作数是否严格相等。“==”运算符称做相等运算符(equality operator),它用来检测两个操作数是否相等,这里“相等”的定义非常宽松,可以允许进行类型转换。
“!=”和“!= =”运算符的检测规则是“= =”和“= = =”运算符的求反。如果两个值通过“= =”的比较结果为 true,那么通过“!=”的比较结果则为 false。如果两值通过
“= = =”的比较结果为 true,那么通过“!= =”的比较结果则为 false。“!=”称做“不相等”、“!= =”称做“不严格相等”。
严格相等运算符“===”计算规则
- 如果两个值类型不相同,则它们不相等。
- 如果两个值都是 nul1 或者都是 undefined,则它们不相等。如果两个值都是布尔值 true 或都是布尔值 false,则它们相等。
- 如果其中一个值是 NaN,或者两个值都是 NaN,则它们不相等。
- 如果两个值为数字且数值相等,则它们相等。如果一个值为 0,另一个值为-0,则它们同样相等。
- 如果两个值为字符串,且所含的对应位上的 16 位数(参照 3.2 节)完全相等,则它们相等。如果它们的长度或内容不同,则它们不等。两个字符串可能含义完全一样且所显示出的字符也一样,但具有不同编码的 16 位值。JavaScript 并不对 Unicode 进行标准化的转换,因此像这样的字符串通过“= = =”和“= =”运算符的比较结果也不相等。
- 如果两个引用值指向同一个对象、数组或函数,则它们是相等的。如果指向不同的对象,则它们是不等的,尽管两个对象具有完全一样的属性。
相等运算符“==”计算规则
- 如果两个操作数的类型相同,则和上文所述的严格相等的比较规则一样。如果严格相等,那么比较结果为相等。如果它们不严格相等,则比较结果为不相等。
- 如果两个操作数类型不同,“==”相等操作符也可能会认为它们相等。检测相等将会遵守如下规则和类型转换:
- 如果一个值是 null,另一个是 undefined,则它们相等。
- 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较。
- 如果其中一个值是 true,则将其转换为 1 再进行比较。如果其中一个值是 false,则将其转换为 0 再进行比较。
- 如果一个值是对象,另一个值是数字或字符串,则使用转换规则将对象转换为原始值,然后再进行比较。对象通过 toString()方法或者 value0f()方法转换为原始值。JavaScript 语言核心的内置类首先尝试使用 valueof(),再尝试使用 toString(),除了日期类,日期类只使用 toString()转换。那些不是 JavaScript 语言核心中的对象则通过各自的实现中定义的方法转换为原始值。
- 其他不同类型之间的比较均不相等。
比较运算符
比较操作符的操作数可能是任意类型。然而,只有数字和字符串才能真正执行比较操作,因此那些不是数字和字符串的操作数都将进行类型转换,类型转换规则如下:
- 如果操作数为对象,那么这个对象将依照转换规则转换为原始值:如果 valueof()返回一个原始值,那么直接使用这个原始值。否则,使用 tostring()的转换结果进行比较操作。
- 在对象转换为原始值之后,如果两个操作数都是字符串,那么将依照字母表的顺序对两个字符串进行比较,这里提到的“字母表顺序”是指组成这个字符串的 16 位 Unicode 字符的索引顺序。
- 在对象转换为原始值之后,如果至少有一个操作数不是字符串,那么两个操作数都将转换为数字进行数值比较。0 和一 0 是相等的。Infinity 比其他任何数字都大(除了 Infinity 本身),-Infinity 比其他任何数字都小(除了它自身)。如果其中一个操作数是(或转换后是)NaN,那么比较操作符总是返回 false。
需要注意的是,JavaScript 字符串是一个由 16 位整数值组成的序列,字符串的比较也只是两个字符串中的字符的数值比较。由 Unicode 定义的字符编码顺序和任何特定语言或者本地语言字符集中的传统字符编码顺序不尽相同。注意,字符串比较是区分大小写的,所有的大写的 ASCII 字母都“小于”小写的 ASCII 字母。
参照 String.localCompare()方法来获取更多字符串比较的相关信息,String.1ocalCompare()方法更加健壮可靠,这个方法参照本地语言的字母表定义的字符次序。对于那些不区分字母大小写的比较来说,则需要首先将字符串转全部换为小写字母或者大写字母,通过 String.toLowerCase()和 String.toupperCase()做大小写的转换。
in 运算符
in 运算符希望它的左操作数是一个字符串或可以转换为字符串,希望它的右操作数是一个对象。如果右侧的对象拥有一个名为左操作数值的属性名,那么表达式返回 true,例如:
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];//拥有三个元素的数组
"o" in data//=>true:数组包含元素”0”
1 in data//=>true:数字转换为字符串
3 in data//=>false:没有索引为3的元素
2
3
4
5
6
7
8
instanceof 运算符
instanceof 运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回 true;否则返回 false。
var d=new Date();//通过Date()构造函数来创建一个新对象
d instanceof Date;//计算结果为true,d是由Date()创建的
d instanceof Object;//计算结果为true,所有的对象都是0bject的实例
d instanceof Number;//计算结果为false,d不是一个Number对象
vara=[1,2,3];//通过数组直接量的写法创建一个数组
a instanceof Array;//计算结果为true,a是一个数组
a instanceof Object;//计算结果为true,所有的数组都是对象
a instanceof RegExp;//计算结果为false,数组不是正则表达式
2
3
4
5
6
7
8
需要注意的是,所有的对象都是 0bject 的实例。当通过 instanceof 判断一个对象是否是一个类的实例的时候,这个判断也会包含对“父类”(superclass)的检测。如果 instanceof 的左操作数不是对象的话,instanceof 返回 false。如果右操作数不是函数,则抛出一个类型错误异常。
逻辑表达式
逻辑与(&&)
一般来讲,当“ ”右侧的表达式具有副作用的时候(赋值、递增、递减和函数调用表达式)要格外小心。因为这些带有副作用的表达式的执行依赖于左操作数的计算结果。
逻辑或(||)
和“&8”运算符一样,同样应当避免右操作数包含一些具有副作用的表达式,除非你目地明确地在右侧使用带副作用的表达式,而有可能不会计算右侧的表达式。
逻辑非(!)
“!”运算符是一元运算符。它放置在一个单独的操作数之前。它的目的是将操作数的布尔值进行求反。
作为一个一元运算符,“!”具有很高的优先级,并且和操作数紧密绑定在一起。如果你希望对类似 p8&q 的表达式做求反操作,则需要使用圆括号:!(p&&q)。
赋值表达式
“=”具有非常低的优先级,通常在一个较长的表达式中用到了一条赋值语句的值的时候,需要补充圆括号以保证正确的运算顺序。
(a = b) == 0;
赋值操作符的结合性是从右至左,也就是说,如果一个表达式中出现了多个赋值运算符,运算顺序是从右到左。
带操作的赋值运算
只有在 data[i++]包含具有副作用的表达式(比如函数调用和赋值操作)的时候,两者才不等价。比如,下面两个表达式就不等价:
data[i++]*=2;
data[i++]=data[i++]*2;
2
表达式计算
和其他很多解释性语言一样,JavaScript 同样可以解释运行由 JavaScript 源代码组成的字符串,并产生一个值。JavaScript 通过全局函数 eval()来完成这个工作。
eval()讲解
eva1()只有一个参数。如果传入的参数不是字符串,它直接返回这个参数。如果参数是字符串,它会把字符串当成 JavaScript 代码进行编译(parse),如果编译失败则抛出一个语法错误(SyntaxError)异常。如果编译成功,则开始执行这段代码,并返回字符串中的最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则最终返回 undefined。如果字符串抛出一个异常,这个异常将把该调用传递给 eval()。
关于 eva1()最重要的是,它使用了调用它的变量作用域环境。也就是说,它查找变量的值和定义新变量和函数的操作和局部作用域中的代码完全一样。如果一个函数定义了一个局部变量 x,然后调用 eval("x"),它会返回局部变量的值。如果它调用 eval("x=1"),它会改变局部变量的值。如果函数调用了 eval("var y=3;"),它声明一个新的局部变量 y。同样地,一个函数可以通过如下代码声明一个局部函数:
eval("function f(){return x+1;}");
如果在最顶层代码中调用 eval(),当然,它会作用于全局变量和全局函数。
需要注意的是,传递给 eva1()的字符串必须在语法上讲的通—不能通过 eval()往函数中任意粘贴代码片段,比如,eval("return;")是没有意义的,因为 return 只有在函数中才起作用,并且事实上,eva1 的字符串执行时的上下文环境和调用函数的上下文环境是一样的,这不能使其作为函数的一部分来运行。如果字符串作为一个单独的脚本是有语义的(就像诸如 x=0 的短代码),那么将其传递给 eva1()作参数是完全没有问题的,否则,eval()将抛出语法错误异常。
全局 eval()(了解)
ECMAScript5 是反对使用 EvalError 的,并且规范了 eval()的行为。“直接的 eval”,当直接使用非限定的“eval”名称(eval 看起来像是一个保留字)来调用 eval()函数时,通常称为“直接 eval”(direct eval)。直接调用 eva1()时,它总是在调用它的上下文作用域内执行。其他的间接调用则使用全局对象作为其上下文作用域,并且无法读、写、定义局部变量和函数。下面有一段示例代码:
var geval=eval;//使用别名调用eval将是全局eval
var x="global",y="global";//两个全局变量
function f(){//函数内执行的是局部eval
var x="local";//定义局部变量
eval("x+='changed';");//直接eval更改了局部变量的值
return x;
}//返回更改后的局部变量
function g(){//这个函数内执行了全局eval
var y="1ocal";//定义局部变量
geval("y+='changed';");//间接调用改变了全局变量的值
return y;
}//返回未更改的局部变量
console.1og(f(),x);//更改了局部变量:输出"local changed global":
console.1og(g(),y);//更改了全局变量:输出“local globalchanged”:
2
3
4
5
6
7
8
9
10
11
12
13
14
严格 eval()
ECMAScript 5 严格模式(参照 5.7.3 节)对 eval()函数的行为施加了更多的限制,甚至对标识符 eval 的使用也施加了限制。当在严格模式下调用 eva1()时,或者 eva1()执行的代码段以“use strict”指令开始,这里的 eva1()是私有上下文环境中的局部 eval。也就是说,在严格模式下,eval 执行的代码段可以查询或更改局部变量,但不能在局部作用域中定义新的变量或函数。
此外,严格模式将“eval”列为保留字,这让 eva1()更像一个运算符。不能用一个别名覆盖 eva1()函数。并且变量名、函数名、函数参数或者异常捕获的参数都不能取名为“eval”。
其他运算符
条件运算符(?:)
条件运算符的操作数可以是任意类型。第一个操作数当成布尔值,如果它是真值,那么将计算第二个操作数,并返回其计算结果。否则,如果第一个操作数是假值,那么将计算第三个操作数,并返回其计算结果。第二个和第三个操作数总是会计算其中之一,不可能两者同时执行。
typeof 运算符
typeof 是一元运算符,放在其单个操作数的前面,操作数可以是任意类型。返回值为表示操作数类型的一个字符串。表 4-3 列出了任意值在 typeof 运算后的返回值:
尽管 JavaScript 中的函数是对象的一种,但 typeof 运算符还是将函数特殊对待,对函数做 typeof 运算有着特殊的返回值。在 JavaScript 中,函数和“可执行的对象”(callable object)有着微妙的区别。所有的函数都是可执行的(callable),但是对象也有可能是可执行的,可以像调用函数一样调用它,但它并不是一个真正的函数。ECMAScript 5 规范则扩充至所有可执行对象,包括内置对象(native object)和宿主对象(host object),所有可执行对象进行 typeof 运算都将返回“function”。
大多数浏览器厂商也将 JavaScript 的原生函数对象(native function object)当成它们的宿主对象的方法来使用。但微软却一直将非原生可执行对象(non-native callableobject)当成其客户端的方法来使用,在 IE9 之前的版本中,非原生可执行对象的 typeof 运算将返回“object”,尽管它们的行为和函数非常相似。而在 IE9 中,这些客户端方法是真正的内置函数对象(native function object)。
delete 运算符
delete 是一元操作符,它用来删除对象属性或者数组元素。就像赋值、递增、递减运算符一样,delete 也是具有副作用的,它是用来做删除操作的,不是用来返回一个值的。
delete 希望他的操作数是一个左值,如果它不是左值,那么 delete 将不进行任何操作同时返回 true。否则,delete 将试图删除这个指定的左值。如果删除成功,delete 将返回 true。
然而并不是所有的属性都可删除,一些内置核心和客户端属性是不能删除的,用户通过 var 语句声明的变量不能删除。同样,通过 function 语句定义的函数和函数参数也不能删除。
在 ECMAScript 5 严格模式中,如果 delete 的操作数是非法的,比如变量、函数或函数参数,delete 操作将抛出一个语法错误(SyntaxError)异常,只有操作数是一个属性访问表达式的时候它才会正常工作。在严格模式下,delete 删除不可配置的属性时会抛出一个类型错误异常。在非严格模式下,这些 delete 操作都不会报错,只是简单地返回 false,以表明操作数不能执行删除操作。
var o={x:1,y:2};//定义一个变量,初始化为对象
delete o.x;//删除一个对象属性,返回true
typeof o.x;//属性不存在,返回“undefined”
delete o.x;//删除不存在的属性,返回true
delete o;//不能删除通过var声明的变量,返回false
∥在严格模式下,将抛出一个异常
delete 1;//参数不是一个左值,返回true
this.x=1;//给全局对象定义一个属性,这里没有使用var
delete x;//试图删除它,在非严格模式下返回true
//在严格模式下会抛出异常,这时使用“delete this.x“来代替
x;//运行时错误,没有定义x
2
3
4
5
6
7
8
9
10
11
void 运算符
void 是一元运算符,它出现在操作数之前,操作数可以是任意类型。这个运算符并不是经常使用:操作数会照常计算,但忽略计算结果并返回 undefined。由于 void 会忽略操作数的值,因此在操作数具有副作用的时候使用 void 来让程序更具语义。
这个运算符最常用在客户端的 URL—javascript:URL 中,在 URL 中可以写带有副作用的表达式和运算符|,而 void 则让浏览器不必显示这个表达式的计算结果。例如,经常在 HTML 代码中的<a>标签里使用 void 运算符:
<a href="javascript:void window.open();">打开一个新窗口</a)
逗号运算符(,)
逗号运算符是二元运算符,它的操作数可以是任意类型。它首先计算左操作数,然后计算右操作数,最后返回右操作数的值。
总是会计算左侧的表达式,但计算结果忽略掉,也就是说,只有左侧表达式具有副作用,才会使用逗号运算符让代码变得更通顺。逗号运算符最常用的场景是在 for 循环中,这个 for 循环通常具有多个循环变量。
//for循环中的第一个逗号是var语句的一部分
//第二个逗号是逗号运算符
//它将两个表达式(i++和j--)放在一条(for循环中的)语句中
for(var i=0,j=10;i<j;i+,j--)
console.1og(i+j);
2
3
4
5