#Js中的数据类型、运算符、流程控制、函数作用域链、预解析、匿名函数自执行、怪异的几个地方
1.js是弱类型的语言,并非是没有类型,js类型分类一般有两种,1:undefined,null,boolean,string,number,object;2:undefined,boolean,string,number,object,function。个人比较推崇第二种,一方面是因为一切都是对象的说法会让初学者感到迷惑,另一方面函数是一个很庞大的东西,往往需要单独拿出来谈。
2.js中的运算符运算的时候会有隐式类型转换,而且js容错能力特别强,允许各种比较怪异的写法,这一点会让一些发一些‘js好牛的感慨’,这也是触发各种奇技淫巧的原因。
2.1 js中加号左右两边只有一个字符串,那么这个表达式按照字符串链接执行,3+’4’=>34;3+4=>7;但是需要注意的是:3+4+’5’=>75;这里按照优先级,前面一个加号还是会正常的按数字做加法的;7-‘3’=>4;js中会做隐式类型转换让左右两边的类型使得表达式能够正常的计算。(注:=>表示前面表达式的结果是后面那个,并不是大于等于号)
2.2=== 必须是相同类型才有可能相等(全等于),在这个前提下,基本类型==就===;而对象和函数类型,必须是同一个引用才会相等,比如var obj={a:3};obj==={a:3}//false.== 判断的时候可以有隐式类型转换,3==new Number(3) //true ; ’33’==33;//true true==1;//true false==0;//true 对象和字符串或数字类型比都是用它的toString和valueOf
方法,但是很少拿对象出来比。Switch/case语句中是按===比较的,比如说Switch(3){case ’3’://code}不会通过case的。
2.3>和< 这两个比较运算符,都是数字正常的比较,都是字符串按Unicode码比较,都是boolean类型,true>false,都是对象按valueOf方法比较;数字和字符串比较,必须字符串是能够转化成数字的才能比较,不然无论怎样都返回false,3<’4’ //true 3<’4a’//false 3>’4a’//false; 数字和boolean比较,true转化成1,false转化成0比较; 字符串和boolean比较,true转化成’true’,false转化成’false’比较;
字符串和对象比较,对象按toSting转化,一般都不拿基本类型和对象比较。
2.4 几个比较的特殊情况,null==undefined//true NaN==NaN//false(用isNaN判断) typeof null//object null instanceof Object //false 3 instanceof Object//false new Number(3) instanceof Object //true typeof NaN //number
{}==={}//false var str=’12a’;typeof str++;//number str的值现在是NaN,++和+=并不是完全全等的(typeof的结果都是小写的字符串)
2.5 []==true //false []==flase//true if([]){} []&&true//true []在和true或false比较的时候表现出来的是false,但是在其它操作,例如与操作if判断里都是表现出true的。null,undefined, 0, "", NaN都是直接表现为false,
3. Js的流程控制除了一般的if/else switch/case for/while ?三目 还有一个for/in for in循环一般用在无序的对象里,一般来说for/in的性能不是太好,尽量避免使用;还有一个和其它的语言不同的是标签,标签这东西就是一个小范围的goto语句,在js中在作用域内有效(即函数),目的是跳出多重循环或判断,一般需要使用标签的时候,程序本身就存在问题,个人也觉得没有使用的必要。
4.js中一般情况下只有函数才有独立的作用域(除了eval这种提升作用域),作用域链这个东西是针对变量的访问来说的,作用域链只和函数定义的地方有关,当访问一个变量的时候,js引擎会在当前函数作用域中找这个变量,找不到会去它的父函数的作用域去找,这样一直找到最外层的函数还找不到,就到window对象上找,还找不到就报错了,这就是作用域链;作用域链还和一个重要的概念有关——闭包,当子函数引用了父函数的变量,假如子函数一直可能被访问(return出去了,或者作为window或其它对象的属性),则父函数的内存空间一直得不到释放,这就是闭包。
5.预解析是指把函数和变量的声明提前到作用域的顶端,var形式的变量/函数声明预解析相当于把var aaa=3;分成了var aaa;aaa=3;两个语句,并且把前面一个提前到作用域的顶端,函数预解析则是把整个函数声明提前到了作用域的顶端,主要var形式声明的函数也是分成两个部分解析的。
6. 匿名函数自执行,function(){}()是有语法错误的,可以写成(function(){}())或者(function(){})(),其实这个只是为了语法上的分隔,其实~function(){}()或+function(){}() 都是可以的。
7.js中的颜色,ie中是使用的hex值,即你赋值的什么值,就是什么。FF和Chrome使用的 rgb和red,blue这种,所以如果要在js中使用颜色值做一些判断等操作的话,一律使用rgb值,得到统一。浏览器对red,blue,pink这种也支持不统一,一般也不用。(除了测试)
8. 0.1+0.2 !=0.3 计算机无法精确的表示大部分小数,0.1并不是真的0.1,所以一般小数相加减,如a+b用(100*a+100*b)/100代替,100后面的0的个数表示精度,如果要比较小数,如a+b==c用a+b-c<0.000001代替
9.在js中undefined尽管有特殊的含义,但是它不是保留字,并且在ie中(只测试了低版本)undefined的值可以重写,如undefined='abc';var a;a==undefined;//false 'aac'+undefined //aacabc 当然这个只是在ie中,高级浏览器不存在这种情况,所以在jquery源码中,undefined是被当作参数传进去的,防止有人篡改undefined的值,不过没有人会这么干
10. '10 13 21 48 52'.replace(/\d+/g, function(match) {return parseInt(match) <30?
'*' : match;}) //* * * 48 52 正则的replace方法可以接受回调函数,参数是匹配到的值(是字符串),return 出来的就是替换的。要匹配多个要是全局匹配,g
11. 函数中变量重名,var 赋值>函数声明>函数参数>var 声明(预解析). 这里的顺序和声明的位置没有关系。如:function fun1(a){var a=3;function a(){} alert(a)} fun1(222); 结果是弹出3;function fun1(a){var a;function a(){} alert(a)} fun1(222),结果是弹出function;function fun1(a){var a; alert(a)}
fun1(222); 结果是弹出222;这里,无论顺序是怎样的,只有函数参数和变量名或函数名有重名,只要有一个改变,变量的值就是arguments[0]的值
12.javascript 严格区分大小写,在HTML中是不区分大小写的,所以在html中属性名是可以大写也可以小写的,但是js中必须小写
13. js中的注释是//code 或者 /*code */ html中注释:<!-- code --> css注释:/*code*/
14.return{a:3};如果在函数中后面的这个json不和return在一行的话,其实最后的函数的结果是undefined,因为js每行语句最后一个语句是可以不加分号的,js引擎会帮你加上,但是最好加上,便于压缩,可读性。
15. 获取对象的属性,可以用 . [] getAttribute ,其中html中非标准的属性, chrome等高级浏览器会过滤,所以要用getAttribue获取,但是它不能获取[] .赋值的属性,它只能获取html中的自定义属性和sertAttribute设置的属性
16.基本类型都有一个包装类型,比如说'abc'.length,其实js会把字符串转换成它的包装类型, 再看它的length属性,它本身是没有属性的,调用结束后这个临时对象就销毁了,alert(1.abc)是错的,但是alert((1).abc)就会弹出undefined
17.var str='33aaa3';str.toUpperCase(); 结果是'33AAA3',但是str的值还是'33aaa3',以为toUppercase()函数,是不会改变字符串本身的,它会返回一个结果,字符串的很多方法,如replace方法都是不会改变字符串本身的,如果要用最后产生的值,最好可以存一份,但是数组pop,push等方法是会改变数组本身的。
18. 33/0 //infinity -332/0 //-infinity 0/0 //NaN
19. 在js中是for/if/swicth/try等控制语句是没有单独作用域的,虽然声明有预解析。但是有一种写法是不兼容的,if(22>14){function aaa(){alert(1)}}else{function aaa(){alert(2)}} aaa(); aaa() 是在FF下是弹出1,其它浏览器弹出2.所以尽量不要在if语句中声明函数,如果真要声明,写成 var aaa=function(){//code},这样就没有问题了。前面说过var声明,函数声明和函数重名会出现的结果。js中理论上说只有函数作用域,记住函数传参中的实参中的this和变量都还是执行这个函数的地方的this。如:
var ddd={ccc:'abc',
aa:function(){
function bb(aaaa){alert(aaaa);}
bb(this.ccc);
}
}
this.ccc=333;ddd.aa.call(window);
这里的bb(this.ccc); 的this还是aa这个函数执行环境中this,这里是window,结果是333
如果换成ddd.aa()这种执行方式,结果就是'abc'
20. 作用域链和原型链,作用域链是查找变量,原型链是查找对象属性,作用域链只和函数定义有
关,函数中的this只和函数执行有关。原型链和继承有关,子对象可以访问父级的属性和方法
21. [aa(),3+5,2];function aa(){return 33;} 这种写法一点错都没有,数组的每一个元素都可以
是表达式,但是一般真要用,先把函数值用变量存起来,在数组中变量就可以。
22. new Object() ,new Date();如果没有参数这个括号是可以省略的,如:new Object,new Date 都可以,这就说明了它是一个表达式
23.delete obj.attr child instanceof parent delete,instanceof都是一个运算符,虽然var声明的变量是window对象上的属性,但是delete是删除不掉的,但是不用var声明的在高级浏览器下就可以删除(ie下不能删除)。 一般自定义对象的自定义属性是可以delete掉的,系统定义的一般是delete不掉的。 ????????
24.js中的&&和||都是有短路现象,a&&b如果a是false,b不会执行,如果a是true,a&&b的结果就是b的值,需要注意的是:a&&b不会把a和b都转化的布尔值了再计算,如果a是ture,a&&b就是b的值,不是b对应的布尔值,if(g() && [] == ![]) 应该看作if ( (g() && [] )== ![]),假设g()为ture,所以(g() && [] ) 就相当于(true && []),结果是[ ]不是ture,![
]的结果是false,所以if判断可以简化为:[]==false,js中对象和布尔值比较,有一个怪异的现象(前面已提),同理:[ ]==![ ] 竟然也是true}所以if条件成立。所以走if语句。 a++和a+=1,是不同的,前面已提过。
25. eval() setInterval('alert(33)',30),new Function(''),后面两种都可以变相的使用eval功能,但是尽量不要这么用,但是js中有一个地方是不得不用的,就是ajax和jsonp获取字符串以后都不得不eval一下,把它变成真正的js语句。setInterval 是历史遗留问题。
26.a>b?result1:result2 这个条件运算符,result中就是一个数字或者一个单独的表达式,不要用几个,隔开的表达式,那种用法是报错的, typeof 是一个操作符,优先级比较高,如果有运算,尽量打括号。
27. switch(n){case num://code;case num2://code;default://code;}这里的n和num都可以是一个变量或表达式,但是必须是===,即类型必须是一样 ‘3’ 和3 是不等的。
28. for(var key in obj){obj[key]} for in循环虽然是说是无须的,但是比如说数组,会按push进去的顺序枚举,但 for in的效率很低,尽量不用。并且对象的有些属性是不可枚举的。 hasOwnProperty:是用来判断一个对象是否有你给出名称的属性或对象。不过需要注意的是,此方法无法检查该对象的原型链中是否具有该属性, 该属性必须是对象本身的一个成员。所以如果是要for in 对象本身,用if(obj.hasOwnProperty(key))
29. 标签语句,都是和if,for等结合使用的,不能单个语句使用,tag:if(a>b){//code break tag;}跳出if循环,在循环中使用就是跳出循环,continue跳出这一次循环,其实就是为了多层嵌套使用的,意义不大,并且标签语句只有在当前函数作用域有效。
30. throw new Error('错了,哈哈');抛出一个错误,和语句运行出错是一样的。try{//code}catch(ex){alert(ex)}finally{//code} 如果try 出错,执行catch中的语句,最后执行finally,有时候因为用户操作而造成的偶然错误,又急着上线,就可以先放在try中,一个try可以有多个catch,很少用,finally也很少用。
31. with(obj){} 语句会把 obj 对象添加到作用域的顶端,只有前缀很长的时候会使用,但是把前缀用变量存一下是更好的选择。一般不用
32. debugger;如果不需要弹什么东西,只是看语句执行到哪执行到没有,可以用debugger,方便。不兼容,调试用。
33. 'use strict' 严格模式,'use strict'只是个字符串,但是它必须出现在脚本的最前面,或者函数的最前面。 严格模式下,禁用了很多东西,比如说with是禁用的,单纯的函数执行,this不是window了而是undefined,同名参数会出错,arguments和参数变成了同一个引用。 (尼玛,大早上的写了这么多没保存,太伤心了)
34. ES5提供了一个对象访问原型的不标准的属性__proto__,但是一般用obj.constructor.prototype来访问,constructor是正确的。
35.对象序列号其实就是指对象转化成字符串,反过来一样,但是前后台交互数据现在一般都是json格式的, 所以序列化/反 一般都是针对json来说的,ES5 新定义了两个方法JSON.stringify(json1)/ JSON.prase(json2)分别是序列化和反序列化的,其实只要for/in循环一下,eval一下也同样很容易做到序列化/反
36.数组里新建一个对象a=new Arrar(5) 是创建5个空元素,不是创建一个5,而且没有[]直观 所以一般不用new Array,也没必要。pop/push/unshift/shift 记得就行,清空数组用arr.length=0就行,delete arr[3]其实就是跟arr[3]设置一个undefined值。 不会改变length长度,数组遍历for(var i=0,len=arr.length;i<len;i++) 就一次访问length属性,特别是当访问dom对象伪数组的时候,减少访问dom的次数可以提高性能,数组元素之间类型是可以不一样的,但是js里面没有提供多维数组的直接方法。但是你可以通过把方法:join
arr.join('-'),就是用-把每个连接起来的字符串。arr.reverse 把arr逆序。arr.sort(function(a,b){reutrun a-b;})这是个排序方法,参数是两个元素,可以先处理两个元素,比如说先把字符串弄成小写,在比较,return 正数 就是a,b 排列;负数就b,a排列.arr.slice(num1,num2)截取num1到num2的元素组成的数组,如果是负数是从数组往前面数的,-1是最后一个元素,如果只有一个参数,就是到数组最后,但是第二个参数所在位置是不包含的。arr.splice(num1,num2),splice不仅返回了从num1到num2组成的数组,
而且在数组arr中把这段删除了,arr.concat 方法,数组连接,参数可以是多个,单个元素和数组都行。ES5中的方法,forEach,map,indexof等方法,这些方法都很容易实现,面试可能会问到除了forEach其它意义不是很大。pop/push/unshift/shift/splice/reverse/sort 等方法修改了原数组,join/concant/slice 没有修改,如果不记得有没有修改,就用一个变量把结果存起来,不要纠结。有些伪数组有时候也需要用数组的方法,可以用Array.prototype.concat.call(obj,obj2);但是一般没必要这么用,结果有时候比较诡异,不是每一个伪数组,每一个方法都是可以这么用的。
37.function 中 函数名.length 形参的个数,arugments.length 实参的个数。arguments的callee、caller属性,在严格模式下都是会出错的。arguments的caller属性的值是调用这个函数的函数,arguments的callee的值是这个函数本身,常常用于递归.
38.如果需要定义一个静态的函数执行不会重置的变量,不要用全局变量,函数名.属性名=3;就可以了,但是一般在函数外面定义,利用闭包访问就可以了。call/apply 都是Function原型上的方法,第一个参数都是给this赋值,call后面是一堆参数,apply是一个数组
39. A instanceof B 如果A能访问B的原型,则 true。判断一个变量是不是字符串,(typeof arr)=='string' || arr.constructor==String 其实 就算是基本类型arr.constructor 也有值,str的也是String,所以直接用arr.constructor==String 就可以判断,判断数组也是一样。
40. 正则表达式: [ ] 表示[ ]的任何一个,也可以\w 这种,[^ ] 除了[ ] 中的字符,大写就是和小写相反的,\s任何空白符,\w数字或字母。\d数字 . 除换行符终止符以外的任意字符 , ? 0/1 次,+ 1次或多次,* 0次或多次,{n,m}至少n次不超过m次,{n,}至少n次,{n} n次, | 是或的意思,[ab]可以写成a|b ,但是[]可以[a-b] 是这种范围,正则中可以用括号,如果要匹配括号,那么要转义。/^$/表示开头和结尾i是不区分大小写,g是全局匹配,字符串方法'JavaScript'.replace(/javascript/gi,'javascript')
第二个参数可以是函数,在1.7中已经说过。replace方法不改变字符串本身,只要返回的值。‘ff df jj j’.split(/\s/) split 的参数也可以是正则。str.match(/\d/)返回值是匹配到的字符串的一个数组。reg.test(str) 和reg.exec(str)如果正则是g全局的,每次匹配不会从头开始匹配,比如/\w/.test('ab')执行第三次的时候就会返回false,所以用这两个方法的时候,一般不要加g。exec结果是匹配到的结果数组。 没匹配到是null。
41.只使用一个var声明变量,有利于压缩代码,算是养成一个习惯吧。