JavaScript 第二章
二、类型、值和变量
计算机程序的进行需要对值(value)进行操作。在编程语言中,能够表示并操作的值的类型乘坐数据类型(type),编程语言最基本的特性就是能够支持多种数据类型。JavaScript的数据类型分为两类;原始类型(primitive type)和对象类型(object type)。 JavaScript的原始数据类型包括数字、字符串和布尔值。另外,JavaScript具有两个特殊的原始值:null(空)和undefined(未定义),他们不是数字、字符串和布尔值,他们们通常分别代表了各自特殊类型的唯一的成员。 JavaScript中除了数字、字符串、布尔值、null和undefined之后的就是对象了。对象(object)是属性(property)的集合。每个属性都由”名/值对”(值可以是原始值、不如数字、字符串、也可以是对象构成)。其中,有一个比较特殊的对象— 全局对象(global object)。
2.1数字
和其他编程语言不同,JavaScript不区分整数值和浮点数值。JavaScript中的所有数字均用浮点数值表示。JavaScript用IEEE 754标准定义的64为浮点格式表示数字,这意味着他能表示的最大值是±1.7978931348623157 x 10^308,最小值是±5 x 10^-324。按照JavaScript中的数字格式,能够表示的整数范围是从-253~253,包含边界值。如果超过此范围的整数,则无法报这个低位数字的精度。然而需要注意的是,JavaScript中实际的操作则是基于32位整数。
2.1.1 整数直接量
在JavaScript程序中,用一个数字序列表示一个十进制整数。例如:
0
3
10000000
除了十进制的整形直接量,JavaScript同样可以识别十六进制(以16为基数)值。所谓十六进制的直接量是指以”0x”或”0X”为前缀,其后跟随十六进制数串的直接量。十六进制值是0-9之间的数字和a(A)-f(F)之间的字母构成,a-f的字母对应的是表示数字10-15。下列是十六进制整型直接量的例子:
0xff //15*16+15=255(十进制)
0xCAFE911
尽管ECMAScript(是JavaScript的规格,是JavaScript基本语法)标准不支持八进制直接量,但JavaScript的某些实现可以允许采用八进制(基数为8)形式表示整数。八进制直接量以数字0开始,其后跟随一个由0-7(包括0和7)之间的数字组成的序列。例如:
0377 //3*64+7*8+7=255(十进制)
由于某些JavaScript的实现支持八进制直接量,而有的不支持,因此最好不要使用以0为前缀的整型直接量,毕竟我们也无法得知当前JavaScript的实现是否支持八进制的解析。 在ECMAScript6的严格模式下,八进制直接量是明令禁止的。
2.1.2浮点型直接量
浮点型直接量可以含有小数点,他们采用的是传统的实数写法。一个实数由整数部分、小数点和小数部分组成。此外,可以使用科学计数法的形式进行记数,是由前面的实数乘以10的指数次幂。
可以使用更加简洁的语法表示:
[digits][.digits][(E|e)[(+|-)]digits]
例如:
3.14
2345.789
.3333333
6.02e23 //6.02x10的23次幂
2.1.3 JavaScript中的算术运算
JavaScript程序是使用语言本身提供的算术运算符来进行数字运算的。这些运算符包括加法运算符(+)、减法运算符(-)、乘法运算符(*)、除法运算符(/)和求余(求整除后的余数)运算符(%)。 除了基本的运算外,JavaScript还支持更加复杂的算术运算,这些复杂运算通过作为Math对象的属性定义的函数和常量来实现:
Math.pow(2,53) // => 9007199254740992:2的53次幂
Math.round(.6) // => 1.0:四舍五入
Math.ceil(.6) // => 1.0:向上取整
Math.floor(.6) // => 0.0:向下取整
JavaScript中的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不好报错。当数字运算结果超过了JavaScript所能表示的数字上限(溢出),结果为一个特殊的无穷大(infinity)值,在JavaScript中以Infinity表示。同时地,当负数的值超过了JavaScript所能表示的负数范围,结果以无穷大,在JavaScript中以-Infinity表示。无穷大值的行为特性和我们所期望的是一致的:基于他们的加减乘除运算结果还是无穷大值(当然还保留他们的正负号)。下溢(underflow)是当于是运算结果无线接近于零并比JavaScript能表示的最小值还小的时候出现的一种情形。这种情况下,JavaScript将会返回0.当一个附属发生下溢时,JavaScript返回一个特殊的值”负零”。这个值(负零)几乎和正常的零完全一样,JavaScript程序员很少用到负零。
2.1.4二进制浮点数和四舍五入错误
实数有无数个,但JavaScript通过浮点数的形式只能表示其中有限的个数(确定地来说是 18 437 736 874 454 810 627个)。也就是说,当在JavaScript中使用实数的时候,常常只是真实值的一个近似表示。JavaScript采用了IEEE-754浮点数表示法(几乎所有现代编程语言所采用),这是一种二进制表示法,可以精确的表示带分数,比如1/2,1/8和1/1024.遗憾的是,我们常用的分数(特别是在金融计算方面)都是十进制分数1/10、1/100等。二进制浮点数表示法并不能精确的表示精确0.1这样简单的数字。JavaScript中的数字具有足够的精度,并可以极其近似于0.1。但事实上,数字不能精确表述带来的问题。例如:
var x= .3-.2; // 30美分减去20美分
var y= .2-.1; // 20美分减去10美分
x==y // =>false:两值不相等
x==.1 // => false:.3-.2不等于.1
y==.1 // =>true: .2-.1等于.1
由于舍入误差,0.3和0.2之间的近似差值实际上并不等于0.2和0.1之间的近似插值。这个问题并不是只在JavaScript中才出现。理解这一点非常重要,在任何二进制浮点数的编程语言中都会有这个问题。同时需要注意的是,上述代码中的x和y值非常接近彼此和最终的正确值。这种计算结果可以胜任大多数的计算任务。这问题也只有在比较两个值是否相等的时候出现。
2.1.5 日期和时间
JavaScript语言核心包括Date()构造函数,用来创建表示时间和日期的对象。这些日期对象的方法为日期计算提供了简单的API。日期对象不想数字那样是基本数据类型。本节给出了使用对象的一个简单教程。
VaTthen = new Date(2011,0,1);//11 2011年1月1日
var later = new Date(2011,0,1,17,10,30);//同一天,当地时间5:10:30pm,var now= new Date();12当前日期和时间
vaI elapsed = now-then;// 71日期减法:计算时间间隔的毫秒数
later. getFullYear( );// => 2011
later. getMonth();// => 0:从0开始计数的月份
later。getDate() ;// => 1:从1开始计数的天数
later.getDay(); // => 5:得到星期几,0代表星期日,5代表星期一
later. gethours(); // =>当地时间17:5pm
later. getUTCHours(); // 使用UTC表示小时的时间,基于时区
2.2 文本
字符串(String)是一组由16位值组成的不可变的有序序列,每个字符通常来自于Unicode字符集。JavaSrcipt通过字符串类型来表示文本。字符串的长度(length)是其所含16位值的个数。JavaScript字符串(和其数组)的索引从零开始,第一个字符的位置是0,第二个字符的位置是1,以此类推。空字符串(empty string)长度为0,JavaScript中并没有表示单个字符的“字符型。要宝石一个16位值,只需将其复制给字符串变量即可,这个字符串长度位1。
2.2.1字符串直接量
在JavaScript程序中的字符串直接量,是由单引号或者双引号括起来的字符序列。由单引号定界的字符串中可以包含双引号,由双引号定界的字符串中也可以包含单引号。例如:
'teseting'
"3,14"
"name=myform"
"This string\nhas two lines"
在ECMAScript5中,字符串直接量可以拆分为数行,每行必须以反斜线(\)结束,反斜线和行结束符都不算是字符串直接量的内容。如果希望在字符串直接两中另起一行看,可以使用转义字符\n:
"two\nlines" //这里定义了一个显示位两行的字符串
"one\ //用三行代码定义了显示位单行的字符串,只有在EMCAScript 5中使用
long\
line" //
在客户端JavaScript程序设计中,JavaScript代码会夹杂HTML代码的字符串,HTML代码也会夹杂JavaScript代码。和JavaScript一样,HTML也使用单引号或者双引号来定界字符串。因此,当JavaScript代码和HTML代码混在一起的时候,最好在JavaScript和HTML代码中各自使用独立的引号风格。例如,在JavaScript表达式中使用单引号表示字符串”Thank you”,而在HTML事件处理程序属性中则使用双引号表示字符串。例如:
<button onclick="alert('Thank you ')">click me </button>
2.2.2转移字符
在JavaScript字符串中,反斜线()有着特殊的红提,反斜线符号后加一个字符,将不在表示他们的字面含义。例如,\n即使一个转义字符,她表示的是一个换行符。另外,上节中提到的转义字符‘\n’,表示单引号(撇号)。当需要在一个单引号定界的字符串内使用撇号的时候,他就显得非常有用,一维反斜线可以是我们避免使用常规方式解释单引号,当单引号不是用是做标记字符串结尾时,他只是是一个撇号:
You\'re right,it can not be a quote'
JavaScript转义字符
\o // NULL字符(\u0000)
\b // 退格符(\u0008)
\t // 水平制表符(\u0009)
\n // 换行符(\u000A)
\v // 垂直制表符(\y000B)
\f // 换页符(\u000C)
\r // 回车符(\u000D)
\" // 双引号(\u0022)
\' // 撇号或单引号(\u0027)
\\ // 反斜线(\u005C)
\xXX // 由两位十六进制xx指定的Latin-1字符
\uXXX // 由思维十六进制数xxxx指定的Unicode字符
2.2.3 字符串的使用
JavaScript的内置功能之一就是字符串连接。如果将加号(+)运算符用于数字,表示两数相加。但将它作用于字符串,则表示字符串连接,将第二个字符串拼接在第一个之后,例如:
msg="hello",+"world"; // 生成字符串"hello,world"
要确定一个字符串的长度-其所包含的十六位值的格式——可以使用字符串的length属性。例如,向得到字符串s的长度:s.length 除了length属性,字符串还提供许多可以调用的方法:
var s="hello world" // 定义一个字符串
s.charAt(0) // =>'h':第一个字符
s.charAt(s.length-1) // =>'d':最后一个字符
s.substring(1,4) // =>'ell':第2-4个字符
s.slice(1,4) // =>'ell'同上
s.slice(-3) // =>'rld':最后三个字符
s.indexOf("l") // =>'2':字符l首次出现的位置
s.lastIndexOf('1') // =>10:字符串1最后一次出现的位置
s.indexOf("l",3) // =>3:在位置3及之后首次出现字符l的位置
s.split(",") // =>['hello',’world‘]分割成字串
s.replace('h','H') // =>"Hello,world"全文字符替换
s.toUpperCase() // =>"HELLO,WORLD"
2.2.4 模式匹配
JavaScript定义了RegExp()构造函数,用来创建文本匹配模式的对象。这些模式成为“正则表达式”,JavaScript采用Perl中的正则表达式语法。String和RegExp对象均定义了利用正则表达式进行模式匹配和查找与替换的函数。在两条斜线之间的文本构成了一个正则表达式直接量。第二条斜线之后也可以跟随一个或者多个字母,用来修饰匹配模式的含义。例如:
/^html/ // 匹配以HTML开始的字符串
/[1-9][0-9]*/ // 匹配一个非零数字,后面是任意个数字
/\bjavascript\b/i // 匹配单词"javascript",忽略大小写
RegExp对象定义了很多有用的方法,字符串同时具有可以接收RegExp参数的方法,例如:
var text="testing:1,2,3"; // 文本示例
var pattern=/d+/g // 匹配所有包含一个或者多个数字的实例
pattern.test(test) // =>true:匹配成功
text.search(pattern) // =>9:首次匹配成功的位置
text.match(pattern) // =>["1","2","3"]:所有匹配组成的数组
2.3布尔值
布尔值代真或假、开或关、是或否。这个类型只有两个值,保留字true和false。JavaScript程序中的比较难语句的结果通常都是布尔值,例如:
a==4 //这段代码用来检测变量a的值是否等于4.如果等于,比较结果的布尔值是true,如果不等,比较结果则为false。
undefined、null、0、-0、NaN、” “这些值会被转换成false。其他值,包括对象(数组)都会转换成true。布尔值包含tostring()方法,因此可以使用这个方法将字符串转换为”true”或”false”,但它并不包含其他有用的方法。除了这个不重要的API,还是三个重要的布尔运算符。“&&”运算符执行了逻辑与(AND)操作,当且仅当两个操作数都是真值时它才返回true,否则返回false;“||”运算符是布尔或(OR)操作,如果两个操作数其中之一为真值他就返回true,如果两个操作数都是假值则返回false。最后,一元操作符“!”执行了布尔非(NOT)操作;如果操作数是真值则返回false;如果是假值,则返回true。
if ((x==0 &&y==0)//!(z==0)){
//x和y都是零或者z是非零
}
2.4 null和undefined
null是JavaScript语言的关键字,它表示一个特殊值,常用来描述”空值”。对null执行typeof预算,结果返回字符串”object“,也就是说,可以将null认为是一个特殊的对象值,含义是”非对象”。但实际上,通常认为null是它自由类型的唯一一个成员,它可以表示数字,字符串和对象是”无值”的。 undefined是JavaScript第二个表示值的空缺。用未定义的值表示更深陈词的”空值”。他是比那辆的一个取值,表明变量没有初始化,如果要查询对象属性或数组元素的值时返回undefined,则说明这属性或元素不存在。如果函数没有返回任何值,则返回undefined。 尽管null和undefined时不同的,但他们都表示”值的空缺”,两者往往可以呼唤。判断相等于运算符”==”认为两者时相等的。underfined是表示系统级的,出乎意料的或类似错误的值的空缺,而null是表示程序级的,正常的或在医疗之中的值的空缺。
2.5 全局对象
全局对象(global object)在JavaScript中有着重要的用途。当JavaScript解释器启动时(或者任何web浏览器加载新页面的时候),他将创建一个新的全局对象,并给它一组定义的初始属性。
-
全局属性:例如:undefined、Infinity和NaN
-
全局函数:比如IsNaN()、parseInt()和eval()
-
构造函数,比如Date()、string()、Array()、object()
-
全局对象:比如Math和Json 全局对象初始属性并不是保留字,但他们应该当做保留字来对待,或者通过别名”Global”来找到这些全局变量。在代码的最顶级——不在任何函数内的JavaScript代码——可以使用JavaScript关键字this来引用全局对象
var global=this; //定义一个引用全局对象的全局变量
在客户端JavaScript中,在其表示的浏览器窗口中的所有JavaScript代码中,Window对象充当了全局对象。这个全局Window对象有一个属性window引用其自身,它可以替代this来引用全局对象。Window对象定义了全局属性,但他也针对Web浏览器和客户端JavaScript定义了一小部分其他全局属性。
2.6 包装对象
JavaScript对象时一种复合值,它是属性或已命名值的集合。通过“.”符号来引用属性值。当属性值是一个函数的时候,称其为方法。通过o.m()来调用对象o中的方法。例如:
var s="hello,world" //一个字符串
var word=s.substring(s.indexOf("")+1,s.length) // 使用字符串的属性
只要引用了字符串s的属性,JavaScript就会将字符串通过调用new Sting(s)的方式转换成对象,这个对象继承了字符串的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会销毁。 同字符串一样,数字和布尔值也具有各自的方法,通过Number()和Boolean()构造函数创建一个临时对象,这些方法的调用均来自于这个临时对象。null和undefined没有包装对象,访问他们的属性会造成一个类型错误。注意的是String()、Number()或Boolean()构造函数来创建包装对象。JavaScript会在必要时将包装对象转换成原始值。“==”等于运算符将原始值和其包装对象视为相同,但是“===”全等运算符将他们视为不等,通过typeof于是暖夫可以看到原始值和其包装对象的不同。
var s="test",n=1,b=true; // 一个字符串、数字和布尔值
var S=new String(s); // 一个字符串对象
var N=new Number(n); // 一个数值对象
var B=new Boolean(b); // 一个布尔对象
2.7 不可变的原始值和可变的对象引用
JavaScript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本的区别。原始值时不可更改的;任何方法都无法更改一个原始值。对数字和布尔值来说显然如此——改变数字的值本身就说不通,但是对字符串来说并不明显,因为字符串可以看作由字符组成的数组。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的时一个欣的字符串值。
var s="hello"; // 定义一个由小写字母组成的文本
s.toUpperCase(); // 返回"HELLO",但并没有改变s的值
s // =>"hello":原始字符串的值并没改变
原始值的比较是值的比较:只有在他们的值相同时他们在相同。对于字符串来说则并不明显,如果比较两个单独的字符串,当且仅当他们的长度相同且每个索引的字符都想相同,JavaScript才认为他们相同。 对象和初始值不同,首先,对象是可变的——他们的值是可以修改的。对象的比较并非值的比较,即使两个对象包含相同的属性以相同的值,他们也是不相等的。各个索引元素完全相同的数组也不相同。
var s={x:1} // 定义一个对象
s.x=2; // 通过修改对象属性值来更改对象
s.y=3; // 再次更改这个对象,给他增加一个新的属性
如果想要比较两个单独的对象或者数组,则必须比较他们的属性或者元素。例如:
function equalArray(a,b){
if (a.length==b.length) return false;
for (var i=0;i<a.length;i++)
if(a[i]!==b[i]) return false;
return true;
}
2.8 类型转换
JavaScript中的取值类型非常灵活,我饿们已经从布尔值看到了这一点:当JavaScript期望使用一个布尔值的时候,你可以提供任意类型值,JavaSrcipt将根据需要自行转换类型。一些真值转换为true,其他值转换为false。同时在其他类型中同样使用。例如:
10+"object"; // =>"10 object",将数字10 转换为字符串
"7"*4; // =>28:两个字符串均转换为数字
var n=1-"x"; // =>NaN:字符串“x”无法转换为数字
2.8.1 转换和相等性
由于JavaScript可以做灵活的类型转换,因此“==”等于运算符也随相等的含义灵活多变。例如:
null == undefined //这两值被认为相等
"0" == 0 //在比较之前字符串转换成数字
注意的是,一个值转换为另外一个值并不意味着两个值相等。比如,如果在期望使用布尔值的地方使用了undefined,它将转换为false,但是并不表明undefined==false。 2.8.2 显式类型转换 为了使得代码更加清晰易读而做的显式转换。做显式类型转最简单的方法是使用Boolean()、Number()、String()和object()。当不通过new运算符调用这些类型的时候,他们将作为类型转换函数进行类型转换。
Number("3") // =>3
String(false) // =>"false"或者false.toString()
需要注意的是,除了null或underfined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法返回的结果是一致的。 parseInt()函数和parseFloat()函数,他们是全局函数,不从属于任何一个一个的方法。parseInt()直接解析整数,而parseFloat()则可以解析整数和浮点数。如果字符串前缀是“0X”或者是”0x“,parseInt()将其解释为十六进制数,parseInt()和parseFloat()都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接两,将最终返回NaN 。
parseInt(" 3 blind mice") // => 3
parseFloat("3.14 meters") // =>3.14
parseInt("0.1") // =>0
parseInt()可以接收第二个可选参数,这个参数指定数字转换的基数,合理的取值范围是2-36。例如:
parseInt("11",2) // =>3 (1*2+1)
parseInt("ff",16) // =>255:(15*16+15)
parseInt("077",8) // =>63:(7*8+7)
2.9 变量声明
在JavaScript程序中,使用一个变量之前应当先声明。变量是使用关键字var来声明的。
var i; // 声明一个变量
var i,sum; // 声明多个变量
var i=0,j=0,k=0; // 可以将变量的初始赋值和变量声明何在一起
for (var i=0;i<10;i++) consloe.log(i); //在for和in循环中同样也可以使用var语句
2.10 变量作用域
一个变量的作用域是程序源代码中定义这个变量的区域。全局变量拥有全局作用域。在JavaScript代码中的任何地方都是有定义的。然而在函数内声明的比那辆只能在函数体内有定义。他么是局部变量,作用域是局部性的。函数参数也是局部变量,他们只能在函数体内有定义。 在函数体内,局部变量优先级高于同名的全局变量,如果在函数内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就会被局部变量所覆盖。
var s='global'; //声明一个全局变量
function checkscope(){
var s='local'; // 声明一个同名的局部变量
return s; // 返回局部变量的值,而不是全局变量的值
}
checkscope() // =>'local'
2.10.1 函数作用域和声明提前
在一些类似c语言的编程语言中,花括号内的每一段代码都具有给咱的作用域,而且变量在声明他们的代码段之外是不可见的,我们称为块级作用域。而在JavaScript中没有块级作用域。JavaScript取而代之的是使用函数作用域。变量在声明他们的函数体以及这个函数体嵌套的任意函数体内都有定义的。
function test(w){
var i=0; // i在整个函数体内均是有定义的
if (typeof w=='object'){
var j=0; // j在函数体内是有定义的 ,不仅仅实在这个代码段内
for (var k=0;k<10;k++){ // k在函数体内是有定义的,不仅仅在循环内
console.log(k); // 输出数字0-9
}
console.log(k); // k已经定义完了,输出k=10
}
console.log(j); //j定义完了,但可能没有初始化
}
JavaSript的函数作用域是指在函数内声明的所有变量在函数题内始终是可见的,这意味着变量在声明之前甚至已经可用。JavaSript的这个特性被非正式的称为声明提前
var s='global';
function f(){
console,log(s); // 输出“undefined” ,而不是"global"
var s='local'; // 变量在这里赋初始值,但变量本身在函数体内任何地方的都是有定义的
console.log(s); //输出‘local'
}
在具有块级作用域的编程语言中,在狭小的作用域里让变量声明和使用变量的代码尽可能靠近彼此,通常来讲,这是一个非常不错的习惯。由于JavaScript没有块级作用域,因此一些程序员特意将变量声明放在函数体顶部,而不是将声明靠近在使用变量指出。这种做法使得他们的源代码费差个青协的反映了真实的变量作用域。
2.10.2 作为属性的变量
当申明一个JavaScript全局变量的时候,实际上定义了全局对象的一个属性。当使用var声明一个变量的时候,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。如果你没有严格模式并给一个没有声明的变量复制的话,JavaScript会自动创建一个全局变量,以这方式创建的变量是全局对象的正常的可配置属性,并可以删除他们。
var s=1; // 声明一个不可删除的全局变量
sd=2; // 创建全局对象的一个可删除的属性
this.qw=3; // 同上
delete s // => false:变量并没有被删除
delete sd // => true :变量被删除
delete this.qw // => true:变量被删除