菜鸟的javascript学习笔记


开场白

================

作为WEB世界的第一语言,从事网络应用开发的我当然要把JS作为主要语言来学习喽!而且,Javascript的功能越来越强大,能干的事越来越多(几乎能干任何事),足以媲美主流语言。

万丈高楼平地起,这篇文章的目的在于打好javascript语言(以下简称JS)基础,为以后学习众多库和框架,应用它们、修改它们(主要的程序开发方式)铺路。

ECMAscript是javascript的语言规范和核心(严格来讲,Javascript是ECMAscript的一种实现,并且还包含DOM和BOM两部分内容)。这里以ECMAscript 第5版,即ISO/IEC 16262:2011为蓝本来学习。ECMAscript 6 虽然已经出现,但很多浏览器还不支持。感觉版本6就是把大量需要外部库支持的功能整合进原生JS了。我的逻辑是“保持语言核心尽可能简单,扩展功能尽量采用外部专用工具”,所以,还是以第5版为基础。

http://www.ecma-international.org网站上公布了这个规范的全文,以下内容均出自对这个规范和Node.org官方文档的学习。(ECMA5中文版见http://www.w3.org/html/ig/zh/wiki/ES5)

主要的学习教材还有阮一峰的《Javascript标准教程》(http://javascript.ruanyifeng.com/),在此鸣谢。


对于Javascript,最精辟的概括是“一门为web而生的面向对象的脚本语言“。它说明了JS的三大特点“与WEB紧密关联”,“面向对象”,“脚本语言”。其深刻含义需要慢慢体会。JS还有一大特点是强烈依赖宿主环境(Hosting environment),包括浏览器和服务器端。很多功能是依靠宿主环境才能实现的,所以,学习JS离不开浏览器和服务器端执行环境。


这篇学习笔记的一个目的是取出javascript最精华的子集,规范它的用法。






开发环境

================

传统上JS是运行在浏览器里的,其实是由浏览器里的解释器来解释运行。但在浏览器里调试程序实在是不太方便。好在Node.js横空出世,我们可以让JS脱离浏览器运行了。实际上node.js和chrome等浏览器都是通过V8来解释运行JS的。

小快灵的SublimeText就可以搭配NodeJS来做JS解释器。在SublineText中用Tool菜单-build system-new build system,在打开的build配置文件中写上

{

"cmd": ["node", "$file"],


"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",


"selector": "source.js"

}

保存为一个名字,比如NodeJs,就可以了。以后,任何打开的JS文件用Ctrl-B就可以自动解释输出了。

V8,就像它的名字一样,是马力十足的JS发动机(引擎)。V8是google开发的JS引擎,使用C++开发,并在谷歌chrome/chromium浏览器中使用。在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8中实现的ECMAScript中指定 ECMA - 262 ,第3版, 可跨平台运行(支持x86、IA - 32或ARM处理器)。V8可以独立运行,也可以嵌入到任何C/C++应用程序。项目托管在Google Code上[http://code.google.com/p/v8] ,基于BSD协议,任何组织或个人可以将其源码用于自己的项目中。

关于V8,我们以后有很多移植的工作要做,现在先不去管它,只要知道它是我们的“高级语言翻译”,能把我们写的JS代码翻译成机器语言让计算机执行就可以了。

node.js是JS的运行环境,对Google V8引擎的封装和优化,提供了很多API,让你可以使用V8以及其他很多功能,只要能够部署node.js的地方(它也是跨平台的)我们都可以放心大胆地使用JS了。

好了,假设我们已经装好了Sublime Text和Node.js,打开Sublime Text,随便写个test.js文件,如console.log("Hello world"); 点击“运行”,就可以看到亲切的“Hello world"显示在下面了(调用了node.js执行 node test.js)。当然,我们也可以在命令行中运行node test.js,同样会得到这个输出。好了,开启我们的JS之旅吧。







打印输出

=================

第一课,我们先学学如何显示内容。这要用console.log()语句。如console.log("Hello world");在命令行输出hello world。

注意,每个JS的语句最后都要有个分号(;),这是JS的结束符,解释器会凭它判断一个语句的结束。

console.log是以后我们最常用到的语句,它的作用是在标准输出中显示内容。这其实是node.js的一个功能,不是JS里固有的。还有console.err、console.time等玩法,我们先不管。只要知道可以用console.log("......“)显示内容就可以了。在调试程序的过程中,我们经常要用它打印出程序运行的状态。

如果要换行呢?用console.log("Hello\n“,"World"),你会看到打印出两行文字

Hello

World

你甚至可以用下面这行代码打印出一首诗来:

console.log(" 长亭外,古道边,\n","芳草碧连天。\n","一壶浊酒笛声残,\n","夕阳山外山。");

长亭外,古道边,

芳草碧连天。

一壶浊酒笛声残,

夕阳山外山。


如果我们引入变量,就可以实现动态输出的效果。

var a ="Bob";

console.log("Hello", a);

这将输出“Hello Bob"。

在console.log语句中,使用+号可以连接字符串。如:

console.log("Hello"+"World");

将输出“HelloWorld"。







注释

===================

用//开头的语句都被当作注释而不会被执行。

多行注释可以用

/*

.......

*/

在调试程序的过程中,我们经常要临时屏蔽掉某段程序的运行,这时可以用/* */来把这段程序注释掉。







变量与赋值

======================

上面讲到了用var a = "Bob" 实现动态替换,这里a就是变量,var a = "Bob"就是变量的赋值语句。当然,你可以简单地声明变量var a;而先不赋值(这时a的值是undefined)。

变量在计算机程序中是极为重要的概念,它有两个作用:为我们操作的对象起个名字;在内存中为它开辟一个空间存放它的值。

如果你接触过C/C++或其他编程语言,你知道变量有很多类型,int,long,float,char等,这和变量存放在内存中的空间有关。Javascript是“弱类型语言”(也有number,string等数据类型,但没有编译器在编译时检查类型错误),所有变量都可以用var来声明。这使我们不用费脑筋预先规定变量的类型。在JS中,所有数字都是64位浮点数(足够长),所有字符和字符串都是由16位字符组成的字符串。

在JS中也没有“常量”的概念,因为常量的意义在于“常量表达式在编译时求值而不在运行时求值”,而JS不是编译型语言,所以不需要常量。当然,你可以用一个简短的名字来代表一个反复用到的数字或字符串。

JavaScript允许省略var,直接对未声明的变量赋值。也就是说,var a = 1 与 a = 1,这两条语句的效果相同。但是由于这样的做法很容易不知不觉地创建全局变量(尤其是在函数内部),所以建议总是使用var命令声明变量。

严格地说,var a = 1 与 a = 1,这两条语句的效果不完全一样,主要体现在delete命令无法删除前者。不过,绝大多数情况下,这种差异是可以忽略的。

var 变量名=值 是最简单的变量赋值语句(其中的等号一定要念做“赋值为”而不是“等于”,因为这不是逻辑语句中的相等),如果要声明多个变量,或为多个变量赋值,可以这样写:

var a,b,c,d; 或

var a=1, b=2, c=3, d=4; //最好不要写成a=b=c=d=1这种形式,既不灵活,也不安全。

最好在所有函数的一开始就声明所有变量(不但好找,而且避免后面使用了未声明的变量造成全局变量污染)并赋一个初始值,象这样:

function f() {

var a = 0,

b = 0,

sum = a + b;

//函数体

console.log("sum =", sum);

}

f();

像这种初始化变量同时初始化值的做法是很好的。这样子可以防止逻辑错误(所有未初始化但声明的变量的初始值是undefined)和增加代码的可读性。在你看到代码后,你可以根据初始化的值知道这些变量大致的用途,例如是要当作对象呢还是当作整数来使。

所有教授javascript的书都谈到要避免全局变量污染。这是因为在JS中所有未定义的变量都不会抛出错误而直接被当成全局变量。全局变量有可能重名造成逻辑错误(特别是当你使用了第三方库的时候)。另外一个避免全局变量的原因是可移植性。如果你想你的代码在不同的环境下(主机下)运行,使用全局变量如履薄冰,因为你会无意中覆盖你最初环境下不存在的主机对象(所以你原以为名称可以放心大胆地使用,实际上对于有些情况并不适用)。因此,最佳实践是仅使用局部变量,即在函数内部声明的变量,它的作用域仅限于函数内部。你大可写

var f = function() {

var a = 1,

b = 2,

sum = a + b;

//函数体

console.log("sum =", sum);

};


var g = function() {

var a = 1,

b = 2,

sum = a + b;

//函数体

console.log("sum =", sum);

};

f();

g();

这里的同名变量a,b,sum由于在不同的函数内,所以不会冲突。

但有时必须要用到全局变量怎么办呢?比如需要一个多个函数共用的变量,它必须在所有函数外面被声明。我们可以使用“名称空间”。其实就是先定义一个以程序本身为名的全局对象,把要用到的全局变量都作为它的属性,这些属性可以在以后的任何函数中使用。

var MYAPP = { "id":0, "name":"John"};

//这里MYAPP指向的是顶层对象。



function f() {

MYAPP.id = 3;

console.log(MYAPP.id);

}


function g() {

MYAPP.id += 5;

MYAPP.name = "Creg";

console.log(MYAPP.id, MYAPP.name);

}


f();

g();

console.log("MYAPP.id=", MYAPP.id, "MYAPP.name=", MYAPP.name);


你会发现每个函数中都单独操作了MYAPP.id和MYAPP.name,而且结果保持到了最后。在实践中,MYAPP的名字最好换成你的专有名称。







标识符与起名规范

===============

标识符(identifier)是用来识别具体对象的一个名称。最常见的标识符就是变量名,以及后面要提到的函数名。JavaScript语言的标识符对大小写敏感,所以a和A是两个不同的标识符。


标识符有一套命名规则,不符合规则的就是非法标识符。JavaScript引擎遇到非法标识符,就会报错。


简单说,标识符命名规则如下:


第一个字符可以是任意Unicode字母,以及美元符号($)和下划线(_)。

第二个字符及后面的字符,还可以用数字。


JavaScript有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。

另外,还有三个词虽然不是保留字,但是因为具有特别含义,也不应该用作标识符:Infinity、NaN、undefined。


简单说,我们最好用英文字母组合作为变量名,推荐采用“驼峰写法”,如myVariable,dataCollection1,越是拼写完整,越容易帮你记住变量的意义。

对象名字有约定采用首字母大写形式,如Animal;函数名字采用下横线连接两个单词,如json_parse。







语句

=================

JavaScript程序的执行单位为行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句。


语句(statement)是为了完成某种任务而进行的操作,比如下面就是一行赋值语句:


var a = 1 + 3 ;

这条语句先用var命令,声明了变量a,然后将 1+3 的运算结果赋值给变量a。


“1+3”叫做表达式(expression),指一个为了得到返回值的计算式。语句和表达式的区别在于,前者主要为了进行某种操作,后者则是为了得到返回值。凡是JavaScript语言中预期为值的地方,都可以使用表达式。比如,赋值语句的等号右边,预期是一个值,因此可以放置各种表达式。


语句以分号结尾,一个分号就表示一个语句结束。多个语句可以写在一行内。函数体、循环体的后面不用加分号,因为它们只是表述,不是完整的语句。注:虽然现代的JS实现了自动在句尾加分号的功能,但为避免错误,还是写上分号为好。


var a = 1 + 3 ; var b = "abc";

分号前面可以没有任何内容,JavaScript引擎将其视为空语句。


表达式不需要分号结尾。一旦在表达式后面添加分号,则JavaScript引擎就将表达式视为语句,这样会产生一些没有任何意义的语句。


1 + 3;

"abc";

上面两行语句有返回值,但是没有任何意义,因为只是返回一个单纯的值,没有任何其他操作。

末尾是否加分号,可以总结为:一般语句要加分号,函数体、循环结构、条件语句和单纯表达式后不加分号。






条件语句

=====================

学了语句,那么我们就写些语句吧。首先,请记住“===”是“相等”的意思,“!==”是代表不相等。

计算机的智能主要体现在能够根据不同条件执行不同的动作。这基于条件语句。

JavaScript提供if结构和switch结构,完成条件判断。


(1)if 结构


if结构先判断一个表达式的布尔值,然后根据布尔值的真伪,执行不同的语句。


if (expression)

statement

上面是if结构的基本形式。需要注意的是,expression(表达式)必须放在圆括号中,表示对表达式求值。如果结果为true,就执行紧跟在后面的statement(语句);如果结果为false,则跳过statement。


if (m === 3)

m += 1;

上面代码表示,只有在m等于3时,才会将其值加上1。


这种写法要求statement只能有一个语句。如果想将多个语句放在statement之中,必须在if的条件判断之后,加上大括号。


if (m === 3) {

m += 1;

}

建议总是在if语句中使用大括号,因为这样方便插入语句。注意,if体后面并不加分号。


2)if...else结构


if代码块后面,还可以跟一个else代码块,表示括号中的表示式为false时,所要执行的代码。


if (m === 3) {

// then

} else {

// else

}

上面代码判断变量m是否等于3,如果等于就执行if代码块,否则执行else代码块。


对同一个变量进行多次判断时,多个if...else语句可以连写在一起。注意,不是elseif。


if (m === 0) {

// ...

} else if (m === 1) {

// ...

} else if (m === 2) {

// ...

} else {

// ...

}

else代码块总是跟随离自己最近的那个if语句。


条件语句可以嵌套。如

if (m !== 1) {

if (n === 2) {

console.log('hello');

} else {

console.log('world');

}

}

为避免程序没有反应,建议每个if语句都要跟else。


(3)switch结构


多个if...else连在一起使用的时候,可以转为使用更方便的switch结构。


switch (fruit) {

case "banana":

// ...

break;

case "apple":

// ...

break;

default:

// ...

}

上面代码根据变量fruit的值,选择执行相应的case。如果所有case都不符合,则执行最后的default部分。需要注意的是,每个case代码块内部的break语句不能少,否则会接下去执行下一个case代码块,而不是跳出switch结构。

为避免程序没有反应,建议每个switch语句都要跟default。








循环语句

=========================

计算机的强大,在于能够不知疲倦地飞速执行众多重复的任务。这靠的是循环语句。


循环语句用于重复执行某个操作,它有多种形式。


(1)while循环


While语句包括一个循环条件,只要该条件为真,就不断循环。


while (expression)

statement

while语句的循环条件是一个表达式(express),必须放在圆括号中。语句(statement)部分默认只能写一条语句,如果需要包括多条语句,必须添加大括号。


while (expression){

statement

}

下面是while语句的一个例子。


var i = 0;


while (i<100){

console.log('i当前为:' + i);

i++;

}

上面的代码将循环100次,直到i等于100为止。



(2)for循环


for语句是循环命令的另一种形式,也是更常用的一种形式。


for(initialize; test; increment){ //注意,中间使用;分隔

statement

}

它分成三步:


初始化(initialize):确定循环的初始值,只在循环开始时执行一次;可以用var来声明一个初始变量。

测试(test):检查循环条件,只要为真就进行后续操作;

递增(increment):完成后续操作,然后返回上一步,再一次检查循环条件。

下面是一个循环打印数组每个元素的例子。


for (var i=0; i < arr.length; i++) { //变量i可以直接声明在这里 i++推荐写作i+=1

console.log(arr[i]);

}

所有for循环,都可以改写成while循环。所以说for语句是while语句的一种紧凑写法。


var i = 0;


while (i < arr.length) {

console.log(arr[i]);

i++;

}


for...in循环

for...in 语句用于对数组或者对象的属性进行循环操作。

for ... in 循环中的代码每执行一次,就会对数组的元素或者对象的属性进行一次操作。

语法:

for (变量 in 对象)

{

在此执行代码

}

“变量”用来指定变量,指定的变量可以是数组元素,也可以是对象的属性。

下例将打印出各种车的名字:

var x;

var mycars = new Array();

mycars[0] = "Saab";

mycars[1] = "Volvo";

mycars[2] = "BMW";


for (x in mycars)

{

console.log(mycars[x]);

}

下例将逐一打印车(对象)的各种属性名:

var x;

var mycar = {

"name" : "Saab",

"engine": "V8",

"type": "Sedan",

"Country": "Sweden"

};


for (x in mycar)

{

console.log(x);

}


(3)do...while循环


do...while循环与while循环类似,唯一的区别就是先运行一次循环体,然后判断循环条件。


do {

statement

} while(expression);

不管条件是否为真,do..while循环至少运行一次,这是这种结构最大的特点。另外,while语句后面的分号不能省略。


(4)跳转语句:break语句和continue语句


break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行。选择哪一个取决于后面是否还要继续循环。


break语句用于跳出代码块或循环。


var i = 0;


while (i<100){

console.log('i当前为:',i);

i++;

if (i === 10) break; //意思是终止循环

}

上面代码只会执行10次循环,一旦i等于10,就会跳出循环。不再继续执行后面的循环。


continue语句用于立即终止本次循环,返回循环结构的头部,开始下一次循环。


var i = 0;


while (i<100){

i++;

if (i%2===0) continue; //意为“跳过”

console.log('i当前为:' + i);

}

上面代码只有在i为奇数时,才会输出i的值。如果i为偶数,则直接进入下一轮循环。


如果存在多重循环,不带参数的break语句和continue语句都只针对最内层循环跳转。那么,如果要跳出上层循环怎么办呢?


(5)标签(label)


JavaScript语言允许,语句的前面有标签(label)。标签通常与break语句和continue语句配合使用,跳出特定的循环(因为JS语言中没有goto语句)。


top:

for (var i=0;i<30;i++){

for (var j=0;j<30;j++){

if (i===10 && j===15) break top;

console.log("i=",i,"j=",j);

}

}

上面代码为一个双重循环区块,加上了top标签(注意,top不用加引号)。当满足一定条件时,使用break语句加上标签名,直接跳出双层循环。如果break语句后面不使用标签,则只能跳出内层循环,进入下一次的外层循环。


continue语句也可以与标签配合使用。


top:

for (var i=0;i<3;i++){

for (var j=0;j<3;j++){

if (i===1 && j===1) continue top;

console.log("i="+i+",j="+j);

}

}

// i=0,j=0

// i=0,j=1

// i=0,j=2

// i=1,j=0

// i=2,j=0

// i=2,j=1

// i=2,j=2

上面代码在满足一定条件时,使用continue语句加上标签名,直接进入下一轮外层循环。在本例中把i=1 J=0后面的本轮循环都跳过了。如果continue语句后面不使用标签,则只能进入下一轮的内层循环。







错误捕捉与显示

======================

在执行JS程序的过程中,各种错误会阻断程序的执行,显示在后台命令行中。我们可以把容易出错的部分加上try...catch语句,这样错误会显示在程序的输出中并不会影响后续语句的执行。

如下例:

console.log("Hi, this is 1st statement.");

foo.bar();

console.log("Hi, this is 2nd statement.");

因为foo.bar()这个函数没有定义,当显示完第一个"Hi, this is 1st statement."后就会报错停下来。而如果我们这样写:

console.log("Hi, this is 1st statement.");


try {

foo.bar();

} catch (err) {

console.log(err.name + " : " + err.message);

}


console.log("Hi, this is 2nd statement.");

就会连续执行,尽管中间会输出ReferenceError:foo is not defined.

这可以用在程序的调试中。








数据类型

=========================

我们说javascript是弱类型语言,但不是没有数据类型。JavaScript的值的类型共有六个类别和两个特殊值。


六个类别的数据类型又可以分成两组:原始类型(primitive type)和合成类型(complex type)。


原始类型包括三种数据类型。


数值(number) //使计算机可以处理数值运算

字符串(string) //使计算机可以处理人类语言

布尔值(boolean) //使计算机可以处理逻辑


“数值”就是整数和小数(比如1和3.14),“字符串”就是由多个字符组成的文本(比如"Hello World"),“布尔值”则是true(真)和false(假)两个特定值。


合成类型也包括三种数据类型。


对象(object)

数组(array)

函数(function)

对象和数组是两种不同的数据组合方式,而函数其实是处理数据的方法。JavaScript把函数当成一种数据类型,可以像其他类型的数据一样,进行赋值和传递,这为编程带来了很大的灵活性,体现了JavaScript作为“函数式语言”的本质。


这里需要明确的是,JavaScript的所有数据,都可以视为对象。不仅合成类型的数组和函数属于对象的特例,就连原始类型的数据(数值、字符串、布尔值)也可以用对象方式调用。


除了上面这六个类别,JavaScript还定义了两个特殊值null(空)和undefined(未定义)。


null和undefined

-----------------------------------------

null表示"没有对象",即该处不应该有值。典型用法是:


作为函数的参数,表示该函数的参数不是对象。


作为对象原型链的终点。


undefined表示"缺少值",就是此处应该有一个值,但是还未定义。典型用法是:


变量被声明了,但没有赋值时,就等于undefined。


调用函数时,应该提供的参数没有提供,该参数等于undefined。


对象没有赋值的属性,该属性的值为undefined。


函数没有返回值时,默认返回undefined。


布尔值

------------

布尔值代表“真”和“假”两个状态。“真”用关键字true表示,“假”用关键字false表示。布尔值只有这两个值。


下列运算符会返回布尔值:


两元逻辑运算符: && (And),|| (Or)

前置逻辑运算符: ! (Not)

相等运算符:===,!==,==,!=

比较运算符:>,>=,<,<=

如果JavaScript预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true。


undefined

null

false

0

NaN

""(空字符串)

布尔值往往用于程序流程的控制,请看一个例子。


if (""){ console.log(true);}

// 没有任何输出

上面代码的if命令后面的判断条件,预期应该是一个布尔值,所以JavaScript自动将空字符串,转为布尔值false,导致程序不会进入代码块,所以没有任何输出。


需要特别注意的是,空数组([])和空对象({})对应的布尔值,都是true。


if ([]){ console.log(true);}

// true


if ({}){ console.log(true);}

// true

更多关于数据类型转换的介绍,参见《数据类型转换》一节。



typeof

-------------------

typeof运算符可以返回一个值的数据类型,可能有以下结果:


(1)原始类型


数值、字符串、布尔值分别返回number、string、boolean。


typeof 123 // "number"

typeof "123" // "string"

typeof false // "boolean"

(2)函数


函数返回function。


// 定义一个空函数

function f(){}


typeof f

// "function"

(3)undefined


undefined返回undefined。


typeof undefined

// "undefined"

利用这一点,typeof可以用来检查一个没有声明的变量,而不报错。


v

// ReferenceError: v is not defined


typeof v

// "undefined"

实际编程中,这个特点通常用在判断语句。


// 错误的写法

if (v){

// ...

}

// ReferenceError: v is not defined


// 正确的写法

if (typeof v === "undefined"){

// ...

}

(4)其他


除此以外,都返回object。


typeof window // "object"

typeof {} // "object"

typeof [] // "object"

typeof null // "object"

从上面代码可以看到,空数组([])的类型也是object,这表示在JavaScript内部,数组本质上只是一种特殊的对象。另外,null的类型也是object,这是由于历史原因造成的,为了兼容以前的代码,后来就没法修改了,并不是说null就属于对象,本质上null是一个类似于undefined的特殊值。







数值

============================

在计算机中要处理的主要对象是数值和字符串,对应的主要工作是数值计算和语言处理。因此,在任何一门程序语言中都必须对数值的处理加以详细规定。

整数和浮点数

JavaScript内部,所有数字统一以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相等的,而且1加上1.0得到的还是一个整数,不会像有些语言那样变成小数。


1 === 1.0 // true


1 + 1.0 // 2

由于浮点数不是精确的值,所以涉及小数的比较和运算要特别小心。


0.1 + 0.2 === 0.3

// false,实际等于0.30000000000000004


0.3 / 0.1

// 2.9999999999999996


(0.3-0.2) === (0.2-0.1)

// false,左边等于0.09999999999999998,右边等于0.1

数值精度

根据国际标准IEEE 754,64位浮点数格式的64个二进制位中,第0位到第51位储存有效数字部分,第52到第62位储存指数部分,第63位是符号位,0表示正数,1表示负数。


因此,JavaScript提供的有效数字的精度为53个二进制位(IEEE 754规定有效数字第一位默认为1,再加上后面的52位),也就是说,绝对值小于等于2的53次方的整数都可以精确表示。


Math.pow(2, 53) // 54个二进制位

// 9007199254740992


Math.pow(2, 53) + 1

// 9007199254740992


Math.pow(2, 53) + 2

// 9007199254740994


Math.pow(2, 53) + 3

// 9007199254740996


Math.pow(2, 53) + 4

// 9007199254740996

从上面示例可以看到,大于2的53次方以后,整数运算的结果开始出现错误。所以,大于等于2的53次方的整数,都无法保持精度。


Math.pow(2, 53)

// 9007199254740992 (900兆)


9007199254740992111

// 9007199254740992000

上面示例表明,大于2的53次方以后,多出来的有效数字(最后三位的111)都会无法保存,变成0。


数值范围

上面说了整数最大能表示到2的53次方。另一方面,64位浮点数的指数部分的长度是11个二进制位,意味着指数部分的最大值是2047(2的11次方减1)。也就是说,64位浮点数的指数部分的值最大为2047,分出一半表示负数,则JavaScript能够表示的数值范围为2的1024次方到2的-1023次方(开区间),超出这个范围的数无法表示。这个数字足够大了,因为全宇宙填满沙子,沙子的数量大约10的100次方,还不到2的400次方(《从一到无穷大》)。


如果指数部分等于或超过最大正值1024,JavaScript会返回Infinity(关于Infinity的介绍参见下文),这称为“正向溢出”;如果等于或超过最小负值-1023(即非常接近0),JavaScript会直接把这个数转为0,这称为“负向溢出”。事实上,JavaScript对指数部分的两个极端值(11111111111和00000000000)做了定义,11111111111表示NaN和Infinity,00000000000表示0。


数值的表示法

JavaScript的数值有多种表示方法,可以用字面形式直接表示,也可以采用科学计数法表示,下面是两个科学计数法的例子。


123e3 // 123000

123e-3 // 0.123

以下两种情况,JavaScript会自动将数值转为科学计数法表示,其他情况都采用字面形式直接表示。


(1)小数点前的数字多于21位。


1234567890123456789012

// 1.2345678901234568e+21


123456789012345678901

// 123456789012345680000

(2)小数点后的零多于5个。


0.0000003 // 3e-7

0.000003 // 0.000003

正常情况下,所有数值都为十进制。如果要表示十六进制的数,必须以0x或0X开头,比如十进制的255等于十六进制的0xff或0Xff。如果要表示八进制数,必须以0开头,比如十进制的255等于八进制的0377。由于八进制表示法的前置0,在处理时很容易造成混乱,有时为了区分一个数到底是八进制还是十进制,会增加很大的麻烦,所以建议不要使用这种表示法。


烦,所以建议不要使用这种表示法。


特殊数值

JavaScript提供几个特殊的数值:0, NaN和Infinity。


正零和负零

严格来说,JavaScript提供零的三种写法:0、+0、-0。它们是等价的。


-0 === +0 // true

0 === -0 // true

0 === +0 // true

但是,如果正零和负零分别当作分母,它们返回的值是不相等的。


(1/+0) === (1/-0) // false

上面代码之所以出现这样结果,是因为除以正零得到+Infinity,除以负零得到-Infinity,这两者是不相等的(关于Infinity详见后文)。


NaN

(1)含义


NaN是JavaScript的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。


5 - 'x'

// NaN

上面代码运行时,会自动将字符串“x”转为数值,但是由于x不是数字,所以最后得到结果为NaN,表示它是“非数字”(NaN)。


另外,一些数学函数的运算结果会出现NaN。


Math.acos(2) // NaN

Math.log(-1) // NaN

Math.sqrt(-1) // NaN

0除以0也会得到NaN。


0 / 0 // NaN

需要注意的是,NaN不是一种独立的数据类型,而是一种特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。


typeof NaN // 'number'

(2)运算规则


NaN不等于任何值,包括它本身。


NaN === NaN // false

由于数组的indexOf方法,内部使用的是严格相等运算符,所以该方法对NaN不成立。


[NaN].indexOf(NaN) // -1

NaN在布尔运算时被当作false。


Boolean(NaN) // false

NaN与任何数(包括它自己)的运算,得到的都是NaN。


NaN + 32 // NaN

NaN - 32 // NaN

NaN * 32 // NaN

NaN / 32 // NaN

(3)判断NaN的方法


isNaN方法可以用来判断一个值是否为NaN。


isNaN(NaN) // true

isNaN(123) // false

但是,isNaN只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回true,这一点要特别引起注意。也就是说,isNaN为true的值,有可能不是NaN,而是一个字符串。


isNaN("Hello") // true

// 相当于

isNaN(Number("Hello")) // true

出于同样的原因,对于数组和对象,isNaN也返回true。


isNaN({}) // true

isNaN(Number({})) // true


isNaN(["xzy"]) // true

isNaN(Number(["xzy"])) // true

因此,使用isNaN之前,最好判断一下数据类型。


function myIsNaN(value) {

return typeof value === 'number' && isNaN(value);

}

判断NaN更可靠的方法是,利用NaN是JavaScript之中唯一不等于自身的值这个特点,进行判断。


function myIsNaN(value) {

return value !== value;

}

Infinity

(1)定义


Infinity表示“无穷”。除了0除以0得到NaN,其他任意数除以0,得到Infinity。


1 / -0 // -Infinity

1 / +0 // Infinity

上面代码表示,非0值除以0,JavaScript不报错,而是返回Infinity。这是需要特别注意的地方。


Infinity有正负之分。


Infinity === -Infinity // false

Math.pow(+0, -1) // Infinity

Math.pow(-0, -1) // -Infinity

运算结果超出JavaScript可接受范围,也会返回无穷。


Math.pow(2, 2048) // Infinity

-Math.pow(2, 2048) // -Infinity

由于数值正向溢出(overflow)、负向溢出(underflow)和被0除,JavaScript都不报错,所以单纯的数学运算几乎没有可能抛出错误。


(2)运算规则


Infinity的四则运算,符合无穷的数学计算规则。


Infinity + Infinity // Infinity

5 * Infinity // Infinity

5 - Infinity // -Infinity

Infinity / 5 // Infinity

5 / Infinity // 0

Infinity减去或除以Infinity,得到NaN。


Infinity - Infinity // NaN

Infinity / Infinity // NaN

Infinity可以用于布尔运算。可以记住,Infinity是JavaScript中最大的值(NaN除外),-Infinity是最小的值(NaN除外)。


5 > -Infinity // true

5 > Infinity // false

(3)isFinite函数


isFinite函数返回一个布尔值,检查某个值是否为正常值,而不是Infinity。


isFinite(Infinity) // false

isFinite(-1) // true

isFinite(true) // true

isFinite(NaN) // false

上面代码表示,如果对NaN使用isFinite函数,也返回false,表示NaN不是一个正常值。


var x = 0.5;

for(var i =0;i<25;i++) x = x*x;


x // 0

上面代码对0.5连续做25次平方,由于最后结果太接近0,超出了可表示的范围,JavaScript就直接将其转为0。


至于具体的最大值和最小值,JavaScript提供Number对象的MAX_VALUE和MIN_VALUE属性表示(参见《Number对象》一节)。


Number.MAX_VALUE // 1.7976931348623157e+308

Number.MIN_VALUE // 5e-324


与数值相关的全局方法

parseInt方法

parseInt方法可以将字符串或小数转化为整数。如果字符串头部有空格,空格会被自动去除。


parseInt("123") // 123

parseInt(1.23) // 1

parseInt(' 81') // 81

如果字符串包含不能转化为数字的字符,则不再进行转化,返回已经转好的部分。


parseInt("8a") // 8

parseInt("12**") // 12

parseInt("12.34") // 12

如果字符串的第一个字符不能转化为数字(正负号除外),返回NaN。


parseInt("abc") // NaN

parseInt(".3") // NaN

parseInt("") // NaN

parseInt方法还可以接受第二个参数(2到36之间),表示被解析的值的进制。


parseInt(1000, 2) // 8,表示2进制的1000就是8.

parseInt(1000, 6) // 216

parseInt(1000, 8) // 512

这意味着,可以用parseInt方法进行进制的转换。


parseFloat方法

parseFloat方法用于将一个字符串转为浮点数。


如果字符串包含不能转化为浮点数的字符,则不再进行转化,返回已经转好的部分。


parseFloat("3.14");

parseFloat("314e-2");

parseFloat("0.0314E+2");

parseFloat("3.14more non-digit characters");

上面四个表达式都返回3.14。


parseFloat方法会自动过滤字符串前导的空格。


parseFloat("\t\v\r12.34\n ")

// 12.34

如果第一个字符不能转化为浮点数,则返回NaN。


parseFloat("FF2") // NaN

parseFloat("") // NaN

上面代码说明,parseFloat将空字符串转为NaN。


这使得parseFloat的转换结果不同于Number函数。


parseFloat(true) // NaN

Number(true) // 1


parseFloat(null) // NaN

Number(null) // 0


parseFloat('') // NaN

Number('') // 0


parseFloat('123.45#') // 123.45

Number('123.45#') // NaN








字符串

============================

字符串就是若干个排在一起的字符,放在单引号或双引号之中。在JS中没有字符的类型,要表示一个字符,只需创建包含一个字符的字符串即可。

'a'

'abc'

"abc"

用单引号表示的字符串,内部可以使用双引号;用双引号表示的字符串,内部可以使用单引号。推荐使用双引号。


'key="value"'

"It's a long journey"

在单引号字符串内,使用单引号(或者在双引号字符串内,使用双引号),必须在内部的单引号(或者双引号)前面加上反斜杠,用来转义。


'Did she say \'Hello\'?'

"Did she say \"Hello\"?"

字符串默认只能写在一行内,分成多行将会报错。


如果长字符串必须分成多行,可以在每一行的尾部使用反斜杠。


var longString = "Long \

long \

long \

string";


longString

// "Long long long string"

上面代码表示,加了反斜杠以后,原来写在一行的字符串,可以分成多行,效果与写在同一行完全一样。


但是,这种写法有两个注意点,首先,它是ECMAScript 5新添加的,老式浏览器(如IE 8)不支持,其次,反斜杠的后面必须是换行符,而不能有其他字符(比如空格),否则会报错。


连接运算符可以连接多个字符串,用来生成一个新字符串。

'c' + 'a' + 't' = 'cat'


转义

反斜杠在字符串内有特殊含义,用来表示一些特殊字符,所以又称为转义符。

需要用反斜杠转义的特殊字符,主要有下面这些:


\0 代表没有内容的字符(\u0000)

\b 后退键(\u0008)

\f 换页符(\u000C)

\n 换行符(\u000A)

\r 回车键(\u000D)

\t 制表符(\u0009)

\v 垂直制表符(\u000B)

\' 单引号(\u0027)

\" 双引号(\u0022)

\\ 反斜杠(\u005C)

\XXX 用三位八进制数(0到377)代表一些特殊符号,比如\251表示版权符号。

\xXX 用两位十六进制数(00到FF)代表一些特殊符号,比如\xA9表示版权符号。

\uXXXX 用四位十六进制的Unicode编号代表某个字符,比如\u00A9表示版权符号。


下面是最后三种字符的特殊写法的例子。

"\251" // "©"

"\xA9" // "©"

"\u00A9" // "©"

如果非特殊字符前面使用反斜杠,则反斜杠会被省略。

"\a"

// "a"

上面代码表示a是一个正常字符,前面加反斜杠没有特殊含义,则反斜杠会被自动省略。


如果字符串的正常内容之中,需要包含反斜杠,则反斜杠前需要再加一个反斜杠,用来对自身转义。


"Prev \\ Next"

// "Prev \ Next"


字符串与数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(从0开始)。

var s = 'hello';


s[0] // "h"

s[1] // "e"

s[4] // "o"


'hello'[1] // "e"

如果方括号中的数字超过字符串的范围,或者方括号中根本不是数字,则返回undefined。


'abc'[3] // undefined

'abc'[-1] // undefined

'abc'["x"] // undefined

但是,字符串与数组的相似性仅此而已。实际上,字符串是类似数组的对象,且无法改变字符串之中的单个字符。


var s = 'hello';


delete s[0];

s

// "hello"


s[1] = 'a';

s

// "hello"


s[5] = '!';

s

// "hello"

上面代码表示,字符串内部的单个字符无法改变和增删,这些操作会默默地失败。


length属性返回字符串的长度,该属性也是无法改变的。

s.length; //5








字符集

==========================

JavaScript使用Unicode字符集,也就是说在JavaScript内部,所有字符都用Unicode表示。ECMAScript 3要求使用Unicode 2.1或以上版本,ECMAScript 5则要求使用Unicode 3及以上版本。


不仅JavaScript内部使用Unicode储存字符,而且还可以直接在程序中使用Unicode,所有字符都可以写成"\uxxxx"的形式,其中xxxx代表该字符的Unicode编码。比如,\u00A9代表版权符号。


var s = '\u00A9';

s // "©"

每个字符在JavaScript内部都是以16位(即2个字节)的UTF-16格式储存。也就是说,JavaScript的单位字符长度固定为2个字节。


但是需要注意的是,UTF-16有两种长度:对于U+0000到U+FFFF之间的字符,长度为16位(即2个字节);对于U+10000到U+10FFFF之间的字符,长度为32位(即4个字节),而且前两个字节在0xD800到0xDBFF之间,后两个字节在0xDC00到0xDFFF之间。举例来说,U+1D306对应的字符为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值