表达式JavaScript中的一个短句,JavaScript解释器会将其计算(evaluate)出一个结果。程序中的常量是最简单的一类表达式。变量名也是一种简单的表达式,它的值就是赋值给变量的值。复杂表达式是有简单表达式组成的。比如,数组访问表达式是由一个表示数组的表达式、左方括号、一个整数表达式和右方括号构成。他们所组成的新的表达式的运算结果是该数组的特定位置的元素值。同样的,函数调用表达式由一个表示函数对象的表达式和0个或多个参数表达式构成。
将简单表达式组合成复杂表达式是最常用的方法就是使用运算符。运算符按照特定的运算规定对操作数进行运算,并计算出新值。乘法运算符“*”是比较简单的例子。表达式x*y是对两个变量表达式x和y进行运算并得出结果。有时我们更愿意说运算符返回了一个值而不是“计算”出了一个值。
4.1 原始表达式
最简单的表达式是“原始表达式”。原始表达式是表达式的最小单位–他们不再包含其他表达式。JavaScript中的原始表达式包含常量或直接量、关键字和变量。
直接量是直接在程序中出现的常数值。他们看起来像:
1.23(数字直接量) (3.1)
“hello”(字符串直接量) (3.2)
/pattern/(正则表达式直接量)(3.2.4、10)
JavaScript中的一些保留字构成了原始表达式:
true false null this
我们在3.3和3.4中学习了true、false和null。和其他关键字不同,this并不是一个常量,它在程序的不同地方返回的值也不相同。this关键字经常在面对对象编程中出现。在一个方法体内,this返回调用这个方法的对象。
最后,第三种原始表达式是变量:
i sum undefined(是全局变量,和null不同,他不是一个关键字)
当JavaScript代码中出现了标识符,JavaScript会将其当做变量而去查找它的值。如果变量名不存在,表达式运算结果为undefined。然而,在ECMAScript 5的严格模式中,对不存在的变量进行求值会抛出一个引用错误异常。
4.2 对象和数组的初始化表达式
对象和数组初始化表达式实际上是一个新创建的对象好数组。这些初始化表达式有时称做“对象直接量”和“数组直接量”。然而和布尔直接量不同,他们不是原始表达式,因为它们所包含的成员或者元素都是子表达式。数组初始化表达式语法非常简单,我们以此开始,
数组初始化表达式是通过一对方括号和其内由逗号隔开的列表构成的。初始化的结果是一个新创建的数组。数组的元素是逗号分隔的表达式的值:
[]//一个空数组:[]内留空即表示该数组没有任何元素
[1+2,3+4]//拥有两个元素的数组,第一个是3,第二个是7
数组初始化表达式中的元素初始化表达式也可以是数组初始化表达式。也就是说,这些表达式是可以嵌套的:
var matrix =[[1,2,3],[4,5,6],[7,8,9]];
JavaScript对数组初始化表达式进行求值的时候,数组初始化表达式中的元素表达式也都会各自计算一次。也就是说,数组初始化表达式每次计算的值有可能是不同的。
数组直接量中的列表逗号之间的元素可以省略,这时省略的空位会天充值undefined。例如,下面这个数组包含5个元素,其中三个元素是undefined:
var sparseArray[1,,,,5];
数组直接量的元素列表结尾处可以留下单个逗号,这时并不会创建一个新的值为unidentified的元素。
对象初始化表达式和数组初始化表达式非常类似,只是方括号被花括号代替,并且每个子表达式都包含一个属性名和一个冒号作为前缀:
var p ={x:2.3,y:-1.0};
对象直接量也可以嵌套,比如:
var p ={ upperLeft:{X:2,Y:2},
lowerRight:{X:4,Y:5}};
JavaScript求对象初始化表达式的值的时候,对象表达式也都会各自计算一次,并且它们不必包含常数值:它们可以是任意JavaScript表达式。同样,对象直接量中的属性名称可以是字符串而不是标识符(这在那些只能使用保留字或一些非法标识符作为属性名的地方非常有用):
var side = 1;
var square ={ "upperLeft":{x:p.x,y:p.y}
"lowerRight":{x:p.x+side,y:p.y+side}};
4.3 函数定义表达式
函数定义表达式定义一个JavaScript函数。表达式是值是这个新定义的函数。从某种意义上讲,函数定义表达式可称为“函数直接量”,毕竟对象初始化表达式也称为“对象直接量”。一个典型的函数定义表达式包含关键字function,跟随其后的是一对圆括号,括号内是一个以逗号分割的列表,列表含有0个或多个标识符(参数名),然后在跟随一个由花括号包裹的JavaScript代码段(函数体),例如:
var s = function(x){return x*x;}
4.4 属性访问表达式
属性访问表达式运算得到一个对象属性或一个数组元素的值。JavaScript为属性访问定义了两种语法:
expression.identifier
expression[expression]
第一种写法是一个表达式后跟随一个句号和标识符。表达式指定对象,标识符则指定需要访问的属性的名称。第二种写法是使用方括号,方括号内是另外一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的名称或者代表要访问数组元素的索引。
不管使用哪种形式的属性访问表达式,在“.”和“[”之前的表达式总是会首先计算。如果计算结果是null或者undefined,表达式会抛出一个类型错误异常,因为这两个值都不能包含任意属性。如果运算结果不是对象(或者数组),JavaScript会将其转换为对象(3.6)。如果对象表达式后跟随句点和标识符,则会查找由这个标识符所指定的属性的值,并将其作为整个表达式的值返回。如果对象表达式后跟随一对方括号,则会计算方括号内的表达式的值并将它转换为字符串。不论哪种情况,如果命名的属性不存在,那么整个属性访问表达式的值就是undefined。
显然.identifier的写法更加简单,但需要注意的是,这种方式只适用于要访问的属性名称是合法的标识符,并且需要知道要访问的属性的名字。如果属性名称是一个保留字或者包含空格和标点符号,或是一个数字(对于数组来说),则必须使用方括号的写法。当属性名是通过运算得出的值而不是固定的值的时候,这时必须会用方括号写法(6.2.1)
4.5 调用表达式
JavaScript中的调用表达式是一种调用(或者执行)函数或方法的语法表示。它以一个函数表达式开始,这个函数表达式指代了要调用的函数。函数表达式后跟随一对圆括号,括号内是一个以逗号隔开的参数列表,参数可以有0个也可有多个,例如:
f(0)//f是一个函数表达式;0是一个参数表达式
Math.max(x,y,z)//Math.max是一个函数;x,y,z是参数
a.sort()//a.sort是一个函数,它没有参数。
当对调用表达式进行求值的时候,首先计算函数表达式,然后计算参数表达式,得到一组参数值。如果函数表达式的值不是一个可调用的对象,则抛出一个类型错误异常(所有的函数都是可调用的,即使宿主对象不是函数它也有可能被调用)。然后。实参的值被依次赋值给形参,这些形参是定义函数时指定的,接下来开始执行函数体。如果函数使用returen语句给出了一个返回值,那么这个返回值就是整个调用表达式的值。否则,调用表达式的值就是undefined。函数调用–包括当形参表达式的个数和函数定义中实参的个数不匹配的时候的运行情况。
任何一个调用表达式都包含一对圆括号和左圆括号之前的表达式。如果这个表达式是一个属性访问表达式,那么这个调用称做“方法调用”。在方法调用中,执行函数体的时候,作为属性访问主题的对象和数组便是其调用方法内this的指向。这种特性使得在面向对象编程范例中,函数可以调用其宿主对象。(9)
并不是方法调用的调用表达式通常使用全局对象作为this关键字的值。然而在ECMAScript 5中,那些通过严格模式定义的函数在调用时将使用undefined作为this的值,this不会指向全局对象。(5.7.3)
4.6 对象创建表达式
对象创建表达式创建一个对象并调用一个函数(构造函数)初始化新对象的属性。对象创建表达式和函数调用表达式非常类似,只是对象创建表达式之前多了一个关键字new:
new Object()
new Point(2,3)
如果一个对象创建表达式不需要传入任何参数给构造函数的话,那么这对空圆括号是可以省略掉的:
new Object
当计算一个对象创建表达式的值时,和对象初始化表达式通过{}创建对象的做法一样,JavaScript首先创建一个新的空对象,然后,JavaScript通过传入指定的参数并将这个新对象当做this的值来调用一个指定的函数。这个函数可以使用this来初始化这个新创建对象的属性。那些被当成构造函数的函数不会返回一个值,并且这个新创建并被初始化后的对象就是整个对象创建表达式的值。如果一个构造函数确实返回了一个对象值,那么这个对象就作为整个对象创建表达式的值,而新创建的对象就废弃了。
4.7 运算符概述
JavaScript中的运算符用于算术表达式、比较表达式、逻辑表达式、赋值表达式等。
需要注意的是,大多数运算符都是有标点符号表示,而另外一些运算符则是由关键字表示的,比如delete和instanceof。关键字运算符和标点符号所表示的运算符一样都是正规的运算符,他们的语法都非常言简意赅。
4.7.2 操作数类型和结果类型
4.7.3 左值
左值(lvalue)是一个古老的术语,它是指“表达式只能出现在赋值运算符的左侧”。在JavaScript中,变量、对象属性和数组元素均是左值。ECMAScript 规范允许内置函数返回一个左值,但自定义的函数则不能返回左值。
4.7.4 运算符的副作用
计算一个简单的表达式不会对程序的运行状态造成任何影响,程序后续执行的计算也不会受到该计算的影响。而有一些表达式则具有很多副作用,前后的表达式运算会相互影响。赋值运算符是最明显的一个例子:如果给一个变量过属性赋值,那么那些使用这个变量或属性的表达式的值都会发生改变。“++”和“–”递增和递减运算符与此类似,因为它们包含隐式的赋值。delete运算符同样有副作用:删除一个属性就像()给这个属性赋值undefined。
其他的JavaScript运算符都没有副作用,但函数调用表达式和对象创建表达式有些特别,在函数体或者构造函数内部运行了这些运算符并产生了副作用的时候,我们说函数调用表达式和对象创建表达式是有副作用的。
4.7.5 运算符优先级
PS:属性访问表达式和调用表达式的优先级要比表中列出的所有运算符都要高。例如;
typeof my.functionsx
尽管typeof是优先级最高的运算符之一,但typeof也是在两次属性访问和函数调用之后执行的。
实际上,如果你真的不确定你所使用的运算符的优先级,最简单的方法就是使用圆括号来强行指定运算次序。有些重要规则需要熟记:乘法和除法的优先级高于加法和减法,赋值运算的优先级非常低,通常总是最后执行的。
4.7.6 运算符的结合性
结合性指定了在多个具有同样优先级的运算符表达式中的运算顺序。
4.7.7运算顺序
b = a+a*a;
b = (a++)+a*a; // 2 5
只有在任何一个表达式具有副作用而影响到其他表达式的时候,其求值顺序才会和看上去有所不同。
4.8 算术表达式
在JavaScript中,所有的数字都是浮点型的,除法运算的结果也是浮点型,比如,5/2的结果是2.5。除数为0的运算结果为正无穷大或负无穷大。而0/0的结果为NaN,所有这些运算均不会报错。
%
5%2 =1
6.5%2.1=0.2
4.8.1 “+”运算符
二元加法运算符“+”可以对两个数字做加法,也可以做字符串连接操作:
1+2 =3
“x”+“y”=“xy”
“1”+“2”=“12”
加号的转换规则优先考虑字符串连接,
4.8.2 一元算术运算符
var a = "1";
var b = (a++)+a;
console.log(b); //3 而不是12
运算符和操作数之间不能有换行符。
4.8.3 位运算符
1<<5 = 100000 = 32
位运算符要求它的操作数是整数,这些整数表示位32为整型而不是64位浮点型。
移位运算符要求右操作数在0至31之间。
位运算符会将NaN、Infinity和-Infinity都转换为0。
按位操作符(Bitwise operators) 将其操作数(operands)当作32位的比特序列(由0和1组成),而不是十进制、十六进制或八进制数值。例如,十进制数9,用二进制表示则为1001。按位操作符操作数字的二进制形式,但是返回值依然是标准的JavaScript数值。
按位与(&)
只有两个操作数中相对应的位都是1,结果中的这一位才是1. 0x1234&0x00FF ==0x0034
按位或(|)
位运算符“|”对它的整型操作数逐位执行布尔或(OR)操作。如果其中一个操作数相应的位为1,或者两个操作数相应位都是1,那么结果中的这一位就为1。将任一数值 x 与 0 进行按位或操作,其结果都是 x。将任一数值 x 与 -1 进行按位或操作,其结果都为 -1。
console.log(12|03); //13
1100(12)
1110(13)
console.log(0x12|0x03);//19
0x12 =18
0x03 =3
0x13 =19
按位异或(^)
位运算符“|”对它的整型操作数逐位执行布尔异或(XOR)操作。异或是指第一个操作数为true或第二个操作数为true,但两者不能同时为true。如果两个操作数中只有一个相应位为1(不能同时为1),那么结果中的这一位就是1.例如(),0xFF00^0xF0F0 = 0x0FF0。实现基于16位进制。十进制数之间的按位异或只有在特殊的情况下才可以实现。例如100^10 = 110。
按位非(~)
位于一个整型参数之前,它将操作数的所有位取反。根据JavaScript中带符号的整数的表示方法,对一个值使用“~”运算符相当于改变它的符号并减一。
运算符“”
左移(<<)
进第一个操作数的所有二进制位进行左移操作。将一个值移动一位相当于乘以2,移动两位相当于乘以4,以此类推。
7<<2 = 28. 111 11100 右侧补零原则
带符号右移(>>)
与左移相反。
操作符会将第一个操作数向右移动指定的位数。向右被移出的位被丢弃,拷贝最左侧的位以填充左侧。由于新的最左侧的位总是和以前相同,符号位没有被改变。所以被称作“符号传播”。
7>>1 = 3 111 11
-7>>1 = -4 -111 -100(相当于用它除以2(忽略余数))
-15>>1 = -8 -1111 -1000
无符号右移(>>>)
运算符“>>>”和运算符“>>”一样,只是左边的高位总是填补0,与原来的操作符号无关,例如:
-1>>4 = -1 但是-1>>>4 = 0x0FFFFFFF.结果恒为268435455
4.9 关系表达式
关系运算符用于测试两个值之间的关系,根据关系是否存在而返回true或false。关系表达式总是返回一个布尔值,通常在if、white或者for语句中使用关系表达式,用以控制程序的执行流程。
4.9.1 相等和不等运算符
“==”和“===”运算符用于比较两个值是否相等。当然他们对相等的定义不不尽相同。两个运算符允许任意类型的操作数,如果操作数相等则返回true,否则返回false。“===”也称为严格相等运算符(恒等运算符),它用来检测两个操作数是否相等,这里“相等”的定义非常宽松,可以允许进行类型转换。
JavaScript支持“=”、“==”和“===”运算符。你应当理解这些(赋值、相等、恒等)运算符之间的区别,并在编码过程中小心使用。尽管他们都可以称做“相等”,但为了减少概念混淆,应当把“=”称做“得到或赋值”,把“==”称做“相等”,把“===”称做“严格相等”。
“!=”和“!==”运算符的检测规则是“===”和“==”运算符的求反。“!”运算符是布尔非运算符。
在3.7中,JavaScript对象的比较是引用的比较,而不是值的比较。对象和其本身是相等的,但和其他任何对象都不相等。如果两个不同的对象具有相同数量的属性,相同的属性名和值,它们依旧是不相等的。相应位置的数组元素是相等的两个数组也是不相等的。
严格相等运算符“===”首先计算其操作数的值,然后比较这两个值,比较过程没有任何类型转换:
·如果两个值类型不相同,则他们不相等。
·如果两个值都是null或者都是undefined,则他们不相等。
·如果两个值都是布尔值true或都是布尔值false。则他们相等。
·如果其中一个值是NaN,或者两个值都是NaN,则他们不想等。NaN和其他任何值都不相等的,包括它本身!通过x!==x来判断x是否为NaN,只有在x为NaN的时候,这个表达式的值才为true。
·如果两个值为数字且数值相等,则他们相等。如果一个值为0,另一个为-0,则他们同样相等。
·如果两个值为字符串,且所含的对应位上的16位数(3.2)完全相等,则他们相等。如果他们的长度或内容不同,则他们不等。两个字符串可能含义完全一眼且所显示出的字符也一样,但具有不同编码的16位值。JavaScript并不对Unicode进行标准化的转换,因此像这样的字符串通过“===”和“==”运算符的比较结果也不相等。第三部分的String.localeCompare()提供了另一种比较字符串的方法。
·如果两个引用值指向同一个对象、数组或函数,则他们是相等的。如果指向不同的对象,则他们是不等的,尽管两个对象具有完全一样的属性。
相等运算符“==”和恒等运算符相似,但相等运算符的比较并不严格。如果两个操作数不是同一类型,那么相等运算符会尝试进行一些类型转换,然后进行比较:
·如果两个操作数的类型相同,则和上文所述的严格相等的比较规则一样。如果严格相等,那么比较结果为相等。如果他们不严格相等,则比较结果为不相等。
·如果两个操作数类型不同,“==”相等操作符也可能会认为它们相等。检测相等将会遵守如下规则和类型转换:
-如果一个值是null,另一个undefined,则他们相等。
-如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较。
-如果其中一个值是true,则将其转换为1再进行比较。如果其中一个值是false,则将其转换为0再进行比较。
-如果一个值是对象,另一个值是数字或字符串,则使用3.8.3中提到的转换规则将对象转换为原始值,再进行比较。对象通过toString()方法或者valueOf()方法转换为原始值。JavaScript语言核心的内置类首先尝试使用valueOf(),再尝试使用toString(),除了日期类,日期类只使用toString()转换。那些不是JavaScript语言核心中的对象则通过各自的实现中定义的方法转换为原始值。
-其他不同类型之间的比较均不相等。
4.9.2 比较运算符
比较操作符的操作数可能是任意类型。然而,只有数字和字符串才能真正执行比较操作,因此那些不是数字和字符串的操作数都将进行类型转换,类型转换规则如下:
·如果操作数为对象,那么这个对象一依照3.8.3结尾处所描述的转换规则转换为原始值:如果valueOf()返回一个原始值,那么直接使用这个原始值。否则,使用toString()的转换结果进行比较操作。
·在对象转换为原始值之后,如果两个操作数都是字符串,那么将依照字母表的顺序对两个字符串进行比较,这里提到的“字母表顺序”是指组成这个字符串的16位Unicode字符的索引顺序。
·在对象转换为原始值之后,如果至少有一个操作数不是字符串,那么两个操作数都将转换为数字进行数值比较。0和-0是相等的。Infinity比其他任何数字都大(除了Infinity本身外),-Infinity比其他任何数字都小(除了它自身)。如果其中一个操作数是(或转换后是)NaN,那么比较操作符总是返回false。
需要注意的是,所有大写的ASCII字母都“小于”小写的ASCII字母。
对于数字和字符串操作符来说,加号运算符和比较运算符的行为都有所不同,前者更偏爱字符串,如果它的其中一个操作数是字符串的话,则进行字符串连接操作。而比较运算符则更偏爱数字,只有在两个操组数都是字符串的时候,才会进行字符串的比较:
1+2 =3
“1”+“2”=“12”
“1”+2=“12”
“11”<3 数字的比较false
“11”<“3” 字符串的比较true
“one”<3 数字的比较,“one”转换为NaNfalse
最后,需要注意的是,“<=”和“>=”运算符在判断相等的时候,并不依赖于相等运算符严格相等运算符的比较规则。相反,小于等于运算符只是简单的“不大于”,大于等于运算符也只是“不小于”。只有一个例外,那就是当其一个操作数(或传换后是)NaN的时候,所有四个比较运算符均返回false。
4.9.3 in运算符
instanceof运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回true,否则返回false。
var d = new Object();
console.log(d instanceof Object);
console.log(d instanceof Number);
需要注意的是,所有的对象都是Object的实例。当通过instanceof判断一个对象是否是一个类的实例的时候,这个判断也会包含对“父类”的检测。如果instanceof的左操组数不是对象的话,instanceof返回false。如果右操作数不是函数,则抛出一个类型错误异常。
为了理解instanceof运算符是如何工作的,必须首先理解“原型链”。原型链作为JavaScript的继承机制,(6.2.2)。为了计算表达式o instanceof f,JavaScript首先计算f.prototype,然后在原型链中查找o,如果找到,那么o是f(或者f的父类)的一个实例,表达式返回true。如果f.prototype不在o的原型链中的话,那么o就不是f的实例,instanceof返回false。
4.10 逻辑表达式
逻辑运算符“&&”、“||”好“!”是对操作数进行布尔算术运算,经常和关系运算符一起配合使用,逻辑运算符将多个关系表达式组合起来组成一个更复杂的表达式。这些运算符在下面几节中会一一讲述。(3.3)
4.10.1 逻辑与(&&)
“&&”运算符可以从三个不同的层次进行理解。
·当操作数都是布尔值的时候,返回值为布尔值。
·“&&”可以对真值和假值进行布尔与(AND)操作。两个操作数都是真值则返回一个真值;否则,至少一个操作数是假值的话,则返回一个假值。
·运算符首先计算左操作数的值,即首先计算“&&”左侧的表达式。如果计算结果是假值,返回左操作数的值,不对右操作数进行计算。反过来,如果左操作数是真值,那么整个表达式的结果依赖于右操数的值。“&&”运算符将计算右操作数的值并将其返回作为整个表达式的结果:
var d = {x:1};
var p = null;
console.log(d&&d.x); //1
console.log(p&&p.x); //null
“&&”的行为有时称做“短路”:
if(a==b)stop();
(a==b)&&stop();
一般来讲,当“&&”右侧的表达式具有副作用的时候(赋值、递增、递减和函数调用表达式)要格外小心。因为这些带有副作用的表达式的执行依赖于左操作数的计算结果。
大多数情况下,“&&”仅用来对真值和假值做布尔计算。
4.10.2 逻辑或(||)
尽管“||”运算符大多数情况下只是做简单布尔或(OR)运算,和“&&”一样,它也具有一些更复杂的行为。它会首先计算第一个操作数的值,也就是说会首先计算左侧的表达式。如果计算结果为真值,那么返回这个真值。否则,在计算第二个操作数的值,即计算右侧的表达式,并返回这个表达式的计算结果。
4.10.3 逻辑非(!)
“!”运算符是一元运算符。具有很高的优先级,并且和操作数紧密绑定在一起。
和“&&”与“||”运算符不同,“!”运算符首先将其操作数转换为布尔值(3),然后再对布尔值求反。也就是说“!”总是返回true或者false,并且,可以通过使用两次逻辑非运算来得到一个值的等价布尔值:!!x
4.11 赋值表达式
“=”运算符希望它的左操作数是一个左值:一个变量或者对象属性(或数组元素)。它的右操作数可以是任意类型的任意值。赋值表达式的值就是右操作数的值。赋值表达式的副作用是,右操作数的值赋值给左侧的变量或对象属性,这样的话,后续对这个变量和对象属性的引用都将得到这个值。
“=”优先级非常低,通常在一个较长的表达式中用到了一个赋值语句的值的时候,需要补充圆括号以保证正确的运算顺序。
带操作的赋值运算
在大多数情况下,表达式为:
a op = b
这里op代表一个运算符,这个表达式和下面的表达式等价:
a = a op b
在第一行中,表达式a计算了一次,在第二行中,表达式计算了两次。只有在a包含具有副作用的表达式(比如函数调用和赋值操作)的时候,两者才不等价。比如。下面两个表达式就不等价:
data[i++] *= 2;
data[i++] = data[i++] * 2;
4.12 表达式计算
和其他很多解释性语言一样,JavaScript同样可以解释运行由JavaScript源代码组成的字符串,并产生一个值。JavaScript通过全局函数eval()来完成这个工作:
eval(“3+2”) //5
动态判断源代码中的字符串是一种强大的语言特性,几乎没有必要在实际中应用。
4.12.1 eval()
eval()只有一个参数。如果传入的参数不是字符串,它直接返回这个参数。如果参数是字符串。它会把字符串当成JavaScript代码进行编译,如果编译失败则抛出一个语法错误异常。如果编译成功,则开始执行这段代码,并返回字符串中的最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则最终返回undefined。如果字符串抛出异常,这个异常将把该调用传递给eval()。
关于eval()最重要的是,它使用了调用它的变量作用域环境。也就是说,它查找变量的值和定义新变量和函数的操作和局部作用域中的代码完全一样。如果一个函数定义了一个局部变量x,然后调用eval(“x”),它会返回局部变量的值。如果它调用eval(“x=1”),它会改变局部变量的值。如果函数调用了eval(“var y = 3;”),它声明一个新的局部变量y。
如果在最顶层代码中调用eval(),当然,它会作用于全局变量和全局函数。
需要注意的是,传递给eval()的字符串必须在语法上讲的通–不能通过eval()往函数中任意粘贴代码片段,比如,eval(“return;”)是没有意义的,因为return只有在函数中才起作用,并且事实上,eval的字符串执行时的上下文环境和调用函数的上下文环境是一样的,这不能使其作为函数的一部分来运行。如果字符串作为一个单独的脚本是有语义的(就像诸如x=0的短代码),那么将其传递给eval()作参数是完全没有问题的,否则,eval()将抛出语法错误异常。
4.12.2 全局eval()
eval()具有更改局部变量的能力,这对于JavaScript优化器来说是一个很大的问题。然而作为一种权宜之计,JavaScript解释器针对那些调用了eval()的函数所做的优化并不多。但当脚本定义了eval()的一个别名,且用另一个名称调用它,JavaScript解释器又会如何工作呢?为了让JavaScript解释器的实现更加简化,ECMAScript3标准规定了任何解释器都不允许对eval()赋予别名。如果eval()函数通过别名调用的话,则会抛出一个EvalError异常。
实际上,大多数的实现并不是这么做的。当通过别名调用时,eval()会将其字符串当成顶层的全局代码来执行。执行的代码可能会定义新的全局变量和全局函数,或者给全局变量赋值,但却不能使用或修改主调函数中的局部变量,因此,这不会影响到函数内的代码优化。
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 = "local"; //定义局部变量
geval("y += 'changed';");//间接调用改变了全局变量的值
return y; //返回未更改的局部变量
}
consle.log(f(),x);//更改了局部变量:local changed global
consle.log(g(),y);//更改了全局变量:local global changed
我们注意到,全局eval的这些行为不仅仅是出于代码优化器的需要而做出的一种折中方案,它实际上是一种非常有用的特性,它允许我们执行那些对上下文没有任何依赖的全局脚本代码段。
4.13 其他运算符
4.13.1 条件运算符(?:)三元运算符
x>0?x:-x //求x的绝对值
条件运算符的操作数可以是任意类型。
第一个操作数当成布尔值,如果他是真值,那么将计算第二个操作数,并返回其计算结果。否则,如果第一个操作数是假值,那么将计算第三操作数,并返回其计算结果。第二个和第三个操作数总是会计算其中之一,不可能两者同时执行。
其中使用if语句也会带来同样的效果。
greeting = "hello" +(username ? sername :"there");
greeting = "hello";
if(username)
greeting += username;
else
greeting += "there";
4.13.2 typeof运算符
typeof是一元运算符,放在其单个操作数的前面,操作数可以是任意类型。返回值为表示操作数类型的一个字符串。
console.log(typeof(undefined));//undefined
console.log(typeof(null));//object
console.log(typeof(true));//boolean
console.log(typeof(100));//number
console.log(typeof("str"));//string
console.log(typeof(this.f));//function
console.log(typeof(this.label));//object
//任意宿主对象:有编辑器各自实现的字符串,但不是“undefined”、“Boolean”、“number”或“string”
typeof最常用的用法是写在表达式中:
(typeof value == “string”)?“‘” + value +“‘”:value
typeof运算符同样在switch语句(5.4.3)中非常有用,需要注意的是,typeof运算符可以带上圆括号,这让typeof看起来像一个函数名,而不是一个运算符关键字:
typeof(i)
如果想将null和对象区分开,则必须针对特殊值显示检测。对于宿主对象来说,typeof有可能并不返回“object”,而返回字符串。但实际上客户端JavaScript的大多数宿主对象都是“object”类型。
所有对象和数组的typeof运算结果是“object”而不是“function”,因此它对于区分对象和其他原始值来说很有帮助。使用instanceof运算符(4.9.4)、class特性(6.8.2)以及constructor属性(6.8.1 9.2.2)可以区分对象的类。
尽管JavaScript中的函数是对象的一种,但typeof运算符还是将函数特殊对待,对函数做typeof运算有着特殊的返回值。在JavaScript中,函数的“和执行的对象”有着微妙的区别。所有的函数都是可执行的,但是对象也有可能是可执行的。可以像调用函数一样调用它,但它并不是一个真正的函数。根据ECMAScript3规范,对于所有内置可执行对象,typeof运算符一律返回“function”。ECMAScript5规范则扩充至所有可执行对象,包括内置对象和宿主对象,所有可执行对象进行typeof运算都将返回“function”。大多数浏览器厂商也将JavaScript的原生函数对象当成他们的宿主对象的方法来使用。但微软却一直将非原生可执行对象当成其客户端的方法来使用,则IE9之前的版本中,非原生可执行对象的typeof运算将返回“object”,尽管他们的行为和函数非常相似。而在IE9中,这些客户端方法是真正的内置函数对象。要了解真正的函数和可执行对象之间的详细差别请参照8.7.7.
4.13.3 delete运算符
delete是一元操作符,它用来删除对象属性或者数组元素。就像赋值、递减、递增运算符一样,delete也是具有副作用的,它是用来做删除操作的,不是用来返回一个值的。
var o = {x:1,y:2};
console.log(o);
delete o.x;
console.log(o.x in o);
console.log(o);
var a = [1,2,3];
console.log(a);
delete a[2];
console.log(a[2] in a);
console.log(a);
删除属性或者删除数组元素不仅仅是设置一个undefined的值。当删除一个属性时,这个属性将不再存在。读取一个不存在的属性将返回undefined,但是可以通过in运算符来检测这个属性是否存在对象中。
delete希望他的操作数是一个左值,如果它不是左值,那么delete将不进行任何操作同时返回true。否则,delete将试图删除这个指定的左值。如果删除成功,delete将返回ture。然而并不是所有的属性都可删除,一些内置核心和客户端属性是不能删除的,用户通过var语句声明的变量不能删除。同样,通过function语义定义的函数和函数参数也不能删除。
在ECMAScript5严格模式中,如果delete的操作数是非法的,比如变量、函数或函数参数,delete操作数将抛出一个语法错误异常,只有操作数是一个属性访问表达式(4.4)的时候它才会正常工作。在严格模式下,delete删除不可配置的属性(6.7)时会抛出一个类型错误异常。在非严格模式下,这些delete操作都不会报错,只是简单地返回galse,以表明操作数不能执行删除操作。
var u = {x:1,y:2}; //定义一个变量,初始化为对象
console.log(delete u.x);
// //删除一个对象属性,返回true
typeof u.x; //属性不存在,返回“undefined”
delete u.x;//删除不存在的属性,返回true
delete 0; //不能删除通过var声明的变量,返回false
// //在严格模式下,将抛出一个异常
console.log(delete 1);
// //参数不是一个左值,返回true
this.x = 1;//给全局对象定义一个属性,这里没有使用var
console.log( delete this.x);
// //试图删除它,在非严格模式下返回true
// //在严格模式下会抛出异常,这时使用“delete this.x”;来代替
console.log(this.x);
//运行时错误,没有定义x
6.3
4.13.4void 运算符
void 是一元运算符,他出现在操作之前,操作数可以是任何类型,由于运算符并不是经常使用:操作数会照常计算,但忽略计算结果并返回undefined。由于void会忽略操作数的值,因此在操作数具有副作用的时候使用void来让程序更具语义。
这个运算符最常用在客户端的URL–JavaScript:URL中,在URL可以写带有副作用表达式,而void则让浏览器不必显示这个表达式的计算结果。例如,经常在HTML代码中的标签使用void运算符:
打开一个新窗口
通过给的onclick绑定一个事件处理程序要比在href中写“JavaScript:URL”要更加清晰,当然,这样的话void操作符就可有可无了。
4.13.5 逗号运算符(,)
逗号运算符是二元运算符,它的操作数可以是任意类型。它首先计算左操作数,然后计算右操作数,最后返回右操作数的值:
i=0,j=1,k=2;
计算结果是2,它和下面的代码基本上是等价的:
i =0;j = 1; k = 2;
总是会计算左侧的表达式,但计算结果忽略掉,也就是说,只有左侧表达式具有副作用,才会使用逗号运算符让代码变得更通顺。逗号运算符最常用的场景是在for循环中(5.5.3),这个for循环通常具有多个循环变量:
for循环中的第一个逗号是var语句的一部分,第二个逗号是逗号运算符它将两个表达式(i++和j++)放在一条(for循环中的)语句中
for (var i = 0,j=10; i < j; i++,j--) {
console.log(i+j);
}