数据类型:计算机程序的运行需要对值(value)比如数字3.14或者文本"hello world"进行操作,在编程语言中,能够表示并操作的值的类型叫做数据类型(type)。
JavaScript的数据类型有两类:原始类型(数字、字符串、布尔值)和对象类型,还有null、undefined(两个特殊的原始值)。
javascript除了数字、字符串、布尔值、null、undefined之外就是对象了。对象:属性的集合。键值对的存在。
普通的javascript对象是“命名值”的无需集合。javascript同样定义了一种特殊对象--数组(array),表示带编号的值的有序集合。javascript为数组定义了专用的语法。使数组拥有一些和普通对象不同的特有的行为属性。
javascript还定义了一种特殊的对象--函数。函数是具有与它想关联的可执行代码的对象,通过调用函数来运行科执行代码,并返还运算结果。和数组一样,函数行为特征和其它对象都不一样。javascript为使用函数定义了专用语法。对javascript函数来讲。最重要的是,他们都是真值,并且javascript可以讲他们当做普通对象来对待。
如果函数初始化(使用new运算符)一个新建对象,我们称之为构造函数(constructor)。每个构造函数定义了一类(class)对象--构造函数初始化对象组成的集合。类可以看做对象类型的子类型。除了数组(array)类和函数(function)类之外,javascript还定义了其它三种由用的类。日期(date)定义了代表日期的对象。正则(regExp)定义了正则表达式的对象。错误(error)类定义了那行表示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不区分整数和浮点数,所有数字均用浮点数值表示。标准时64位(有最大值和最小值),实际操作中是32位。
javascript支持多种格式的数字直接量。注意:在任何数字前直接添加负号(-)可以得到它们的负值,但负号是一元求反运算符。,并不是数字直接量语法的组成部分。
1.1.整型直接量
十进制:
十六进制:0x或0X开头
ES标准不支持八进制直接量,但某些实现还是允许使用的。0开头。但是最好不要使用为好,ES6是明令禁止的。
1.2.浮点型直接量
可以含小数点。
还可以使用指数计数法表示,用e或E。
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: 向下求整 Math.abs(-5) // => 5: 求绝对值 Math.max(x,y,z) // 返回最大值 Math.min(x,y,z) // 返回最小值 Math.random() // 生成一个大于等于0小于1.0的伪随机数 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) // 10的自然对数 Math.log(100)/Math.LN10 // 以10为底100的对数 Math.log(512)/Math.LN2 // 以2为底512的对数 Math.exp(3) // e的三次幂
JavaScript中的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不会报错。
上溢:超出了JavaScript所能表示的上限,结果为一个特殊的无穷大(infinity)值或无穷小(-Infinity)表示。
var a = 10 / 0 //返回Infinity var b = -10 / 0 //返回-Infinity
无穷大值的行为特性和我们所期望的是一致的:基于它们的加、减、乘和除运算结果还是无穷大值(当然还保留它们的正负号)。
下溢:当运算结果无限接近于零并超出JavaScript所能表示的最小值时,被称为下溢,用零来表示。
var a = Number.MIN_VALUE / 2 //返回0
MIN_VALUE 属性是 JavaScript 中可表示的最小的数(接近 0 ,但不是负数)。它的近似值为 5 x 10-324。
MAX_VALUE 属性是 JavaScript 中可表示的最大的数。它的近似值为 1.7976931348623157 x 10308。
被零整除在JavaScript并不报错,但是零除以零是没有意义的,这种整除运算结果也是一个非数字(not-a-number)值,用NaN表示。
同时,无穷大除以无穷大、给任意负数作开方运算或者算术运算符与不是数字或无法转换为数字的操作数一起使用时都将返回NaN。
JavaScript预定义了全局变量Infinity和NaN。在ECMAScript 3中,这两个值是可读/写的,并可修改。ECMAScript 5修正了这个错误,将它们定义为只读的。在ECMAScript 3中Number对象定义的属性值也是只读的。这里有一些例子:
Infinity // 将一个可读/写的变量初始化为infinity Number.POSITIVE_INFINITY //最大值。同样的值,只读 1/0 // Infinity 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、Infinity或-Infinity的时候返回true。
负零值同样有些特殊,它和正零值是相等的:
var zero = 0; // 正常的零值 var negz = -0; // 负零值 zero === negz // => true: 正零值和负零值相等 1/zero === 1/negz // => false: 正无穷大和负无穷大不等
1.4.二进制浮点数和四舍五入错误
实数有无数个,但JavaScript通过浮点数的形式只能表示其中有限个数。也就是说,在JavaScript中使用实数的时候,常常只是一个真实值的一个近似表示。
JavaScript采用了IEEE-754浮点数表示法,这是一种二进制表示法,可以精确的表示如1/2、1/8和1/1024、这样的分数,但是我们常用的分数是十进制分数1/10、1/100等。二进制浮点数表示法并不能准确地表示类似0.1这样简单的数。
JavaScript中的数字具有足够的精度 ,并可以极其近似于0.1。这样不能精确确实带来问题了:
var x = 0.3 -0.2; //x=0.09999999999999998 var y = 0.2 - 0.1; // y=0.1 x == y //false x == 0.1 //false y == 0.1 //true
由于舍入误差,x、y并不相等,这个问题不只在javascript中存在,理解这一点十分重要:在任何使用二进制浮点数的编程语言中都会有这个问题。同样需要注意的是,上述代码中x和y的值非常接近彼此和最终的正确值。这种计算结果可以胜任大多数的计算任务。这个问题也是只有比较两个值是否相等的时候才才会出现。
1.5.日期与时间
JavaScript语言核心包括Date()构造函数,用来创建表示日期和时间的对象。这些日期对象的方法为日期计算提供了简单的API。
var then = new Date(2015, 0, 1); // 2015年1月1日 var later = new Date(2015, 0, 1, 17, 10, 30); // 同一天, 当地时间5:10:30pm, var now = new Date(); // 当前日期和时间 var elapsed = now - then; // 日期减法:计算时间间隔的毫秒数 later.getFullYear() // => 2011 later.getMonth() // => 0: 从0开始计数的月份 later.getDate() // => 1: 从1开始计数的天数 later.getDay() // => 5: 得到星期几, 0代表星期日,5代表星期一 later.getHours() // =>当地时间17: 5pm later.getUTCHours() // 使用UTC表示小时的时间,基于时区
2.文本
字符串(string)是一组由16位值组成的不可变的有序序列,每个字符通常来自于Unicode字符集。 JavaScript通过字符串类型来表示文本。JavaScript字符串(和其数组)的索引从零开始。
2.1.字符串直接量
在JavaScript程序中的字符串直接量,是由单引号或双引号括起来的字符序列。由单引号定界的字符串中可以包含双引号,由双引号定界的字符串中也可以包含单引号。
在ES3中,字符串直接量必须写在一行。在ECMAScript 5中,字符串直接量可以拆分成数行,每行必须以反斜线(\)结束,反斜线和行结束符都不算是字符串直接量的内容。如果希望在字符串直接量中另起一行,可以使用转义字符\n。
"two\nlines" // 这里定义了一个显示为两行的字符串 "one\ // 用三行代码定义了显示为单行的字符串,只在ECMAScript 5中可用
long\ line"
需要注意的是,当使用单引号来定界字符串时,需要格外小心英文中的缩写和所有格写法,比如can't和O'Oracle's。因为撇号和单引号是同一个字符,所以必须使用反斜线(\)来转义所有的撇号。
2.2.转义字符
'You\'re right, it can\'t be a quote'
列出了JavaScript中的转义字符以及它们所代表的含义。其中有两个是通用的,通过十六进制数表示Latin-1或Unicode中的任意字码。例如,\xA9表示版权符号,版权符号的Latin-1编码是十六进制数A9。同样,\u表示由4个十六进制数指定的任意Unicode字符,比如,\u03c0表示字符π。
如果“\”字符位于没有在表3-1中列出的字符前,则忽略“\”。
2.3.字符串的使用
JavaScript的内置功能之一就是字符串连接。
要确定一个字符串的长度——其所包含的16位值的个数——可以使用字符串的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("l") // => 10:字符l最后一次出现的位置 s.indexOf("l", 3) // => 3:在位置3及之后首次出现字符l的位置 s.split(", ") // => ["hello", "world"] 分割成子串 s.replace("h", "H") // => "Hello, world": 全文字符替换 s.toUpperCase() // => "HELLO, WORLD"
记住,在JavaScript中字符串是固定不变的,类似replace()和toUpperCase()的方法都返回新字符串,原字符串本身并没有发生改变。在ECMAScript 5中,字符串可以当做只读数组,除了使用charAt()方法,也可以使用方括号来访问字符串中的单个字符(16位值):
s = "hello, world"; s[0] // => "h"
2.4.模式匹配
JavaScript定义了RegExp()构造函数,用来创建表示文本匹配模式的对象。这些模式称为“正则表达式”(regular expression),JavaScript采用Perl中的正则表达式语法。String和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.replace(pattern, "#"); // => "testing: #, #, #" text.split(/\D+/); // => ["","1","2","3"]: 非数字字符截取字符串
3.布尔值
这个类型只有两个值,保留字true和false。
比较语句的结果通常是布尔值。
常用语JavaScript的控制语句中。
任意JavaScript的值都可以转换为布尔值。下面这些值会被转换成false:
undefined null 0 –0 NaN "" // 空字符串
布尔值包含toString()方法,因此可以使用这个方法将字符串转换为“true”或“false”,但它并不包含其他有用的方法。除了这个不重要的API,还有三个重要的布尔运算符。&&”运算符;“||”运算符;一元操作符“!”执行了布尔非(NOT)。
4.null和undefined
null是JavaScript语言的关键字,它表示一个特殊值,常用来描述“空值”。对null执行typeof运算,结果返回字符串“object”,也就是说,可以将null认为是一个特殊的对象值,含义是“非对象”。但实际上,通常认为null是它自有类型的唯一一个成员,它可以表示数字、字符串和对象是“无值”的。
JavaScript还有第二个值来表示值的空缺。用未定义的值表示更深层次的“空值”。它是变量的一种取值,表明变量没有初始化,如果要查询对象属性或数组元素的值时返回undefined则说明这个属性或元素不存在。如果函数没有返回任何值,则返回undefined。引用没有提供实参的函数形参的值也只会得到undefined。undefined是预定义的全局变量(它和null不一样,它不是关键字),它的值就是“未定义”。在ECMAScript 3中,un-defined是可读/写的变量,可以给它赋任意值。这个错误在ECMAScript 5中做了修正,undefined在该版本中是只读的。如果使用typeof运算符得到undefined的类型,则返回“undefined”,表明这个值是这个类型的唯一成员。
尽管null和undefined是不同的,但它们都表示“值的空缺”,两者往往可以互换。判断相等运算符“==”认为两者是相等的(要使用严格相等运算符“===”来区分它们)。
5.全局对象
全局对象(global object)在JavaScript中有着重要的用途:全局对象的属性是全局定义的符号,JavaScript程序可以直接使用。当JavaScript解释器启动时(或者任何Web浏览器加载新页面的时候),它将创建一个新的全局对象,并给它一组定义的初始属性:
• 全局属性,比如undefined、Infinity和NaN
• 全局函数,比如isNaN()、parseInt()
• 构造函数,比如Date()、RegExp()、String()、Object()和Array()
• 全局对象,比如Math和JSON
全局对象的初始属性并不是保留字,但它们应该当作保留字来对待。
可以在第三部分中通过名称查找到,或者通过别名“Global”来找到这些全局对象。对于客户端JavaScript来讲,Window对象定义了一些额外的全局属性。
在代码的最顶级——不在任何函数内的JavaScript代码——可以使用JavaScript关键字this来引用全局对象:
var global = this; // 定义一个引用全局对象的全局变量
当初次创建的时候,全局对象定义了JavaScript中所有的预定义全局值。这个特殊对象同样包含了为程序定义的全局值。如果代码声明了一个全局变量,这个全局变量就是全局对象的一个属性。
6.包装对象
JavaScript对象是一种复合值:它是属性或已命名值的集合。通过“.”符号来引用属性值。当属性值是一个函数的时候,称其为方法。通过o.m()来调用对象o中的方法(函数也称方法,以后我们会经常互换)。我们看到字符串也同样具有属性和方法:
var s = "hello world!"; // 一个字符串 var word = s.substring(s.indexOf(" ") + 1, s.length); //使用字符串的属性
字符串既然不是对象,为什么它会有属性呢?只要引用了字符串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、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。虽然字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。例如:
var s = "hello"; // 定义一个由小写字母组成的文本 s.toUpperCase(); // 返回"HELLO",但并没有改变s的值 s // => "hello": 原始字符串的值并未改变
原始值的比较是值的比较:只有在它们的值相等时它们才相等。
对象和原始值不同,首先,它们是可变的——它们的值是可修改的:
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: 两个单独的对象永不相等 var a = [], b = []; // 两个单独的空数组 a === b // => false: 两个单独的数组永不相等
我们通常将对象称为引用类型(referencetype)。对象值都是引用(reference),对象的比较均是引用的比较:当且仅当它们引用同一个基对象时,它们才相等。
var a = []; // 定义一个引用空数组的变量 var b = a; // 变量b引用同一个数组 b[0] = 1; // 通过变量b来修改引用的数组 a[0] // => 1: 变量a也会修改 a === b // => true:a和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会将其他类型的数据转换为相应的布尔值。根据需求来转换。
10 + " objects" // => "10 objects". 数字10转换成字符串 "7" * "4" // => 28: 两个字符串均转换为数字 var n = 1 - "x"; // => NaN: 字符串"x"无法转换为数字 n + " objects" // => "NaN objects": NaN转换为字符串"NaN"
表3-2简要说明了在JavaScript中如何进行类型转换。表3-2中的粗体部分突出显示了那些让你倍感意外的类型转换。空单元格表示不必要也没有执行转换。
以数字表示的字符串可以直接转换为数字,也允许在开始和结尾处带有空格。但在开始和结尾处的任意非空格字符都不会被当成数字直接量的一部分,进而造成字符串转换为数字的结果为NaN。
原始值到对象的转换也非常简单,原始值通过调用String()、Number()或Boolean()构造函数,转换为它们各自的包装对象。
null和undefined属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误(TypeError)异常,而不会执行正常的转换。
对象到原始值就复杂了,接下来有讲。
8.1.转换和相等性
JavaScript可以做灵活的类型转换。如下这些比较结果均是true:
null == undefined // 这两值被认为相等 "0" == 0 // 在比较之前字符串转换成数字 0 == false // 在比较之前布尔值转换成数字 "0" == false // 在比较之前字符串和布尔值都转换成数字
需要特别注意的是,一个值转换为另一个值并不意味着两个值相等。
8.2.显式类型转换
做显式类型转换最简单的方法就是使用Boolean()、Number()、String()或Object()函数。
Number("3") // => 3 String(false) // => "false" 或使用 false.toString() Boolean([]) // => true Object(3) // => new Number(3)
需要注意的是,除了null或undefined之外的任何值都具有toString()方法,这个方法的执行结果通常和String()方法的返回结果一致。同样需要注意的是,如果试图把null或undefined转换为对象则会像表3-2所描述的那样抛出一个类型错误(TypeError)。Object()函数在这种情况下不会抛出异常:它仅简单地返回一个新创建的空对象。
JavaScript中的某些运算符会做隐式的类型转换,有时用于类型转换。“+”运算符、一元“+”运算符将其操作数转换为数字、一元“!”运算符将其操作数转换为布尔值并取反。
x + "" // 等价于String(x) + x // 等价于 Number(x).也可以写成 x-0 !! x // 等价于 Boolean(x). 注意是双叹号
在计算机程序中数字的解析和格式化是非常普通的工作,JavaScript中提供了专门的函数和方法用来做更加精确的数字到字符串(number-to-string)和字符串到数字(string-to-number)的转换。
Number类定义的toString()方法可以接收表示转换基数(radix)的可选参数,如果不指定此参数,转换规则将是基于十进制。同样,亦可以将数字转换为其他进制数(范围在2~36之间),例如:
var n = 17; binary_string = n.toString(2); // 转换为 "10001" octal_string = "0" + n.toString(8); // 转换为 "021" hex_string = "0x" + n.toString(16); // 转换为 "0x11"
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()函数和parse-Float()函数(它们是全局函数,不从属于任何类的方法)更加灵活。parseInt()只解析整数,而parse-Float()则可以解析整数和浮点数。如果字符串前缀是“0x”或者“0X”,parseInt()将其解释为十六进制数,parseInt()和parseFloat()都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空格字符是非法的数字直接量,将最终返回NaN:
parseInt("3 blind mice") // => 3 parseFloat(" 3.14 meters") // => 3.14 parseInt("-12.34") // => -12 parseInt("0xFF") // => 255 parseInt("0xff") // => 255 parseInt("-0XFF") // => -255 parseFloat(".1") // => 0.1 parseInt("0.1") // => 0 parseInt(".1") // => NaN: 整数不能以"."开始 parseFloat("$72.47"); // => 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)
8.3.对象转换为原始值
对象到布尔值:所有的对象(包括数组和函数)都转换为true。对于包装对象亦是如此:new Boolean(false)是一个对象而不是原始值,它将转换为true。
对象到字符串(object-to-string)和对象到数字(object-to-number)的转换是通过调用待转换对象的一个方法来完成的,这样的方法有两个,所有的对象继承了两个转换方法。。值得注意的是,这里提到的字符串和数字的转换规则只适用于本地对象(native object)。宿主对象(例如,由Web浏览器定义的对象)根据各自的算法可以转换成字符串和数字。
第一个方法:toString(),它的作用是返回一个反映这个对象的字符串。默认的toString()方法并不会返回一个有趣的值:
({x:1, y:2}).toString() // => "[object Object]" [1,2,3,4,5,6,7,8,9].toString() // => "1,2,3,4,5,6,7,8,9" 数组类:将每个数组元素转换为一个字符串,并在元素之间添加逗号后合并成结果字符串 (function(){return 1;}).toString() // =>"function (){return 1;}" 函数类:返回这个函数的实现定义的表示方式。 new Date(2010,0,1).toString() // => "Fri Jan 01 2010 00:00:00 GMT-0800 (PST)" 日期类:返回了一个可读的(可被JavaScript解析的)日期和时间字符串 /\d+/g.toString() // => "/\d+/g" RegExp类:将RegExp对象转换为表示正则表达式直接量的字符串:
另一个转换对象的函数是valueOf()。这个方法的任务并未详细定义:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。数组、函数和正则表达式简单地继承了这个默认方法,调用这些类型的实例的valueOf()方法只是简单返回对象本身。日期类定义的valueOf()方法会返回它的一个内部表示:1970年1月1日以来的毫秒数。
var d = new Date(2010, 0, 1); // 2010年1月1日(太平洋时间) d.valueOf() // => 1262332800000
JavaScript中对象到字符串的转换经过了如下这些步骤:
· 如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。需要注意的是,原始值到字符串的转换在表3-2中已经有了详尽的说明。
· 如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JavaScript会调用valueOf()方法。如果存在这个方法,则JavaScript调用它。如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。
· 否则,JavaScript无法从toString()或val-ueOf()获得一个原始值,因此这时它将抛出一个类型错误异常。
在对象到数字的转换过程中,JavaScript做了同样的事情,只是它会首先尝试使用valueOf()方法:
· 如果对象具有valueOf()方法,后者返回一个原始值,则JavaScript将这个原始值转换为数字(如果需要的话)并返回这个数字。
· 否则,如果对象具有toString()方法,后者返回一个原始值,则JavaScript将其转换并返回。
· 否则,JavaScript抛出一个类型错误异常。
JavaScript中的“+”运算符可以进行数学加法和字符串连接操作。如果它的其中一个操作数是对象,则JavaScript将使用特殊的方法将对象转换为原始值,而不是使用其他算术运算符的方法执行对象到数字的转换,“==”相等运算符与此类似。如果将对象和一个原始值比较,则转换将会遵照对象到原始值的转换方式进行。
12 + [1,2,3] // =>"121,2,3"
“+”和“==”应用的对象到原始值的转换包含日期对象的一种特殊情形。日期类是JavaScript语言核心中唯一的预先定义类型,它定义了有意义的向字符串和数字类型的转换。对于所有非日期的对象来说,对象到原始值的转换基本上是对象到数字的转换(首先调用valueOf()),日期对象则使用对象到字符串的转换模式,然而,这里的转换和上文讲述的并不完全一致:通过valueOf或toString()返回的原始值将被直接使用,而不会被强制转换为数字或字符串。
12 + (new Date()) //"12Wed Mar 08 2017 11:23:06 GMT+0800 (中国标准时间)"
和“==”一样,“<”运算符以及其他关系运算符也会做对象到原始值的转换,但要除去日期对象的特殊情形:任何对象都会首先尝试调用val-ueOf(),然后调用toString()。不管得到的原始值是否直接使用,它都不会进一步被转换为数字或字符串。
“+”、“==”、“!=”和关系运算符是唯一执行这种特殊的字符串到原始值的转换方式的运算符。其他运算符到特定类型的转换都很明确,而且对日期对象来讲也没有特殊情况。例如“-”(减号)运算符把它的两个操作数都转换为数字。下面的代码展示了日期对象和“+”、“-”、“==”以及“>”的运行结果:
var now = new Date(); // 创建一个日期对象 typeof(now + 1) // => "string": "+"将日期转换为字符串 typeof(now - 1) // => "number": "-"使用对象到数字的转换 now == now.toString() // => true: 隐式的和显式的字符串转换 now > (now - 1) // => true: ">"将日期转换为数字
9.变量声明
在JavaScript程序中,使用一个变量之前应当先声明。变量是使用关键字var来声明的。
在JavaScript的变量声明中并没有指定变量的数据类型。JavaScript变量可以是任意数据类型。
重复的声明和遗漏的声明
在ECMAScript 5严格模式中,给一个没有声明的变量赋值也会报错。在非严格模式下,如果给一个未声明的变量赋值,JavaScript实际上会给全局对象创建一个同名属性,并且它工作起来像一个正确声明的全局变量。
10.变量作用域
全局变量拥有全局作用域,在JavaScript代码中的任何地方都是有定义的。然而在函数内声明的变量只在函数体内有定义。它们是局部变量,作用域是局部性的。函数参数也是局部变量,它们只在函数体内有定义。
10.1 函数作用域和声明提前
在一些类似C语言的编程语言中,花括号内的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,我们称为块级作用域(block scope),而JavaScript中没有块级作用域。JavaScript取而代之地使用了函数作用域(function scope):变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
在如下所示的代码中,在不同位置定义了变量i、j和k,它们都在同一个作用域内——这三个变量在函数体内均是有定义的。
function test(o) { var i = 0; // i 在整个函数体内均是有定义的 if (typeof 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" }
由于函数作用域的特性,局部变量在整个函数体始终是有定义的,也就是说,在函数体内局部变量遮盖了同名全局变量。尽管如此,只有在程序执行到var
语句的时候,局部变量才会被真正赋值。因此,上述过程等价于:将函数内的变量声明“提前”至函数体顶部,同时变量初始化留在原来的位置。
在具有块级作用域的编程语言中,在狭小的作用域里让变量声明和使用变量的代码尽可能靠近彼此,通常来讲,这是一个非常不错的编程习惯。由于JavaScript没有块级作用域,因此一些程序员特意将变量声明放在函数体顶部,而不是将声明靠近放在使用变量之处。这种做法使得他们的源代码非常清晰地反映了真实的变量作用域。
10.2 作为属性的变量
当声明一个JavaScript全局变量时,实际上是定义了全局对象的一个属性。当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。可能你已经注意到了,如果你没有使用严格模式并给一个未声明的变量赋值的话,JavaScript会自动创建一个全局变量。以这种方式创建的变量是全局对象的正常的可配值属性,并可以删除它们:
var truevar = 1; // 声明一个不可删除的全局变量 fakevar = 2; // 创建全局对象的一个可删除的属性 this.fakevar2 = 3; // 同上 delete truevar // => false: 变量并没有被删除 delete fakevar // => true: 变量被删除 delete this.fakevar2 // => true: 变量被删除
JavaScript全局变量是全局对象的属性,这是在ECMAScript规范中强制规定的。对于局部变量则没有如此规定,但我们可以想象得到,局部变量当做跟函数调用相关的某个对象的属性。EC-MAScript 3规范称该对象为“调用对象”(call ob-ject),ECMAScript 5规范称为“声明上下文对象”(declarative environment record)。
10.3 作用域链
每一段JavaScript代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码“作用域中”的变量。当JavaScript需要查找变量x的值的时候(这个过程称做“变量解析”(variable resolution)),它会从链中的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中不存在名为x的属性,JavaScript会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。
当定义一个函数时,它实际上保存一个作用域链。当调用这个函数时,它创建一个新的对象来存储它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。对于嵌套函数来讲,事情变得更加有趣,每次调用外部函数时,内部函数又会重新定义一遍。