jQuery片段:
1. var
2. // Will speed up references to window, and allows munging its name.
3. window = this,
4. // Will speed up references to undefined, and allows munging its name.
5. undefined,
6. // Map over jQuery in case of overwrite
7. _jQuery = window.jQuery,
8. // Map over the $ in case of overwrite
9. _$ = window.$,
10.
11. jQuery = window.jQuery = window.$ = function( selector, context ) {
12. // The jQuery object is actually just the init constructor 'enhanced'
13. return new jQuery.fn.init( selector, context );
14. },
15.
16. // A simple way to check for HTML strings or ID strings
17. // (both of which we optimize for)
18. quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
19. // Is it a simple selector
20. isSimple = /^.[^:#\[\.,]*$/;
在这一节,我们将讨论同一段jQuery片段的另一个知识点:数据类型和对象。为了让我们更好地理解代码,我们必须对这一部分内容深入了解。没有牢固的基础,是不可能构筑起坚实的堡垒的。
- 内置数据类型
内置数据类型,也称作固有数据类型,也就是JS的基本的数据类型。首先,让我们的大脑热一下身:回想一下,我们所有编程语言中实际可能运用到的数据都有些什么?
基本如你所想,但实质上我们需要的只是有意义的文字而已。但对于电脑来说,它能认识的不是文字,而是逻辑电路中电平的高低。为此,我们在程序语言中,将 这些高低电平转换成0和1,并使用这些二进制的数字串构造成人类更加好理解的数字逻辑。这些数字逻辑实际上就是所谓的数据类型了。(我承认我在胡说八 道……)
现在让我们看看JS是怎么理解这些数字逻辑,来让我们更好地使用它的(至少JS的设计者初衷是这样)。
- undefined
我们第一个看到的数据类型是undefined。或许很多人都会怀疑到——你没有搞错吧?undefined也是一种数据类型?!然后,我可以很镇定的告诉你,我没搞错……
1. alert(undefined);// "undefined"
2. alert(typeof undefined);// "undefined"
3. alert(undefined instanceof Object);// "false"
4. alert(undefined instanceof undefined);// 语法错误:instanceof不能对undefined运算
5. alert(undefined instanceof Undefined);// 错误:"Undefined"未定义
看到上面的例子后,你还有疑问吗?
联系一下我们前面所说的内容,如果调用一个没有声明的变量被直接调用,应该是会报错的,但上例没有,这就证明了undefined是JS里固有的变量。 而且,根据typeof的结果,这个变量的数据类型是undefined。但它不是一个Object。而且不能用instanceof来判断他是不是 undefined实例。
那么,让我们来小结一下:undefined是一种类似单态的数据类型,他在全局作用域中以变量标识undefined存在,并且拥有唯一值undefined。
除了上面所说的以外,还有一些需要注意的地方,请看下例:
1. // 如何判断一个变量是否声明
2. alert(typeof x == "undefined");// true
3. alert(x==undefined);// 报错:"x"未定义
所以,如果你需要判断一个变量是否已声明,请使用typeof运算符,再和"undefined"这个字符串比较。如果你看得仔细,你或许会产生一个疑问:在上一节中不是说,如果在作用域链中找不到变量标识的时候,不是会创建空值标识吗?如果你有此一问,那么我得赞你一下,你真的用心思考了!这个问题将在稍后解答。
1. var x;
2. alert(undefined == "undefined");// false
3. alert(x==undefined);// true
4. alert(x=="undefined");// false
另外,别以为undefined和字符串"undefined"是相等的,undefined不是字符串,而是另一种数据类型。
- null
相信看完undefined后,你已经不再怀疑null也是一种数据类型了吧。可是null又有何特别呢?请看:
1. alert(null);// "null"
2. alert(typeof null);// "object"
3. alert(null instanceof Object);// false
4. alert(null instanceof null);// 报错:instanceof不能对null运算
你有感到奇怪了么?奇怪在哪里呢?嗯。我也觉得奇怪:为啥typeof的运算结果会是“object”,但instanceof判断又为false呢?但对于这个问题,能找到的唯一解答是如此描述的:
“这点潜在的混淆是为了向下兼容。”
但怎么向下兼容,我已经不能考究也不想考究下去了。我能告诉大家的是typeof null的返回值“object”是ECMA-262中规定的内容。并且这规定只是为了兼容性,实际上null并不是一个Object的实例。
至此,我们再小结一下:null和undefined很相似,只是我们从根本上还是属于两种不同的数据类型。但是他们真的“不同”吗?让我们再来看看下面的例子:
1. alert(null==undefined);// "true"
2. alert(null===undefined);// "false"
那么,看来JS解释器一般认为null和undefined是相等的了,虽然在严格等于运算时结果是false。(据说这是为了兼容以往的浏览器中没有undefined而设的。)
由此引起了我们另一个思考:他们到底有何异同呢?实际上,导致他们严格等于运算返回false的是typeof的运算——在运算符节再详细解释吧。但他们其实还有一个语言规范层级的区别:undefined是作为Global对象的属性存在的,而null则是ECMA规范中设定的一个字面值。换句话说,undefined是存放在Global中的属性(所以像单态),而null是解释执行时产生的值。然而对他们的值作强制转换还是相同的,如:
1. alert(!undefined);// "true"
2. alert(!!undefined);// "true"
3. alert(!null);// "true"
4. alert(!!null);// "true"
5. // 因此我们在使用if作判断的时候能直接判断变量是否为空或未定义
6. alert(parseInt(undefined));// "NaN"
7. alert(parseInt(null));// "NaN"
8. // 注意NaN连自己也是不相等
还值得注意的是,和其他语言不同,他们和0值是不相等的,除非转换成布尔值:
1. alert(undefined==0);// "false"
2. alert(null==0);// "false"
3. alert(!undefined==!0);// "true"
4. alert(!null==!0);// "true"
另外,它们都不带属性的:
1. alert(null.a);// "错误: null has no properties"
2. alert(undefined.a);// "错误: undefined has no properties"
最后,提一点我在项目中经常看到的,某些程序员喜欢将一些控件的属性是否定义的判定交给字符串"undefined"和null,但实际上只有null在起作用呢。
- boolean
下一个我们看到的类型是boolean,也就是我们常用来判断真假的布尔型。它的值只有两个:true和false。让我们来看看它的特点:
1.
1. var x=true;
2. alert(x);// "true"
3. alert(typeof x);// "boolean"
4. alert(!x==0);// "true"
5. alert(x==1);// "true"
6. alert(x==2);// "false"
7. alert(parseInt(x));// "NaN"
8. alert(parseInt(!x));// "NaN"
9. x=null;
10. alert(x);// "null"
11. alert(typeof x);// "object"
12. x=undefined;
13. alert(x);// "undefined"
14. alert(typeof x);// "undefined"
从上例我们可以看到,true和1相等,false和0相等,但是他们不能转换成1和0。并且,因为JS的弱数据类型,所以当x被赋予true和false以外的值后,他们的数据类型将会改变。
布尔型的使用通常是在分支控制上,是JS中很关键的数据类型之一。
- number
接着我们遇到的数据类型是number。顾名思义,number型就是指存 放数字的类型。在其他语言中,我们遇到的数字型变量可能很多,分得很细,例如:short,int,long,float,double等等。但在JS 中,所有的数字都被归纳成数字型的数据。回想一下如果我们对整型数据作不能整除的运算后会有什么结果?嗯,不能整除的部分将被截断。但在JS,结果会变得 不一样:
1. var x=5;
2. alert(x/2);// "2.5"
为什么会这样呢?因为JS实际上会把所有数字型的数据解释成浮点数。也就是无论你使用的数字是不是整数也好,JS的解释器都会把他看成浮点数的特例。
虽然在数据类型上没有其他语言的复杂,但是我们仍然需要面对的是他能解释的数值范围。请看:
1. var x=2;
2. alert(Math.pow(x,1023));// "8.98846567431158e+307
3. alert(Math.pow(x,1023)+Math.pow(x,1022));// "1.348269851146737e+308
4. alert(Math.pow(x,1024));// "Infinity"
5. // PS:JS 中以"e+n"表示10的n次方
可以看到,当数字超过2的1024次方减1后,该值就变为无限大(Infinity)了。我们可以用类似的方法再测出JS可以判定的数值范围,但我在这里就先省去了(其实也就是正负的21024)。我们跟着规范走:JS遵守的浮点标准是IEEE 754,换成具体的数字来说,如果把科学计数法算上,JS的数字运算能力最大可以到±1.7976931348623157x10308,最小到±5x10-324。然而,在我们实际运算中,大多运算符都不能支持这么高位的运算,请看:
1. var x=2;
2. alert(999999999999999+x);// "1000000000000001"
3. alert(999999999999999-x);// "999999999999997"
4. alert(9999999999999999+x);// "10000000000000002"
5. alert(9999999999999999-x);// "9999999999999998"
上例很明显可以看出,当数位超过15位时,计算精度就可以丢失。那么,究竟这个有效精度范围是怎样的呢?
1. var x=2;
2. alert(Math.pow(x,53)-2);// "9007199254740990"
3. alert(Math.pow(x,53)-1);// "9007199254740991"
4. alert(Math.pow(x,53));// "9007199254740992"
5. alert(Math.pow(x,53)+1);// "9007199254740992"
6. alert(Math.pow(x,53)+2);// "9007199254740994"
这次我们就可以明确了,加减运算精度是控制在253内的。也就是说,如果我们要处理253以外的数字运算的话,我们必须使用另外的方法了——例如数位拆分运算(名字我乱起的,实际意思就是将精度不可控的数位拆分成两个精度可控的小数去计算,最后由两个小数生成计算结果)。虽然要使用这种大数运算的机会几乎为零。
虽然JS并不细分数字的数据类型,但是它对各种进制的数字还是有一定支持的。但就初始化用的字面值来说,数字型能支持的有八进制,十进制和十六进制。先看一下示例:
1. alert(10);// "10"
2. alert(010);// "8"
3. alert(0x10);// "16"
字面值中表示十进制的数字无须任何修饰,而且能定义小数。
表示八进制的数字需以0开头,但必须注意的是,后续的数字不能大于7,不然还是会被判断为十进制数字面值的。八进制数能表示负值,但不能表示小数。论坛中曾经有朋友遇到的问题就是因为八进制字面值引起的。因为他拿到的文本值是固定二位数,而单一数位大于7的字面值将被判断为十进制而正常运作,但进位后则变为8进制数,所以实际值与预期值就不相等了。
表示十六进制的数字需要以0x或0X开头,后续数字为符合十六进制表示法的0-9或a-f的大小写字母。同样地,十六进制数能表示负值,但不能表示小数。
从上例中,我们还能看到,无论我们用哪种进制的字面值,经过JS解释后,还是将会返回十进值值的 ——虽然内部存储必定还是二进制。我们也可以用方法转换其的进制显示。
下面,我们看一下JS中的一些典型的字面值和可能产生误会的字面值:
数字 | 描述 | 等价十进制数 |
.0001, 0.0001, 1e-4, 1.0e-4 | 四个相等的浮点数。 | 0.0001 |
3.45e2 | 浮点数。 | 345 |
42 | 整数。 | 42 |
0378 | 整数。虽然看起来是八进制数(以0开头),但是8不是有效的八进制数字,所以为十进制数。 | 378 |
0377 | 八进制整数。注意它虽然看起来比上面的数只小1,但实际数值有很大不同。 | 255 |
0.0001 | 浮点数。虽然以零开头,但由于带有小数点所以不是八进制数。 | 0.0001 |
00.0001 | 错误。两个零开头表示为八进制,但八进制数不能带有小数部分。 | N/A (编译错误) |
0Xff | 十六进制整数。 | 255 |
0x37CF | 十六进制整数。 | 14287 |
0x3e7 | 十六进制整数。注意‘e’并不被认为指数。 | 999 |
0x3.45e2 | 错误。十六进制数不能有小数部分。 | N/A (编译错误) |
- 另外,对于数字型的数据,有以下几个特殊值:
a)NaN(not a number),意思不是一个数,但它却是数字型的一个值,当对不适当的数据进行数学运算时,例如字符串或未定义值,它将会作为运算结果返回。NaN不与任何值相等,包括它自己,但我们可以使用方法isNaN来判定一个值得是否NaN。
b)Infinity,意思是无穷大,相对地,有负无穷大-Infinity。当一个数大于等于21024或小于等于21024,会分别用他们的正负形式来表示。所有Infinity是相等的,当然,区分正负地相等。
PS:或许在规范中还有一个负零,字面值为-0。但是实际上,当其输出时,只是简单的0而已。不过我没对低版本的浏览器测试过。猜差要不就是低版本中会有所区分,要不就是存储时的二进制码有所区分。
在这里说一个实用技巧:我们常会碰到需要把文本框中的数字的小数部分多余的0去掉。这个时候,我们可以使用数字型的特性:
1. alert(5.0000);// "5"
2. alert("5.0000"-0);// "5"
3. alert(typeof("5.0000"-0));// "number"
利用减0把字符强制转换成数字型后,尾数的0自然就去掉了。
最后,要提醒一下,JS的浮点运算真的不怎么样,很容易就会有精度丢失的:
1. alert(0.1+0.2);// "0.30000000000000004"
2. alert(0.1+0.2);// "0.30000000000000004"
解决方法有二:1)放大到整数来运算;2)对计算结果进行精度控制。
- string
string,也就是我们最需要的字符串类型。任何其他类型,其实都是为了最终转换成字符串中人类可以理解的语言而服务的。那么这个令人着迷的数据类型又有何特别呢?我们先来看看如何定义一个字符串:
1. var test="this is a test";
2. var test="this is a test";
是不是很简单呢?我也觉得也是。那么我们再来看点复杂的:
1. alert('test again');
2. alert("Is it a 'test'?");
3. alert('How "test" things go on?');
4. alert("\"test\" is all right now!");
5. alert('That \'test\' sounds great!');
6. alert("\Y\e\s");// "Yes"
这次是不是看到眼花缭乱呢?字符串里最“复杂”的应用莫过于引号的使用和转义符了。
由于JS没有单字符的数据类型——如Java中的char,所以单双引号的成对使用时基本不存在区别——Java中单引号里的是单字符。而且单双引号能互相嵌套使用,但单(双)引号必须包含所有双(单)引号,并且双(单)引号中不能再出现单(双)引号。另一种可以使单(双)引号都能嵌套自身的方法是使用转义符“\”。
另外,string支持unicode字面值,他们以并且只能以“\u”开头,后面跟四位的unicode编码(不区分大小写)。在定义后,所有的string都将按UTF16储存,所以不用担心输出的时候JS没有为你转码——当然,可以转码的是unicode字面值。
最后提一下,string实际上是存放在有序数列中的。
- object
呼呼!总算来到最后一个内置数据类型了。它的大名就是object!在Java中,Object类是所有类的基类,那么声称Java~Script的JS到底把object做了怎样的定位呢?
由于JS中没有类的概念,所以就没有了继承。啊!等等,JS是存在继承的,而它是依赖prototype而存在的。至于这一部分的内容,我们放到prototype节里去讨论。我们现在先来关心一下object这个数据类型有何特别。
object实际上是属性与方法的集合。在ECMA规范对其类型进行划分如下:
本地对象(Native Object):指独立于宿主环境存在的对象,它可能是内置对象,也可能是在运行时由构造函数创建的对象,标准的本地对象是 Global,Math,Object,Function,Number,Boolean,String,Array,Date,Math,RegExp 和Error,非标准的本地对象则是指自建对象;
内置对象(Build-in Object):指独立于宿主环境存在的对象,它们从JS进入运行时就能访问,标准的内置对象包括 Global,Math,Object,Function,Number,Boolean,String,Array,Date,Math,RegExp 和Error,也就是和标准的本地对象是一样的。非标准的内置对象则如JScript实现中的Enumerator等。但无论它是否标准的内置对象,所有内置对象都是本地对象;
宿主对象(Host Object):指依赖于宿主环境存在的对象,例如所有浏览器中的window(BOM)和document(DOM)对象。所有不是本地对象的对象都是宿主对象。
下面我们来看一下如何才能得到一个object:
1. var a = new Object();
2. function B(){
3. this.b = 1;
4. }
5. var b = new B();
6. alert(a);// "[object Object]"
7. alert(b);// "[object Object]"
8. alert(typeof a);// "object"
9. alert(typeof b);// "object"
10. alert(typeof B);// "function"
11. alert(b.b);// "1"
虽然这个例子可能看不出区别,但是可以说得到一个Object的关键是关键字new。但在typeof运算中,遇到Function对象还是会返回"function"的。这个在稍后运算符节再讨论。
从上面的例子,我们可以看到,JS中获取对象的方式和Java很相象。但实际上,它们的差别可大呢。这个留待对象中一一解说。
- 内部数据类型
除了在代码中我们可以使用的数据类型以外,JS中实际上还存在为了内部运行机制而设的三种数据类型:Reference、List和Completion。
- Reference
Reference是一种内部数据类型,它是作为运算中间值存在的。 它的内部有两个属性:base object和property name。而它的内部也有一些对应的方法去获取这两个属性。一般来说,base object里存放的是值,而property name里存放的是需要返回的标识。
前 面曾经提及作用域链找不到标识时,返回的是一个空值标识,其返回形式就是以Reference为数据类型传输的。所赋予的null值是存放于base object中的。而因为typeof的特性,在运算返回结果的时候,会判定base object值为null的Reference的最终typeof运算结果为"undefined"。而直接判定null的时候,因为null为一种数据 类型,不需要经过Reference,所以返回的值是规范中定义的"object"。 - List
List也是一种内部数据类型,也是作为运算中间值存在的。它具体作用于new运算符或函数调用时的参数列表。这些参数列表是简单的有序数列,而且可以是任意长的。PS:它和ArrayList是两种东西——一种是数据类型,另一种是对象。
- Completion
和前面两个内部数据类型一样,Completion也是作为中间值 存在的。它是运作于终结返回操作的,例如return, break, continue和throw。它的内部由三个要素构成:type, value, target。每一个要素都分别有其可以指定的值,这些值如下表:
属性名 | 可能值 |
type | normal, break, continue, return, throw |
value | JS中的任何数据类型的值或者emtpy |
target | JS中的标识或者emtpy |