第四章:Javascript表达式和运算符
表达式是javascript中的一个短语,javascript解释器会将其计算出一个结果。程序中常用量是最简单的一类表达式就是变量。变量名也是一种简单的表达式,它的值就是赋值给变量的值。
复杂的表达式是由简单的表达式组成的。比如数组访问表达式是由一个表示数组的表达式,方括号、一个整数表达式构成。它们所组成新的表达式运算结果是该数组特定位置的元素值。同样的函
数调用表达式由一个表示函数对象的表达式和0个多个参数表达式构成。将简单表达式组成复杂表达式最常用的方法就是运算符。
本章(本文)将讲解所有javascript运算符。同时也讲解不涉及运算符的表达式(比如访问数组元素和函数调用),其语法和编程风格和c语言都很相似。
1.元素表达式
最简单的表达式是“原始表达式”,原始表达式是表达式的最小的单位--它们不包含其他表达式。javascript中的原始表达式包含常量或直接量。关键字和变量。
直接量是直接在程序中出现的常数值。它们看起来像:
1.23 //数字直接量 "hello" //字符串直接量 /pattern/ //正则表达式直接量
javascript中的一些保留字构成了原始表达式
true //布尔值:真 false //假 null //返回一个值:空 this //返回"当前"对象
通过第三章的学习,和其它关键字不同,this并不是一个常量,他在程序的不同地方返回的值也不相同。this关键字经常在面向对象编程中出现。this返回方格方法的对象。
最后,第三种原始表达式是变量
i //返回变量i的值 sum //返回sum的值 undefined //是全局变量,和null不同,它不是一个关键字
2.对象和数组的初始化表达式。
对象和数组初始化实际上是新创建的对象和数组,这些初始化的表达式有时候叫做“对象直接量”和“数组直接量”。然而和布尔直接量不同,他们不是原始表达式,因为他们所包含的成员或者元素都子表达式。
数组的初始化表达式语法非常简单,我们以下开始
数组的初始化表达式是通过一对方括号和其内由逗号隔开的列表构成的,初始化结果是一个新创建的数组。数组的元素是逗号分隔表达式的值。
[] //一个空数组;[]内留空即表示该数组没有任何元素 [1+2,3+4] //有两个元素的数组,第一个3,第二个是7
数组初始化表达式中的元素初始化表达式可以是数组初始化表达式。也就是说表达式是可以嵌套的
var mat = [[1,2,3],[4,5,6],[7,8,9]];
数组直接量中列表之间的元素可以省略,空位就会填充undefined.例如下面:
var a=[1,,,,5]
其中4个元素是undefined.数组直接量的结尾处留下逗号,这时不会创建一个新的值为undefined的元素。
对象初始化表达式和数组初始化表达式非常相似,只是方括号被花括号代替。并每个字表达式包含一个属性名和非冒号作为前缀。
var p = {x: 2.1,y: -3} //一个拥有两个属性成员的对象 var q = {}; //空对象 q.x=2.1;q.y=-3; //q的属性成员和p的一样
对象直接量也可以嵌套,比如
var anh = {left:{x:2,y:3}, right:{x:4,y:5}}
javascript在计算对象初始化表达式的值时候,对象表达式都会各自计算一次,并且他们不必包含常数值:它们可以是任意javascript表达式。同样,对象直接量中属性的名称可以是字符串而不是标识符。(在在那行只能使用保留字或一些非法标识符作为属性名的时候非常有用)
var side = 1; var square = {"left":{x:p.x,y:p.y}, 'right':{x:p.x+side,y:p.y+side}}
第6 7章还会再次讨论对象和数组的初始化表达式。
3.函数表达式
函数定义表达式定义一个javascript函数。表达式的值是这个新定义的函数。从某种意义上将,函数定义表达式可以成为函数直接量,函数表达式可称为“函数直接量”,毕竟对象初始化表达式也称为“对象直接量”。一个典型的函数定义表达式包含关键字function,其后是一对圆括号,括号以内是逗号分隔的列表,列表包含0或多个标识符(参数名)。然后跟随花括号包裹的javascript代码段(函数体).
var square = function(x){ return x*x};
函数定义表达式同样可以包含函数的名字。函数也可以通过函数语句来定义,而不是函数表达式。更多内容会在第八章描述。
4.属性访问表达式
属性访问表达式运算得到一个对象或者一个数组元素的值。javascript为属性访问定义了两种方法。
expression . indentifier expression [expression]
第一种写法是一个表达式后跟随一个句点和标识符。表达式指定对象,标识符则指定要访问的属性明川。
第二章写法是使用方括号,方括号内是一个表达式(这种方法适用于对象和数组)。第二个表达式指定要访问的属性的明川或者代表要访问数组元素的索引。这里有一些具体的例子
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会将其转换为对象(3章6节内容)
虽然.identifier的写法更加简单,但需要注意的是,这种方式只适用于要访问的属性名称是合法的标识符。并且需要知道要访问的属性名字。如果属性名称是一个保留字或者包含空格和标点符号,是一个数字(对于数组来说),则必须使用方括号的写法。当属性名是通过运算符得出的值而不是固定值的时候,这时候必须使用方括号的写法。(6章2节1小节)
5.调运表达式
javascript中的调用表达式(invocation expression)是一种调用(或者执行)函数或方法的语法表示。它以一个函数表达式开始,这个函数表达式指代了要调用的函数。函数表达式后跟随一对圆括号,括号内是一个以逗号隔开的参数列表。参数可以有0个也可以有多个。
f(0) //f是一个函数表达式:0是一个参数表达式。 Math.max(x,y,z) //Math.max是一个函数;x,y和z是参数 a.sort() //a.sort()是一个函数,它没有参数。
当调用表达式进行求值的时候,首先计算函数表达式,然后计算参数表达式,得到一组参数值。如果函数表达式的值不是一个可调用的对象,则抛出一个类型错误异常.然后参数的值依次被赋值给形参,这些形参是定义函数时定义的。接下来执行函数体。如果函数使用return语句给出一个返回值,那么这个返回值就是整个调用表达式的值。否则,调用表达式的值就是undefined.函数调用--包括形参表达式的个数和函数定义中实参的个数不匹配的时候运行的情况--的细节将会在第八章详细说明。
任何一个调用表达式都包含一对圆括号和左圆括号之前的表达式,如果这个表达式是一个属性访问表达式,那么这个调用叫做“方法调用”(method invication)。在方法调用中执行函数体的时候,作为属性访问主体的对象和数组便是其调用方法内this的指向。这种特性使得在面向对象编程的范例中,函数(其OO名称为“方法”)可调用其宿主对象(第9章会有更多相关内容)。
6.对象创建表达式
对象创建表达式(object creation expression)创建一个对象并调用一个函数(构造函数)来初始化对象的属性。对象创建表达式和函数调用表达式非常类似,只是对象创建表达式之前多了一个关键字new:
new Object() new Point(2,3)
如果对象创建表达式不需要传入任何参数给构造函数的话,那么这对圆括号是可以省略掉的,更多构造函数的细节将在9章说明
new Object new Point
7.运算符概述
javascript中的运算符用于算表表达式, 比较表达式, 逻辑表达式 ,赋值表达式等
需要注意的是大多运算符都是标点符号来表示的,比如delete和instanceof.无论是关键字运算符还是符号运算符,所表示的运算符一样都是正规运算符,他们的语法都非常言简意赅。
下标运算符的优先级来排序的,前边的运算符优先级高于后边的运算符优先级。被水平华丰隔开的运算符具有不同的优先级。
A表示运算符的结合性。
L从左至右或者R(从右至左)
标题N的列表表示操作数的个数。
类型表示期望的操作数的类型,以及运算符的结果类型(在"→"符号之后)
运算符 | 操作 | A | N | 类型 |
++ | 前/后增量 | R | 1 | lval→num |
-- | 前后减量 | R | 1 | lval→num |
- | 求反 | R | 1 | num→num |
+ | 转换为数字 | R | 1 | num→num |
~ | 按位求反 | R | 1 | int→int |
! | 逻辑非 | R | 1 | bool→bool |
delete | 删除属性 | R | 1 | lval→bool |
typeof | 检测操作类型 | R | 1 | any→Str |
void | 返回undefined值 | R | 1 | any→undef |
* 、/、% | 乘 除 求余 | L | 2 | num,num→num |
+、- | 加,减 | L | 2 | num,num→num |
+ | 字符串连接 | L | 2 | str,str→str |
<< | 左移位 | L | 2 | int,int→int |
>> | 右移位 | L | 2 | int,int→int |
>>> | 无符号右移 | L | 2 | int,int→int |
<,<=,>,>= | 比较数字顺序 | L | 2 | num,num→bool |
<,<=,>,>= | 比较在字母中的顺序 | L | 2 | str,str→bool |
instanceof | 测试对象类 | L | 2 | obj,func→bool |
in | 测试属性是否存在 | L | 2 | str,obj→bool |
== | 判断相等 | L | 2 | any,any→bool |
!= | 判断不等 | L | 2 | any,any→bool |
=== | 判断恒等 | L | 2 | any,any→bool |
!== | 判断非恒等 | L | 2 | any,any→bool |
& | 按位与 | L | 2 | int,int→int |
^ | 按位异或 | L | 2 | int,int→int |
| | 按位或 | L | 2 | int,int→int |
&& | 逻辑与 | L | 2 | any,any→any |
|| | 逻辑或 | L | 2 | any,any→any |
?: | 条件运算符 | R | 3 | bool,any,any→any |
= | 变量赋值或对象属性赋值 | R | 2 | lval,any→any |
*= /= %= += -= &= ^= |= <<= >>= >>>= | 运算且赋值 | R | 2 | lval,any→any |
, | 忽略第一个操作数, 返回第二个操作数。 | L | 2 | any,any→any |
i.操作数的个数
运算符可以通过操作数的个数进行分类。
javascript中的大多数运算符是二元运算符,将两个表达式合并成一个稍微复杂的表达式。
javascript也支持一些一元运算符,它们将一个表达式转换为另一个稍微复杂的表达式。表达式-x中的"-"运算符就是一个一元运算符。是将x求负值。
javascript支持一个三元运算符:条件判断运算符“?:”,它将三个表达式合并为一个表达式
ii.操作数类型和结果类型
一些运算符可以用于任何数据类型,但仍然希望它们操作指定的类型的数据。
iii.左值
在表中的赋值运算符和其它少数运算符期望它们的操作数lval类型,左值是一个古老的术语。它是指“表达式只能出现在赋值运算符的左侧”。javascript中,变量、对象属性和数组元素均是左值。ECMAScript规范允许范围内置函数返回一个左值,但定义的函数则不能返回左值。
iiii.运算符的优先级
在上表中,所示的运算符是按照优先级从高到低排序的,每个水平分隔线内一组运算符有相同的优先级。运算符优先级优先控制着运算符的执行顺序。运算符高的(表顶)的执行总是高于优先级低的(表格的底部)运算符。
看如下表达式
w=x+y*z;
乘法运算符“*”比加法“+”具有更高的优先级,所以乘法先执行。然后,由于赋值运算符“=”具有最低优先级。因此赋值操作是在右侧的表达式计算出结果后进行的。
运算符的优先级可以使用园括号来从写。以上表达式可以这样写。
w = (x + y) * z;
需要注意的是,属性访问表达式和调用表达式的优先级要比表中的所有运算符都要高。
typeof my.Function[x](y)
尽管typeof是优先级最高的运算符之一,但typeof也是在两次属性访问和函数调用后执行的。
事实上,如果你真的不确定你所使用的运算符优先级,最简单的方法就是使用园括号来强行指定运算次序。有些重要的规则则要熟记:乘法和除法高于加减法,赋值运算的优先级非常低,通常是最后执行的。
iiiiii.运算符的结合性
在本节表中,标题为A的列说明了运算符的结核性。L指从左至右结合,R指从右至左结合。结核性指定了在多个具有同样优先级的运算符表达式中的运算顺序。
例如,减法运算按照由左到右的执行结合性。
w = x - y - z
和这段代码一样:
w = ((x - y) - z)
反过来讲,下面这个表达式:
x = ~-y; w = x = y = z; q=a?b:c?d:e?f:g;
和这段代码一模一样
x=~(-y); w=(x=(y=z)); q=a?b:(c?d:(e?f:g))
因为一元操作符、赋值和三元条件运算符都具有从右至左的结合性。
iiiiiii.运算顺序
运算符的优先级和结合性规定了它们在赋值的运算式中的运算顺序,但并没有规定字表达式的计算过程中的运算顺序。javascript总是严格按照从左至右的顺序计算表达式,例如:
在表达式 w=x+y*z 中,将首先计算表达式w,然后计算x、y和z,然后,y的值和z相乘,在加上x的值。最后将其表达式w所指代的变量或属性。给表达式添加园括号将会改变乘法,加法和赋值运算的关系。但从左至右的顺序是不会改变的。
8.算术表达式
本节涵盖了那些算术计算的运算符、以及对操作数的算术操作。乘法、除法和减法运算符非常简单。加法运算单独一节,因为加法运算符可以操作字符串的连接,并且其类型转换有些特殊。
基本的算术运算符是*、/、%、+、-。除了+加法,其它的运算符特别简单,只是在必要的时候操作符转化为数字而已,然后求积、商、余(模)和差。所有那些无法转换为数字的操作都将转换为NaN值。如果操作数(或者转换结果)是NaN值,算术运算结果也是NaN
运算符“/”用第二个操作数来除以第一个操作数,如果你使用过那些区分整数型和浮点数型的编程语言。那么用一个整数除以一个整数时,则希望得到的结果也是整数。在javascript中所有的数字都是浮点数型的,除法运算的结果也是浮点型。比如5/2结果是2.5,而不是2。除数为0的运算结果为正无穷大或负无穷大。而0/0的结果是NaN。所有这些运算均不会报错。
运算符“%”计算的是第一个操作数对第二个操作数的模,换句话说,就是第一个操作数除以第二个操作鼠的余数。结果的符号和第一个操作鼠(被除数)符号保持一致。例如5%2的结果为1,-5%2为-1。
求余运算符的操作数通常都是整数,但也适用于浮点数。6.5%2.1结果是0.2。(0.19999999999999973)
i.“+”运算符
二元加法运算符“+”可以对两个数字做加法,也可以做字符串连接操作:
1+2 //=> 3 "hello" + "" + "there" // =>"hello there" "1"+"2" //=>"12"
当两个操作数都是数字或都是字符串的时候,计算结果是显而易见的。然而对于其他情况来说,则要进行一些必要的类型转换。并且运算符的行为依赖于类型的转换的结果。从技术上来讲,加法操作符的行为表现为:
- 如果一个操作数是对象,则对象会遵循对象到原始值的转换规则为原始类值(参照3章8节3小节)。日期对对象toString()方法执行转换,其他对象则通过valueOf()方法执行转换(如果valueOf()方法返回一个原始值的话)。由于多数对象都不具备可用的valueOf()方法,因此他们会通过toString()方法来执行抓换
- 在进行了对象到原始值的转换后,如果其中一个操作鼠是字符串的话,另一个操作数也会转换为字符串。然后进行字符串连接。
- 否则,两个操作数都将转换为数字(或者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 + "bmice" //=> "3 bmice" 1 + (2 + "bmice") => "12bmice"
ii.一元运算符
一元运算符作用于一个单独的操作数。并产生一个新值。在javascript中,一元运算符具有很高的优先级,而且都是右结合。本节讲述一元运算符(+,-,++和--),必要时,他们将操作转换为数字。需要注意的的是+ -是一元运算符,也是二元运算符、
一元加法+
一元加法运算符把操作数数转换为数字(或者NaN),并且返回这个转换后的数字。如果操作数本身就是数字,则直接返回这个数字。
一元减法-
当-号做一元运算符时,它会根据需要把操作数转换为数字,然后改变运算结果的符号、
递增++
递增“++”运算符对其操作数进行增量(+1)的操作,操作数一个左值(变量、数组元素或者对象属性)。运算符将操作数转换为数字。然后给数字加1、并将加1后的数值重新赋值给变量、数组元素或者对象属性。
递增++ 运算返回值依赖它对操作数的位置。
当操作符在操作数数之前,称为“前增量”(pre-increment)运算符,它对操作数进行增量计算,并返回计算后的值。
当操作符在操作数之后,称为"后增量"(post-increment)运算符,它对操作数进行增量计算,但返回为做增量计算的(unincremented)值。如
var i = 1,j = ++i //i和j的值都是2 var i = 1,j = i++; //i是2,j是1
需要注意的是,便打算++x并总和x=x+1完全一样,“++”运算符从不进行字符串连接操作,它总会将操作数转换为数字并增1.如果x是字符串“1”,++x的结果就是数字2,而x+1是字符串"11"
递减和递增的操作方式是同样的,它把操作数转换为数组,然后减1.
iii.位运算符
位运算符可以对数字表示的二进制数据进行更低层级的按位运算。尽管它们不是传统的纯数学运算,但这里也归类为算术运算符,因为他们作用于数值类型的操作并返回数字。这些运算符在javascript不常见。(此处不描述,详情自行百度~-~)
9.关系表达式
本节讲述javascript的关系运算符,关系运算符用于测试两个值之间中的关系(相等、小于或“是...的属性”),根据关系是否存在而返回true和false.关系表达式总是返回一个布尔值,通常在if while或者for语句(第五章)中使用关系表达式,以控制程序的执行流程。
接下来几节会讲述相等和不等运算、比较运算符和javascript中其它两个关系符in和instanceof
i相等和不等运算符
“==”和"==="运算符用于比较两个值是否相等,两个运算符允许任意类型的操作符。如果相等则返回true,否则返回false.“===”也称为严格相等运算符(有时称为恒等运算符),它用于检测两个操作数是否严格相等。“==”运算符称作相等运算符,它用来检测两个操作数是否相等,这里的相等定义宽松,可以允许进行类型转换。
javascript支持“=”,“==”,“===”运算符,你应当理解(赋值,相等,恒等)运算符之间的区别。并在编程中小心使用。为了减少混淆,应该把“=”称作“得到或赋值”,把“==”称作“相等”,把“===”称作“严格相等”。
“!=”和“!==”运算符规则是“==”,“===”运算符的求反,“!”是布尔非运算符,我们将“!=”,“!==”称为不相等,不严格相等
javascript对象的比较是引用的比较,而不是值的比较。对象和本身是相等的,但和人和对象都不相等。如果两个对象具有相同数量的属性,相同的属性名和值,它们依然是不相等的。相应位置的数组元素是相等的两个数组也是不相等的。
严格相等运算符"==="首先计算操作数的值,然后比较这两个值,比较过程没有任何类型转换。
- 如果两个值类型不想同,则它们不相等
- 如果两个值都是null或者undefined,则它们不相等
- 如果两个值都是布尔值true或者false, 则它们相等
- 如果其中一个值是NaN,或者两个值都是NaN ,则它们不相等,NaN和其它值都是不相等的,包括它本身。
- 如果两个值为数字且相等,则它们相等。如果一个值为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 语言核心中的对象则通过实现中定义的方法转换为原始值。
- 其它不同类型之间的比较均不相等
这里有一个判断相等的小例子
"1" == true
这个表达式的结果为true,这表明完全不同类型的值比较结果为相等。布尔值true首先转换为数字1 ,然后再执行比较。接下来字符串“1”也会转换为数字1 ,因为两个数字的值相等,因此结果为true.
ii.比较运算符
小于(<)
如果第一个操作数小于第二个操作数,则“<”运算结果我true,否则为false
小于等于(<=)
大于(>)
大于等于(>=)
....(不详细介绍含义)
比较操作符的操作数可能是任意类型。然而只有数字和字符串才能真正执行比较操作符,因此,那些不是数字和字符串的操作数都将进行类型转换。类型转换规则如下:
- 如果操作数为对象,则按照3章8节3小节处锁描述的转换规则转换为原始值:如果valueOf()返回一个原始值,那么直接使用这个原始值。否则使用toString() 的转换结果进行比较。
- 在对转换为原始值之后,如果两个操作数都是字符串,那么将依字母表的顺序对两个字符串进行比较,这里提到的“字母表顺序”是组成这两个字符串的16位Unicode字符的索引顺序。
- 在对象转换为原始值之后,如果至少一个操作数不去是字符串,那么两个操作数都将为数字进行数值的比较。0和-0是相等的。Infinty壁其它任何数字都大(除了infinty本身),-infinty比任何数字都小(除了它自己本身。)如果一个操作数(或转换后)为NaN,那么比较符总是返回false
对于数字和字符串操作符来说,加号运算符和比较运算符的行为有所不同 ,前者更偏爱字符串,如果它的其中一个操作数是字符串的话,则进行字符串连接操作。而比较运算符则更偏爱数字,只有在两个操作数都是字符串串的时候。才会进行字符串的比较。
1 + 2 //=>3 加法,结果为3 "1" + "2" //字符串连接,结果为"12" "1" + 2 //字符串连接,2转换为"2",结果"12" 11 < 3 //数字比较,结果true "11" < "3" //字符串比较,结果为true "11" < 3 //数字的比较,“11”转换为11,结果为true "one" < 3 //数字比较,"one"转换为NaN,结果为falase
最后需要注意的是,“<=”和“>=”运算符在判断相等的时候,并不依赖相等运算符和和严格相等运算比较规则。相反,小于等于运算符芝是简单的“不大于”,大于等于运算只是“不小于”。只有一个例外,的那个一个操作数(后转换后)是NaN的时候,所有4个比较运算符均会返回fasle.
iii.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, 8] "0" in data //=>true 数组包含0 1 in data //=>true 数字转换为字符串 3 in data //=>fase 没有索引为3的元素
iiii.instanceof运算符
instanceof运算符希望左操作符为一个对象,右操作数标示对象的类。如果左侧的对象是右侧类的实例,则表达式返回true;负责返回false.第9章将会讲到。javascript对象的类是通过初始化他们的构造函数的来定义的。这样的话,instanceof的右操作数应当是一个函数。比如:
var d = new Date(); //构造一个新对象 d instanceof Date; //计算结果为true, d是Date() 创建的 d instanceof Object //计算结果为true ,所有的对象都是Object的实例 d instanceof Number //计算结果为 false,d不是一个Number对象 var a = [1,2,3] //数组直接量创建数组 a instanceof Array //计算结果true a为数组 a instanceof Object //true 所有的数组都是对象 a instanceof RegExp //fasle 数组不是正则表达式
需要注意的是,所有对象都是Object的实例。当通过instanceof盘对一个对象是否为一个类的实例的时候,这个判断也叫“父类”(superclass)的检测,如果instanceof的左侧操作对象不是对象的话,instanceof返回false。如果右侧操作不是函数,则抛出类型错误的异常。
为了理解instanceof运算符是如何工作的,必须首先理解“原型类”(prototype chain),原型链作为javascript的继承机制,将在6章2节2小节详细描述。
为了计算表达式o instanceof f ,javascript笔仙首先计算f.prototyoe,然后在原型链中查询o,如果找到,那么o是f(或者f的父类)的一个实例,那么返回true。反之false
10.逻辑表达式
逻辑运算符"&&"、“||”、“!”是对操作进行布尔算术运算,经常和关系运算符一起配合使用,逻辑运算符将多个关系表达式组合起来组成一个更复杂的表达式。
i.逻辑与
"&&"运算符可以从三个不同的层次进行理解。最简单一层理解是,当操作数都是布尔值是,“&&”对两个布尔值执行布尔与(AND)操作,只有在第一个操作数和第二个操作数都是true的时候,它才返回true.如果其中有一个操作数为false.则它返回false.
"&&"长用来连接两个关系表达式
x == 0 && y == 0; //只有在x和y都是0时,才返回true
关系表达式总是返回true或false,因此当这样使用的时候,“&&”本身也返回true或 false。关系运算符的优先级要比"&&"(和“||”)要高,因此类似这种表达式可以放心地书写,而不用补充园括号。
"&&"操作数并不一定是布尔值,回想一下,有些值是可以当做“真值”和“假值”的。(如3章3节,假值是:false null undefined 0 -0 NaN和"",所有和其它的值包括所有的对象都是真值)。对“&&”第二层理解是,“&&”是可以对真值和假值进行布尔与(AND)操作。如果两个操作数都是真值的,则那么返回一个真值;否则,至少一个操作数是假值的。javascript中在任何使用布尔值的地方的时候,表达式语句都会将其当做真值或假值来对待,因此实际上“&&”并不总是返回true和false.但也并无大碍。
需要注意的是,上文提到了运算符返回“真值”和“假值”,但并没说说明这个“真值”或者“假值”到底是什么值,为此我们深入讨论对“&&”第三层的理解。运算符首先计算左操作数的值,即首先计算“&&”左侧的表达式,如果计算结果是假值,那么整个表达式的结果一定是假值,因此“&&”这时简单的返回左操作的值,而并不会对右边的操作数进行计算。
var o = { x: 1 }; var p = null; o && o.x; //=>1 : 1:0是真值,因此返回值是o.x p && p.x //= null :p是假值,因此将其返回,而并不计算p.x
这对于理解“&&”可能不计算右操作数的情况至关重要,在上述代码中,变量P的值是null,而如果计算p.x的话则会抛出一个异常错误,因此,只有p为真值(不能是null或undefined)的情况下才计算p.x
"&&"的行为有时候被称为“短路”(short circuiting),我们经常能看到很多代码利用了这一也行来有条件的执行代码。例如下面的两条代码是等价的
if (a == b) stop(); //只有a==b时才能调运stop() (a == b) && stop(); //同上
一般来讲,当“&&”右侧的表达式具有副作用的时候(赋值,递增,递减和函数调用表达式)要格外小心。因为这些带有副作用的表达式的执行时候,依赖于左操作鼠的计算结果。
尽管“&&”可以按照第二层和第三层的理解进行一些复杂的表达式运算,但大多数的情况下,“&&”仅用来对真值和假值的做布尔计算。
ii.逻辑或(||)
"||"运算符对两个操作数做布尔或(OR)运算。如果其中一个为真值,则返回真值,两个操作数都为假值,返回假值。
尽管“||”运算符大多情况下只是做简单的布尔或(OR)运算,和“&&”一样,也具备一些更复杂的行为,它首先计算第一个操作数的值,也就是说回首先计算左侧的表达式,如果计算结果为真,则返回真值,否则,再计算第二个值。
和“&&”一样,用于应该避免右操作数包含一些具有副作用的表达式,除非你目地明确在右侧使用带副作用的表达式,而有可能不会计算右侧的表达式。
这个运算符最常用的方式是用来从一组备选的表达中选取第一个真值的表达式。
//如果max_width已经定义了,则直接使用它。赋值在preferences对象中查找max_width //如果没有定义它,则使用一个写死的常量。 var max =max_width || preferences.max_windth || 500;
这种贯用法通常在函数体内,用来给参数提供默认值。
//将o成功的属性复制到p中,并返回p function copy(o, p) { p = p || {}; //如果向参数p没有传入任何对象,则使用一个新创建对象。 //函数体内的主逻辑
iii.逻辑非(!)
"!"运算符是一元运算符,它放置在一个单独操作数之前。它的目的是将操作数的布尔值求反。
和"&&"、"||"运算符不同,“!”运算符首先将其操作数转换为布尔值(参考第三章的讲诉规则),然后再对布尔值求反。也就是"!"总是返回true和 false。并且,可以通过使用两次逻辑非运算来得到一个值的布尔值:(!!x,参照第三章第八节第2小节)
“!”具有很高的优先级,并且和操作数紧密的绑在一起,如果希望对p && q,则需要园括号!(p && q)。如下代码:
!(p && q) === !p || !q !(p || q) === !p && !q
对于p和q取任何值,这两个表达式永远成立。
11.赋值表达式
javascript使用"="运算符给变量或者属性来赋值,例如:
i = 0 //将变量i设置为0 o.x = 1 //将对象o的属性x 设置为1
“=”运算符希望它的左操作数为一个左值:一个变量或者对象属性(或数组元素),它的右操作鼠可以是任意的类型的任意值。赋值表达式的值就是右操作数的值。赋值表达式的副作用是,右操作数的值赋值给左侧的变量或对象属性。这样的话,后续对这个变量和对象的属性的引用都将得到这个值。
尽管赋值表达式的值非常简单,但有时候会看到一些复杂表达式包含赋值表达式的情况。例如:将赋值和检测操作放在一个表达式中:
(a = b) == 0
如果这样的话,应该清楚地知道"="和"=="区别!,需要注意的是,“=”有非常低的优先级,通常在一个较长的表达式中用到一条赋值语句时,需要补充园括号以保障正确的运算顺序。
赋值操作符的结合性是从右至左,也就是说,一个表达式中出现了多个赋值运算符,运算顺序也从右至左,因此,可以通过以下方式对多个变量赋值。
i=j=k=0; //把三个变量初始化为0
带操作的赋值运算:
除了常规的赋值运算外,javascript还支持需要其他的赋值运算符,这些运算符将赋值运算符合其他的运算符连接起来。提供一种更为快捷的运算方式。例如+=运算符执行的是加法运算符和赋值操作,下面的表达式:
total += salaes_tax;
和下面的表达式等价的
total = total + salaes_tax
运算符“+=”可以作用于数字或字符串,如果其操作是数字,它将执行加法运算和赋值操作;如果是字符串,他就执行字符串的连接和赋值操作。
此类型的运算符还包括,"-=","*=","&="等,如下表赋值运算符
运算符 | 示例 | 等价于 |
+= | a+=b | a=a+b |
-= | a-=b | a=a-b |
*= | a*=b | a=a*b |
/= | a/=b | a=a/b |
%= | a%=b | a=a%b |
<<= | a<<=b | a=a<<b |
>>= | a>>=b | a=a>>b |
>>>= | a>>>=b | a=a>>>b |
&= | a&=b | a=a&b |
|= | a|=b | a=a|b |
^= | a^=b | a=a^b |
大多数情况下,表达式为
a op =b
这里的op代表一个运算符,这个表达式等价于
a =a op b
在第一行中,表达式a计算了一次,在第二行中,表达式a计算了两次。
只有 a包含具有副作用的表达式(比如函数调用和赋值操作)的时候,两者才不等价。如下两个表达式不等价
data[i++] *= 2; data[i++] = data[i++] * 2
12.表达式计算
和很多解释性语言一样,javascript同样可以解释运行由javascript源代码组成的字符串,并产生一个值。javascript通过全局函数eval()来完成这个工作。
eval("3+2") //=>5
动态判断源代码中的字符串是一种强大语言的特性,几乎没有必要在实际中应用。如果你使用了eval(),你应该仔细考虑真的需要它。
下面降价eval()基础用法,并介绍两种严格使用它的方法,从代码优化的角度来讲,这两种方法对原有的代码影响是最小的。
i.eval(eval()是一个函数,但由于它已经被当做运算符来对待了。)
eval()只有一个参数,如果传入的参数不是字符串,它直接返回这个参数。如果参数是字符串,它会把字符串当成javascript进行编译(parse),如果编译失败则抛出一个语法错误(SyntaxError)。如果编译成功,则开始执行这段代码,并返回字符串中最后一个表达式或语句的值,如果最后一个表达式没有语句或者值,则最终返回undefined。如果字符串抛出一个异常,这个异常把该调用的传给eval()
关于eveal()最重要的是,它使用了调用它的变量作用域环境,也就是说,它查找变量的值和定义新变量和函数的操作和局部的代码作用域中的代码一样。如果一个函数定义了一个局部变量x,然后调用了eval("x"),它会返回局部变量的值。如果它调用eval("x=1"),它会改变局部变量的值。如果函数调用了eval("var y=3;")它声明一个新的局部变量y。同样的,一个函数可以通过如下代码声明一个局部函数:
eval("function f(){return x+1;}");
如果最顶层的代码中调用了eval()。当然它会作用于全局变量和全局函数。
ii.全局eval()
eval()具有改变局部变量的能力,这对javascript优化器来说,是一个很大的问题,然而作为一种权宜之计,javascript征对那行调用了eval()函数所做的优化并不多。但当脚本定义了一个别名,并且用令一个名称来调用它,javascript解释器又如何工作呢,为了javascript解释器更加简化。ECMAScipt3标准规定了任何解释器都不允许对eval()赋予别名。如果eval()使用别的别名来调用的话,则会抛出EvalError异常。
实际上,大多数的实现并不是这样做的。当通过别名调用时,eval()会将其字符串当成顶层的全局代码来执行。执行代码可能会定义新的全局变量和全局函数。执行的代码可能会定义新的全局变量和全局函数,或者给全局变量赋值。但却不能修改或修改主调函数中的局部变量,因此这不会影响到函数内的代码优化。
ECMAScript5是反对使用EvalError的,并且规范了eval()的行为。“直接的eval”,当直接使用非限定的“eval”名称,来调用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; //返回未更改的局部变量 } console.log(f(), x); //更改了局部变量,输出local changed global console.log(g(), y); //更改了全局变量,输出local globalchanged
13.其它运算符。
javascript支持很多其它各种各样的运算符。
i.条件运算符(?:)
条件运算符是javascript中的唯一一个三元运算符。通常这个运算符写成"?:",这个运算符拥有三哥操作数,第一个操作数在"?"之前,第二个操作数在“?”和":"之间。第三个操作数早在":"之后,例如
x > 0 ? x : -x; //求x的绝对值
条件运算符的操作数可以是任意类型。第一个操作数当成布尔值,如果它是真值,那么将计算第二个操作数,并返回计算结果。赋值如果第一个值操作数是假值,那么将计算第三个操作数。并返回计算结果。第二个和第三个操作数总会计算其中之一。不可能两者同时进行。其实使用if语句也达到同样的效果(5.4.1),“?:”运算符只是提供了一种简写形式。这里是一个"?:"的典型使用场景,判断一个变量是否有定义,如果有定义则使用它,如果无定义,则使用一个默认值。
grett = "hello" + (username ? username : "three");
和以下的代码是等价的,但上面的更加简洁
grett = "hello"; if (username) grett += username; else grett + "three"
ii.typeof()运算符
typeof是一元运算符,放在单个操作数前面,操作数可以是任何类型,返回值表示操作类型的一个字符串。
x __ typeof x undefined __ "undefined" null __ "object" ture或false __"boolean" 任意数字或NaN __ "Number" 任意字符串 __ "String" 任意函数 __ "function" 任意内容对象(非函数)__ "object" 任意宿主对象 __ 由编译器各自实现的字符串,但不是"undefined" "boolean" "number" "string"
typeof最常用的用法写在表达式中们就像这样
(typeof value == "string") ? "" + value + "":value;
typeof运算符同样在swith语句中(5.4.3)非常有用,需要注意的是,typeof运算可以带上园括号。这样让typeof看起来像一个函数名,而非关键字
typeof(i)
iii.delete运算符
delete是一元操作符,它用来删除对象的属性或者数组的元素。就像赋值、递增、递减运算符一样。delete也是具有副作用的。它是用来做删除操作的。不是用来返回一个值的。
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 元素2已经在数组中不存在了 a.length; //=>3,注意,数组长度并没有改变,尽管上一行删除了这个元素,但删除操作留下了一个洞。实际上并没有修改数组的长度,因此a的长度仍然为3
需要注意的是,删除属性或删除数组元素不仅仅设置了一个undefined值,当删除一个属性时,这个属性不复存在。读取一个不存在的值将会返回undefined.关于delete删除还有严格模式下的一些情况,需要学习的人自己试验,这里给一些例子。
var o = {x: 1,y: 2}; delete o.x; //删除一个对象属性,返回true typeof o.x; //属性不存在,返回"undefined" delete o.x; //删除不存在的属性,返回true; delete o; //不能删除通过var关键字声明的变量,返回false delete 1; //参数不是一个左值。 this.x = 1;// 给全局定义一个属性,这里没有使用var delete x ; //试图删除它,在非严格模式下返回true //在严格模式下回抛出异常,这时使用"delete this.x"来代替 x; //运行时出错,没有定义x
6章第三节还有关于delete的讨论。
iii.void运算符。
void是一元运算符,在出现操作数之前,操作数可以是任何类型。这个运算符并不是经常使用:操作数会照常计算,但会忽略计算结果并返回undefined。由于void会忽略操作数的值,因此在操作数具有副作用时使用void来程序更有意义。
这个最常用的带客户端url.在url写带有副作用的表达式,而void则让浏览器不显示在这个表达式的运算结果。
<a href="javascript:void window.open();">new</a>
iiii.逗号运算符。(,)
逗号运算符是二元运算符,它的操作数可以是任意类型。它首先计算左操作数,然后计算右操作数。
i = 0, j = 1, k = 2;
它和下面的代码基本上等价的
i = 0; j = 1; k = 2;
总是会计算左侧的表达式,但计算结果忽略掉,也就是说,只有左侧表达式具有副作用,才会使用逗号运算让代码变得更通畅。逗号运算符最常用的场景是for循环中,这个for循环通常有多个循环变量。
//for循环中的第一个逗号是var语句的一部分 //第二个逗号是逗号运算符 //它将两个表达式(i++和j++)放在一条(for循环中)语句中 for (var i = 0, j = 10; i < j; i++, j--); console.log(i + j);
(本文完)