第一章 词法结构
可选的分号
javascript不会再所有的换行出添加分号,只有如下几种情况会自动补全分号:
1.上一条语句与下一条语句连接在一起出现解析错误时,会在上一条语句后面不全分号;
栗子一:
var a a = 3 console.log(a)
解析过程:
首先,var a a出现解析错误,在var a后面加分号;var a;
其次,a=3console.log(a),解析出错,在a=3后面加分号;a=3;
最后,在console.log(a)后面加分号,console.log(a);
最终解析为:
var a; a=3; console.log(a);
栗子二:
var y=x+f (a+b).toString()
解析过程:
var y=x+f(a+b).toString(),不会出现错误,所以不会再第一条语句后加分号;最终结果为var y=x+f(a+b).toString();
但是我们想要的结果是:
var y=x+f; (a+b).toString();
这个可能与我们处理逻辑不通,导致出错。
2.如果return,break,continue后面直接换行,会在后面自动补全分号;
来个栗子:
//我们要返回的是true { ...... return true }
解析的结果:
{ ...... return; true; } //结果返回了null;
3.++或者- -操作符,即可以作为前缀,也可以作为后缀,但是如果不加分号,javascript会默认当做前缀处理;
来个栗子:
a ++ b /**最终的结果**/ a; ++b;
第二章 类型,值,变量
2.1 数字
javascript不区分整数和浮点数,所有的数字均采用浮点数表示,采用IEEE 754标准定义的64位浮点格式表示数字,其表示范围在[-253-253],如果超出了范围,则无法保证低位数的精度;但是
实际情况,javascript操作的整数则是基于32位的。
整型值变量:
采用十进制和16进制表示,ECMAScript严格模式下禁止使用八进制;十六进制加前缀0x或者0X;
二进制浮点数和四舍五入错误
javascript采用的是IEEE 754格式表示浮点数,它是一种二进制表示方法,它可以精确的表示类似于1/2,1/4,1/8,.....等,但是我们常用的十进制分数,二进制浮点数无法表示类似于0.1这样的小数,但是表示的值及其接近0.1
十进制 二进制 0.1 0.0001 1001 1001 1001 ... 0.2 0.0011 0011 0011 0011 ... 0.3 0.0100 1100 1100 1100 ... 0.4 0.0110 0110 0110 0110 ... 0.5 0.1 0.6 0.1001 1001 1001 1001 ... 所以比如 1.1,其程序实际上无法真正的表示 ‘1.1',而只能做到一定程度上的准确,这是无法避免的精度丢失:1.09999999999999999
/**栗子一**/ var x=1.0-0.9; var y=0.1; console.log(x===y)//false console.log(x)//0.09999999999999998 //那如何来避免这类 1.0-0.9 != 0.1 的非bug型问题发生呢?下面给出一种目前用的比较多的解决方案, 在判断浮点运算结果前对计算结果进行精度缩小,因为在精度缩小的过程总会自动四舍五入: //通过isEqual工具方法判断数值是否相等,精度digist必须在0-20之间 function isEqual(number1, number2, digits){ digits = digits == undefined? 10: digits; // 默认精度为10 return number1.toFixed(digits) === number2.toFixed(digits); } console.log(isEqual(1.0-0.9, 0.1)); //true //例如结合律。对于任意实数 x,y,z总满足(x+y)+z=x+(y+z) 浮点数就不一定: (0.1+0.2)+0.3; //0.6000000000000001 0.1+(0.2+0.3); //0.6
/** * 浮点数的精确运算 **/ //加法函数,用来得到精确的加法结果 //说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 //调用:accAdd(arg1,arg2) //返回值:arg1加上arg2的精确结果 function accAdd(arg1,arg2){ var r1,r2,m; try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0} try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0} m=Math.pow(10,Math.max(r1,r2)) return (arg1*m+arg2*m)/m } //给Number类型增加一个add方法,调用起来更加方便。 Number.prototype.add = function (arg){ return accAdd(arg,this); } //减法函数,用来得到精确的减法结果 //说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的减法结果。 //调用:accSub(arg1,arg2) //返回值:arg1减去arg2的精确结果 function accSub(arg1,arg2){ var r1,r2,m,n; try{r1=arg1.toString().split(".")[1].length}catch(e){r1=0} try{r2=arg2.toString().split(".")[1].length}catch(e){r2=0} m=Math.pow(10,Math.max(r1,r2)); //last modify by deeka //动态控制精度长度 n=(r1>=r2)?r1:r2; return ((arg1*m-arg2*m)/m).toFixed(n); } //除法函数,用来得到精确的除法结果 //说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。 //调用:accDiv(arg1,arg2) //返回值:arg1除以arg2的精确结果 function accDiv(arg1,arg2){ var t1=0,t2=0,r1,r2; try{t1=arg1.toString().split(".")[1].length}catch(e){} try{t2=arg2.toString().split(".")[1].length}catch(e){} with(Math){ r1=Number(arg1.toString().replace(".","")) r2=Number(arg2.toString().replace(".","")) return (r1/r2)*pow(10,t2-t1); } } //给Number类型增加一个div方法,调用起来更加方便。 Number.prototype.div = function (arg){ return accDiv(this, arg); } //乘法函数,用来得到精确的乘法结果 //说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。 //调用:accMul(arg1,arg2) //返回值:arg1乘以arg2的精确结果 function accMul(arg1,arg2) { var m=0,s1=arg1.toString(),s2=arg2.toString(); try{m+=s1.split(".")[1].length}catch(e){} try{m+=s2.split(".")[1].length}catch(e){} return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,m) } //给Number类型增加一个mul方法,调用起来更加方便。 Number.prototype.mul = function (arg){ return accMul(arg, this); }
//验证一下: console.log(accAdd(1.79, 0.12)); //1.91 console.log(accSub(2.01, 0.12)); //1.89 console.log(accDiv(0.69, 10)); //0.069
console.log(accMul(1.01, 1.3)); //1.313
注:详细参考链接:http://www.cnblogs.com/ppforever/p/5011660.html
Math常用方法:
Math.round(num)//四舍五入 Math.ceil(num)//向上求整 Math.floor(num)//向下取整 Math.random()//[0,1)随机数 num.toFixed(digest)//对num保留digest位小数 Math.pow(2,53)//2^53 Math.pow(2,1/3)//2的立方根 Math.sqrt(2)//2的平方根 Math.max(x,y)//取最大值 Math.min(x,y)//取最小值 Math.PI//圆周率
2.2 null与undefined
typeof null ==='object'//空对象 typeof undefined === 'undefined'//未初始化 null==undefined//true; null===undefined//false
javascript中返回undefined的场景:
//变量未初始化 var a; a===undefined //函数没有传递实参导致形参未空 function fun(x){ x===undefined; } //return; (function f(){ return; })()===undefined 但是在定义变量时,对于没有初始化的变量最好使用null;
2.3全局变量
在javascript中,全局变量定义的方式有三种: (1)在函数外部定义 var a=1; function(){} (2)在函数内部未定义但是赋值 (function f(){ a=1;//此处未用var 声明,表示全局变量 })(); (3)在window上定义属性 window.a=1; javascript中连等号的执行顺序:从右向左赋值 A=B=C; 等价于 B=C; A=B; 来个栗子: 猜一猜,下面的打印结果是什么? (function(){ var a=b={n:1}; a.n=2; console.log(a); console.log(b); })(); console.log(b); console.log(a); //答案:{n:2},{n:2},{n:2},Uncaught ReferenceError: a is not defined a是局部变量,b是全局变量。 参考链接: http://www.tuicool.com/articles/vUFbeu http://www.jb51.net/article/36548.htm
2.4 包装对象
javascript为什么要引入包装对象?
在回答这个问题之前先来个栗子热热身:
var str='test'; str.name='awng'; console.log(str.name); //结果是'test' javascript中有三种包装对象,String、Number、Boolean,对应的基本类型就是string,number,boolean。在使用基本类型时,我们希望能向对象类型一样,通过.或者[]来访问属性或者方法时,此时就要借助包装对象。 比如: var str='test'; console.log(str.length)//4 我们知道,str时基本类型,不是对象类型,没有length属性,此处过程: 1.是想创建一个临时的String对象var temp=new String('test'); 2.在使用零食对象操作属性length;temp.length; 3.最后在console.log()执行完后销毁temp. 此时在回头看看栗子 由于在str.name='awng';执行完成后,临时对象被销毁; 在执行console.log(str.name)时候又会重新创建一个临时对象,该临时对象的name属性为undefined;
2.5 类型转换
typeof Number('111')==='number' typeof String('111')==='string' typeof Boolean('111')==='boolean' //转换成整型,radix:0X代表十六进制,0代表八进制 parseInt(string, radix) 1.忽略字符串前面的空格,直至找到第一个非空字符 2.如果第一个字符不是数字符号或者负号,返回NaN 3.如果第一个字符是数字,则继续解析直至字符串解析完毕或者遇到一个非数字符号为止 4.如果上步解析的结果以0开头,则将其当作八进制来解析;如果以0x开头,则将其当作十六进制来解析 5.如果指定radix参数,则以radix为基数进行解析 parseFloat(string)函数,将字符串转换为浮点数类型的数值。 引用类型转换为布尔,始终为true 引用类型转换为字符串 1.优先调用toString方法(如果有),看其返回结果是否是原始类型,如果是,转化为字符串,返回。 2.否则,调用valueOf方法(如果有),看其返回结果是否是原始类型,如果是,转化为字符串,返回。 3.其他报错。 引用类型转化为数字 1.优先调用valueOf方法(如果有),看其返回结果是否是基本类型,如果是,转化为数字,返回。 2.否则,调用toString方法(如果有),看其返回结果是否是基本类型,如果是,转化为数字,返回。 3.其他报错。 栗子: var a = {}; console.dir(a.toString()); // "[object Object]" console.dir(a.valueOf()); // 对象本身 var b = [1, 2, 3]; console.dir(b.toString()); // "1,2,3" console.dir(b.valueOf()); // 对象本身 var c = [[1],[2]]; console.dir(c.toString()); // "1,2" console.dir(c.valueOf()); // 对象本身 var d = function() {return 2}; console.dir(d.toString()); // "function() {return 2}" console.dir(d.valueOf()); // 对象本身 双等号= =,如果两边类型不同,会有隐式转换发生。 1,null和undefined,相等。 2,数字和字符串,转化为数字再比较。 3,如果有true或false,转换为1或0,再比较。 4,如果有引用类型,优先调用valueOf。 5,其余都不相等。 栗子: console.log([[2]] == '2') 原因如下: [[2]]的valueOf是对象本身,不是基本类型。 尝试调用toString的结果是'2'。 因此变成了'2'和数字2的比较。根据第2条,相等。 参考文章:http://www.jb51.net/article/103830.htm
2.6 javascript作用域和变量提升
先来几个栗子尝尝:
栗子1: var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); //10 栗子2: var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a); //1 以上栗子表现出,在javascript中,无论运行中代码是否能够执行到,函数声明和变量声明总是会被解释器悄悄地被“提升”到方法体的最顶部; 栗子3: function foo() { var x = 1; if (x) { (function () { var x = 2; }()); } console.log(x); } //1 javascript没有块状作用域,但是可以通过函数来模仿块状作用域 栗子4: (function(){ var a=1; function a(){ } console.log(a); })(); //1 函数的提升优先于变量的提升 栗子5: baz();// valid spam();//ReferenceError "spam is not defined" var baz = function spam() {}; // 命名函数,只有baz被提升,spam不会被提升。 总结: 如果变量在函数体类声明,则它是函数作用域。否则,它是全局作用域(作为global的属性)。 变量将会在执行进入作用域的时候被创建。 块不会定义新的作用域,只有函数声明和程序(译者以为,就是全局性质的代码执行)才会创造新的作用域。 变量在创建的时候会被初始化为undefined。 如果变量声明语句里面带有赋值操作,则赋值操作只有被执行到的时候才会发生,而不是创建的时候。
参考链接:http://blog.csdn.net/sunxing007/article/details/9034253#
第三章 表达式和运算符
3.1 数组的初始化
var arr=new Array(5)//构造函数,[undefined × 5] var arr=[1,2,3];//字面量 //初始化可以用,隔开,自动填充undefined,数组最后一个是逗号,不会创建一个新元素 arr=[,,,,]; console.log(arr)//[undefined*4] //可以跳过中间的索引,给指定的索引赋值,javascript没有数组越界; var arr=[1,2,3] arr[5]=10; console.log(arr)//[1,2,3,undefined,10] //可以通过length属性,删除后面的字符串 arr.length=2; console.log(arr)//[1,2] arr.length=5; console.log(arr)//[1,2,undefined*3]
数组中for循环和for in 的区别
来个栗子:
var arr=['a','b','c']; for(var i=0;i再来个栗子:
Array.prototype.d='d'; var arr=['a','b','c']; for(var i=0;i这样难道for 与 for in 相同了吗?答案是no
再来个栗子,我保证这是数组最后一个栗子了,哈哈:
既然数组也是对象,我能不能给它添加属性呢? var arr=[1,2,3] arr['-10']=-10; console.log(arr) 猜猜结果是什么? //[1,2,3] 神马情况,我添加的属性怎么没有了!!! 我们通过for循环来看看 for(var i=0;i3.2 数组常用的操作
数组元素的添加
arrayObj. push([item1 [item2 [. . . [itemN ]]]]);// 将一个或多个新元素添加到数组结尾,并返回数组新长度 arrayObj.unshift([item1 [item2 [. . . [itemN ]]]]);// 将一个或多个新元素添加到数组开始,数组中的元素自动后移,返回数组新长度 arrayObj.splice(insertPos,0,[item1[, item2[, . . . [,itemN]]]]);//将一个或多个新元素插入到数组的指定位置,插入位置的元素自动后移,返回""。数组元素的删除
arrayObj.pop(); //移除最后一个元素并返回该元素值 arrayObj.shift(); //移除最前一个元素并返回该元素值,数组中元素自动前移 arrayObj.splice(deletePos,deleteCount); //删除从指定位置deletePos开始的指定数量deleteCount的元素,数组形式返回所移除的元素数组的截取和合并
arrayObj.slice(start, [end]); //以数组的形式返回数组的一部分,注意不包括 end 对应的元素,如果省略 end 将复制 start 之后的所有元素 arrayObj.concat([item1[, item2[, . . . [,itemN]]]]); //将多个数组(也可以是字符串,或者是数组和字符串的混合)连接为一个数组,返回连接好的新的数组数组元素的排序
arrayObj.reverse(); //反转元素(最前的排到最后、最后的排到最前),返回数组地址 arrayObj.sort(); //对数组元素排序,返回数组地址数组的拷贝
arrayObj.slice(0); //返回数组的拷贝数组,注意是一个新的数组,不是指向 arrayObj.concat(); //返回数组的拷贝数组,注意是一个新的数组,不是指向数组元素的字符串化
arrayObj.join(separator); //返回字符串,这个字符串将数组的每一个元素值连接在一起,中间用 separator 隔开。3.3 数组类型判断
typeof arr ==='object' && arr instanceof Array typeof arr ==='object' && arr.constructor ===Array Object.prototype.toString.call(arr)==='[object Array]'