第4章 表达式和运算符
4.1 原始表达式
原始表达式(primary expression)是表达式的最小单位——它们不再包含其它表达式。
1)直接量:数字直接量、字符串直接量、正则表达式直接量
2)保留字:true、false、null、this
3)变量
4.2 对象和数组的初始化表达式
也称“对象直接量”和“数组直接量”,不是原始表达式
数组直接量中的列表逗号之间的元素可以省略,空位填充undefined
4.3 函数定义表达式
也称函数直接量
eg:
var square = function(x){return x*x;}
4.4 属性访问表达式
方括号或点
"." 和 "[" 之前的表达式总会先计算,如果计算结果是null或undefined,表达式会抛出一个类型错误异常
4.5 调用表达式(invocation expression)
4.6 对象创建表达式(object creation expression) -> 创建一个对象并调用一个函数(构造函数)初始化新对象的属性,对象创建表达式有一个关键字new
4.7 运算符概述
一个需要注意的例子 -> "5"*"3"结果是数字15
4.8 算术表达式
JS中,所有的数字都是浮点型的,除法运算的结果也是浮点型,比如 5/2=2.5
0/0 -> NaN, someNum/0 -> 正负无穷大 (注意:不会报错)
-5%2 = -1, 6.5%2.1 = 0.2
"1"+2 => "12"
true + ture => 2
2 + null => 2
2 + undefined => NaN
注意:"++" 运算符从不进行字符串连接操作
表达式++x并不总和x=x+1完全一样,例如x是"1",前者是2,后者是"11"
4.9 关系表达式
4.9.1 相等和不相等运算符
"===" 严格相等运算符(strict equality)或恒等运算符(identity operator)
"==" 相等运算符(equality operator)
建议:
"=" 得到 或 赋值
"==" 相等
"===" 严格相等
"!=" 不相等
"!==" 不严格相等
控制台检验的一些例子:
1=="1" => true
null==undefined => true
null===undefined => false
--------------------------
下面两个和书里写的不一样
null===null
undefined===undefined
--------------------------
"1"==true => true, 如果一个值是数字,另一个是字符串,先将字符串转换为数字
4.9.2 比较运算符
一些例子:
"1" + 2 => 2转换为"2",结果是"12"
"11" < "3" => 字符串比较,结果为true,注意所有大写的ASCII字母都“小于”小写的ASCII字母
"11" < 3 => 数字比较,"11"转换为11,结果为false
"one" < 3 => 数字比较,如果其中一个操作数是(或转换后是)NaN,那么比较操作符总是返回false(>=,=<同理)
4.9.3 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]; // 拥有三个元素的数组
"0" in data // => true:数组包含元素"0"
1 in data // => true:数字转换为字符串
3 in data // => false:没有索引为3的元素
4.9.4 instanceof运算符
如果左侧的对象是右侧类的实例,则表达式返回true
第9章将会讲到,JavaScript中对象的类是通过初始化它们的构造函数来定义的。这样的话,instanceof的右操作数应当是一个函数。
var d = new Date();// 通过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; // 计算结果为false,数组不是正则表达式
需要注意的是,所有的对象都是Object的实例。当通过instanceof判断一个对象是否是一个类的实例的时候,这个判断也会包含对“父类”(superclass)的检测。如果instanceof的左操作数不是对象的话,instanceof返回false。如果右操作数不是函数,则抛出一个类型错误异常。
理解原型链(prototype chain)6.2.2节
4.10 逻辑表达式
x==0 && y==0 //关系运算符的优先级比“&&”和“||”要高,可以不写括号
!(p && q) // "!"优先级很高,和操作数紧密绑在一起
1.逻辑与
&&并不总是返回true或false
- 如果计算结果是假值,则返回左操作数的值
- 当左操作数是真值时,将计算右操作数的值并将其返回作为整个表达式的计算结果
&&的行为有时称为短路(short circuiting)
eg:以下两行等价
if(a==b) stop();
(a==b) && stop();
具有副作用-> 赋值、递增、递减和函数表达式
2.逻辑或
最常用的方式是从一组备选表达式中选出一个真值表达式,通常用在函数体内,用来给参数提供默认值
var max = max_width || preferences.max_width || 50;
3.逻辑非
总是返回true或false(注意这点和前两者不一样)
可以通过 !!x 得到一个等价的布尔值
4.12 表达式计算(没看明白,先跳过)
4.13.2 typeof运算符
注意:
>typeof(null)
"object"
所有对象和数组的typeof返回object
可执行对象(callable)的typeof返回function
了解真正的函数和可执行对象参照8.7.7
4.13.3 delete运算符(看完第6章再看看)
用来删除对象属性或数组元素
delete也具有副作用,它是用来删除操作数的,不是用来返回一个值的
1)delete希望它的操作数是一个左值(变量、对象属性和数组元素),如果它不是左值,那么delete将不进行任何操作同时返回true
2)否则将试图删除 2.1)删除成功返回true,只有操作数是一个属性访问表达式(4.4节)的时候它才会正常工作
2.2)一些内置核心客户端属性不能删除,用户通过var语句声明的变量不能删除,通过functon语句定义的函数和函数参数也不能删除。如果操作非法,delete操作将抛出一个语法错误(SyntaxError)异常
3)在严格模式下,delete操作删除不可配置的属性(6.7节)时会抛出一个类型错误异常,在非严格模式下不会报错仅返回false
第五章 语句
5.2 复合语句和空语句
关于空语句,注意:
[例]
if(( a==0 ) || (b==0)) ; //这一行代码什么都没做,最后的分号是一条空语句
o = null; //这一行代码总会执行
如果有特殊的目的需要使用空语句,最好在代码中添加注释
[例]
for (i =0; i < a.length; a[i++] = 0) /*empty*/;
5.3 声明语句
var和function都是声明语句,它们声明或定义函数或变量。
5.3.1 var
var语句出现在函数体 -> 局部变量,作用域:这个函数
var语句在顶层代码 -> 全局变量,作用域:整个JS
注意:
全局变量是全局对象的属性,但是var声明的变量不能通过delete删除(3.10.2节有提到)。
如果var语句中的变量没有指定初始化表达式,那么这个变量的值为undefined
5.3.2 function
两种定义写法
var f = function(x) {return x+1;} //函数定义表达式
function f(x){return x+1;} //函数声明表达式
1.区别:两种方法都创建了新的函数对象,但1)函数声明语句中的函数名是一个变量,变量指向函数对象。
函数声明语句通常出现在JS代码的最顶层,也可以嵌套在其它函数体内(嵌套时只能出现在所嵌套函数的顶部)。使用函数声明语句,函数名和函数体均提前,可以在声明一个JS函数之前调用它。
2)和通过var声明变量一样,函数定义语句中的函数被显式地“提前”到了脚本或函数的顶部。
2. 和var语句一样,函数声明语句创建的变量也是无法删除的。
5.4 条件语句(else,else if,switch)
5.4.3 switch
当执行这条switch语句的时候,它首先计算expression的值,然后查找case子句中的表达式是否和expression的值相同(这里的“相同”是按照“===”运算符进行比较的)。
在每一个case语句块的结尾处都使用了关键字break。break语句可以使解释器跳出switch语句或循环语句。在switch语句中,case只是指明了要执行的代码起点,但并没有指明终点。
如果在函数中使用switch语句,可以使用return来代替break,return和break都用于终止switch语句,
eg:
function convert(x){
switch(typeof x){
case 'number': // 将数字转换为十六进制数
return x.toString(16);
case 'string': // 返回两端带双引号的字符串
return '"' + x + '"';
default: // 使用普通的方法转换其他类型
return String(x);
}
}
"default:"标签一般出现在switch的末尾,位于所有case标签之后。实际上,它可以放置在switch语句内的任何地方。
5.5 循环(while,do/while,for,for/in)
5.5.2 do/while
do statement
while(expression); //至少执行一次,最后这个分号不要忘记
5.5.3 for
for(initialize ; test ; increment)
statement
和以上等价的while循环
initialize;
while(test){
statement
increment;
}
for循环中数字是最常用的,但不是必需的。下面这段代码就使用for循环来遍历链表数据结构,并返回链表中的最后一个对象(也就是第一个不包含next属性的对象):
function tail(o) { // 返回链表的最后一个节点对象
for(; o.next; o = o.next) /* empty */ ; // 根据判断o.next是不是真值来执行遍历
return o;
}
for循环中那三个表达式中的任何一个都可以忽略,但是两个分号必不可少。如果省略test表达式,那么这将是一个死循环,同样,和while(true)类似,死循环的另外一种写法是for(;;)。
5.5.3 for/in
for(variable in object)
statement
使用for循环来遍历数组元素是非常简单的:
for(var i = 0; i < a.length; i++) //i代表了数组元素的索引
console.log(a[i]); //输出数组中的每个元素
而for/in循环则是用来更方便地遍历对象属性成员:
for(var p in o) // 将属性名字赋值给变量p
console.log(o[p]); //输出每一个属性的值
在执行for/in语句的过程中,JavaScript解释器首先计算object表达式。
1)如果表达式为null或者undefined,JavaScirpt解释器将会跳过循环并执行后续的代码 。
2)如果表达式等于一个原始值,这个原始值将会转换为与之对应的包装对象(wrapper object)(见3.6节)。
3)否则,expression本身已经是对象了。JavaScript会依次枚举对象的属性来执行循环。然而在每次循环之前,JavaScript都会先计算variable表达式的值,并将属性名(一个字符串)赋值给它。
例如,可以使用下面这段代码将所有对象属性复制至一个数组中:
var o = {x:1, y:2, z:3};
var a = [], i = 0;
for(a[i++] in o)/* empty */;
JavaScript数组不过是一种特殊的对象,因此,for/in循环可以像枚举对象属性一样枚举数组索引。
例如,在上面的代码之后加上这段代码就可以枚举数组的索引0、1、2:
for(i in a)console.log(i);
注意:for/in循环并不会遍历对象的所有属性,只有“可枚举”的属性才会遍历到(参照6.7节)。由JavaScript语言核心所定义的内置方法就不是“可枚举的”。比如,所有的对象都有方法toString(),但for/in循环并不枚举toString这个属性。
不可枚举(nonenumerable):内置方法、很多内置对象的属性
可枚举(enumerable):代码中定义的所有属性和方法、继承的自定义属性
属性枚举的顺序(暂时没看明白 囧)
5.6 跳转
5.6.1 标签语句
break和continue是JS中唯一可以使用语句标签的语句
mainloop: while(token != null){
// 忽略这里的代码...
continue mainloop; // 跳转到下一次循环
// 忽略这里的代码...
}
5.6.2 break语句
单独使用break语句的作用是立即退出最内层的循环或switch语句。
break labelname;
当break和标签一块使用时,程序将跳转到这个标签所标识的语句块的结束,或者直接终止这个闭合语句块的执行。
当你希望通过break来跳出非就近的循环体或者switch语句时,就会用到带标签的break语句。下面是示例代码:
var matrix = getData(); // 从某处得到一个二维数组
// 将矩阵中所有元素进行求和
var sum = 0, success = false;
//从标签名开始,以便在报错时退出程序
compute_sum: if(matrix){
for(var x = 0; x < matrix.length; x++){
var row = matrix[x];
if(!row)break compute_sum;
for(var y = 0; y < row.length; y++){
var cell = row[y];
if(isNaN(cell))break compute_sum;
sum += cell;
}
}
success = true;
}
//break语句跳转至此
//如果在success == false的条件下到达这里,说明我们给出的矩阵中有错误
//否则将矩阵中所有的元素进行求和
最后,需要注意的是,不管break语句带不带标签,它的控制权都无法越过函数的边界。比如,对于一条带标签的函数定义语句来说,不能从函数内部通过这个标签来跳转到函数外部。
5.6.2 continue语句
不管continue语句带不带标签,它只能在循环体内使用。在其他地方使用将会报语法错误。
注意:continue语句在while和for循环中的区别,while循环直接进入下一轮的循环条件判断,但for循环首先计算其increment表达式,然后判断循环条件。
和break语句类似,带标签的continue语句可以用在嵌套的循环中,用以跳出多层次嵌套的循环体逻辑。
5.6.4 return语句
return语句只能在函数体内出现。return语句不带表达式,函数会向调用程序返回undefined。
5.6.5 throw语句
5.6.6 try/catch/finally语句
1)当try块内某处发生了异常时,调用catch内的代码逻辑。
catch从句后跟随finally块,后者中放置清理代码,不管try块中是否产生异常,finally块内的逻辑总是会执行。
2)尽管catch和finally都是可选的,但try从句需要至少二者之一与之组成完整的语句。
3)try、catch和finally语句块都需要使用花括号括起来(即使只有一条语句也不能省略)。
如果使用try/finally语句,就能使用while循环来正确模拟包含continue的for循环:
//模拟for(initialize;test;increment)body;initialize ;
while(test){
try { body ; }
finally { increment ; }
}
5.7 其它语句类型
5.7.1 with语句
with语句用于临时扩展作用域链(scope chain),它具有如下的语法:
with(object)statement
这条语句将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态。
在严格模式中(参照5.7.3节)是禁止使用with语句的,并且在非严格模式里也是不推荐使用with语句的,尽可能避免使用with语句
在对象嵌套层次很深的时候通常会使用with语句来简化代码编写。例如,在客户端JavaScript中,可能会使用类似下面这种表达式来访问一个HTML表单中的元素:
document.forms[0].address.value
如果这种表达式在代码中多次出现,则可以使用with语句将form对象添加至作用域链的顶层:
with(document.forms[0]){
// 直接访问表单元素,例如:
name.value = "";
address.value = "";
email.value = "";
}
不使用with语句的等价代码可以写成这样:
var f = document.forms[0];
f.name.value = "";
f.address.value = "";
f.email.value = "";
只有在查找标识符的时候才会用到作用域链,创建新的变量的时候不使用它,看一下下面这行代码:
with(o)x = 1;
如果对象o有一个属性x,那么这行代码给这个属性赋值为1。但如果o中没有定义属性x,这段代码和不使用with语句的代码x=1是一模一样的。它给一个局部变量或者全局变量x赋值,或者创建全局对象的一个新属性。with语句提供了一种读取o的属性的快捷方式,但它并不能创建o的属性。
5.7.2 debugger
这条语句用来产生一个断点(breakpoint)
5.7.3 “use strict”
“use strict”是ECMAScript 5引入的一条指令。
严格模式和非严格模式之间的最重要的三条区别:
1)在严格模式中禁止使用with语句。
2)在严格模式中,所有的变量都要先声明,如果给一个未声明的变量、函数、函数参数、catch从句参数或全局对象的属性赋值,将会抛出一个引用错误异常。
3)在严格模式中,调用的函数(不是方法)中的一个this值是undefined。(在非严格模式中,调用的函数中的this值总是全局对象)。可以利用这种特性来判断JavaScript实现是否支持严格模式:
var hasStrictMode =(function(){ "use strict"; return this===undefined}());