计算机程序的运行需要对值(value)比如数字3.14或者文本"hello world"进行操作,在编程语言中,能够表示并操作的值的类型叫做数据类型(type),编程语言最基本的特性就是主持多种数据类型。当程序需要将值保持起来以备将来使用时,便将其赋值给(将值“保存”到)一个变量(variable)。变量是一个值的符号名称,可以通过名称获得对值的引用。变量的工作机制是编程语言的令一个基本特性。本章将参照上节帮助理解本章内容,后续将更深入的讲解。
javascript的数据分为两类:原始类(primitive type)和对象类型(object type)
javascript中的原始类包括数字,字符串,布尔值,本章会有单独的章节专门讲述javascript的数字、字符串、布尔值。javascript还有两个特殊的原始值,null(空)和Undefined(未定义),他们不是数字、字符串、布尔值。它们分别代表了各自特殊类型的唯一成员。
javascript除了数字、字符串、布尔值、null、undefined之外就是对象了。对象 (object)是属性(property)的集合。每个属性都由"名/值对"(值可以是原始值,比如数字,字符串,也可以是对象)构成。其中一个比较特殊的对象(全局对象(global object)会在第五小姐介绍,第六小节将更详细的描述)
普通的javascript对象是“命名值”的无需集合。javascript同样定义了一种特殊对象--数组(array),表示带编号的值的有序集合。javascript为数组定义了专用的语法。使数组拥有一些和普通对象不同的特有的行为属性。
javascript还定义了一种特殊的对象--函数。函数是具有与它想关联的可执行代码的对象,通过调用函数来运行科执行代码,并返还运算结果。和数组一样,函数行为特征和其它对象都不一样。javascript为使用函数定义了专用语法。对javascript函数来讲。最重要的是,他们都是真值,并且javascript可以讲他们当做普通对象来对待。
如果函数初始化(使用new运算符)一个新建对象,我们称之为构造函数(constructor)。每个构造函数定义了一类(class)对象--构造函数初始化对象组成的集合。类可以看做对象类型的子类型。除了数组(array)类和函数(function)类之外,javascript还定义了其它三种由用的类。日期(date)定义了代表日期的对象。正则(regExp)定义了正则表达式的对象。错误(error)类定义了那行表示javascript程序中运行时错误和语法错误对象。可以通过定义自己的构造函数来定义需要的类。
javascript解释器有自己的内存管理机制,可以自动对内存进行垃圾回收(garbagecollection)。这意味着程序程序可以按需创建对象,程序员则不必担心这些对象的销毁了内存回收。当不再有任何一个引用指向一个对象,解释器就知道这个对象没有用了,然后会自动回收它所占用的内存资源。
javascript是一种面向对象的语言。不严格的讲,这意味着我们不用全局的定义函数去操作不同类型的值,数据类型本身可以定义方法(method)来使用值,例如要对数组a中的元素进行排序,不必要将a传入sort()函数,而是调运a的一个方法sort()
a.sort(); //sort(a)面向对象的版本
从技术上来将,只有javascript对象才能拥有方法。然而,数字,字符串,布尔值也拥有自己的方法。在javascript中,只有null和undefined是无法拥有方法的值。
javascript的类型可以分为原始类型和对象类型,可分为可以拥有方法的类型和不能拥有方法的类型。同样可分为可变(mutable)和不可变(immutable)类型。可变类型的值是可以修改的,对象和数组属于可变类型:javascript程序可以改变对象的属性值和数组元素的值。
数字、布尔值、null和undefined属于不可改变的类型。比如,修改一个数组的内容本身就说不通。字符串可以看做是字符组成的数组,你可以认为它是可以变的。然而在javascript中,字符串是不可变的。可以访问字符串任意位置的文本,但javascript并未提供修改一直字符串文本内容的方法。
javascript可以自由地进行数据类型转换。比如,如果在程序期望使用字符串的地方使用了数字,javascript会自动将数字转换为字符串。如果期望在使用布尔值的地方使用了非布尔值,javascript也会相应的转换。javascript中对灵活的类型抓换规则对“判断相等”(equality)
javascript的变量是无类型的(untyped),变量可以被赋予人和类型的值,使用var关键字来声明(declare)变量。javascript采用语法作用域,不在任何函数内声明的变量称为全局变量(global variable),它在javascript的程序 中任何地方都是可见的。
1.数字
和其它编程语言不同,javascript不区分整数数值和浮点数数值。javascript中的数值均用浮点数数值来表示。当一个数字直接出现在javascript程序中,我们陈之为数字直接量(numeric literal),javascript支持多种格式的数字直接量。(注意:在任何数字前直接添加负号(-)可以得到它们的负值)但负号是一元求反运算符。,并不是数字直接量语法的组成部分。)
i整数型直接量
javascript中用一个数组序列表示一个十进制的整数
除了十进制的整数直接量,javascript同样识别十六机制(16)为基数的的值。所谓十六进制是以“0X”或者"0x"为前缀,其后紧跟十六进制数串的直接量。十六进制数值是0-9的数字和a(A)-f(F)之间的字母构成。a-f的字母对于的表述数字10-15下面是十六进制整型直接量的例子
代码如下:
0xff //15*16+15=255
0xCAFE911
尽管ECMAScript不支持八进制直接量,但javascript的某些实现可以允许采用八进制(基数为8)形式表示整数。八进制直接量以数字0开始,其后跟随着一个0-7之间数字组成的序列。
代码如下:
0377 // 3*64 +7*8 +7 =255(十进制)
由于某些javascript的实现支持八进制的之间量,而有些不支持,因此最好不要使用以0为前缀的整数之间量,毕竟我们也无法得知当前javascript的实现是否支持八进制的解析。在ECMAScript6的严格模式下,八进制的直接量是明令禁止的。
ii.浮点型直接量
浮点型直接量可以含有小数点,它们采用的是传统的实数写法。一个实数由整数部分,小数点和小数部分组成。
此外,还可以使用指数计数法表示浮点型直接量。即在实数后跟字母E或e,后面再跟正负号,其后再加一个整型的指数。这种计数方法表示的数值,是有前面的实数乘以10的指数幂。
可以使用更简洁的语法来表示
代码如下:
[digits][.digits][(E|e)[(+|-)]digits]
3.14
2345.455
.33333333333333333
6.02e23 //6.02*10的23次方
1.255454E-23 //1.255454*10的负23次方
iii.javascript中的算术运算
javascript程序是使用语言本省提供的算术运算符来进行数字运算的的。这些运算符包含+ - * /和求余(整除后的余数)运算符%
除了基本的运算符之外,javascript还支持更加复杂的算术运算,这个线复杂的运算通过作为Math对象的属性定义的函数和常量实现。
代码如下:
Math.pow(2, 53) //=>9007199254740992 document.write(Math.pow(2,53) )
Math.round(.6) //=>1.0 四舍五入
Math.ceil(.6) //=>1.0向上求整
Math.floor(.6) //=>0.0向下求整
Math.abs(-5) //=>5 求绝对值
Math.max(x, y, z) //返回最大值
Math.min(x, y, z) //返回最小值
Math.random() //生成一个大于0小于1的伪随机数
Math.PI //圆周率π
Math.E //e:自然对数的底数
Math.sqrt(3) //3的平方根
Math.pow(3, 1 / 3) //3的立方根
Math.sin(0) //三角函数,还有Math.cos,Math.atan等
Math.log(10) //=>2.302585092994046 以10为底的自然对数
Math.log(512) / Math.LN2 //以2为底的512的对数
Math.log(100) / Math.LN10 //以10为底的100的对数
Math.exp(3) //e的三次幂
javascript中的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不会报错。但数字运算结果超过了javascript中所能表示的数字上线(溢出),结果为一个特殊的无穷大的值(infinty)值,在javascript中以infinty表示。同样地,当负数的值超过了javascript所能表达的负数范围,结果为负无穷大,在javascript中以-Infinty表示。无穷大值的行为特性和我们所期望的是一致的:基于它们的加减乘除运算结果是无穷大(保留正负号)
下溢(underflow)是当运算结果无线接近于零并比 javascript能表示的最小值还小的时候发生的一种情形。当一个负数发生下溢时,javascript返回一个特殊的值,“负零”,这个(负零)几乎和正常的零完全一样。javascript程序员很少用到负零。
javascript预定义了全局变量Infinaty和NaN,用来表达正无穷大河非数字值,在ECMAScipt3中,这两个值是可以读写的。ECMAScript5修正了这个问题,将他们定义为只读的。ECMAScipt3中的Number对象定义的属性值也是只读的,这里有一些例子:
代码如下:
Infinity //将一个可读/写的变量初始化为infinty
Number.POSITIVE_INFINITY //同样的值,只读
1 / 0 //这也是同样的值
Number.MAX_VALUE + 1 //计算结果还是Infinity
Number.NEGATIVE_INFINITY //表示了负无穷大
-Infinity
-1/0
-Number.MAX_VALUE -1
NaN //将一个可读/写的变量初始化为NaN
Number.NaN //同样的值,但是只读
0/0 //计算结果还是NaN
Number.MIN_VALUE/2 //发生下溢。计算结果为0
-Number.MIN_VALUE/2 //负零
-1/Infinity //负零
-0 //负零
javascript中的非数字值有一点特殊,它和人和值都不相等,包括自身。也就是说没法通过x==NaN来判断x是否为NaN。相反,应当使用x!=x来判断,当且仅当x为NaN的时候,表达式的结果为true.函数isNaN()作用与此相似,如果参数是NaN或者是一个非数字值(比如字符串和对象),则返回true。javascript中有一个类似的函数isFinite(),在参数不是NaN、Infinty或-Infinity的时候返回true.
负零值同样有些特殊,它和正负零是相等的(甚至使用javascript的严格相等测试来判断)、这意味这两个值几乎是一模一样的,除了作为除数之外:
代码如下:
var zero = 0;
var negz = -0;
zero === negz //=>true 正负零值相等
1/zero === 1/negz //false 正无穷大和负无穷大不等
iiii.二进制浮点数和四舍五入错误
实数有无数个,但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;
var y = .2 - .1;
alert(x == y) //=>false 两值不相等
x == .1 //=>false .3-.2 不等于.1
y == .1 //=>true .2-.1等于1
由于舍入误差,0.3和0.2之间的近似差值实际上并不等于0.2和0.1之间的近似差值(在真实模拟环境中,0.3-0.2=0.099 999 999 999 999 98).这个问题不只在javascript中存在,理解这一点十分重要:在任何使用二进制浮点数的编程语言中都会有这个问题。同样需要注意的是,上述代码中x和y的值非常接近彼此和最终的正确值。这种计算结果可以胜任大多数的计算任务。这个问题也是只有比较两个值是否相等的时候才才会出现。
javascript的未来版或许支持十进制数字类型以避免这个问题,在这之前你可能更愿意使用大整数进行重要的金融计算。例如,要使用整数“分”,而不要使用小数“元”进行基于货币单位的运算。
iiiii.日期和时间
javascript语言核心包含Date()构造函数,原来创建日期和时间的对象,这些日期对象的方法为日期计算提供了简单的API,日期对象不能像数字那样是基本数据类型。
代码如下:
var zhen = new Date(2011, 0, 1); //2011年1月1日
var later = new Date(2011, 0, 1, 17, 10, 30); //同一天
var now = new Date(); //当前的日期和时间
var elapsed = now - zhen; //日期减法。计算时间间隔的毫秒数
later.getFullYear(); //=>2011
later.getMonth(); //=>0 从0开始计数的月份
later.getDate(); //=>1 从1开始计数的天数
later.getDay(); //=>5 得到星期几。 0代表星期日,5代表星期1
later.getHours() //=>当地时间
later.getUTCHours() //使用UTC表示小时的时间,基于时区。
2.文本
字符串(string)是一组16位值组成的不可变的有序序列,每个字符通常来自于Unicode字符集。javascript通过字符串类型来表示文本。字符串的长度(length)是其所含的16位值的个数。javascript字符串(和其数组)的索引从0开始。空字符串的(empty string)长度为0,javascript中并没有表示单个字符的“字符型”。要表示一个16位值,只需要将其赋值给字符串变量即可。这个字符串的长度为1。
字符集,内码和javascript字符串
javascript采用UTF-16编码的Unicode字符集,javascript字符串是由一组无序号的16位值组成的序列。最常用的Unicode字符都是通过16位的内码表示,并代表字符串中的单个字符,那行不能表示为16位的Unicode字符则遵循UTF-16编码规则----用两个16位值组成一个序列(亦称为“代理项对”)表示。这意味着一个长度为2的javascript字符串(两个16位值)可能表示一个Unicode字符。
代码如下:
var p ="π" ; //π由16位内码表示0x03c0
var e = "e"; //e由17位内码表示0x1d452
p.length // =>1 p包含一个16位的值
e.length // =>2 e通过UTF-16编码后包含两个值:"\ud835\udc52"
javascript定义的各式字符串操作方法均作用于16位值,而非字符,且不会对代理项对做单独处理。同样javascript不会对字符串做标准化的加工。甚至不能保证字符串是合法的UTF-16格式
i字符串直接量
在javascript程序中的字符串直接量,是由单引号或双引号括起来的字符序列,由单引号定界的字符串中可以包含双引号,由双引号定界的字符串中也可以包含单引号。这里有几个字符串直接量的例子。
代码如下:
"" //空字符串,0个字符
'testing'
"3.14"
'name="myform"'
"wouldn't you prefer O'Reily's book?"
ECMAScript3中,字符串直接量必须写在一行中,而在ECMAScript5中,字符串的直接量可以拆分为数行,每行必须以反斜线(\)结束,反斜线和行结束符都不是字符串直接量的内容。如果希望在一起,则可以使用\n转义字符。
需要注意的是,当使用单引号定界字符串时,需要格外小心英文中的缩写和所有格式写法,英文撇号和单引号是同一个字符,所以必须使用反斜线(\)来转义。
ii转义字符
在javascript字符串中,反斜线(\)有着特殊的用途,反斜线后加一个字符,就不再表示他们的字面含义了,比如\n 就是一个转义字符,它表示一个换行符。
代码如下:
\o //NUL字符
\b //退格符
\t //水平制表符
\n //换行符
\v //垂直制表符
\f //换页符
\r //回车符
\" //双引号
\\ 反斜线
\xXX 由两位十六进制指定的Latin-1字符
\xXXXX 由四位十六进制XXXX指定的Unicode字符
iii字符串的使用
javascript的内置功能之一就是字符串连接。将运算符+用于字符串,表示字符串连接。例如
代码如下:
var msg = "hello" + "world"; //生成字符串hello world
要确定一个字符串的长度——其所包含的16位值的个数,可以使用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) // 最后出现的3个字符
s.indexOf(l ")//2字符l 第一次出现的位置
s.lastIndexOf("l") //10 字符l最后一次出现的位置
s.indexOf("l",3)//在位置3之后,l字符首次出现的位置
s.split(",") //=> ["hello","world"]分隔成子串
s.replace("h","H")// =>"Hllo,world"全文字符替换
s.toUpperCase() //=>"HELLO,WORLD"
在javascript中,字符串是固定不变的,类似replace()和toUpperCase()方法都返回了新的字符串,原来的字符本身没有发生变化。
在ECMAScript中,字符可以当做只读数组,除了使用charAt()方法,也可以使用方括弧来访问字符串中的单个字符。(16位值)
代码如下:
s = "hello,world"
s[0] //=>"h"
s[s.length-1] //=>"d"
Foxfire很久之前就支持这样方法的字符串索引,多数现代浏览器(IE除外)也紧跟Mozailla的脚步,在ECMAScript成型之前就完成了这一特性
iiii模式匹配
javascript定义了RegExp()构造函数,用来创建表示文本模式匹配的对象,这些模式被称为“正则表达式”(regular expression),javascript彩阳Perl中的正则表达语法。String和RegExp对象均定义了利用正则表达式进行模式匹配和查找与替换的函数。
RegExp对象并不是语言中的基本数据类型,和Date一样,它只是一种具有实用API的特殊对象。正则表达式的语法很复杂,API也很丰富。在第10章节会详细介绍。RegExp是一种强大和常用的文本处理工具,此处只是一个概述。
尽管RegExp并不是语言中的基本数据类型,但是他们依然具有直接量的写法,可以直接在javascript中使用。在两条斜线之间的文本构成了一个正则表达式的直接量。第二条斜线之后也可以跟随一个或多个字母。用来修饰匹配模式的含义。例如:
代码如下:
/^HTML/ //匹配以HTML开始的字符串
/[1-9][0-9]*/ //匹配一个非零数字,后面是任意个数字
/\bjavascript\b/i/ //匹配单词javascript,并忽略大小写
RegExp对象定义了很多有用的方法,字符串同样具有可以接受RegExp参数的方法。例如:
代码如下:
var text = "testing:1,2,3"; //文本示例
var pattern = /\d+/g / //匹配所有包含一个或多个数字的实例
pattern.test(text) // =>true:匹配成功
text.search(pattern) //=>9 :首次匹配成功的位置
text.match(pattern) //=> ["1","2","3"]所有匹配组成数组
text.repeat(pattern,"#"); //=>"testing:#,#,#"
text.split(/\D+/); //=>["","1","2","3"]:用非数字字符截取字符串
3.布尔值
布尔值指代真或假,开或关,这个类型只有两个值,保留字true或false
javascript中的比较语句的结果通常都是布尔值。例如
a==4
这段代码用来检测变量的a的值是否等于4.如果等于,则值为true,如果不等值为false
布尔值通常用于javascript的控制语句中,例如javascript中的if/else语句,如果布尔值为true执行第一段逻辑,如果为false执行另一段代码,例如
代码如下:
if (a == 4)
b = b + 1;
else
a = a + 1;
任意javascript的值都可以转化为布尔值,下面这些值都被转化为false
代码如下:
undefined
null
0
-0
NaN
""//空字符串
所有其它值,包括所有对象(数组)都会被转换为true,false和上面6个可以转化为false的值有时候称为“假值”,javascript期望使用一个布尔值时,假值会被当做false,真值会被当做true
来看一个例子,加上变量o是一个对象或是null,可以通过一条if语句来检测o是否是非null值。
if(o!==null)...
不等操作符“!==”将o和null比较,并得出结果为 true或false。可以先忽略这里的比较语句,null是一个假值,对象是一个真值。
if(o)...
对于第一种情况,只要当o不是null时才会执行if后的代码,第二种情况的限制没有那么严格。只有o不是false或任何假值(比如null或unfined)时才执行这个if。
布尔值包含toString()方法,因此可以使用这个方法将字符串转换为 “true”或"false",但它不包含其他有用的方法,除了这个不重要的API,还有三个重要的布尔值运算符。
&&运算符,||运算符和一元操作符“!”执行了布尔非(NOT)操作,如果真值返回false,假值返回true,比如
代码如下:
if ((x == 0 && y == 0) || !(z == 0)) {
//x和y都是零或z是非零
}
4.null和undefined
null是javascript语言的关键字,它表示一个特殊值“空值”,对于null执行typeof()运算,返回object.也就是说,可以将null认为是一个特殊的对象值,含义是"非对象"。但实际上,通常认为null是它自由类型的唯一一个成员。它可以表示数字,字符串,和对象是“无值”的。大多数编程语言和javascript一样含有null,你可以对null或者nil很熟。
javascript还有第二个值表示值的空缺。用来表示更深层次的“空值”。它是一种变量的一种取值。表示变量的没有初始化。如果要查询对象属性或数组元素的值是返回undefined则表明这个属性或者元素不存在。undefined是预定义的全局变量(它和null不一样,它不是关键字),它的值就是未定义。如果使用typeof来测试undefined类型,则返回“undefined”,表明这个值是这个类型的唯一成员。
尽管null和undefined是不同的,但它们都表示“值的空缺”,两者往往可以互换。判断相等的运算符“==”认为两者是相等的(要使用严格相等运算符"==="来区分它们)。在希望值是布尔类型的地方它们的值都是假值。和false类似。null和undefined都是不包含任何属性和方法。实际上,使用"."和"[]"来存取这两个值的成员或方法,都会产生一个类型错误。
你或许认为undefined是表示系统级的,出乎意料的活类似错误的值的空缺,而null是表示程序级的,正常或在意料之中的值的空缺,如果你想将它们复制变量或者属性,或将它们作为参数传入函数,null是最佳的选择。
5.全局对象
前几节讨论了javascript的元素类型和原始值。对象类型——对象、数组和函数/但有一类非常重要的对象,不得现在就必须将清楚:全局对象
全局对象(global object)在javascript中有着重要的用途。全局对象的属性是全局定义的符号。javascript程序可以直接使用。当javascript解释器启动时,它将新建一个新的全局对象,并给它一组定义的初始属性。
全局属性 比如undefined Infinty和NaN
全局函数 比如isNaN()、parseInt()和eval()
构造函数,比如Date()、RegExp()、String()、Object()和Array()
全局对象,比如Math 和JSON
全局对象的初始属性并不是保留字,但他们应当当做保留字来对待。
在代码的最顶级——不在任何函数内的javascript代码,可以通过javascript关键字来引用全局对象。
var global = this; //定义一个引用全局对象的全局变量。
在客户端javascript中,window对象充当了全局对象,这个全局window对象有一个熟悉window引用其本身,它可以代替this来引用全局对象,window定义了全局核心属性。但也征对web浏览器和和互动javascript定义了一部分其他全局属性。
当初次创建时,全局对象定义了javascript中所有的预定义全局值,这个特殊对象同样包含了为程序定义的全局值。如果代码声明了一个全局变量。这个全局变量就是全局对象的一个属性。
6.包装对象
javascript对象是一种复合值:它是属性或已命名值的集合。通过"."来引用属性值,当属性值是一个函数的时候,陈其为方法,通过o.m()来调运对象o中的方法。
我们看到字符串也同样具有属性和方法。
代码如下:
var s ="hello world";
var word = s.substring(s.indexOf("")+1,s.length);//使用字符串的属性。
document.write(word) //"ello world"
字符串既然不是对象,为什么它有属性呢?只要引用了字符串s的属性,javascript就会将字符串的值通过调用new String(s)的方式转换成对象,这个对象继承了字符串的方法。并被用来处理属性引用。一旦新的属性引用出来。一但引用结束,这个新创建的对象就会被销毁。(实际上并不一定创建或销毁这个临时对象,然而这个过程看起来是这样的。)
如同字符串一样,数字和布尔值也具有各自的方法,通过Number()和Boolean()构造函数创建一个临时对象。这些方法的调用均是来自于这个临时对象。(null和undefined没有包装过对象,访问他们的属性会有一个类型错误)
看如下 代码,思考他们的执行过程
代码如下:
var s = "test";
s.len = 4; //给它设置一个属性
var t = s.len //查找这个属性
当运行这段代码时,t的值是undefined,第二行代码创建一个临时字符串对象,并给len的值为4,随即销毁这个对象,第三行用过原始(没有被修改的)的字符串创建一个新的字符串对象,并尝试读取len 的属性。
这个属性自然不存在,表示结果undefined,这段代码说明了读取字符串、数组和布尔值的属性值(或方法)时,表现的像对象一样,但如果你试图给其属性赋值。则会忽略这个操作;修改只是发生在临时对象身上。而这个临时对象并未保留下来。
需要注意的是,可以通过String(),Number(),Boolean()构造函数来显示创造包装对象:
代码如下:
var s = "test",
n = 1,
b = true;
var S = new String(s);
var N = new Number(n);
var B = new Boolean(b);
javascript会在必要的时候将包装转换为原始值,因此上段代码中的对象S N B常常——但不总是——表现的值和s n b一样,"=="等于运算符将原始值和其包装对象视为相等。
但"==="全筹运算符将它们视为不等,通过typeof运算符可以看到原始值和其包装的对象的不同。
7.不可变的原始值和可变的对象引用。
javascript的原始值(undefined null 布尔值 数字和字符串)与对象(包括数组和函数)有着根本的区别,原始值是不可更改的;任何方法都无法(或突变)一个原始值。对数字和布尔值来说显然如此———改变数字的值本身就说不通,而对字符串来说就不那么明显,因为字符串看起来由字符组成的数组。我们期望可以通过指定的索引来修改字符串中的字符。实际上javascript是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上是返回一个新的字符串。
代码如下:
var s = "hello world";
s.toUpperCase(); //返回"HELLO WORLD"并没更改s的值
s //=> "hello world" 原始的字符串并未改变
原始值的比较是值的比较,只有在他们的值相当时它们在才相等。这对数字、布尔值、null和undefined来说听起来有点难,并没有其他办法来比较他们。同样,对于字符串来说则不那么明显;如果比较两个单独的字符串,当且仅当他们的长度相等且每个索引的字符都相等时,javascript的才认为相等。
代码如下:
var o = {x:1} //定义一个对象
o.x = 2 //通过修改对象的属性来改变对象
o.y = 3 //再次更改这个对象,给它增加一个新属性
var a =[1,2,3] //数组也是可以修改的
a[0]=0; //更改数组中的一个元素
a[3]=4; 给数组增加一个新元素
对象的比较并非值的比较:即使两个对象包含同样的属性及相同的值,他们也是不相等的,各个索引元素完全相等的两个数组也不相等
代码如下:
var o ={x:1}, p={x:1}//两个具有相同属性的两个对象
o === p ;//=>false 两个单独的对象永不相等( o == p ; =>false)
var a =[],b=[]; //两个单独的空数组
a === b ; //=>false两个单独的数组永不相等
我们通常将对象称为引用类型(reference type),以此来和javascript的基本类型区分开来。依照术语的叫法,对象都是引用(reference),对象的比较均是引用的比较;当且当它们应用同一个基对象时,它们才相等。
代码如下:
var a = []; //定义一个引用空数组的变量a
var b = a; //变量b引用同一个数组
b[0] = 1;
a[0] //=>1 变量a也会修改
a === b //=>true a和b引用同一个数组,因此他们相等。
就像你刚才看到的如上代码,将对象(或数组)赋值给一个变量,仅仅是赋值的引用值:对象本身并没有复制一次。
如果你想得到一个对象或数组的副本,则必须显式复制对象的每个属性或数组的每个元素。下面的这个例子则是通过循环来完成对数组的复制。
代码如下:
var a = ['a', 'b', 'c']; //待复制的数组
var b = []; //复制到目标的空数组
for (var i = 0; i < a.length; i++) { //遍历a[]中的每个元素
b[i] = a[i]; //将元素复制到b中。
}
同样的,如果我们想比较两个单独或者数组,则必须比较他们的属性或元素。下面这段代码定义了一个比较练个数组的函数。
代码如下:
function equalArrays(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; // 否则他们相等
}
8.类型转化
javascript中的取值型非常灵活,我们已经从布尔值看到了这一点:当javascript期望使用一个布尔值时候,你可以提供任意类型值。javascript将根据需要自行转换类型。一些值(真值)为true,其它值(假值)转化为false.这在其它类型中同样适用。如果javascript期望使用一个字符串,它把给定的值转换为字符串。如果javascript期望使用一个数组,它把给定的值转换为数字(如果转化结果无意义的话将返回NaN),一些例子如下:
代码如下:
10 + "object" //=> "10object";
"7" * "4" // =>28 两个字符串均转化为数字
var n = 1 - "x" // =>NaN字符串x无法转换为数字
n + " objects" // =>"NaN objects":NaN转换为字符串"NaN"
下表说明了在javascript中如何进行类型转化。粗体突出了那些让你倍感意外的类型转化。空单元格表示不必要也没有执行的转换。
值转换为字符串数字布尔值对象
undefined
null"undefined"
"null"NaN
0false
falsethrows TypeError
throws TypeError
true
false"ture"
"false"1
0 new Boolean(true)
new Boolean(false)
""(空字符串)
"1.2"(非空,数字)
"one"(非空,非数字) 0
1.2
NaNfalse
true
truenew String("")
new String("1.2")
new String("one")
0
-0
NaN
Infinty
-Infinty
1(无穷大,非零)"0"
"0"
"NaN"
"Infinity"
"-Infinity" "1" false
false
false
true
true
truenew Number(0);
new Number(-0);
new Number(NaN)
new Number(Infinty)
new Number(-Infinty)
new Number(1)
{}(任意对象)
[](任意数组)
[9](1个数字元素)
['a'](其它数组)
function(){}(任意函数)参考本小节第三节内容
""
"9"
使用join()方法
参考本小节第三节内容参考本小节第三节内容
0
9
NaN
NaNtrue
true
true
true
true
上表提到的原始值到原始值的转换行对简单,我们已经在第本文第三小节讨论过转换为布尔值的情况了。所有原始值转换为字符串的情形也已经明确定义。转换为数字的情形比较微妙。那些以数字表示的字符串可以直接转化为数字,也允许在开始和结尾处带有空格。但在开始和结尾处的任意非空字符都不会被当成数字量的一部分,进而造成字符串为数字的结果为NaN。有一些数字转换看起来让人奇怪:true转换为1,false、空字符串""转换为0.
原始值到对象的转换也非常简单,原始值通过调用String(),Number()或Boolean()构造函数,转化为它们各自的包装对象。见本文第6节。
null和undefined属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误(TypeError)异常。而不会执行正常的转换。
对象到原始值的转换多少有些复杂,本小节第三小节有专门描述。
i.转换和相等性
由于javascript可以做灵活的类型转换,因此其“==”相等运算符也随相等的含义灵活多变。例如:如下这些比较结果均是true;
null == undefined //这两值被认为相等
"0" == 0 //在比较之前,字符串转换成数字。
0 = false //在这之前布尔值转换成数字。
"0" ==false //在比较之前字符串和布尔值都转换成数字
在第四章9节第一小节相信讲解了“==”等于运算符在判断两个值是否相等时做了那些类型转换,并同样介绍了“===”恒等运算符在判断相等时并未做任何的类型转换。
需要特别注意的是:一个值转换为另一个值并不意味着两个值相等。比如在期望使用布尔值的地方使用了undefined,将会转换为false,但这不表明undefined==false。javascript运算符和语句期望使用多样化的数据类型,并可以互相转换。if语句将undefined转化为false,但“==”运算符从不试图将其转化为布尔值。
ii.显式类型转化
尽管javascript可以做做很多类型转换,但有时仍需要做显式转换,或者为了使代码变得清晰易读而做显式转换。
做显式转换最重简单的方法就是使用Boolean()、Number()、String()或Object函数。我们在本文第6节已经介绍过了. 当不通过new运算符调运这些函数时,他们会作为类型转换函数并按照上边表格所描述的规则做类型转换。
代码如下:
Number("3") //=>3
String(false) //=>"false"或使用false.toString()
Boolean([]) //=>true
Object(3) // =>new Number(3)
需要注意的是,除了null或undefined之外的任何值都具有toString()方法,在这个方法的执行结果通常和String()方法返回的结果一致。同样需要注意的话,如果试图把null或undefined转化为对象。则会抛出一个类型错误typeerro。Object()函数在这种情况下不会抛出异常:它仅简单返回一个新创建的空对象。
javascript中的某些运算符会做隐式的类型转换,有时用于类型转换。如果“+”运算符的一个操作数是字符串,它将会把令一个操作数转换为字符串。一元“+”运算符将其操作数转换为数字。同样,一元“!”运算符将其操作数转换为布尔值取反,在代码中常会看到这种类型转换的惯用法。
代码如下:
x + "" // 等于字符串String(x)
+x //等价于Number(x),也可以写成x-0
!!x //等价于Boolean(x)
在计算机中数字的解析和格式化代码是非常普通的工作。javascript中提供了专门的函数和方法用来更加精确的数字到字符串(number-to-string)和字符串到数字(string-to-number)的抓换。
Nmuber类定义的toString()方法可以接收表示基数(二进制,八进制,十六进制等)的可选参数,如果不指定该参数,转化规则将是十进制。同样也可以将数字转换为其它进制数。(范围在2-36之间)
代码如下:
var n = 17;
b_string = n.toString(2); //转化为10001
o_string = "0" + n.toString(8); //转化为八进制 021
hex_string = "0x" + n.toString(16); //转化为16进制 0x11
javascript为控制输出中小数点位置和有效数字位数,或者决定是否需要指定指数计数法。Number类为这种数字到字符串定义了三个方法。
toFixed()根据小数点后指定位数,将数字转换为字符串,它从不使用指数计数法。toExponential()使用指数计数法,将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位置则由参数指定(也就是说有效数字位数要比指定的位数多一位)。toPrecision()根据指定的有效数字位数,将数字转换为字符串。如果有效数字的位数小于数字整数部分的位数,则转换成指数形式。我们注意到,三个方法都会适当的进行四舍五入或填充0,
代码如下:
var n = 123456.789;
n.toFixed(0); //"123457"
n.toFixed(2); //"123456.79"
n.toFixed(5); //"123456.78900"
n.toExponential(1); //"1.2e+5"
n.toExponential(3); //"1.235e+5"
n.toPrecision(4); // "1.235e+5"
n.toPrecision(7); //"123456.8"
n.toPrecision(10); //"123456.7890"
如果通过Number()转换函数传入一个字符串,它会试图将其转化为一个整数或浮点数直接量,这个方法只能基于十进制进行转换,并且不能出现非法的尾随字符。parseInt()和parseFloat()函数(它们是全局函数,不属于人和类的方法),更加灵活。parseInt()只解析整数。而parseFloat()则可以解析整数和浮点数。如果字符串前边是0x或0X,parseInt()将其解析为16进制数。两个方法都会跳过任意量的前导空格,尽可能解析更多数值字符。并忽略后边的内容。如果第一个是非法的数字直接量,则返回NaN
代码如下:
parseInt("3many nice") //=>3;
parseFloat("3.14meters") //=>3.14
parseInt("-12.34") //=>-12
parseInt("0xff") //=>255
parseInt("-0XFF") //=>-255
parseFloat(".1") // =>0.1
parseInt("0.1") //=> 0
parseInt(".1") //=>NaN 不能以.开始
parseInt("$112") //=>NaN 不能以$开头
parseInt()可以接收第二个可选参数。这个参数指定数字转换的基数。合法的取值范围是2-36
代码如下:
parseInt("11", 2) //=>3(1*2+1)
parseInt("ff", 16) //=> 255(15*16 +15)
parseInt("zz", 36) //=>1295(35*36+35)
parseInt("077", 8) // 63(7*8 +7)
parseInt("077", 10) //77(7*10+7)
iii.对象转化为原始值。
对象到布尔值的转换非常简单:所有的对象(包括数组和函数)都转换为true。对于包装对象亦是如此,new Boolean(false)是一个对象而不是原始值,它将转换为true。 对象到字符串(object-to-String)和对象到数字(object-to-number)的转换是通过调用带转换对象的一个方法来完成的。一个麻烦的事实是,javascript对象有两个不同的方法来执行转换,并且接下来要讨论并且接下来要讨论的场景更加复杂。值得注意的是,这里提到的字符串和数字的转换规则只适用于本地对象(native fangf object).宿主对象(例如:由web浏览器定义的对象),根据各自的算法可以转换成字符串和数字。
所有的对象继承了两个转换方法。第一个是toString(), 它的作用是返回一个反映这个对象的字符串。默认的toString()方法并不会返回一个有趣的值。
({x:1,y:2}).toString() //=>"[object object]"
很多类定义了更多特定版本的toString()方法.
例如:数组类(Array class)的toString()方法将每个数组元素转换为一个字符串,并在元素之间添加逗号后并合成结果字符串。
函数类(Function class)的toString()方法返回这个函数的实现定义的表示方式。实际上,这里的实现方式是通常是将用户定义函数转换为javascript源代码字符串。
日期类(Date class)定义toString()方法返回一个可读的(可被javascript-parsable解析的)日期和事件字符串
RegExp class定义的toString()方法将RegExp对象转换为正则表达式直接量字符串。
代码如下:
[1, 2, 3].toString(); //=> "1,2,3"
(function(x) {f(x);}).toString(); // =>"function(x){\n f(x); \n}"
/\d+/g.toString(); //=> /\\d+/g
new Date(2015, 0, 1).toString() //=>Thu Jan 01 2015 00:00:00 GMT+0800 (中国标准时间)
另外一个函数是valueOf(),这个方法的任务并未详细定义:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示一个原始值,数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的的valueOf()方法简单地返回对象本身。日期类定义的valueOf()方法返回它的一个内部表示:1970年1月1日以来的毫秒数。
代码如下:
var d = new Date(2015, 0, 1); //=>Thu Jan 01 2015 00:00:00 GMT+0800 (中国标准时间)
d.valueOf() //=>1420041600000
通过是用我们刚才讲解过的toString()和valueOf()方法,就可以做到对象到字符串和对象到数字的转换了。但在某些场景中,javascript执行了完全不同的对象到原始值的转换。这些特殊的场景在本节的最后会讲到。
javascript对象到字符串的转换经过了如下这些步奏
如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,javascript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
如果对象没toString()方法,或者这个方法并不返回一个原始值,那么javascript会调用valueOf()方法。如果存在这个方法,则javascript调用它。如果返回值是原始值,javascript将责怪值转换为字符串。
9.变量声明。
在javascript程序中,使用一个变量之前应该先声明,变量是通过var来声明的,如下所示:
var i;
var sum;
也可以通过一个var关键字声明多个变量
var i,sun;
而且还可以将变量的初始值和变量声明和写在一起;
var message = "hello";
var i=0 ,j=0,k=0;
如果在var声明语句中给变量指定初始值,那么虽然声明了这个变量,但在给它存入一个值前,它的初始值是undefined. 我们注意到,在for和fo/in循环中同样可以使用var语句,这样可以更加简洁地声明在循环体语法中内使用的循环变量。例如:
代码如下:
for (var i = 0; i < 10; i++) log(i);
for (var i = 0, j = 10; i < 10, j = 100; i++, j--) console.log(i * j)
for (var p in o) console.log(p);
如果在var声明语句中给变量指定初始值,那么虽然声明了这个变量,但在给它存入一个值前,它的初始值是undefined. 我们注意到,在for和fo/in循环中同样可以使用var语句,这样可以更加简洁地声明在循环体语法中内使用的循环变量。例如:
代码如下:
var i=10;
i="ten";
10.变量作用域
一个变量的左右域(scope)是程序源代码中定义这个变量的区域,全局变量拥有全局作用域,在javascript代码中的任何地方都是定义。然而在函数内部声明变量只在函数体内有定义。他们是局部变量,作用是局部性的。函数参数也是局部变量,它们只在函数体内有定义。
在函数体内,局部变量的优先级高于同名的全局变量。如果在函数内声明一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖。
代码如下:
var scope = "global"; //声明一个全局变量
function checkscope() {
var scope = "local"; //声明一个同名的局部变量
return scope;
}
checkscope(); //=>"local"
尽管在全局作用域编写代码时可以不写var语句,但声明局部变量时则必须使用var语句。
代码如下:
scope = "global"; //声明一个全局变量,甚至不使用var来声明
function checkscope2() {
scope = "local"; //修改了全局变量
myscope = "local"; //这里显示式得声明了一个新的全局变量
return [scope, myscope]; //
}
checkscope2(); //=> ["local","local"]:产生了副作用
scope // =>"local"全局变量修改了
myscope //=> "local"全局命名空间搞乱了。
函数定义是可以嵌套的。由于每个函数都有它直接的作用域,因此会出现几个局部作用域嵌套的情况。
代码如下:
var scope = "global scope"; //全局变量
function checkscope() {
var scope = "local scope"; //局部变量
function nested() {
var scope = "sested scope"; //嵌套作用域内的局部变量
return scope;
}
return nested();
}
checkscope() //=>"嵌套作用域" sested scope
i.函数作用域和声明提前
在一些类似c语言的编程语言中,花括号内的每一段代码都具有各自的左右域,而且变量在声明他们的代码之外是不可见的我们称之为块级作用域(block scope),而javascript中没有块级作用域,javascript取而代之的使用了函数作用域(function scope);变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有意义的。
如下代码,在不同的位置定义了i j k,他们都在同一个作用域内,这三个变量在函数体内均有定义的。
代码如下:
function test(o) {
var i = 0; //i在整个函数体内均是定义的
if (typeif o == "object") {
var j = 0; //j在函数体内是有定义的,不仅仅是在这个代码段内
for (var k = 0; k < 10; k++) { //k在函数体内是有定义的,不仅仅是在循环内
console.log(k); //输出数字0-9
}
console.log(k); //k已经定义,输出10
}
console.log(j); //j已经定义了,但可能没有初始化。
}
javascript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。有意思的是,这意味这变量在声明之前甚至已经可用。javascript的这个特性被非正式的称为声明提前(hoisting),即javascript函数里声明的所有变量(但不涉及赋值)都被提前至函数整体的顶部。如下代码:
代码如下:
var scope = "global";
function f() {
console.log(scope); //输出"undefined",而不是"global"
var scope = "local"; //变量在这里赋初始值,但变量本身在函数体内任何地方都是有定义的
console.log(scope); //输出"local"
你可能误以为函数的第一行会输出"global",因为代码还没有执行到var语句声明局部变量的地方。其实不然,由于函数作用域的特性模具部变量在整个函数体内始终有定义的,也就是说,在函数体内局部变量覆盖了同名全局变量。尽管如此,只有在程序执行到var语句的时候,局部变量才能正真的被赋值。
因此,上述的过程等价于:将函数内的变量声明"提前"至函数顶部,同时变量初始化留在原来的位置:
代码如下:
function f() {
var scope; //在函数的顶部声明了局部变量
console.log(scope); //变量存在,但其值是"undefined"
scope = "local"; //在这里将其初始化,并赋值
console.log(scope); //这里它具有了我们所期望的值
}
在具有块级作用域的编程语言中,在狭小的作用域里让变量声明和使用变量的代码尽可能靠近彼此,通常来说,这是一个非常不错的编程习惯。由于在javascript中没有块级作用域,因此一些程序员特意将变量声明放在函数体顶部,而不是将声明放在靠近使用变量之处。这种做法使得他们的源代码非常清晰地反映了真实的变量作用域。
ii作为属性的变量
当声明一个javascript全局变量时面试及上是定义了全局对象的一个属性。见本文第三节。
当使用var声明一个变量时,创建的这个属性是不可配置的。见第六章第7节。也就是说这个变量无法通过delete运算符删除。可能你已经注意到了,如果你没有使用严格模式并给一个未声明的变量赋值的话。javascript会自动创建一个全局变量。以这种方式创建变量是全局对象正常的可配置属性。可以删除它们。
代码如下:
var truevar = 1; //声明一耳光不可删除的全局变量
fakevar = 2; //创建全局对象的一个可删除的属性
this.fakevar2 = 3; //同上
delete truevar // =>false 变量并没有删除
delete fakevar //=>true 变量被删除
delete this.fakevar2 //=>true 变量被删除
javascript全局变量是全局对象的属性,这是在ECMAScript规范中强制规定的。对于局部变量则没有此规定,但我们可以想象得到,局部变量当做跟函数调用相关的某个对象的属性。ECMAScript3规范称对象为“调用对象”(call object),ECMAScript5规定范称为“声明上下文对象”(declarative environment record)。javascript可以允许使用this关键字引用全局对象,却没有方法可以引用局部变量中存放的对象。这种存放局部变量的对象的特有性质,是一种对我们不可见的内部实现。然而,这些局部变量对象存在的观念是非常重要的。
iii作用域链
javascript是基于词法作用域的语言:通过阅读包含变量定义在内的舒航源码就能知道变量的作用域。
全局变量在程序中始终是都是有定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。
更多信息请查看IT技术专栏