词法结构
类型、值和变量
词法结构
- JS是一门高端的、动态的、弱类型的编程语言,非常适合面向对象和函数式的编程风格
- 火狐浏览器下使用Firebug进行调试,F12用来唤醒/关闭Firebug操作面板,Cmd+Shift+J用来唤醒错误控制台
- JS程序是用Unicode字符集编写的,<strong>区分大小写</strong>
类型、值和变量
- 在编程语言中,能够表示并操作的值的类型称作数据类型。JS的数据类型分为两类:
1.原始类型(primitive type)
数字、字符串、布尔值、null(空)、undefined(未定义)
2.对象类型(object type)
对象、数组、函数、日期类、正则类、错误类
- JS有自己的内存管理机制,可以自动对内存进行垃圾回收
- 从技术上讲,只有JS对象才能拥有方法,然而数字、字符串和布尔值也可以拥有自己的方法(原理将在后文讲述)。在JS中只有null和undefined是无法拥有方法的值
- JS类型可分为原始类型和对象类型,也可以分为拥有方法的类型和不能拥有方法的类型,同样可分为可变类型(mutable)和不可变类型(immutable)。可变类型的值是可以修改的,对象和数组属于可变类型;数字、布尔值、null和undefined属于不可变类型。字符串可以看成由字符组成的数组,然而在JS中,字符串数不可变的:可以访问字符串任意位置的文本,但JS并未提供修改已知字符串的文本内容的方法
- JS可自由的进行数据类型转换。
- JS变量是无类型的(untyped),变量可以被赋予任何类型的值,同样一个变量也可以重新赋予不同类型的值,使用var关键字来声明declare变量
- JS采用词法作用域(lexical scoping),不在任何函数内声明的变量称作全局变量(global variable),它在JS程序中的任何地方都是可见的;在函数内声明的变量具有函数作用域(function scope),并且只在函数内可见。
- JS不区分整数值和浮点数值,在JS中所有数字军用浮点数值表示。JS采用IEEE 754标准定义的64位浮点格式表示数字(双精度double)。整数的范围(-2的53次方到2的53次方),需要注意的是,JS实际的操作(比如数组索引、位操作符)则是基于32位整数
- JS支持十进制和十六进制,但有些不支持八进制(ECMAScript标准不支持),因此最好不要使用以0位前缀的整型直接量。
- JS中的算术运算,除了基本运算+、-、*、/、%之外,JS还支持更加复杂的算术运算,这些复杂运算通过作为Math对象的属性定义的函数和常量来实现。
Math.pow(2,53) // =>9007199254740992:2的53次幂
Math.round(.6) // =>1.0:四舍五入
Math.ceil(.6) // =>1.0:向上取整
Math.floor(.6) //=>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(o) // 三角函数:还有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的3次幂
- 算数运算在溢出(overflow)、下溢(underflow)或被0整除时不会报错。当数字运算结果超过了JS所能表示的数字上限(溢出),结果为一个特殊的无穷大(infinity)值,在JS中以Infinity表示。同样的,当负数超过了JS所能表示的负数范围,结果为负无穷大,在JS中以-Infinity表示。它们的加减乘除运算结果还是无穷大值。
- 下溢是当运算结果无限接近于0并比JS所能表示的最小值还小的时候发生的一种情况。这种情况下,JS会返回0.当一个负数发生下溢时,JS返回一个特殊的值“负零”。这个负零几乎和正常的零完全一样,JS程序员很少用到负零。
- 被零整除在JS中并不报错:它只是简单的返回无穷大(Infinity)或负无穷大(-Infinity)。但有一个例外,零除以零是没有意义的,这种整除运算结果也是一个非数字(not-a-number)值,用NaN表示。无穷大除以无穷大,给任意负数作开方运算或者算术运算符与不是数字或无法转换为数字的操作数一起使用时都会返回NaN。
- JS预定义了全局变量Infinity和NaN,用来表示正无穷大和非数字值,在ECMAScript 5中,定义为只读。
- JS中的非数字值有一点特殊:它和任何值都不相等,包括自身。也就是说没有办法通过x==NaN来判断变量x是否是NaN,相反,应该使用x!=x来判断,当且仅当x为NaN的时候,表达式的结果才是true。函数isNaN()的作用与此类似,如果参数是NaN或者一个非数字值的时候,返回true。JS中有一个类似的函数isFinite(),在参数不是NaN、Infinity、-Infinity的时候返回true。负零值同样有些特殊,它和正零值是相等的,这两个值几乎是一模一样的,除了作为除数以外。
var zero = 0; // 正常的零值
var negz = -0; // 负零值
zero === negz; // => true: 正零值和负零值相等
1/zero === 1/negz // =>false: 正无穷大和负无穷大不等
- 二进制浮点数和四舍五入的错误
// 数字具有足够的精度,并可以极其近似于0.1.但事实上,数字不能精确表述带来了一些问题
var x = .3 -.2;
var y = .2 -.1;
console.log(x); // 0.9999999999998
console.log(y); // 0.1
console.log(x==y); // false
console.log(x==.1); // false
console.log(y==.1); //true
- 字符串string是一组由16位值组成的不可变的有序序列,JS中并没有单个字符的“字符型”。记住JS中字符串是固定不变的,因此有关字符串的方法比如replace()、toUpperCase()都是返回新字符串,元字符串本身并没有改变。
- undefined、null、0、-0、NaN、""(空字符串)都会被转换成false,所有其他值包括所有对象(数组)都会转换成true
- null和undefined的区别
1.null是JS的关键字,表示一个特殊值,常用来描述“空值”,对null执行typeof预算,结果返回
“object”,也就是说,可以将null认为是一个特殊的对象值,含义是“非对象”。但实际上,通常
认为null是它自有类型的唯一一个成员,它可以表示数字、字符串和对象是“无值”的。
2.undefined未定义来表示更深层次的“空值”。它是变量的一种取值,表明变量没有初始化,如果
要查询对象属性或数组元素的值时返回undefined则说明这个属性或者元素不存在。如果函数没有
返回值,则返回undefined。引用没有提供实参的函数形参的值也只会得到undefined。
undefined是预定义的全局变量(它和null不同,它不是关键字),它的值就是“未定义”。
在ECMAScript 5中是只读的。使用typeof云素服得到undefined的类型,返回的是
“undefined”,表明这个值是这个类型的唯一成员。
3.尽管null和undefined是不同的,但它们都表示“值的空缺”,两者往往是可以互换的。判断相等
运算符“==”认为两者是相等的(要使用严格相等运算符“===”来区分它们)。在希望是布尔类型的地
方它们的值都是假值,和false类似。null和undefined都不包含任何属性和方法。实际上,使用
“.”和“[]”来存取这两个值的成员和方法都会产生一个类型错误。如果想将它们赋值给变量或者属性
,或将它们作为参数传入函数,最佳选择是使用null。
- 全局对象(global object)的属性时全局定义的符号,JS程序可以直接使用。当JS解释器启动时(或者web浏览器在加载新页面的时候),它将创建一个新的全局对象,并给它一组定义的初始属性:
全局属性,比如undefined、Infinity和NaN
全局函数,比如isNaN()、parseInt()、eval()
构造函数,比如Date()、RegExp()、String()、Object()和Array()
全局对象,比如Math和JSON
- 包装对象,现在回到前边遗留的一个问题:数字、字符串和布尔值既然不是对象为什么可以拥有自己的属性和方法呢?原因是只要引用了字符串s的属性,JS就会将字符串值通过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); // 一个布尔对象
JS会在必要时将包装对象转换成原始值,因此上段代码中的对象S、N和B常常但又不总是表现的和s、n和b一样。“==”等于运算符将原始值和其包装对象视为相等,但“===”全等运算符将它们视为不等。通过typeof运算符可以看到原始值和其包装对象的不同。
- 不可变的原始值和可变的对象引用:原始值(undefined、null、布尔值、数字和字符串)是不可更改的,任何方法都不可更改。字符串中的所有方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串。
- 原始值的比较是值的比较:只有在它们的值相等时它们才相等,比较两个单独的字符串,当且仅当它们的长度相等且每个索引的字符都相等时,JS才认为它们相等。
var string1 = "hehe";
var string2 = "hehe";
string1 == string2; // => true
string1 === string2; // => true
- 对象和原始值不同,对象是可变可修改的,对象的比较并非值的比较:即使两个对象包含同样的属性及相同的值,它们也是不相等的。各个索引元素完全相等的两个数组也是不相等的
// 具有相同属性的两个对象
var object1 = {x:1};
var object2 = {x:1};
object1 == object2; // false: 两个单独的对象永不相等
object1 === object2; // false: 两个单独的对象永不相等
alert(object1 === object2);
// 具有相同元素的两个数组
var array1 = [1,2];
var array2 = [1,2];
array1 == array2; // false: 两个单独的数组永不相等
array1 === array2; // false: 两个单独的数组永不相等
我们通常将对象称为引用类型(reference type),以此来和JS的基本类型区分开来。对象值都是引用,对象的比较均是引用的比较;当且仅当它们引用同一个基对象时,它们才相等。
var a= []; // 顶一个引用空数组的变量a
var b = a; // 变量b引用同一个数组
b[0] = 1; // 通过变量b来修改引用的数组
a[0] // => 1: 变量a也会修改
a === b // => true: a和b引用同一个数组,因此它们相等
上边代码中,将对象(数组)赋值给一个变量,仅仅是赋值的引用值:对象本身并没有复制一次。如果想得到一个对象或者数组的副本,则必须显示复制对象的每个属性或数组的每个元素。下面例子则是通过循环来完成数组的复制
var a = ['a', 'b', 'c']; // 待复制的数组
var b = []; // 复制到的目标空数组
for (var i = 0; i < a.length; i++) {
b[i] = a[i];
}
alert(b); // => ['a', 'b', 'c']
同样的如果相比较两个单独的对象或者数组,则必须比较他们的属性或元素。下边代码比较两个数组的函数
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;
}
- 类型转换
10 + "objects" // =>"10 objects" 数字10转换成字符串
"7" * "4" // =>28: 两个字符串均转换成数字
var n = 1 - "x" // =>NaN:字符串“x”无法转换为数字
n + "objects" // =>"NaN objects": NaN转换成字符串“NaN”
- JS类型转换
值 | 转字符串 | 转数字 | 转布尔值 | 转对象 |
---|---|---|---|---|
undefined | "undefined" | NaN | false | throws TypeErrow |
null | "null" | 0 | false | throws TypeErrow |
true | "true" | 1 | new Boolean(true) | |
false | "false" | 0 | new Boolean(false) | |
''"(空字符串) | 0 | false | new String("") | |
"1.2"(非空,数字) | 1.2 | true | new String("1.2") | |
"one"(非空,非数字) | NaN | true | new String("one") | |
0 | "0" | false | new Number(0) | |
-0 | "0" | false | new Number(-0) | |
NaN | "NaN" | false | new Number(NaN) | |
Infinity | "Infinity" | true | new Number(Infinity) | |
-Infinity | "-Infinity" | true | new Number(-Infinity) | |
1(无穷大,非零) | "1" | true | new Number(1) | |
{}(任意对象) | 下文有解释 | 下文有解释 | true | |
[]'(任意数组) | "" | 0 | true | |
[9]'(1个数字元素) | "9" | 9 | true | |
['a']'(其他数组) | 使用join()方法 | NaN | true | |
function(){}(任意函数) | 下文有解释 | NaN | true |
原始值到对象的的转换很简单,原始值通过调用String()、Number()或Boolean()构造函数,转换为它们各自的包装对象。null和undefined属于例外,当将它们用在期望是一个对象的地方都会造成一个类型错误(TypeError)异常,而不会执行正常的转换
- 转换和相等性
由于JS可以做灵活的类型转换,因此其“==”相等运算符也随相等的含义灵活多变
下面的比较结果都是true
null == undefined // 这两值被认为相等
"0" == 0 // 在比较之前字符串转换成数字
0 == false // 在比较之前布尔值转换成数字
"0" == false // 在比较之前字符串和布尔值都转换成数字
注:"=="等于运算符在判断两个值是否相等时做了类型转换,而"==="恒等运算符在判断相等时并未做任何类型
转化
需要注意的是,一个值转换成另外一个值并不意味着两个值相等。比如如果在期望使用布尔值的地方使用了undefined,它将转换成false,但这并不意味着undefined==false
- 显示类型转化
最简单的方法就是使用Boolean()、Number()、String()或Object()函数
除了null或undefined之外的任何值都具有toString()方法,这个方法执行的结果通常是和String()方法返回的结果一致。 - Number类定义的toString()类型,在做数字到字符串的转换过程中,可以接受表示转换基数的可选参数,如不指定,默认是十进制。例子如下:
var n = 17;
binary_string = n.toString(2); // 转换为"10001"
octal_string = n.toString(8); // 转换为"021"
hex_string = n.toString(16); // 转换为"0x11"
- 控制小数点位置和有效数字位数的方法
var n = 123456.789;
// toFixed()根据小数点后指定位数将数字转换成字符串
n.toFixed(0); // "123457"
n.toFixed(2); // "123456.79"
n.toFixed(5); // "123456.78900"
// toExponential()使用指数计数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数
由参数决定
n.toExponential(1); // "1.2e+5"
n.toExponential(3); // "1.235e+5"
// toPrecision()根据指定有效数字位数将数字转换成字符串
n.toPrecision(4); // "1.235e+5"
n.toPrecision(7); // "123456.8"
n.toPrecision(10); // "123456.7890"
- parseInt()只解析整数,parseFloat()可以解析整数和浮点数。如果字符串前缀是“0x”或者“0X”,parseInt()将其解释为十六进制数。这两个函数都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。但是如果第一个非空字符串是非法的数字直接量,将最终返回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.67") // NaN 数字不能以"$"开始
parseInt()可以接收第二个参数,这个参数指定数字转换的基数
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)
- 对象转换成原始值
对象到布尔值的转换很简单:所有对象(包括数组和函数)都转换成true。对于包装对象亦是如此: new Boolean(false)是一个对象而不是原始值,将转换成true - 所有的对象继承了两个转换方法:第一个是toString(),它的作用是返回一个反映这个对象的字符串
数组类:[1,2,3].toString() // => "1,2,3"
数组类的toString()方法将每个数组元素转换成一个字符串,并在元素之间添加逗号后合并成结果字符串
函数类:(function(x) { f(x); }).toString() // =>"function(x) {\n f(x); \n}"
函数类的toString()方法返回这个函数的实现定义的表示方式,转换成JS源代码字符串
日期类:new Date(2010,0,1).toString() // =>"Fri Jan 01 2010 00:00:00 GMT-0800(PST)"
日期类的toString()方法返回一个可读的日期和时间字符串
RegExp类:/\d+/g.toString() // => "/\\d+/g"
RegExp类的t0String()方法将RegExp对象转换成表示正则表达式直接量的字符串
- 另一个转换对象的函数式valueOf()。如果存在任意原始值,它就默认将对象转换成它的原始值。日期类的valueOf()方法返回它的一个内部表示:1970年1月1日以来的毫秒数
var d = new Date(2010, 0, 1); // 2010年1月1日(太平洋时间)
d.valueOf() // => 1262332800000
- JS中对象到字符串的转换经过了以下过程
(1)如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JS将这个值转换成字符串(如果本身不是字符串的话),并返回这个字符串结果
(2)如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JS会调用valueOf()方法。如果存在这个方法,则JS调用它。如果返回值是原始值,JS将这个值转换成字符串(如果本身不是字符串的话),并返回这个字符串结果。
(3)否则JS无法从toString()或valueOf()获得一个原始值,因此这时它将抛出一个错误类型异常 - JS中对象到数字的转换过程,JS做了同样的事情,只是它会首先尝试使用valueOf()方法
(1)如果对象具有valueOf()方法,后者返回一个原始值,则JS将这个原始值转换成数字(如果需要的话)返回这个数字
(2)否则,如果对象具有toString()方法,后者返回一个原始值,则JS将其转换并返回
(3)否则,JS跑出一个错误类型异常
对象转换为数字的细节揭示了为什么空数组会被转为数字0以及为什么具有单个元素的数组同样会转换为一个数字。数组继承了默认的valueOf()方法,这个方法返回一个对象而不是一个原始值,因此,数组到数字的转换则调用了toString()方法,空数组转换成空字符串,空字符串转换成数字0 - 声明提前:JS的函数作用域是指在函数内声明的所有变量在函数体内都是可见的。有意思的是,这意味着变量在声明之前甚至已经可用。JS的这个特性被非正式的称为声明提前,即JS函数里声明的所有变量都被“提前”至函数体的顶部
- 作为属性的变量。当声明一个JS全局变量时,实际上是定义了全局对象的一个属性。当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说这个变量无法通过delete运算符删除。如果没有使用严格模式并给一个未声明的变量赋值的话,JS会自动创建一个全局变量,以这种方式创建的变量是全局对象的正常的可配置属性,并可以删除它们:
var truevar = 1; // 声明一个不可删除的全局变量
fakevar = 2; // 创建全局对象的一个可删除的属性
this.fakevar2 = 3; // 同上
delete truevar; // =>false:变量并没有被删除
delete fakevar; // =>true:变量被删除
delete this.fakevar2; // =>true:变量被删除