逻辑运算
JavaScript中支持两种逻辑运算,“逻辑或(||)”和“逻辑与(&&)”,他们的使用方法与基本的布尔运算一致:
var str= ‘hello’;
var obj = {};
x = str || obj;
y = str && obj;
这种运算符的特殊之处在于,他既不改变运算元的数据类型,也不强制运算结果的数据类型。除此之外,还有两条特性:
- 运算符会将运算元理解为布尔值;
- 运算过程支持布尔短路。
因此上例中,
x运算结果为:str;
y运算结果为:obj。
比较运算
1.等值检测
等值检测的目的是判断两个变量是否相等或相同。
相等是指运算符“==”和“!=”,相同是指运算符“===” 和"!=="。
名称 |
运算符 |
说明 |
相等 |
== |
比较两个表达式,看是否相等。 |
不等 |
!= |
比较两个表达式,看是否不相等 |
相同 |
=== |
比较两个表达式,看值是否相等并具有相同的数据类型。 |
不同 |
!== |
比较两个表达式,看是否具有不相等的值或不同的数据类型。 |
等值检测中“相等”的运算规则
类型 |
运算规则 |
两个值类型进行比较 |
转换成相同数据类型的值进行“数据等值”比较。 |
值类型与引用类型进行比较 |
将引用类型的数据转换成为与值类型数据相同的数据,再进行“数据等值”比较。 |
两个引用类型比较 |
比较引用地址。 |
等值检测中“相同”的运算规则
类型 |
运算规则 |
两个值类型进行比较 |
如果数据类型不同,则必然“不相同”;数据类型相同时,进行“数值等值”比较。 |
值类型与引用类型进行比较 |
必然“不相同”。 |
两个引用类型比较 |
比较引用地址。 |
特别举例说明引用类型的等值比较,“比较引用地址” 的实际意义是:如果不是同一个变量或其引用,则两个变量不相等,也不相同。
var str = ‘abcdef‘;
var obj1 = new String(str);
var obj2 = new String(str);
alert(obj1==obj2);//返回false
alert(obj1===obj2);//返回false
“===“和“!==”运算符的一般性用途
在JavaScript中false、‘’、0和Null、undefined是两两“相等”的:
alert(‘‘==0);
alert(0==false);
alert(false==‘‘);
alert(undefined==null);
以上的结果都是true,因此当比较的数据元中包含以上5种情况时,应使用“===”和“!==”。
PS:一个值应该与其自身“相等/相同”,但存在一个例外:一个NaN值,与自身不相等,也不相同。
2.序列比较(比较大小)
可比较序列的类型 |
序列值 |
Boolean |
0~1 |
String |
|
Number |
负无穷大~正无穷大 |
类型 |
运算规则 |
两个值类型进行比较 |
直接比较数据在序列中的大小。 |
值类型与引用类型进行比较 |
将引用类型的数据转换为与值类型数据相同的数据,再进行“序列大小”比较。 |
两个引用类型比较 |
无意义。(*注1) |
注1:其实,对引用类型进行序列检测运算仍然是等可能的,但这与valueOf()运算的效果有关。
以下是上述的例子:
//值类型的比较,布尔值与数值
var b0 = false;
var b1 = true;
var num = 1;
alert(b1<num);//false
alert(b1<=num);//true
alert(b1>b0);//true
//值类型与引用类型
var num = 1;
var ref = new String();
alert(num > ref);//true
//引用类型的比较
var o1={};
var o2={};
alert(o1 > o2 || o1 < o2 || o1 == o2);//false
//两个运算元都是字符串,比较字符串中每个字符的大小
var s1 = "abc";
var s2 = "ab";
alert(s1 > s2);//true
//当字符串与其他类型值比较时,将字符串转换为数值比较
var s3 = "101";
var i= "100";
alert(s3 > i);true
//当字符串转换为数值时得到NaN,任何数据与NaN比较都为false
var s4 = "abc";
var i = "100";
alert(s4 > i);
void运算符
使用void的语法格式是:[void 表达式]。
其作用是使后面的表达式立即执行,但忽略返回值(返回undefined)。
以下的例子很好的诠释了这句话的含义:
var s1 = "1";
function testVoid()
{
s1 = "2";
}
var s2 = void testVoid();
alert(s1);//2
alert(s2);//undefined
利用这种特性,我们可以使<a>标签中的Href属性失去作用,就像我们常看到的那样:
这里void后的括号并不是必须的,只不过它仍然满足[void 表达式]这一语法格式,表达式等于(0)而已,千万不要当成了函数调用。
由此应该可以想到,既然这里使用到的是void 忽略返回值这一特性,那么该链接么有执行默认行为的原因并不是我们原以为的0在起作用,如下形式会得到同样的效果:
<a href="javascript: void 1">I am a useless link</a>
<a href="javascript: void ‘abcde‘">I am a useless link</a>
<a href="javascript: void (1 + 2)">I am a useless link</a> <!-- 这里不加括号行不行呢,大家自己去验证吧~ -->
函数调用
最常用的函数调用方式:
//具名函数直接调用
function foo()
{
}
foo();
//匿名函数通过引用来调用
var fooRef = function(){}
fooRef();
下面来看看几种不常见的函数调用方式:
//没有引用的匿名函数调用1
( //这里的括号对表示强制运算符
function()
{
alert(‘test‘);
}()//这里的括号对表示函数调用运算符 (*注)
);
//执行完后,弹出‘test’表明函数执行成功。
下面来分析一下这段代码,首先来看内部的函数,
function()
{
alert(‘test‘);
}()
这样还不够清晰,这段代码等同于
function fun1()
{
alert(‘test‘);
}()
如果还觉得不够清晰的话,其实它还等同于
function fun1()
{
alert(‘test‘);
};
();
也就是说第1个分号前的函数申明是作为完整的语法结构被解释,而后面的“();”则显然是一段错误的语法,
那么是什么让上面的函数调用得以成功执行呢?答案是最外层的强制运算符。
我们知道强制运算符是把本不会产生运算关系的运算元强制进行运算,比如:1*2+3*4,不会按照2+3这样的逻辑进行运算,但1*(2+3)*4之后就可以了。
回到上面的例子,最外层的强制运算符把本应单独解释的两段语句强制执行,执行的效果就是产生函数调用!
//没有引用的匿名函数调用2
(
function()
{
}
)();//这里的括号对表示函数调用运算符 (*注)
经过上面的例子,这个就比较好理解了。
这里强制运算符运算的是“函数直接量声明”这个表达式,并返回一个函数的引用,然后通过函数调用运算符“()”来操作这个函数引用。
//没有引用的匿名函数调用3
void function()
{
}();//这里的括号对表示函数调用运算符 (*注)
运算符void用于使其后的函数表达式执行运算,可理解为一种强制运算,类似示例1。
上面3个例子中标(*注)的地方都是函数调用运算符,既然是函数调用的语法,那么和普通的函数调用一样,也是可以传递参数的。
void function(s)
{
alert(s);
}(‘test‘);
break的不常见用法
一般情况下,break子句作用于循环语句的内层或者switch语句中,但break子句也具有一种扩展的语法,以指示它所作用的范围。
该范围用已经声明过的标签来表示:
break some_label;
some_label:
{
var s1 = "1";
if(s1 =="1")
{
break some_label;
}
s1 ="2";
}
alert(s1);// 结果为1,表明s1 ="2"并没有执行。
continue的不常见用法
continue后面也可以带一个标签,这时它表明从循环体内部中止,并继续到标签指示处开始执行。
some_label:
for(var i = 1; i <3;i++)
{
for(var k =11; k < 13;k++)
{
if(k==12)
{
continue some_label;
}
alert("i = " + i + ";k = " + k);
}
}
//依次弹出i = 1;k = 11 i = 2;k = 11
//直接看代码不容易理解,可以加个断点调试一下,其实这里的continue some_label的意义是从内部循环中跳出来,
//并继续执行外部的下一次循环,普通的continue是执行当前循环的下一次循环。
//这里需要注意的是continue对应的标签后不能添加大括号。
JavaScript中没有GOTO语句,break和continue语句提供了另一种选择的可能。
运算符的二义性
一、加号“+”的二义性
加号作为运算符在JavaScript中有三种作用:
1.字符串连接符
2.表达式求和运算符
3.数字取正运算符
第3点不常用,他的意思是表示数学里的正号。
var n = 1;
alert(+n);//表示+(1),其结果还是1。可与alert(-n),进行比较,这里的结果是-1
这三种情况中,1、2两点最容易出现二义性问题。因为这两种处理运算处理方式依赖与数据类型,而无法从运算符上进行判断。如:
c = a + b;
是根本无法知道他真实的含义是在求和还是在做字符串连接。
由于这种二义性的存在会出现一些问题。浏览器中,由于DOM模型的许多值看起来是数字,但实际上确实字符串。因此试图做加法运算时,实际却变成了字符串连接。
<img id = "testImg" style="border: 1 solid red" />
<script>
alert(testImg.style.borderWidth + 10);//1px10
</script>
另外,还有一条关于加号运算符的规则:如果表达式中存在字符串,则优先按字符串连接进行运算。
alert("123"+456);//123456
alert(123+"456");//123456
二、逗号“,”的二义性
先看下面几个示例,分析其表达的含义:
//示例1
a=(1,2,3);
//示例2
a=1,2,3;
//示例3
a=[1,2,(3,4,5),6];
//示例4
var a = 1,2,3;
示例1中,逗号被作为连续运算符使用,连续运算符(也叫逗号表达式)的意义是返回最后一个表达式的值,所以示例1的效果是“变量a赋值为3”。
示例2整个表达式为一个逗号表达式,因此整个表达式的值为最后一个表达式的值3,而变量a被赋值为1.
示例3中的逗号有两中含义(3,4,5)中的逗号表示连续运算符,而在1,2中则表示数组声明时的语法分隔符,因此示例3为将a声明为一个[1,2,5,6]的数组。
示例4并不会像示例2一样正常执行。因为这里的逗号被解释成了语句var声明时用来分隔多个变量的语法分隔符(var a, b,c),而数字2,3为不合法的语法声明,因此会在解释期就提示出错。
三、方括号“[]”的二义性
//示例1
a = [ [1][1] ];
这个奇怪的语句并没有语法错误,尽管我们几乎不能理解,但JavaScript解释器可以理解。为了弄清这个问题,必须了解方括号的作用:
1.用于声明数组直接量。
2.存取数组下标的运算符。
3.对象成员存取。
因此第一个"[1]"被理解成了一个数组的直接量,它只有一个元素。接下来,由于他是对象,所以第二个“[1]”就被理解为取下标为1的元素,很显然,这个元素还没有声明。
因此“[1][1]”的结果为undefined,而a就变成了a =[undefined],只有一个元素的数组,该元素为undefined.
下面再来看一个稍微复杂一点的例子:
var a = [ [‘a‘,1,2][‘b‘,3,4] ];
[‘a‘,1,2]仍然是一个数组的直接量,但[‘b‘,3,4]怎么解释呢?
首先要了解‘b‘,3,4被解释成为了一个逗号表达式,而他的值为4,因此[4]表示数组成员的存取,后面的过程就与上一个示例一样了。